@@ -30,6 +30,23 @@ const (
3030 ToolStatusCanceled
3131)
3232
33+// ToolMessageItem represents a tool call message in the chat UI.
34+type ToolMessageItem interface {
35+ MessageItem
36+
37+ ToolCall() message.ToolCall
38+ SetToolCall(tc message.ToolCall)
39+ SetResult(res *message.ToolResult)
40+}
41+
42+// DefaultToolRenderContext implements the default [ToolRenderer] interface.
43+type DefaultToolRenderContext struct{}
44+
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
48+}
49+
3350// ToolRenderOpts contains the data needed to render a tool call.
3451type ToolRenderOpts struct {
3552 ToolCall message.ToolCall
@@ -60,21 +77,26 @@ func (opts *ToolRenderOpts) Status() ToolStatus {
6077 return ToolStatusRunning
6178}
6279
63−// ToolRenderFunc is a function that renders a tool call to a string.
64−type ToolRenderFunc func(sty *styles.Styles, width int, t *ToolRenderOpts) string
80+// ToolRenderer represents an interface for rendering tool calls.
81+type ToolRenderer interface {
82+ RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string
83+}
6584
66−// DefaultToolRenderer is a placeholder renderer for tools without a custom renderer.
67−func DefaultToolRenderer(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
68− return "TODO: Implement Tool Renderer For: " + opts.ToolCall.Name
85+// ToolRendererFunc is a function type that implements the [ToolRenderer] interface.
86+type ToolRendererFunc func(sty *styles.Styles, width int, opts *ToolRenderOpts) string
87+
88+// RenderTool implements the ToolRenderer interface.
89+func (f ToolRendererFunc) RenderTool(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
90+ return f(sty, width, opts)
6991}
7092
71−// ToolMessageItem represents a tool call message that can be displayed in the UI.
72−type ToolMessageItem struct {
93+// baseToolMessageItem represents a tool call message that can be displayed in the UI.
94+type baseToolMessageItem struct {
7395 *highlightableMessageItem
7496 *cachedMessageItem
7597 *focusableMessageItem
7698
77− renderFunc ToolRenderFunc
99+ toolRenderer ToolRenderer
78100 toolCall message.ToolCall
79101 result *message.ToolResult
80102 canceled bool
@@ -89,21 +111,23 @@ type ToolMessageItem struct {
89111 expanded bool
90112}
91113
92−// NewToolMessageItem creates a new tool message item with the given renderFunc.
93−func NewToolMessageItem(
114+// newBaseToolMessageItem is the internal constructor for base tool message items.
115+func newBaseToolMessageItem(
94116 sty *styles.Styles,
95117 toolCall message.ToolCall,
96118 result *message.ToolResult,
119+ toolRenderer ToolRenderer,
97120 canceled bool,
98−) *ToolMessageItem {
121+) *baseToolMessageItem {
99122 // we only do full width for diffs (as far as I know)
100123 hasCappedWidth := toolCall.Name != tools.EditToolName && toolCall.Name != tools.MultiEditToolName
101− t := &ToolMessageItem{
124+
125+ t := &baseToolMessageItem{
102126 highlightableMessageItem: defaultHighlighter(sty),
103127 cachedMessageItem: &cachedMessageItem{},
104128 focusableMessageItem: &focusableMessageItem{},
105129 sty: sty,
106− renderFunc: ToolRenderer(toolCall),
130+ toolRenderer: toolRenderer,
107131 toolCall: toolCall,
108132 result: result,
109133 canceled: canceled,
@@ -117,16 +141,42 @@ func NewToolMessageItem(
117141 LabelColor: sty.FgBase,
118142 CycleColors: true,
119143 })
144+
120145 return t
121146}
122147
148+// NewToolMessageItem creates a new [ToolMessageItem] based on the tool call name.
149+//
150+// It returns a specific tool message item type if implemented, otherwise it
151+// returns a generic tool message item.
152+func NewToolMessageItem(
153+ sty *styles.Styles,
154+ toolCall message.ToolCall,
155+ result *message.ToolResult,
156+ canceled bool,
157+) ToolMessageItem {
158+ switch toolCall.Name {
159+ case tools.BashToolName:
160+ return NewBashToolMessageItem(sty, toolCall, result, canceled)
161+ default:
162+ // TODO: Implement other tool items
163+ return newBaseToolMessageItem(
164+ sty,
165+ toolCall,
166+ result,
167+ &DefaultToolRenderContext{},
168+ canceled,
169+ )
170+ }
171+}
172+
123173// ID returns the unique identifier for this tool message item.
124−func (t *ToolMessageItem) ID() string {
174+func (t *baseToolMessageItem) ID() string {
125175 return t.toolCall.ID
126176}
127177
128178// 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 {
130180 if !t.isSpinning() {
131181 return nil
132182 }
@@ -134,7 +184,7 @@ func (t *ToolMessageItem) StartAnimation() tea.Cmd {
134184}
135185
136186// 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 {
138188 if !t.isSpinning() {
139189 return nil
140190 }
@@ -142,7 +192,7 @@ func (t *ToolMessageItem) Animate(msg anim.StepMsg) tea.Cmd {
142192}
143193
144194// 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 {
146196 toolItemWidth := width - messageLeftPaddingTotal
147197 if t.hasCappedWidth {
148198 toolItemWidth = cappedMessageWidth(width)
@@ -155,7 +205,7 @@ func (t *ToolMessageItem) Render(width int) string {
155205 content, height, ok := t.getCachedRender(toolItemWidth)
156206 // if we are spinning or there is no cache rerender
157207 if !ok || t.isSpinning() {
158− content = t.renderFunc(t.sty, toolItemWidth, &ToolRenderOpts{
208+ content = t.toolRenderer.RenderTool(t.sty, toolItemWidth, &ToolRenderOpts{
159209 ToolCall: t.toolCall,
160210 Result: t.result,
161211 Canceled: t.canceled,
@@ -175,47 +225,51 @@ func (t *ToolMessageItem) Render(width int) string {
175225}
176226
177227// ToolCall returns the tool call associated with this message item.
178−func (t *ToolMessageItem) ToolCall() message.ToolCall {
228+func (t *baseToolMessageItem) ToolCall() message.ToolCall {
179229 return t.toolCall
180230}
181231
182232// 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) {
184234 t.toolCall = tc
185235 t.clearCache()
186236}
187237
188238// 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) {
190240 t.result = res
191241 t.clearCache()
192242}
193243
194244// SetPermissionRequested sets whether permission has been requested for this tool call.
195−func (t *ToolMessageItem) SetPermissionRequested(requested bool) {
245+// TODO: Consider merging with SetPermissionGranted and add an interface for
246+// permission management.
247+func (t *baseToolMessageItem) SetPermissionRequested(requested bool) {
196248 t.permissionRequested = requested
197249 t.clearCache()
198250}
199251
200252// SetPermissionGranted sets whether permission has been granted for this tool call.
201−func (t *ToolMessageItem) SetPermissionGranted(granted bool) {
253+// TODO: Consider merging with SetPermissionRequested and add an interface for
254+// permission management.
255+func (t *baseToolMessageItem) SetPermissionGranted(granted bool) {
202256 t.permissionGranted = granted
203257 t.clearCache()
204258}
205259
206260// isSpinning returns true if the tool should show animation.
207−func (t *ToolMessageItem) isSpinning() bool {
261+func (t *baseToolMessageItem) isSpinning() bool {
208262 return !t.toolCall.Finished && !t.canceled
209263}
210264
211265// ToggleExpanded toggles the expanded state of the thinking box.
212−func (t *ToolMessageItem) ToggleExpanded() {
266+func (t *baseToolMessageItem) ToggleExpanded() {
213267 t.expanded = !t.expanded
214268 t.clearCache()
215269}
216270
217271// 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 {
219273 if btn != ansi.MouseLeft {
220274 return false
221275 }