summaryrefslogtreecommitdiffhomepage
path: root/packages/tui/internal/components
diff options
context:
space:
mode:
authorDax <[email protected]>2025-08-07 16:32:12 -0400
committerGitHub <[email protected]>2025-08-07 16:32:12 -0400
commitc34aec060f2abb78486857b0e0d678cac52e2fab (patch)
tree7d2b42a0c87ab016ffbc096e332b186ec4bce7ac /packages/tui/internal/components
parent12f1ad521fef2e1fa6f1b0ce6ec939664d61899f (diff)
downloadopencode-c34aec060f2abb78486857b0e0d678cac52e2fab.tar.gz
opencode-c34aec060f2abb78486857b0e0d678cac52e2fab.zip
Merge agent and mode into one (#1689)
The concept of mode has been deprecated, there is now only the agent field in the config. An agent can be cycled through as your primary agent with <tab> or you can spawn a subagent by @ mentioning it. if you include a description of when to use it, the primary agent will try to automatically use it Full docs here: https://opencode.ai/docs/agents/
Diffstat (limited to 'packages/tui/internal/components')
-rw-r--r--packages/tui/internal/components/chat/editor.go25
-rw-r--r--packages/tui/internal/components/chat/message.go43
-rw-r--r--packages/tui/internal/components/chat/messages.go8
-rw-r--r--packages/tui/internal/components/dialog/complete.go56
-rw-r--r--packages/tui/internal/components/status/status.go22
5 files changed, 122 insertions, 32 deletions
diff --git a/packages/tui/internal/components/chat/editor.go b/packages/tui/internal/components/chat/editor.go
index 1263a6e72..509de6244 100644
--- a/packages/tui/internal/components/chat/editor.go
+++ b/packages/tui/internal/components/chat/editor.go
@@ -288,6 +288,31 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.textarea.InsertAttachment(attachment)
m.textarea.InsertString(" ")
return m, nil
+ case "agents":
+ atIndex := m.textarea.LastRuneIndex('@')
+ if atIndex == -1 {
+ // Should not happen, but as a fallback, just insert.
+ m.textarea.InsertString(msg.Item.Value + " ")
+ return m, nil
+ }
+
+ cursorCol := m.textarea.CursorColumn()
+ m.textarea.ReplaceRange(atIndex, cursorCol, "")
+
+ name := msg.Item.Value
+ attachment := &attachment.Attachment{
+ ID: uuid.NewString(),
+ Type: "agent",
+ Display: "@" + name,
+ Source: &attachment.AgentSource{
+ Name: name,
+ },
+ }
+
+ m.textarea.InsertAttachment(attachment)
+ m.textarea.InsertString(" ")
+ return m, nil
+
default:
slog.Debug("Unknown provider", "provider", msg.Item.ProviderID)
return m, nil
diff --git a/packages/tui/internal/components/chat/message.go b/packages/tui/internal/components/chat/message.go
index 0a1982718..60d0fab55 100644
--- a/packages/tui/internal/components/chat/message.go
+++ b/packages/tui/internal/components/chat/message.go
@@ -209,6 +209,7 @@ func renderText(
width int,
extra string,
fileParts []opencode.FilePart,
+ agentParts []opencode.AgentPart,
toolCalls ...opencode.ToolPart,
) string {
t := theme.CurrentTheme()
@@ -229,9 +230,47 @@ func renderText(
// Apply highlighting to filenames and base style to rest of text BEFORE wrapping
textLen := int64(len(text))
+
+ // Collect all parts to highlight (both file and agent parts)
+ type highlightPart struct {
+ start int64
+ end int64
+ color compat.AdaptiveColor
+ }
+ var highlights []highlightPart
+
+ // Add file parts with secondary color
for _, filePart := range fileParts {
- highlight := base.Foreground(t.Secondary())
- start, end := filePart.Source.Text.Start, filePart.Source.Text.End
+ highlights = append(highlights, highlightPart{
+ start: filePart.Source.Text.Start,
+ end: filePart.Source.Text.End,
+ color: t.Secondary(),
+ })
+ }
+
+ // Add agent parts with secondary color (same as file parts)
+ for _, agentPart := range agentParts {
+ highlights = append(highlights, highlightPart{
+ start: agentPart.Source.Start,
+ end: agentPart.Source.End,
+ color: t.Secondary(),
+ })
+ }
+
+ // Sort highlights by start position
+ slices.SortFunc(highlights, func(a, b highlightPart) int {
+ if a.start < b.start {
+ return -1
+ }
+ if a.start > b.start {
+ return 1
+ }
+ return 0
+ })
+
+ for _, part := range highlights {
+ highlight := base.Foreground(part.color)
+ start, end := part.start, part.end
if end > textLen {
end = textLen
diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go
index 5702cec5d..46036b75f 100644
--- a/packages/tui/internal/components/chat/messages.go
+++ b/packages/tui/internal/components/chat/messages.go
@@ -300,12 +300,17 @@ func (m *messagesComponent) renderView() tea.Cmd {
}
remainingParts := message.Parts[partIndex+1:]
fileParts := make([]opencode.FilePart, 0)
+ agentParts := make([]opencode.AgentPart, 0)
for _, part := range remainingParts {
switch part := part.(type) {
case opencode.FilePart:
if part.Source.Text.Start >= 0 && part.Source.Text.End >= part.Source.Text.Start {
fileParts = append(fileParts, part)
}
+ case opencode.AgentPart:
+ if part.Source.Start >= 0 && part.Source.End >= part.Source.Start {
+ agentParts = append(agentParts, part)
+ }
}
}
flexItems := []layout.FlexItem{}
@@ -355,6 +360,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
width,
files,
fileParts,
+ agentParts,
)
content = lipgloss.PlaceHorizontal(
m.width,
@@ -433,6 +439,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
width,
"",
[]opencode.FilePart{},
+ []opencode.AgentPart{},
toolCallParts...,
)
content = lipgloss.PlaceHorizontal(
@@ -453,6 +460,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
width,
"",
[]opencode.FilePart{},
+ []opencode.AgentPart{},
toolCallParts...,
)
content = lipgloss.PlaceHorizontal(
diff --git a/packages/tui/internal/components/dialog/complete.go b/packages/tui/internal/components/dialog/complete.go
index f18d97510..d3d2ea9d7 100644
--- a/packages/tui/internal/components/dialog/complete.go
+++ b/packages/tui/internal/components/dialog/complete.go
@@ -66,11 +66,16 @@ func (c *completionDialogComponent) Init() tea.Cmd {
func (c *completionDialogComponent) getAllCompletions(query string) tea.Cmd {
return func() tea.Msg {
- allItems := make([]completions.CompletionSuggestion, 0)
+ // Collect results from all providers and preserve provider order
+ type providerItems struct {
+ idx int
+ items []completions.CompletionSuggestion
+ }
+
+ itemsByProvider := make([]providerItems, 0, len(c.providers))
providersWithResults := 0
- // Collect results from all providers
- for _, provider := range c.providers {
+ for idx, provider := range c.providers {
items, err := provider.GetChildEntries(query)
if err != nil {
slog.Error(
@@ -84,33 +89,46 @@ func (c *completionDialogComponent) getAllCompletions(query string) tea.Cmd {
}
if len(items) > 0 {
providersWithResults++
- allItems = append(allItems, items...)
+ itemsByProvider = append(itemsByProvider, providerItems{idx: idx, items: items})
}
}
- // If there's a query, use fuzzy ranking to sort results
- if query != "" && providersWithResults > 1 {
+ // If there's a query, fuzzy-rank within each provider, then concatenate by provider order
+ if query != "" && providersWithResults > 0 {
t := theme.CurrentTheme()
baseStyle := styles.NewStyle().Background(t.BackgroundElement())
- // Create a slice of display values for fuzzy matching
- displayValues := make([]string, len(allItems))
- for i, item := range allItems {
- displayValues[i] = item.Display(baseStyle)
- }
- matches := fuzzy.RankFindFold(query, displayValues)
- sort.Sort(matches)
+ // Ensure stable provider order just in case
+ sort.SliceStable(itemsByProvider, func(i, j int) bool { return itemsByProvider[i].idx < itemsByProvider[j].idx })
- // Reorder items based on fuzzy ranking
- rankedItems := make([]completions.CompletionSuggestion, 0, len(matches))
- for _, match := range matches {
- rankedItems = append(rankedItems, allItems[match.OriginalIndex])
+ final := make([]completions.CompletionSuggestion, 0)
+ for _, entry := range itemsByProvider {
+ // Build display values for fuzzy matching within this provider
+ displayValues := make([]string, len(entry.items))
+ for i, item := range entry.items {
+ displayValues[i] = item.Display(baseStyle)
+ }
+
+ matches := fuzzy.RankFindFold(query, displayValues)
+ sort.Sort(matches)
+
+ // Reorder items for this provider based on fuzzy ranking
+ ranked := make([]completions.CompletionSuggestion, 0, len(matches))
+ for _, m := range matches {
+ ranked = append(ranked, entry.items[m.OriginalIndex])
+ }
+ final = append(final, ranked...)
}
- return rankedItems
+ return final
}
- return allItems
+ // No query or no results: just concatenate in provider order
+ all := make([]completions.CompletionSuggestion, 0)
+ for _, entry := range itemsByProvider {
+ all = append(all, entry.items...)
+ }
+ return all
}
}
func (c *completionDialogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
diff --git a/packages/tui/internal/components/status/status.go b/packages/tui/internal/components/status/status.go
index d57c228c5..be34dd938 100644
--- a/packages/tui/internal/components/status/status.go
+++ b/packages/tui/internal/components/status/status.go
@@ -121,7 +121,7 @@ func (m *statusComponent) View() string {
var modeBackground compat.AdaptiveColor
var modeForeground compat.AdaptiveColor
- switch m.app.ModeIndex {
+ switch m.app.AgentIndex {
case 0:
modeBackground = t.BackgroundElement()
modeForeground = t.TextMuted()
@@ -148,31 +148,31 @@ func (m *statusComponent) View() string {
modeForeground = t.BackgroundPanel()
}
- command := m.app.Commands[commands.SwitchModeCommand]
+ command := m.app.Commands[commands.SwitchAgentCommand]
kb := command.Keybindings[0]
key := kb.Key
if kb.RequiresLeader {
key = m.app.Config.Keybinds.Leader + " " + kb.Key
}
- modeStyle := styles.NewStyle().Background(modeBackground).Foreground(modeForeground)
- modeNameStyle := modeStyle.Bold(true).Render
- modeDescStyle := modeStyle.Render
- mode := modeNameStyle(strings.ToUpper(m.app.Mode.Name)) + modeDescStyle(" MODE")
- mode = modeStyle.
+ agentStyle := styles.NewStyle().Background(modeBackground).Foreground(modeForeground)
+ agentNameStyle := agentStyle.Bold(true).Render
+ agentDescStyle := agentStyle.Render
+ agent := agentNameStyle(strings.ToUpper(m.app.Agent().Name)) + agentDescStyle(" AGENT")
+ agent = agentStyle.
Padding(0, 1).
BorderLeft(true).
BorderStyle(lipgloss.ThickBorder()).
BorderForeground(modeBackground).
BorderBackground(t.BackgroundPanel()).
- Render(mode)
+ Render(agent)
faintStyle := styles.NewStyle().
Faint(true).
Background(t.BackgroundPanel()).
Foreground(t.TextMuted())
- mode = faintStyle.Render(key+" ") + mode
- modeWidth := lipgloss.Width(mode)
+ agent = faintStyle.Render(key+" ") + agent
+ modeWidth := lipgloss.Width(agent)
availableWidth := m.width - logoWidth - modeWidth
branchSuffix := ""
@@ -206,7 +206,7 @@ func (m *statusComponent) View() string {
View: logo + cwd,
},
layout.FlexItem{
- View: mode,
+ View: agent,
},
)