mirror of
https://github.com/charmbracelet/crush.git
synced 2026-05-30 18:47:33 +00:00
Prior to this, nested tool calls would start in one color and finish in another.
193 lines
6.0 KiB
Go
193 lines
6.0 KiB
Go
package chat
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"github.com/charmbracelet/crush/internal/agent/tools"
|
|
"github.com/charmbracelet/crush/internal/message"
|
|
"github.com/charmbracelet/crush/internal/ui/styles"
|
|
)
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Fetch Tool
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// FetchToolMessageItem is a message item that represents a fetch tool call.
|
|
type FetchToolMessageItem struct {
|
|
*baseToolMessageItem
|
|
}
|
|
|
|
var _ ToolMessageItem = (*FetchToolMessageItem)(nil)
|
|
|
|
// NewFetchToolMessageItem creates a new [FetchToolMessageItem].
|
|
func NewFetchToolMessageItem(
|
|
sty *styles.Styles,
|
|
toolCall message.ToolCall,
|
|
result *message.ToolResult,
|
|
canceled bool,
|
|
) ToolMessageItem {
|
|
return newBaseToolMessageItem(sty, toolCall, result, &FetchToolRenderContext{}, canceled)
|
|
}
|
|
|
|
// FetchToolRenderContext renders fetch tool messages.
|
|
type FetchToolRenderContext struct{}
|
|
|
|
// RenderTool implements the [ToolRenderer] interface.
|
|
func (f *FetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
|
|
cappedWidth := cappedMessageWidth(width)
|
|
if opts.IsPending() {
|
|
return pendingTool(sty, "Fetch", opts.Anim, opts.Compact)
|
|
}
|
|
|
|
var params tools.FetchParams
|
|
if err := json.Unmarshal([]byte(opts.ToolCall.Input), ¶ms); err != nil {
|
|
return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
|
|
}
|
|
|
|
toolParams := []string{params.URL}
|
|
if params.Format != "" {
|
|
toolParams = append(toolParams, "format", params.Format)
|
|
}
|
|
if params.Timeout != 0 {
|
|
toolParams = append(toolParams, "timeout", formatTimeout(params.Timeout))
|
|
}
|
|
|
|
header := toolHeader(sty, opts.Status, "Fetch", cappedWidth, opts.Compact, toolParams...)
|
|
if opts.Compact {
|
|
return header
|
|
}
|
|
|
|
if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
|
|
return joinToolParts(header, earlyState)
|
|
}
|
|
|
|
if opts.HasEmptyResult() {
|
|
return header
|
|
}
|
|
|
|
// Determine file extension for syntax highlighting based on format.
|
|
file := getFileExtensionForFormat(params.Format)
|
|
body := toolOutputCodeContent(sty, file, opts.Result.Content, 0, cappedWidth, opts.ExpandedContent)
|
|
return joinToolParts(header, body)
|
|
}
|
|
|
|
// getFileExtensionForFormat returns a filename with appropriate extension for syntax highlighting.
|
|
func getFileExtensionForFormat(format string) string {
|
|
switch format {
|
|
case "text":
|
|
return "fetch.txt"
|
|
case "html":
|
|
return "fetch.html"
|
|
default:
|
|
return "fetch.md"
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// WebFetch Tool
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// WebFetchToolMessageItem is a message item that represents a web_fetch tool call.
|
|
type WebFetchToolMessageItem struct {
|
|
*baseToolMessageItem
|
|
}
|
|
|
|
var _ ToolMessageItem = (*WebFetchToolMessageItem)(nil)
|
|
|
|
// NewWebFetchToolMessageItem creates a new [WebFetchToolMessageItem].
|
|
func NewWebFetchToolMessageItem(
|
|
sty *styles.Styles,
|
|
toolCall message.ToolCall,
|
|
result *message.ToolResult,
|
|
canceled bool,
|
|
) ToolMessageItem {
|
|
return newBaseToolMessageItem(sty, toolCall, result, &WebFetchToolRenderContext{}, canceled)
|
|
}
|
|
|
|
// WebFetchToolRenderContext renders web_fetch tool messages.
|
|
type WebFetchToolRenderContext struct{}
|
|
|
|
// RenderTool implements the [ToolRenderer] interface.
|
|
func (w *WebFetchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
|
|
cappedWidth := cappedMessageWidth(width)
|
|
if opts.IsPending() {
|
|
return pendingTool(sty, "Fetch", opts.Anim, opts.Compact)
|
|
}
|
|
|
|
var params tools.WebFetchParams
|
|
if err := json.Unmarshal([]byte(opts.ToolCall.Input), ¶ms); err != nil {
|
|
return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
|
|
}
|
|
|
|
toolParams := []string{params.URL}
|
|
header := toolHeader(sty, opts.Status, "Fetch", cappedWidth, opts.Compact, toolParams...)
|
|
if opts.Compact {
|
|
return header
|
|
}
|
|
|
|
if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
|
|
return joinToolParts(header, earlyState)
|
|
}
|
|
|
|
if opts.HasEmptyResult() {
|
|
return header
|
|
}
|
|
|
|
body := toolOutputMarkdownContent(sty, opts.Result.Content, cappedWidth, opts.ExpandedContent)
|
|
return joinToolParts(header, body)
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// WebSearch Tool
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// WebSearchToolMessageItem is a message item that represents a web_search tool call.
|
|
type WebSearchToolMessageItem struct {
|
|
*baseToolMessageItem
|
|
}
|
|
|
|
var _ ToolMessageItem = (*WebSearchToolMessageItem)(nil)
|
|
|
|
// NewWebSearchToolMessageItem creates a new [WebSearchToolMessageItem].
|
|
func NewWebSearchToolMessageItem(
|
|
sty *styles.Styles,
|
|
toolCall message.ToolCall,
|
|
result *message.ToolResult,
|
|
canceled bool,
|
|
) ToolMessageItem {
|
|
return newBaseToolMessageItem(sty, toolCall, result, &WebSearchToolRenderContext{}, canceled)
|
|
}
|
|
|
|
// WebSearchToolRenderContext renders web_search tool messages.
|
|
type WebSearchToolRenderContext struct{}
|
|
|
|
// RenderTool implements the [ToolRenderer] interface.
|
|
func (w *WebSearchToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
|
|
cappedWidth := cappedMessageWidth(width)
|
|
if opts.IsPending() {
|
|
return pendingTool(sty, "Search", opts.Anim, opts.Compact)
|
|
}
|
|
|
|
var params tools.WebSearchParams
|
|
if err := json.Unmarshal([]byte(opts.ToolCall.Input), ¶ms); err != nil {
|
|
return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
|
|
}
|
|
|
|
toolParams := []string{params.Query}
|
|
header := toolHeader(sty, opts.Status, "Search", cappedWidth, opts.Compact, toolParams...)
|
|
if opts.Compact {
|
|
return header
|
|
}
|
|
|
|
if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
|
|
return joinToolParts(header, earlyState)
|
|
}
|
|
|
|
if opts.HasEmptyResult() {
|
|
return header
|
|
}
|
|
|
|
body := toolOutputMarkdownContent(sty, opts.Result.Content, cappedWidth, opts.ExpandedContent)
|
|
return joinToolParts(header, body)
|
|
}
|