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), ¶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.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), ¶ms); 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), ¶ms); 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}