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