diff options
| author | Dax <[email protected]> | 2025-08-07 16:32:12 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-08-07 16:32:12 -0400 |
| commit | c34aec060f2abb78486857b0e0d678cac52e2fab (patch) | |
| tree | 7d2b42a0c87ab016ffbc096e332b186ec4bce7ac /packages/tui/internal/components | |
| parent | 12f1ad521fef2e1fa6f1b0ce6ec939664d61899f (diff) | |
| download | opencode-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.go | 25 | ||||
| -rw-r--r-- | packages/tui/internal/components/chat/message.go | 43 | ||||
| -rw-r--r-- | packages/tui/internal/components/chat/messages.go | 8 | ||||
| -rw-r--r-- | packages/tui/internal/components/dialog/complete.go | 56 | ||||
| -rw-r--r-- | packages/tui/internal/components/status/status.go | 22 |
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, }, ) |
