@@ -0,0 +1,98 @@
+package chat
+
+import (
+ "encoding/json"
+ "strings"
+
+ "github.com/charmbracelet/crush/internal/message"
+ "github.com/charmbracelet/crush/internal/stringext"
+ "github.com/charmbracelet/crush/internal/ui/styles"
+)
+
+// GenericToolMessageItem is a message item that represents an unknown tool call.
+type GenericToolMessageItem struct {
+ *baseToolMessageItem
+}
+
+var _ ToolMessageItem = (*GenericToolMessageItem)(nil)
+
+// NewGenericToolMessageItem creates a new [GenericToolMessageItem].
+func NewGenericToolMessageItem(
+ sty *styles.Styles,
+ toolCall message.ToolCall,
+ result *message.ToolResult,
+ canceled bool,
+) ToolMessageItem {
+ return newBaseToolMessageItem(sty, toolCall, result, &GenericToolRenderContext{}, canceled)
+}
+
+// GenericToolRenderContext renders unknown/generic tool messages.
+type GenericToolRenderContext struct{}
+
+// RenderTool implements the [ToolRenderer] interface.
+func (g *GenericToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
+ cappedWidth := cappedMessageWidth(width)
+ name := genericPrettyName(opts.ToolCall.Name)
+
+ if opts.IsPending() {
+ return pendingTool(sty, name, opts.Anim)
+ }
+
+ var params map[string]any
+ if err := json.Unmarshal([]byte(opts.ToolCall.Input), ¶ms); err != nil {
+ return toolErrorContent(sty, &message.ToolResult{Content: "Invalid parameters"}, cappedWidth)
+ }
+
+ var toolParams []string
+ if len(params) > 0 {
+ parsed, _ := json.Marshal(params)
+ toolParams = append(toolParams, string(parsed))
+ }
+
+ header := toolHeader(sty, opts.Status, name, cappedWidth, opts.Compact, toolParams...)
+ if opts.Compact {
+ return header
+ }
+
+ if earlyState, ok := toolEarlyStateContent(sty, opts, cappedWidth); ok {
+ return joinToolParts(header, earlyState)
+ }
+
+ if !opts.HasResult() || opts.Result.Content == "" {
+ return header
+ }
+
+ bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
+
+ // Handle image data.
+ if opts.Result.Data != "" && strings.HasPrefix(opts.Result.MIMEType, "image/") {
+ body := sty.Tool.Body.Render(toolOutputImageContent(sty, opts.Result.Data, opts.Result.MIMEType))
+ return joinToolParts(header, body)
+ }
+
+ // Try to parse result as JSON for pretty display.
+ var result json.RawMessage
+ var body string
+ if err := json.Unmarshal([]byte(opts.Result.Content), &result); err == nil {
+ prettyResult, err := json.MarshalIndent(result, "", " ")
+ if err == nil {
+ body = sty.Tool.Body.Render(toolOutputCodeContent(sty, "result.json", string(prettyResult), 0, bodyWidth, opts.ExpandedContent))
+ } else {
+ body = sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
+ }
+ } else if looksLikeMarkdown(opts.Result.Content) {
+ body = sty.Tool.Body.Render(toolOutputCodeContent(sty, "result.md", opts.Result.Content, 0, bodyWidth, opts.ExpandedContent))
+ } else {
+ body = sty.Tool.Body.Render(toolOutputPlainContent(sty, opts.Result.Content, bodyWidth, opts.ExpandedContent))
+ }
+
+ return joinToolParts(header, body)
+}
+
+// genericPrettyName converts a snake_case or kebab-case tool name to a
+// human-readable title case name.
+func genericPrettyName(name string) string {
+ name = strings.ReplaceAll(name, "_", " ")
+ name = strings.ReplaceAll(name, "-", " ")
+ return stringext.Capitalize(name)
+}
@@ -255,14 +255,7 @@ func NewToolMessageItem(
if strings.HasPrefix(toolCall.Name, "mcp_") {
item = NewMCPToolMessageItem(sty, toolCall, result, canceled)
} else {
- // TODO: Implement other tool items
- item = newBaseToolMessageItem(
- sty,
- toolCall,
- result,
- &DefaultToolRenderContext{},
- canceled,
- )
+ item = NewGenericToolMessageItem(sty, toolCall, result, canceled)
}
}
item.SetMessageID(messageID)
@@ -1399,6 +1392,6 @@ func prettifyToolName(name string) string {
case tools.WriteToolName:
return "Write"
default:
- return name
+ return genericPrettyName(name)
}
}
@@ -1124,11 +1124,6 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
cmds = append(cmds, m.toggleCompactMode())
m.dialog.CloseDialog(dialog.CommandsID)
case dialog.ActionToggleThinking:
- if m.isAgentBusy() {
- cmds = append(cmds, uiutil.ReportWarn("Agent is busy, please wait..."))
- break
- }
-
cmds = append(cmds, func() tea.Msg {
cfg := m.com.Config()
if cfg == nil {