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