#2490 · @Stefanqn · opened Sep 8, 2025 at 12:17 PM UTC · last updated Mar 21, 2026 at 12:50 PM UTC

feat: add /model x/y command to switch models

tuifeat
35
+319816 files

Score breakdown

Impact

7.0

Clarity

5.0

Urgency

3.0

Ease Of Review

3.0

Guidelines

4.0

Readiness

4.0

Size

0.0

Trust

5.0

Traction

2.0

Summary

This PR introduces a new TUI command, /model x/y, allowing users to directly switch models without using the dialog. While adding a convenience feature for TUI users, the PR is quite large and includes an unrelated file change. Review will be difficult due to the lack of clear testing instructions or visual proofs.

Open in GitHub

Description

switch model directly by command, not by /models dialog

Linked Issues

None.

Comments

PR comments

Stefanqn

I just rebased

Stefanqn

Is there anything I can do so it can be merged?

Changed Files

STATS.md

+26280
@@ -1,82 +1,264 @@
# Download Stats
| Date | GitHub Downloads | npm Downloads | Total |
| ---------- | ---------------- | ---------------- | ----------------- |
| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217)

packages/tui/internal/app/app.go

+200
@@ -410,6 +410,26 @@ func (a *App) SwitchToAgent(agentName string) (*App, tea.Cmd) {
return a, a.SaveState()
}
func (a *App) SwitchToModel(fullModelID string) (*App, tea.Cmd) {
provider, model := findModelByFullID(a.Providers, fullModelID)
if provider == nil || model == nil {
return a, toast.NewErrorToast(fmt.Sprintf("Model not found: %s", fullModelID))
}
a.Provider = provider
a.Model = model
a.State.AgentModel[a.Agent().Name] = AgentModel{
ProviderID: provider.ID,
ModelID: model.ID,
}
a.State.UpdateModelUsage(provider.ID, model.ID)
return a, tea.Sequence(
a.SaveState(),
toast.NewSuccessToast(fmt.Sprintf("Switched to %s (%s)", model.Name, provider.Name)),
)
}
// findModelByFullID finds a model by its full ID in the format "provider/model"
func findModelByFullID(
providers []opencode.Provider,

packages/tui/internal/commands/command.go

+70
@@ -123,6 +123,7 @@ const (
AppHelpCommand CommandName = "app_help"
SwitchAgentCommand CommandName = "switch_agent"
SwitchAgentReverseCommand CommandName = "switch_agent_reverse"
SwitchModelCommand CommandName = "switch_model"
EditorOpenCommand CommandName = "editor_open"
SessionNewCommand CommandName = "session_new"
SessionListCommand CommandName = "session_list"
@@ -304,6 +305,12 @@ func LoadFromConfig(config *opencode.Config, customCommands []opencode.Command)
Description: "previous agent",
Keybindings: parseBindings("shift+tab"),
},
{
Name: SwitchModelCommand,
Description: "switch model",
Trigger: []string{"model"},
Custom: true,
},
{
Name: ThemeListCommand,
Description: "list themes",

packages/tui/internal/components/chat/editor.go

+121
@@ -505,7 +505,18 @@ func (m *editorComponent) Submit() (tea.Model, tea.Cmd) {
expandedValue = expandedValue[1:] // Remove the "/"
commandName := strings.Split(expandedValue, " ")[0]
command := m.app.Commands[commands.CommandName(commandName)]
var command = m.app.Commands[commands.CommandName(commandName)]
if command.Name == "" {
for _, cmd := range m.app.Commands {
if cmd.MatchesTrigger(commandName) {
command = cmd
}
}
}
if command.Name == "" {
return m, util.CmdHandler(toast.NewErrorToast("Unknown command: /" + commandName + " Use /help to list available commands."))
}
if command.Custom {
args := ""
if strings.HasPrefix(expandedValue, command.PrimaryTrigger()+" ") {

packages/tui/internal/tui/tui.go

+80
@@ -409,6 +409,14 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
cmds = append(cmds, cmd)
}
case app.SendCommand:
// Handle SwitchModelCommand locally
if msg.Command == string(commands.SwitchModelCommand) {
updated, cmd := a.app.SwitchToModel(msg.Args)
a.app = updated
cmds = append(cmds, cmd)
break
}
// If we're in a child session, switch back to parent before sending prompt
if a.app.Session.ParentID != "" {
parentSession, err := a.app.Client.Session.Get(context.Background(), a.app.Session.ParentID, opencode.SessionGetParams{})

packages/web/src/content/docs/tui.mdx

+100
@@ -161,6 +161,16 @@ List available models.
/models
```
---
### model
Set the large language model for the current session.
```bash frame="none"
/model anthropic/claude-3-5-sonnet-20240620
```
**Keybind:** `ctrl+x m`
---