1package chat
2
3import (
4 "encoding/json"
5
6 "github.com/charmbracelet/crush/internal/agent/tools"
7 "github.com/charmbracelet/crush/internal/fsext"
8 "github.com/charmbracelet/crush/internal/message"
9 "github.com/charmbracelet/crush/internal/ui/styles"
10)
11
12// -----------------------------------------------------------------------------
13// Glob Tool
14// -----------------------------------------------------------------------------
15
16// GlobToolMessageItem is a message item that represents a glob tool call.
17type GlobToolMessageItem struct {
18 *baseToolMessageItem
19}
20
21var _ ToolMessageItem = (*GlobToolMessageItem)(nil)
22
23// NewGlobToolMessageItem creates a new [GlobToolMessageItem].
24func NewGlobToolMessageItem(
25 sty *styles.Styles,
26 toolCall message.ToolCall,
27 result *message.ToolResult,
28 canceled bool,
29) ToolMessageItem {
30 return newBaseToolMessageItem(sty, toolCall, result, &GlobToolRenderContext{}, canceled)
31}
32
33// GlobToolRenderContext renders glob tool messages.
34type GlobToolRenderContext struct{}
35
36// RenderTool implements the [ToolRenderer] interface.
37func (g *GlobToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
38 if opts.IsPending() {
39 return pendingTool(sty, "Glob", opts.Anim)
40 }
41
42 var params tools.GlobParams
43 if err := json.Unmarshal([]byte(opts.ToolCall.Input), ¶ms); err != nil {
44 return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
45 }
46
47 toolParams := []string{params.Pattern}
48 if params.Path != "" {
49 toolParams = append(toolParams, "path", params.Path)
50 }
51
52 header := toolHeader(sty, opts.Status, "Glob", width, opts.Compact, toolParams...)
53 if opts.Compact {
54 return header
55 }
56
57 if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
58 return joinToolParts(header, earlyState)
59 }
60
61 if !opts.HasResult() || opts.Result.Content == "" {
62 return header
63 }
64
65 bodyWidth := width - toolBodyLeftPaddingTotal
66 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
67 return joinToolParts(header, body)
68}
69
70// -----------------------------------------------------------------------------
71// Grep Tool
72// -----------------------------------------------------------------------------
73
74// GrepToolMessageItem is a message item that represents a grep tool call.
75type GrepToolMessageItem struct {
76 *baseToolMessageItem
77}
78
79var _ ToolMessageItem = (*GrepToolMessageItem)(nil)
80
81// NewGrepToolMessageItem creates a new [GrepToolMessageItem].
82func NewGrepToolMessageItem(
83 sty *styles.Styles,
84 toolCall message.ToolCall,
85 result *message.ToolResult,
86 canceled bool,
87) ToolMessageItem {
88 return newBaseToolMessageItem(sty, toolCall, result, &GrepToolRenderContext{}, canceled)
89}
90
91// GrepToolRenderContext renders grep tool messages.
92type GrepToolRenderContext struct{}
93
94// RenderTool implements the [ToolRenderer] interface.
95func (g *GrepToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
96 if opts.IsPending() {
97 return pendingTool(sty, "Grep", opts.Anim)
98 }
99
100 var params tools.GrepParams
101 if err := json.Unmarshal([]byte(opts.ToolCall.Input), ¶ms); err != nil {
102 return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
103 }
104
105 toolParams := []string{params.Pattern}
106 if params.Path != "" {
107 toolParams = append(toolParams, "path", params.Path)
108 }
109 if params.Include != "" {
110 toolParams = append(toolParams, "include", params.Include)
111 }
112 if params.LiteralText {
113 toolParams = append(toolParams, "literal", "true")
114 }
115
116 header := toolHeader(sty, opts.Status, "Grep", width, opts.Compact, toolParams...)
117 if opts.Compact {
118 return header
119 }
120
121 if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
122 return joinToolParts(header, earlyState)
123 }
124
125 if opts.HasEmptyResult() {
126 return header
127 }
128
129 bodyWidth := width - toolBodyLeftPaddingTotal
130 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
131 return joinToolParts(header, body)
132}
133
134// -----------------------------------------------------------------------------
135// LS Tool
136// -----------------------------------------------------------------------------
137
138// LSToolMessageItem is a message item that represents an ls tool call.
139type LSToolMessageItem struct {
140 *baseToolMessageItem
141}
142
143var _ ToolMessageItem = (*LSToolMessageItem)(nil)
144
145// NewLSToolMessageItem creates a new [LSToolMessageItem].
146func NewLSToolMessageItem(
147 sty *styles.Styles,
148 toolCall message.ToolCall,
149 result *message.ToolResult,
150 canceled bool,
151) ToolMessageItem {
152 return newBaseToolMessageItem(sty, toolCall, result, &LSToolRenderContext{}, canceled)
153}
154
155// LSToolRenderContext renders ls tool messages.
156type LSToolRenderContext struct{}
157
158// RenderTool implements the [ToolRenderer] interface.
159func (l *LSToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
160 if opts.IsPending() {
161 return pendingTool(sty, "List", opts.Anim)
162 }
163
164 var params tools.LSParams
165 if err := json.Unmarshal([]byte(opts.ToolCall.Input), ¶ms); err != nil {
166 return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
167 }
168
169 path := params.Path
170 if path == "" {
171 path = "."
172 }
173 path = fsext.PrettyPath(path)
174
175 header := toolHeader(sty, opts.Status, "List", width, opts.Compact, path)
176 if opts.Compact {
177 return header
178 }
179
180 if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
181 return joinToolParts(header, earlyState)
182 }
183
184 if opts.HasEmptyResult() {
185 return header
186 }
187
188 bodyWidth := width - toolBodyLeftPaddingTotal
189 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
190 return joinToolParts(header, body)
191}
192
193// -----------------------------------------------------------------------------
194// Sourcegraph Tool
195// -----------------------------------------------------------------------------
196
197// SourcegraphToolMessageItem is a message item that represents a sourcegraph tool call.
198type SourcegraphToolMessageItem struct {
199 *baseToolMessageItem
200}
201
202var _ ToolMessageItem = (*SourcegraphToolMessageItem)(nil)
203
204// NewSourcegraphToolMessageItem creates a new [SourcegraphToolMessageItem].
205func NewSourcegraphToolMessageItem(
206 sty *styles.Styles,
207 toolCall message.ToolCall,
208 result *message.ToolResult,
209 canceled bool,
210) ToolMessageItem {
211 return newBaseToolMessageItem(sty, toolCall, result, &SourcegraphToolRenderContext{}, canceled)
212}
213
214// SourcegraphToolRenderContext renders sourcegraph tool messages.
215type SourcegraphToolRenderContext struct{}
216
217// RenderTool implements the [ToolRenderer] interface.
218func (s *SourcegraphToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
219 if opts.IsPending() {
220 return pendingTool(sty, "Sourcegraph", opts.Anim)
221 }
222
223 var params tools.SourcegraphParams
224 if err := json.Unmarshal([]byte(opts.ToolCall.Input), ¶ms); err != nil {
225 return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, width)
226 }
227
228 toolParams := []string{params.Query}
229 if params.Count != 0 {
230 toolParams = append(toolParams, "count", formatNonZero(params.Count))
231 }
232 if params.ContextWindow != 0 {
233 toolParams = append(toolParams, "context", formatNonZero(params.ContextWindow))
234 }
235
236 header := toolHeader(sty, opts.Status, "Sourcegraph", width, opts.Compact, toolParams...)
237 if opts.Compact {
238 return header
239 }
240
241 if earlyState, ok := toolEarlyStateContent(sty, opts, width); ok {
242 return joinToolParts(header, earlyState)
243 }
244
245 if opts.HasEmptyResult() {
246 return header
247 }
248
249 bodyWidth := width - toolBodyLeftPaddingTotal
250 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
251 return joinToolParts(header, body)
252}