tool.go

  1package messages
  2
  3import (
  4	"fmt"
  5
  6	tea "github.com/charmbracelet/bubbletea/v2"
  7	"github.com/charmbracelet/lipgloss/v2"
  8	"github.com/charmbracelet/x/ansi"
  9	"github.com/opencode-ai/opencode/internal/llm/agent"
 10	"github.com/opencode-ai/opencode/internal/llm/tools"
 11	"github.com/opencode-ai/opencode/internal/message"
 12	"github.com/opencode-ai/opencode/internal/tui/layout"
 13	"github.com/opencode-ai/opencode/internal/tui/styles"
 14	"github.com/opencode-ai/opencode/internal/tui/theme"
 15	"github.com/opencode-ai/opencode/internal/tui/util"
 16)
 17
 18type ToolCallCmp interface {
 19	util.Model
 20	layout.Sizeable
 21	layout.Focusable
 22	GetToolCall() message.ToolCall
 23	GetToolResult() message.ToolResult
 24}
 25
 26type toolCallCmp struct {
 27	width   int
 28	focused bool
 29
 30	call      message.ToolCall
 31	result    message.ToolResult
 32	cancelled bool
 33}
 34
 35type ToolCallOption func(*toolCallCmp)
 36
 37func WithToolCallCancelled() ToolCallOption {
 38	return func(m *toolCallCmp) {
 39		m.cancelled = true
 40	}
 41}
 42
 43func WithToolCallResult(result message.ToolResult) ToolCallOption {
 44	return func(m *toolCallCmp) {
 45		m.result = result
 46	}
 47}
 48
 49func NewToolCallCmp(tc message.ToolCall, opts ...ToolCallOption) ToolCallCmp {
 50	m := &toolCallCmp{
 51		call: tc,
 52	}
 53	for _, opt := range opts {
 54		opt(m)
 55	}
 56	return m
 57}
 58
 59func (m *toolCallCmp) Init() tea.Cmd {
 60	return nil
 61}
 62
 63func (m *toolCallCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 64	return m, nil
 65}
 66
 67func (m *toolCallCmp) View() string {
 68	box := m.style()
 69
 70	if !m.call.Finished && !m.cancelled {
 71		return box.PaddingLeft(1).Render(m.renderPending())
 72	}
 73
 74	r := registry.lookup(m.call.Name)
 75	return box.PaddingLeft(1).Render(r.Render(m))
 76}
 77
 78// GetToolCall implements ToolCallCmp.
 79func (m *toolCallCmp) GetToolCall() message.ToolCall {
 80	return m.call
 81}
 82
 83// GetToolResult implements ToolCallCmp.
 84func (m *toolCallCmp) GetToolResult() message.ToolResult {
 85	return m.result
 86}
 87
 88func (m *toolCallCmp) renderPending() string {
 89	return fmt.Sprintf("%s: %s", prettifyToolName(m.call.Name), toolAction(m.call.Name))
 90}
 91
 92func (m *toolCallCmp) style() lipgloss.Style {
 93	t := theme.CurrentTheme()
 94	borderStyle := lipgloss.NormalBorder()
 95	if m.focused {
 96		borderStyle = lipgloss.DoubleBorder()
 97	}
 98	return styles.BaseStyle().
 99		BorderLeft(true).
100		Foreground(t.TextMuted()).
101		BorderForeground(t.TextMuted()).
102		BorderStyle(borderStyle)
103}
104
105func (m *toolCallCmp) textWidth() int {
106	return m.width - 2 // take into account the border and PaddingLeft
107}
108
109func (m *toolCallCmp) fit(content string, width int) string {
110	t := theme.CurrentTheme()
111	lineStyle := lipgloss.NewStyle().Background(t.BackgroundSecondary()).Foreground(t.TextMuted())
112	dots := lineStyle.Render("...")
113	return ansi.Truncate(content, width, dots)
114}
115
116func (m *toolCallCmp) toolName() string {
117	switch m.call.Name {
118	case agent.AgentToolName:
119		return "Task"
120	case tools.BashToolName:
121		return "Bash"
122	case tools.EditToolName:
123		return "Edit"
124	case tools.FetchToolName:
125		return "Fetch"
126	case tools.GlobToolName:
127		return "Glob"
128	case tools.GrepToolName:
129		return "Grep"
130	case tools.LSToolName:
131		return "List"
132	case tools.SourcegraphToolName:
133		return "Sourcegraph"
134	case tools.ViewToolName:
135		return "View"
136	case tools.WriteToolName:
137		return "Write"
138	case tools.PatchToolName:
139		return "Patch"
140	default:
141		return m.call.Name
142	}
143}
144
145func (m *toolCallCmp) Blur() tea.Cmd {
146	m.focused = false
147	return nil
148}
149
150func (m *toolCallCmp) Focus() tea.Cmd {
151	m.focused = true
152	return nil
153}
154
155// IsFocused implements MessageModel.
156func (m *toolCallCmp) IsFocused() bool {
157	return m.focused
158}
159
160func (m *toolCallCmp) GetSize() (int, int) {
161	return m.width, 0
162}
163
164func (m *toolCallCmp) SetSize(width int, height int) tea.Cmd {
165	m.width = width
166	return nil
167}