Feat: Add Agent Name in the LLM Response Footer (and re-order it) (#1770)

This commit is contained in:
spoons-and-mirrors 2025-08-10 03:22:16 +02:00 committed by GitHub
parent 696ab1a752
commit bd4319f2bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 81 additions and 31 deletions

View file

@ -324,9 +324,37 @@ func renderText(
if time.Now().Format("02 Jan 2006") == timestamp[:11] { if time.Now().Format("02 Jan 2006") == timestamp[:11] {
timestamp = timestamp[12:] 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 { if !showToolDetails && toolCalls != nil && len(toolCalls) > 0 {
content = content + "\n\n" content = content + "\n\n"
for _, toolCall := range toolCalls { for _, toolCall := range toolCalls {

View file

@ -121,30 +121,14 @@ func (m *statusComponent) View() string {
var modeBackground compat.AdaptiveColor var modeBackground compat.AdaptiveColor
var modeForeground 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() modeBackground = t.BackgroundElement()
modeForeground = t.TextMuted() modeForeground = agentColor
case 1: } else {
modeBackground = t.Secondary() modeBackground = agentColor
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 = t.BackgroundPanel() modeForeground = t.BackgroundPanel()
} }

View file

@ -3,6 +3,9 @@ package util
import ( import (
"regexp" "regexp"
"strings" "strings"
"github.com/charmbracelet/lipgloss/v2/compat"
"github.com/sst/opencode/internal/theme"
) )
var csiRE *regexp.Regexp var csiRE *regexp.Regexp
@ -89,5 +92,24 @@ func ConvertRGBToAnsi16Colors(s string) string {
// func looksLikeByte(tok string) bool { // func looksLikeByte(tok string) bool {
// v, err := strconv.Atoi(tok) // 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
}

View file

@ -144,7 +144,15 @@ export function Part(props: PartProps) {
DateTime.DATETIME_FULL_WITH_SECONDS, 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 && (
<span style={{ "font-weight": "bold", color: "var(--sl-color-accent)" }}>
{` | ${props.message.mode}`}
</span>
)}
</Footer> </Footer>
)} )}
</div> </div>
@ -158,7 +166,17 @@ export function Part(props: PartProps) {
{props.part.type === "step-start" && props.message.role === "assistant" && ( {props.part.type === "step-start" && props.message.role === "assistant" && (
<div data-component="step-start"> <div data-component="step-start">
<div data-slot="provider">{props.message.providerID}</div> <div data-slot="provider">{props.message.providerID}</div>
<div data-slot="model">{props.message.modelID}</div> <div data-slot="model">
{DateTime.fromMillis(props.message.time.completed || props.message.time.created).toLocaleString(
DateTime.DATETIME_MED,
)}
{` | ${props.message.modelID}`}
{props.message.mode && (
<span style={{ "font-weight": "bold", color: "var(--sl-color-accent)" }}>
{` | ${props.message.mode}`}
</span>
)}
</div>
</div> </div>
)} )}
{props.part.type === "tool" && props.part.state.status === "error" && ( {props.part.type === "tool" && props.part.state.status === "error" && (
@ -653,9 +671,7 @@ function TaskTool(props: ToolProps) {
<span data-slot="name">Task</span> <span data-slot="name">Task</span>
<span data-slot="target">{props.state.input.description}</span> <span data-slot="target">{props.state.input.description}</span>
</div> </div>
<div data-component="tool-input"> <div data-component="tool-input">&ldquo;{props.state.input.prompt}&rdquo;</div>
&ldquo;{props.state.input.prompt}&rdquo;
</div>
<ResultsButton showCopy="Show output" hideCopy="Hide output"> <ResultsButton showCopy="Show output" hideCopy="Hide output">
<div data-component="tool-output"> <div data-component="tool-output">
<ContentMarkdown expand text={props.state.output} /> <ContentMarkdown expand text={props.state.output} />