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/
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
// Ensure stable provider order just in case
|
||||
sort.SliceStable(itemsByProvider, func(i, j int) bool { return itemsByProvider[i].idx < itemsByProvider[j].idx })
|
||||
|
||||
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...)
|
||||
}
|
||||
|
||||
matches := fuzzy.RankFindFold(query, displayValues)
|
||||
sort.Sort(matches)
|
||||
|
||||
// Reorder items based on fuzzy ranking
|
||||
rankedItems := make([]completions.CompletionSuggestion, 0, len(matches))
|
||||
for _, match := range matches {
|
||||
rankedItems = append(rankedItems, allItems[match.OriginalIndex])
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user