@@ -3,6 +3,7 @@ package messages
4 "encoding/json"
4 "encoding/json"
@@ -95,7 +96,7 @@ func (br baseRenderer) renderWithParams(v *toolCallCmp, toolName string, args []
95 if v.isNested {
96 if v.isNested {
96 width -= 4 // Adjust for nested tool call indentation
97 width -= 4 // Adjust for nested tool call indentation
98− header := makeHeader(toolName, width, args...)
99+ header := br.makeHeader(v, toolName, width, args...)
99 if v.isNested {
100 if v.isNested {
100 return v.style().Render(header)
101 return v.style().Render(header)
@@ -111,6 +112,32 @@ func (br baseRenderer) unmarshalParams(input string, target any) error {
111 return json.Unmarshal([]byte(input), target)
112 return json.Unmarshal([]byte(input), target)
115+// makeHeader builds "<Tool>: param (key=value)" and truncates as needed.
116+func (br baseRenderer) makeHeader(v *toolCallCmp, tool string, width int, params ...string) string {
117+ t := styles.CurrentTheme()
118+ icon := t.S().Base.Foreground(t.GreenDark).Render(styles.ToolPending)
119+ if v.result.ToolCallID != "" {
120+ if v.result.IsError {
121+ icon = t.S().Base.Foreground(t.RedDark).Render(styles.ToolError)
123+ icon = t.S().Base.Foreground(t.Green).Render(styles.ToolSuccess)
125+ } else if v.cancelled {
126+ icon = t.S().Muted.Render(styles.ToolPending)
128+ tool = t.S().Base.Foreground(t.Blue).Render(tool)
129+ prefix := fmt.Sprintf("%s %s: ", icon, tool)
130+ return prefix + renderParamList(width-lipgloss.Width(prefix), params...)
133+// renderError provides consistent error rendering
134+func (br baseRenderer) renderError(v *toolCallCmp, message string) string {
135+ t := styles.CurrentTheme()
136+ header := br.makeHeader(v, prettifyToolName(v.call.Name), v.textWidth(), "")
137+ message = t.S().Error.Render(v.fit(message, v.textWidth()-2)) // -2 for padding
138+ return joinHeaderBody(header, message)
114// Register tool renderers
141// Register tool renderers
115func init() {
142func init() {
116 registry.register(tools.BashToolName, func() renderer { return bashRenderer{} })
143 registry.register(tools.BashToolName, func() renderer { return bashRenderer{} })
@@ -167,12 +194,6 @@ func (br bashRenderer) Render(v *toolCallCmp) string {
170−// renderError provides consistent error rendering
171−func (br baseRenderer) renderError(v *toolCallCmp, message string) string {
172− header := makeHeader("Error", v.textWidth(), message)
173− return joinHeaderBody(header, "")
176// -----------------------------------------------------------------------------
197// -----------------------------------------------------------------------------
177// View renderer
198// View renderer
178// -----------------------------------------------------------------------------
199// -----------------------------------------------------------------------------
@@ -189,7 +210,7 @@ func (vr viewRenderer) Render(v *toolCallCmp) string {
189 return vr.renderError(v, "Invalid view parameters")
210 return vr.renderError(v, "Invalid view parameters")
192− file := removeWorkingDirPrefix(params.FilePath)
213+ file := prettyPath(params.FilePath)
193 args := newParamBuilder().
214 args := newParamBuilder().
194 addMain(file).
215 addMain(file).
195 addKeyValue("limit", formatNonZero(params.Limit)).
216 addKeyValue("limit", formatNonZero(params.Limit)).
@@ -229,7 +250,7 @@ func (er editRenderer) Render(v *toolCallCmp) string {
229 return er.renderError(v, "Invalid edit parameters")
250 return er.renderError(v, "Invalid edit parameters")
232− file := removeWorkingDirPrefix(params.FilePath)
253+ file := prettyPath(params.FilePath)
233 args := newParamBuilder().addMain(file).build()
254 args := newParamBuilder().addMain(file).build()
235 return er.renderWithParams(v, "Edit", args, func() string {
256 return er.renderWithParams(v, "Edit", args, func() string {
@@ -239,7 +260,7 @@ func (er editRenderer) Render(v *toolCallCmp) string {
241 trunc := truncateHeight(meta.Diff, responseContextHeight)
262 trunc := truncateHeight(meta.Diff, responseContextHeight)
242− diffView, _ := diff.FormatDiff(trunc, diff.WithTotalWidth(v.textWidth()))
263+ diffView, _ := diff.FormatDiff(trunc, diff.WithTotalWidth(v.textWidth()-2))
243 return diffView
264 return diffView
@@ -260,7 +281,7 @@ func (wr writeRenderer) Render(v *toolCallCmp) string {
260 return wr.renderError(v, "Invalid write parameters")
281 return wr.renderError(v, "Invalid write parameters")
263− file := removeWorkingDirPrefix(params.FilePath)
284+ file := prettyPath(params.FilePath)
264 args := newParamBuilder().addMain(file).build()
285 args := newParamBuilder().addMain(file).build()
266 return wr.renderWithParams(v, "Write", args, func() string {
287 return wr.renderWithParams(v, "Write", args, func() string {
@@ -494,7 +515,7 @@ func (tr agentRenderer) Render(v *toolCallCmp) string {
494 prompt = strings.ReplaceAll(prompt, "\n", " ")
515 prompt = strings.ReplaceAll(prompt, "\n", " ")
495 args := newParamBuilder().addMain(prompt).build()
516 args := newParamBuilder().addMain(prompt).build()
497− header := makeHeader("Task", v.textWidth(), args...)
518+ header := tr.makeHeader(v, "Task", v.textWidth(), args...)
498 t := tree.Root(header)
519 t := tree.Root(header)
500 for _, call := range v.nestedToolCalls {
521 for _, call := range v.nestedToolCalls {
@@ -524,12 +545,6 @@ func (tr agentRenderer) Render(v *toolCallCmp) string {
524 return joinHeaderBody(header, body)
545 return joinHeaderBody(header, body)
527−// makeHeader builds "<Tool>: param (key=value)" and truncates as needed.
528−func makeHeader(tool string, width int, params ...string) string {
529− prefix := tool + ": "
530− return prefix + renderParamList(width-lipgloss.Width(prefix), params...)
533// renderParamList renders params, params[0] (params[1]=params[2] ....)
548// renderParamList renders params, params[0] (params[1]=params[2] ....)
534func renderParamList(paramsWidth int, params ...string) string {
549func renderParamList(paramsWidth int, params ...string) string {
535 if len(params) == 0 {
550 if len(params) == 0 {
@@ -575,20 +590,27 @@ func renderParamList(paramsWidth int, params ...string) string {
576// earlyState returns immediately‑rendered error/cancelled/ongoing states.
591// earlyState returns immediately‑rendered error/cancelled/ongoing states.
577func earlyState(header string, v *toolCallCmp) (string, bool) {
592func earlyState(header string, v *toolCallCmp) (string, bool) {
593+ t := styles.CurrentTheme()
579 case v.result.IsError:
596 case v.result.IsError:
580− return lipgloss.JoinVertical(lipgloss.Left, header, v.renderToolError()), true
597+ message = v.renderToolError()
581 case v.cancelled:
598 case v.cancelled:
582− return lipgloss.JoinVertical(lipgloss.Left, header, "Cancelled"), true
599+ message = "Cancelled"
583 case v.result.ToolCallID == "":
600 case v.result.ToolCallID == "":
584− return lipgloss.JoinVertical(lipgloss.Left, header, "Waiting for tool to finish..."), true
601+ message = "Waiting for tool to start..."
586 return "", false
603 return "", false
606+ message = t.S().Base.PaddingLeft(2).Render(message)
607+ return lipgloss.JoinVertical(lipgloss.Left, header, message), true
590func joinHeaderBody(header, body string) string {
610func joinHeaderBody(header, body string) string {
611+ t := styles.CurrentTheme()
612+ body = t.S().Base.PaddingLeft(2).Render(body)
591− return lipgloss.JoinVertical(lipgloss.Left, header, "", body, "")
613+ return lipgloss.JoinVertical(lipgloss.Left, header, body, "")
594func renderPlainContent(v *toolCallCmp, content string) string {
616func renderPlainContent(v *toolCallCmp, content string) string {
@@ -596,17 +618,18 @@ func renderPlainContent(v *toolCallCmp, content string) string {
596 content = strings.TrimSpace(content)
618 content = strings.TrimSpace(content)
597 lines := strings.Split(content, "\n")
619 lines := strings.Split(content, "\n")
621+ width := v.textWidth() - 2 // -2 for left padding
599 var out []string
622 var out []string
600 for i, ln := range lines {
623 for i, ln := range lines {
601 if i >= responseContextHeight {
624 if i >= responseContextHeight {
604 ln = " " + ln // left padding
627 ln = " " + ln // left padding
605− if len(ln) > v.textWidth() {
628+ if len(ln) > width {
606− ln = v.fit(ln, v.textWidth())
629+ ln = v.fit(ln, width)
608 out = append(out, t.S().Muted.
631 out = append(out, t.S().Muted.
609− Width(v.textWidth()).
632+ Width(width).
610 Background(t.BgSubtle).
633 Background(t.BgSubtle).
611 Render(ln))
634 Render(ln))
@@ -638,7 +661,7 @@ func renderCodeContent(v *toolCallCmp, path, content string, offset int) string
638 PaddingLeft(4).
661 PaddingLeft(4).
639 PaddingRight(2).
662 PaddingRight(2).
640 Render(fmt.Sprintf("%d", i+1+offset))
663 Render(fmt.Sprintf("%d", i+1+offset))
641− w := v.textWidth() - lipgloss.Width(num)
664+ w := v.textWidth() - 2 - lipgloss.Width(num) // -2 for left padding
642 lines[i] = lipgloss.JoinHorizontal(lipgloss.Left,
665 lines[i] = lipgloss.JoinHorizontal(lipgloss.Left,
644 t.S().Base.
667 t.S().Base.
@@ -669,6 +692,15 @@ func truncateHeight(s string, h int) string {
695+func prettyPath(path string) string {
696+ // replace home directory with ~
697+ homeDir, err := os.UserHomeDir()
699+ path = strings.ReplaceAll(path, homeDir, "~")
672func prettifyToolName(name string) string {
704func prettifyToolName(name string) string {
673 switch name {
705 switch name {
674 case agent.AgentToolName:
706 case agent.AgentToolName: