1package chat
2
3import (
4 "encoding/json"
5 "fmt"
6 "strings"
7
8 "github.com/charmbracelet/crush/internal/message"
9 "github.com/charmbracelet/crush/internal/ui/styles"
10)
11
12// MCPToolMessageItem is a message item that represents a bash tool call.
13type MCPToolMessageItem struct {
14 *baseToolMessageItem
15}
16
17var _ ToolMessageItem = (*MCPToolMessageItem)(nil)
18
19// NewMCPToolMessageItem creates a new [MCPToolMessageItem].
20func NewMCPToolMessageItem(
21 sty *styles.Styles,
22 toolCall message.ToolCall,
23 result *message.ToolResult,
24 canceled bool,
25) ToolMessageItem {
26 return newBaseToolMessageItem(sty, toolCall, result, &MCPToolRenderContext{}, canceled)
27}
28
29// MCPToolRenderContext renders bash tool messages.
30type MCPToolRenderContext struct{}
31
32// RenderTool implements the [ToolRenderer] interface.
33func (b *MCPToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
34 cappedWidth := cappedMessageWidth(width)
35 toolNameParts := strings.SplitN(opts.ToolCall.Name, "_", 3)
36 if len(toolNameParts) != 3 {
37 return toolErrorContent(sty, &message.ToolResult{Content: "Invalid tool name"}, cappedWidth)
38 }
39 mcpName := humanizedToolName(toolNameParts[1])
40 toolName := humanizedToolName(toolNameParts[2])
41
42 mcpName = sty.Tool.MCPName.Render(mcpName)
43 toolName = sty.Tool.MCPToolName.Render(toolName)
44
45 name := fmt.Sprintf("%s %s %s", mcpName, sty.Tool.MCPArrow.String(), toolName)
46
47 if opts.IsPending() {
48 return pendingTool(sty, name, opts.Anim, opts.Compact)
49 }
50
51 var params map[string]any
52 if err := json.Unmarshal([]byte(opts.ToolCall.Input), ¶ms); err != nil {
53 return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
54 }
55
56 var toolParams []string
57 if len(params) > 0 {
58 parsed, _ := json.Marshal(params)
59 toolParams = append(toolParams, string(parsed))
60 }
61
62 header := toolHeader(sty, opts.Status, name, cappedWidth, opts.Compact, toolParams...)
63 if opts.Compact {
64 return header
65 }
66
67 if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
68 return joinToolParts(header, earlyState)
69 }
70
71 if !opts.HasResult() || opts.Result.Content == "" {
72 return header
73 }
74
75 bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
76 body := renderToolResultTextContent(sty, opts.Result.Content, toolResultContentWidths{Body: bodyWidth, Diff: cappedWidth}, opts.ExpandedContent)
77 return joinToolParts(header, body)
78}