From bd4319f2bc28c0ad16946107e7d1d50fa924527e Mon Sep 17 00:00:00 2001 From: spoons-and-mirrors <212802214+spoons-and-mirrors@users.noreply.github.com> Date: Sun, 10 Aug 2025 03:22:16 +0200 Subject: [PATCH] Feat: Add Agent Name in the LLM Response Footer (and re-order it) (#1770) --- .../tui/internal/components/chat/message.go | 32 +++++++++++++++++-- .../tui/internal/components/status/status.go | 30 ++++------------- packages/tui/internal/util/color.go | 24 +++++++++++++- packages/web/src/components/share/part.tsx | 26 ++++++++++++--- 4 files changed, 81 insertions(+), 31 deletions(-) diff --git a/packages/tui/internal/components/chat/message.go b/packages/tui/internal/components/chat/message.go index 6dcdcc989..5f4dc9612 100644 --- a/packages/tui/internal/components/chat/message.go +++ b/packages/tui/internal/components/chat/message.go @@ -324,9 +324,37 @@ func renderText( if time.Now().Format("02 Jan 2006") == timestamp[:11] { timestamp = timestamp[12:] } - info := fmt.Sprintf("%s (%s)", author, timestamp) - info = styles.NewStyle().Foreground(t.TextMuted()).Render(info) + // Check if this is an assistant message with mode (agent) information + var modelAndAgentSuffix string + if assistantMsg, ok := message.(opencode.AssistantMessage); ok && assistantMsg.Mode != "" { + // Find the agent index by name to get the correct color + var agentIndex int + for i, agent := range app.Agents { + if agent.Name == assistantMsg.Mode { + agentIndex = i + break + } + } + + // Get agent color based on the original agent index (same as status bar) + agentColor := util.GetAgentColor(agentIndex) + + // Style the agent name with the same color as status bar + agentName := strings.Title(assistantMsg.Mode) + styledAgentName := styles.NewStyle().Foreground(agentColor).Bold(true).Render(agentName) + modelAndAgentSuffix = fmt.Sprintf(" | %s | %s", assistantMsg.ModelID, styledAgentName) + } + + var info string + if modelAndAgentSuffix != "" { + // For assistant messages: "timestamp | modelID | agentName" + info = fmt.Sprintf("%s%s", timestamp, modelAndAgentSuffix) + } else { + // For user messages: "author (timestamp)" + info = fmt.Sprintf("%s (%s)", author, timestamp) + } + info = styles.NewStyle().Foreground(t.TextMuted()).Render(info) if !showToolDetails && toolCalls != nil && len(toolCalls) > 0 { content = content + "\n\n" for _, toolCall := range toolCalls { diff --git a/packages/tui/internal/components/status/status.go b/packages/tui/internal/components/status/status.go index be34dd938..1634bd6c5 100644 --- a/packages/tui/internal/components/status/status.go +++ b/packages/tui/internal/components/status/status.go @@ -121,30 +121,14 @@ func (m *statusComponent) View() string { var modeBackground compat.AdaptiveColor var modeForeground compat.AdaptiveColor - switch m.app.AgentIndex { - case 0: + + agentColor := util.GetAgentColor(m.app.AgentIndex) + + if m.app.AgentIndex == 0 { modeBackground = t.BackgroundElement() - modeForeground = t.TextMuted() - case 1: - modeBackground = t.Secondary() - modeForeground = t.BackgroundPanel() - case 2: - modeBackground = t.Accent() - modeForeground = t.BackgroundPanel() - case 3: - modeBackground = t.Success() - modeForeground = t.BackgroundPanel() - case 4: - modeBackground = t.Warning() - modeForeground = t.BackgroundPanel() - case 5: - modeBackground = t.Primary() - modeForeground = t.BackgroundPanel() - case 6: - modeBackground = t.Error() - modeForeground = t.BackgroundPanel() - default: - modeBackground = t.Secondary() + modeForeground = agentColor + } else { + modeBackground = agentColor modeForeground = t.BackgroundPanel() } diff --git a/packages/tui/internal/util/color.go b/packages/tui/internal/util/color.go index f0d73bcb2..b387ca655 100644 --- a/packages/tui/internal/util/color.go +++ b/packages/tui/internal/util/color.go @@ -3,6 +3,9 @@ package util import ( "regexp" "strings" + + "github.com/charmbracelet/lipgloss/v2/compat" + "github.com/sst/opencode/internal/theme" ) var csiRE *regexp.Regexp @@ -89,5 +92,24 @@ func ConvertRGBToAnsi16Colors(s string) string { // func looksLikeByte(tok string) bool { // v, err := strconv.Atoi(tok) -// return err == nil && v >= 0 && v <= 255 +// return err == nil && v >= 0 && v <= 255 // } + +// GetAgentColor returns the color for a given agent index, matching the status bar colors +func GetAgentColor(agentIndex int) compat.AdaptiveColor { + t := theme.CurrentTheme() + agentColors := []compat.AdaptiveColor{ + t.TextMuted(), + t.Secondary(), + t.Accent(), + t.Success(), + t.Warning(), + t.Primary(), + t.Error(), + } + + if agentIndex >= 0 && agentIndex < len(agentColors) { + return agentColors[agentIndex] + } + return t.Secondary() // default fallback +} diff --git a/packages/web/src/components/share/part.tsx b/packages/web/src/components/share/part.tsx index 4a9320e6d..772a80dc6 100644 --- a/packages/web/src/components/share/part.tsx +++ b/packages/web/src/components/share/part.tsx @@ -144,7 +144,15 @@ export function Part(props: PartProps) { DateTime.DATETIME_FULL_WITH_SECONDS, )} > - {DateTime.fromMillis(props.message.time.completed).toLocaleString(DateTime.DATETIME_MED)} + {DateTime.fromMillis(props.message.time.completed || props.message.time.created).toLocaleString( + DateTime.DATETIME_MED, + )} + {` | ${props.message.modelID}`} + {props.message.mode && ( + + {` | ${props.message.mode}`} + + )} )} @@ -158,7 +166,17 @@ export function Part(props: PartProps) { {props.part.type === "step-start" && props.message.role === "assistant" && (