search.go

  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), &params); 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), &params); 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), &params); 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), &params); 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}