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 cappedWidth := cappedMessageWidth(width)
39 if !opts.ToolCall.Finished && !opts.Canceled {
40 return pendingTool(sty, "Glob", opts.Anim)
41 }
42
43 var params tools.GlobParams
44 if err := json.Unmarshal([]byte(opts.ToolCall.Input), ¶ms); err != nil {
45 return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
46 }
47
48 toolParams := []string{params.Pattern}
49 if params.Path != "" {
50 toolParams = append(toolParams, "path", params.Path)
51 }
52
53 header := toolHeader(sty, opts.Status(), "Glob", cappedWidth, opts.Nested, toolParams...)
54 if opts.Nested {
55 return header
56 }
57
58 if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
59 return joinToolParts(header, earlyState)
60 }
61
62 if opts.Result == nil || opts.Result.Content == "" {
63 return header
64 }
65
66 bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
67 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.Expanded))
68 return joinToolParts(header, body)
69}
70
71// -----------------------------------------------------------------------------
72// Grep Tool
73// -----------------------------------------------------------------------------
74
75// GrepToolMessageItem is a message item that represents a grep tool call.
76type GrepToolMessageItem struct {
77 *baseToolMessageItem
78}
79
80var _ ToolMessageItem = (*GrepToolMessageItem)(nil)
81
82// NewGrepToolMessageItem creates a new [GrepToolMessageItem].
83func NewGrepToolMessageItem(
84 sty *styles.Styles,
85 toolCall message.ToolCall,
86 result *message.ToolResult,
87 canceled bool,
88) ToolMessageItem {
89 return newBaseToolMessageItem(sty, toolCall, result, &GrepToolRenderContext{}, canceled)
90}
91
92// GrepToolRenderContext renders grep tool messages.
93type GrepToolRenderContext struct{}
94
95// RenderTool implements the [ToolRenderer] interface.
96func (g *GrepToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
97 cappedWidth := cappedMessageWidth(width)
98 if !opts.ToolCall.Finished && !opts.Canceled {
99 return pendingTool(sty, "Grep", opts.Anim)
100 }
101
102 var params tools.GrepParams
103 if err := json.Unmarshal([]byte(opts.ToolCall.Input), ¶ms); err != nil {
104 return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
105 }
106
107 toolParams := []string{params.Pattern}
108 if params.Path != "" {
109 toolParams = append(toolParams, "path", params.Path)
110 }
111 if params.Include != "" {
112 toolParams = append(toolParams, "include", params.Include)
113 }
114 if params.LiteralText {
115 toolParams = append(toolParams, "literal", "true")
116 }
117
118 header := toolHeader(sty, opts.Status(), "Grep", cappedWidth, opts.Nested, toolParams...)
119 if opts.Nested {
120 return header
121 }
122
123 if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
124 return joinToolParts(header, earlyState)
125 }
126
127 if opts.Result == nil || opts.Result.Content == "" {
128 return header
129 }
130
131 bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
132 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.Expanded))
133 return joinToolParts(header, body)
134}
135
136// -----------------------------------------------------------------------------
137// LS Tool
138// -----------------------------------------------------------------------------
139
140// LSToolMessageItem is a message item that represents an ls tool call.
141type LSToolMessageItem struct {
142 *baseToolMessageItem
143}
144
145var _ ToolMessageItem = (*LSToolMessageItem)(nil)
146
147// NewLSToolMessageItem creates a new [LSToolMessageItem].
148func NewLSToolMessageItem(
149 sty *styles.Styles,
150 toolCall message.ToolCall,
151 result *message.ToolResult,
152 canceled bool,
153) ToolMessageItem {
154 return newBaseToolMessageItem(sty, toolCall, result, &LSToolRenderContext{}, canceled)
155}
156
157// LSToolRenderContext renders ls tool messages.
158type LSToolRenderContext struct{}
159
160// RenderTool implements the [ToolRenderer] interface.
161func (l *LSToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
162 cappedWidth := cappedMessageWidth(width)
163 if !opts.ToolCall.Finished && !opts.Canceled {
164 return pendingTool(sty, "List", opts.Anim)
165 }
166
167 var params tools.LSParams
168 if err := json.Unmarshal([]byte(opts.ToolCall.Input), ¶ms); err != nil {
169 return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
170 }
171
172 path := params.Path
173 if path == "" {
174 path = "."
175 }
176 path = fsext.PrettyPath(path)
177
178 header := toolHeader(sty, opts.Status(), "List", cappedWidth, opts.Nested, path)
179 if opts.Nested {
180 return header
181 }
182
183 if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
184 return joinToolParts(header, earlyState)
185 }
186
187 if opts.Result == nil || opts.Result.Content == "" {
188 return header
189 }
190
191 bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
192 body := sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.Expanded))
193 return joinToolParts(header, body)
194}