@@ -30,6 +30,23 @@ const (
30 ToolStatusCanceled
30 ToolStatusCanceled
33+// ToolMessageItem represents a tool call message in the chat UI.
34+type ToolMessageItem interface {
37+ ToolCall() message.ToolCall
38+ SetToolCall(tc message.ToolCall)
39+ SetResult(res *message.ToolResult)
42+// DefaultToolRenderContext implements the default [ToolRenderer] interface.
43+type DefaultToolRenderContext struct{}
45+// RenderTool implements the [ToolRenderer] interface.
46+func (d *DefaultToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
47+ return "TODO: Implement Tool Renderer For: " + opts.ToolCall.Name
33// ToolRenderOpts contains the data needed to render a tool call.
50// ToolRenderOpts contains the data needed to render a tool call.
34type ToolRenderOpts struct {
51type ToolRenderOpts struct {
35 ToolCall message.ToolCall
52 ToolCall message.ToolCall
@@ -60,21 +77,26 @@ func (opts *ToolRenderOpts) Status() ToolStatus {
60 return ToolStatusRunning
77 return ToolStatusRunning
63−// ToolRenderFunc is a function that renders a tool call to a string.
80+// ToolRenderer represents an interface for rendering tool calls.
81+type ToolRenderer interface {
64−type ToolRenderFunc func(sty *styles.Styles, width int, t *ToolRenderOpts) string
82+ RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string
66−// DefaultToolRenderer is a placeholder renderer for tools without a custom renderer.
85+// ToolRendererFunc is a function type that implements the [ToolRenderer] interface.
86+type ToolRendererFunc func(sty *styles.Styles, width int, opts *ToolRenderOpts) string
88+// RenderTool implements the ToolRenderer interface.
67−func DefaultToolRenderer(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
89+func (f ToolRendererFunc) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
68− return "TODO: Implement Tool Renderer For: " + opts.ToolCall.Name
90+ return f(sty, width, opts)
71−// ToolMessageItem represents a tool call message that can be displayed in the UI.
93+// baseToolMessageItem represents a tool call message that can be displayed in the UI.
72−type ToolMessageItem struct {
94+type baseToolMessageItem struct {
73 *highlightableMessageItem
95 *highlightableMessageItem
74 *cachedMessageItem
96 *cachedMessageItem
75 *focusableMessageItem
97 *focusableMessageItem
77− renderFunc ToolRenderFunc
99+ toolRenderer ToolRenderer
78 toolCall message.ToolCall
100 toolCall message.ToolCall
79 result *message.ToolResult
101 result *message.ToolResult
80 canceled bool
102 canceled bool
@@ -89,21 +111,23 @@ type ToolMessageItem struct {
89 expanded bool
111 expanded bool
92−// NewToolMessageItem creates a new tool message item with the given renderFunc.
114+// newBaseToolMessageItem is the internal constructor for base tool message items.
93−func NewToolMessageItem(
115+func newBaseToolMessageItem(
94 sty *styles.Styles,
116 sty *styles.Styles,
95 toolCall message.ToolCall,
117 toolCall message.ToolCall,
96 result *message.ToolResult,
118 result *message.ToolResult,
119+ toolRenderer ToolRenderer,
97 canceled bool,
120 canceled bool,
98−) *ToolMessageItem {
121+) *baseToolMessageItem {
99 // we only do full width for diffs (as far as I know)
122 // we only do full width for diffs (as far as I know)
100 hasCappedWidth := toolCall.Name != tools.EditToolName && toolCall.Name != tools.MultiEditToolName
123 hasCappedWidth := toolCall.Name != tools.EditToolName && toolCall.Name != tools.MultiEditToolName
101− t := &ToolMessageItem{
124+
125+ t := &baseToolMessageItem{
102 highlightableMessageItem: defaultHighlighter(sty),
126 highlightableMessageItem: defaultHighlighter(sty),
103 cachedMessageItem: &cachedMessageItem{},
127 cachedMessageItem: &cachedMessageItem{},
104 focusableMessageItem: &focusableMessageItem{},
128 focusableMessageItem: &focusableMessageItem{},
105 sty: sty,
129 sty: sty,
106− renderFunc: ToolRenderer(toolCall),
130+ toolRenderer: toolRenderer,
107 toolCall: toolCall,
131 toolCall: toolCall,
108 result: result,
132 result: result,
109 canceled: canceled,
133 canceled: canceled,
@@ -117,16 +141,42 @@ func NewToolMessageItem(
117 LabelColor: sty.FgBase,
141 LabelColor: sty.FgBase,
118 CycleColors: true,
142 CycleColors: true,
148+// NewToolMessageItem creates a new [ToolMessageItem] based on the tool call name.
150+// It returns a specific tool message item type if implemented, otherwise it
151+// returns a generic tool message item.
152+func NewToolMessageItem(
154+ toolCall message.ToolCall,
155+ result *message.ToolResult,
158+ switch toolCall.Name {
159+ case tools.BashToolName:
160+ return NewBashToolMessageItem(sty, toolCall, result, canceled)
162+ // TODO: Implement other tool items
163+ return newBaseToolMessageItem(
167+ &DefaultToolRenderContext{},
123// ID returns the unique identifier for this tool message item.
173// ID returns the unique identifier for this tool message item.
124−func (t *ToolMessageItem) ID() string {
174+func (t *baseToolMessageItem) ID() string {
125 return t.toolCall.ID
175 return t.toolCall.ID
128// StartAnimation starts the assistant message animation if it should be spinning.
178// StartAnimation starts the assistant message animation if it should be spinning.
129−func (t *ToolMessageItem) StartAnimation() tea.Cmd {
179+func (t *baseToolMessageItem) StartAnimation() tea.Cmd {
130 if !t.isSpinning() {
180 if !t.isSpinning() {
131 return nil
181 return nil
@@ -134,7 +184,7 @@ func (t *ToolMessageItem) StartAnimation() tea.Cmd {
136// Animate progresses the assistant message animation if it should be spinning.
186// Animate progresses the assistant message animation if it should be spinning.
137−func (t *ToolMessageItem) Animate(msg anim.StepMsg) tea.Cmd {
187+func (t *baseToolMessageItem) Animate(msg anim.StepMsg) tea.Cmd {
138 if !t.isSpinning() {
188 if !t.isSpinning() {
139 return nil
189 return nil
@@ -142,7 +192,7 @@ func (t *ToolMessageItem) Animate(msg anim.StepMsg) tea.Cmd {
144// Render renders the tool message item at the given width.
194// Render renders the tool message item at the given width.
145−func (t *ToolMessageItem) Render(width int) string {
195+func (t *baseToolMessageItem) Render(width int) string {
146 toolItemWidth := width - messageLeftPaddingTotal
196 toolItemWidth := width - messageLeftPaddingTotal
147 if t.hasCappedWidth {
197 if t.hasCappedWidth {
148 toolItemWidth = cappedMessageWidth(width)
198 toolItemWidth = cappedMessageWidth(width)
@@ -155,7 +205,7 @@ func (t *ToolMessageItem) Render(width int) string {
155 content, height, ok := t.getCachedRender(toolItemWidth)
205 content, height, ok := t.getCachedRender(toolItemWidth)
156 // if we are spinning or there is no cache rerender
206 // if we are spinning or there is no cache rerender
157 if !ok || t.isSpinning() {
207 if !ok || t.isSpinning() {
158− content = t.renderFunc(t.sty, toolItemWidth, &ToolRenderOpts{
208+ content = t.toolRenderer.RenderTool(t.sty, toolItemWidth, &ToolRenderOpts{
159 ToolCall: t.toolCall,
209 ToolCall: t.toolCall,
160 Result: t.result,
210 Result: t.result,
161 Canceled: t.canceled,
211 Canceled: t.canceled,
@@ -175,47 +225,51 @@ func (t *ToolMessageItem) Render(width int) string {
177// ToolCall returns the tool call associated with this message item.
227// ToolCall returns the tool call associated with this message item.
178−func (t *ToolMessageItem) ToolCall() message.ToolCall {
228+func (t *baseToolMessageItem) ToolCall() message.ToolCall {
179 return t.toolCall
229 return t.toolCall
182// SetToolCall sets the tool call associated with this message item.
232// SetToolCall sets the tool call associated with this message item.
183−func (t *ToolMessageItem) SetToolCall(tc message.ToolCall) {
233+func (t *baseToolMessageItem) SetToolCall(tc message.ToolCall) {
184 t.toolCall = tc
234 t.toolCall = tc
185 t.clearCache()
235 t.clearCache()
188// SetResult sets the tool result associated with this message item.
238// SetResult sets the tool result associated with this message item.
189−func (t *ToolMessageItem) SetResult(res *message.ToolResult) {
239+func (t *baseToolMessageItem) SetResult(res *message.ToolResult) {
190 t.result = res
240 t.result = res
191 t.clearCache()
241 t.clearCache()
194// SetPermissionRequested sets whether permission has been requested for this tool call.
244// SetPermissionRequested sets whether permission has been requested for this tool call.
245+// TODO: Consider merging with SetPermissionGranted and add an interface for
246+// permission management.
195−func (t *ToolMessageItem) SetPermissionRequested(requested bool) {
247+func (t *baseToolMessageItem) SetPermissionRequested(requested bool) {
196 t.permissionRequested = requested
248 t.permissionRequested = requested
197 t.clearCache()
249 t.clearCache()
200// SetPermissionGranted sets whether permission has been granted for this tool call.
252// SetPermissionGranted sets whether permission has been granted for this tool call.
253+// TODO: Consider merging with SetPermissionRequested and add an interface for
254+// permission management.
201−func (t *ToolMessageItem) SetPermissionGranted(granted bool) {
255+func (t *baseToolMessageItem) SetPermissionGranted(granted bool) {
202 t.permissionGranted = granted
256 t.permissionGranted = granted
203 t.clearCache()
257 t.clearCache()
206// isSpinning returns true if the tool should show animation.
260// isSpinning returns true if the tool should show animation.
207−func (t *ToolMessageItem) isSpinning() bool {
261+func (t *baseToolMessageItem) isSpinning() bool {
208 return !t.toolCall.Finished && !t.canceled
262 return !t.toolCall.Finished && !t.canceled
211// ToggleExpanded toggles the expanded state of the thinking box.
265// ToggleExpanded toggles the expanded state of the thinking box.
212−func (t *ToolMessageItem) ToggleExpanded() {
266+func (t *baseToolMessageItem) ToggleExpanded() {
213 t.expanded = !t.expanded
267 t.expanded = !t.expanded
214 t.clearCache()
268 t.clearCache()
217// HandleMouseClick implements MouseClickable.
271// HandleMouseClick implements MouseClickable.
218−func (t *ToolMessageItem) HandleMouseClick(btn ansi.MouseButton, x, y int) bool {
272+func (t *baseToolMessageItem) HandleMouseClick(btn ansi.MouseButton, x, y int) bool {
219 if btn != ansi.MouseLeft {
273 if btn != ansi.MouseLeft {
220 return false
274 return false