fetch.go

  1package chat
  2
  3import (
  4	"encoding/json"
  5
  6	"github.com/charmbracelet/crush/internal/agent/tools"
  7	"github.com/charmbracelet/crush/internal/message"
  8	"github.com/charmbracelet/crush/internal/ui/styles"
  9)
 10
 11// -----------------------------------------------------------------------------
 12// Fetch Tool
 13// -----------------------------------------------------------------------------
 14
 15// FetchToolMessageItem is a message item that represents a fetch tool call.
 16type FetchToolMessageItem struct {
 17	*baseToolMessageItem
 18}
 19
 20var _ ToolMessageItem = (*FetchToolMessageItem)(nil)
 21
 22// NewFetchToolMessageItem creates a new [FetchToolMessageItem].
 23func NewFetchToolMessageItem(
 24	sty *styles.Styles,
 25	toolCall message.ToolCall,
 26	result *message.ToolResult,
 27	canceled bool,
 28) ToolMessageItem {
 29	return newBaseToolMessageItem(sty, toolCall, result, &FetchToolRenderContext{}, canceled)
 30}
 31
 32// FetchToolRenderContext renders fetch tool messages.
 33type FetchToolRenderContext struct{}
 34
 35// RenderTool implements the [ToolRenderer] interface.
 36func (f *FetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
 37	if opts.IsPending() {
 38		return pendingTool(sty, "Fetch", opts.Anim)
 39	}
 40
 41	var params tools.FetchParams
 42	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
 43		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
 44	}
 45
 46	toolParams := []string{params.URL}
 47	if params.Format != "" {
 48		toolParams = append(toolParams, "format", params.Format)
 49	}
 50	if params.Timeout != 0 {
 51		toolParams = append(toolParams, "timeout", formatTimeout(params.Timeout))
 52	}
 53
 54	header := toolHeader(sty, opts.Status, "Fetch", width, opts.Compact, toolParams...)
 55	if opts.Compact {
 56		return header
 57	}
 58
 59	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
 60		return joinToolParts(header, earlyState)
 61	}
 62
 63	if opts.HasEmptyResult() {
 64		return header
 65	}
 66
 67	// Determine file extension for syntax highlighting based on format.
 68	file := getFileExtensionForFormat(params.Format)
 69	body := toolOutputCodeContent(sty, file, opts.Result.Content, 0, width, opts.ExpandedContent)
 70	return joinToolParts(header, body)
 71}
 72
 73// getFileExtensionForFormat returns a filename with appropriate extension for syntax highlighting.
 74func getFileExtensionForFormat(format string) string {
 75	switch format {
 76	case "text":
 77		return "fetch.txt"
 78	case "html":
 79		return "fetch.html"
 80	default:
 81		return "fetch.md"
 82	}
 83}
 84
 85// -----------------------------------------------------------------------------
 86// WebFetch Tool
 87// -----------------------------------------------------------------------------
 88
 89// WebFetchToolMessageItem is a message item that represents a web_fetch tool call.
 90type WebFetchToolMessageItem struct {
 91	*baseToolMessageItem
 92}
 93
 94var _ ToolMessageItem = (*WebFetchToolMessageItem)(nil)
 95
 96// NewWebFetchToolMessageItem creates a new [WebFetchToolMessageItem].
 97func NewWebFetchToolMessageItem(
 98	sty *styles.Styles,
 99	toolCall message.ToolCall,
100	result *message.ToolResult,
101	canceled bool,
102) ToolMessageItem {
103	return newBaseToolMessageItem(sty, toolCall, result, &WebFetchToolRenderContext{}, canceled)
104}
105
106// WebFetchToolRenderContext renders web_fetch tool messages.
107type WebFetchToolRenderContext struct{}
108
109// RenderTool implements the [ToolRenderer] interface.
110func (w *WebFetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
111	if opts.IsPending() {
112		return pendingTool(sty, "Fetch", opts.Anim)
113	}
114
115	var params tools.WebFetchParams
116	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
117		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
118	}
119
120	toolParams := []string{params.URL}
121	header := toolHeader(sty, opts.Status, "Fetch", width, opts.Compact, toolParams...)
122	if opts.Compact {
123		return header
124	}
125
126	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
127		return joinToolParts(header, earlyState)
128	}
129
130	if opts.HasEmptyResult() {
131		return header
132	}
133
134	body := toolOutputMarkdownContent(sty, opts.Result.Content, width, opts.ExpandedContent)
135	return joinToolParts(header, body)
136}
137
138// -----------------------------------------------------------------------------
139// WebSearch Tool
140// -----------------------------------------------------------------------------
141
142// WebSearchToolMessageItem is a message item that represents a web_search tool call.
143type WebSearchToolMessageItem struct {
144	*baseToolMessageItem
145}
146
147var _ ToolMessageItem = (*WebSearchToolMessageItem)(nil)
148
149// NewWebSearchToolMessageItem creates a new [WebSearchToolMessageItem].
150func NewWebSearchToolMessageItem(
151	sty *styles.Styles,
152	toolCall message.ToolCall,
153	result *message.ToolResult,
154	canceled bool,
155) ToolMessageItem {
156	return newBaseToolMessageItem(sty, toolCall, result, &WebSearchToolRenderContext{}, canceled)
157}
158
159// WebSearchToolRenderContext renders web_search tool messages.
160type WebSearchToolRenderContext struct{}
161
162// RenderTool implements the [ToolRenderer] interface.
163func (w *WebSearchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
164	if opts.IsPending() {
165		return pendingTool(sty, "Search", opts.Anim)
166	}
167
168	var params tools.WebSearchParams
169	if err := json.Unmarshal([]byte(opts.ToolCall.Input), &params); err != nil {
170		return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
171	}
172
173	toolParams := []string{params.Query}
174	header := toolHeader(sty, opts.Status, "Search", width, opts.Compact, toolParams...)
175	if opts.Compact {
176		return header
177	}
178
179	if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
180		return joinToolParts(header, earlyState)
181	}
182
183	if opts.HasEmptyResult() {
184		return header
185	}
186
187	body := toolOutputMarkdownContent(sty, opts.Result.Content, width, opts.ExpandedContent)
188	return joinToolParts(header, body)
189}