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