Files
flynn/docs/plans/2026-02-06-provider-model-switching-design.md
T
William Valentin d4530a7034 feat: add runtime provider/model switching via /model <tier> <provider/model>
- ModelRouter: add setClient(), labels map, getLabel(), getAllLabels()
- TUI commands: parse /model <tier> <provider/model> syntax with autocompletion
- TUI minimal: handle provider switching via createClientFromConfig factory
- Daemon: wire initial labels into router config
- Fix /model alias mappings (opus=complex, sonnet=default, haiku=fast)
- Add design doc and update state.json with feature status
2026-02-06 23:42:14 -08:00

3.4 KiB

Provider/Model Runtime Switching

Date: 2026-02-06 Status: In Progress

Goal

Enable easy runtime switching of model providers per tier via the /model command, using provider/model syntax.

Commands

/model                                    — Show all tiers with provider/model labels
/model fast                               — Switch active tier (existing behavior)
/model default github-copilot/claude-sonnet-4-5  — Change default tier's provider+model
/model complex anthropic/claude-opus-4    — Change complex tier's provider+model
/model fast github-copilot/gpt-4o-mini    — Change fast tier's provider+model

Design Decisions

  1. No presets — Direct provider/model targeting per tier. YAGNI.
  2. Full override — When you set a tier, it fully replaces the previous client.
  3. Local tier excluded/model local continues to use /backend for switching. Local models are a different concern.
  4. Auth — Config-based api_key for most providers. /login OAuth flow for GitHub Copilot (already implemented).
  5. Merged into /model — No separate /provider command. Everything lives under /model.

Implementation

1. ModelRouter (src/models/router.ts)

  • Add setClient(tier: ModelTier, client: ModelClient, label: string) — replaces a tier's client at runtime
  • Add getLabel(tier: ModelTier): string — returns provider/model string for display
  • Track labels in a Map<ModelTier, string> populated at construction and updated by setClient()

2. Client Factory (src/daemon/index.ts)

Extract createClientFromProvider(provider: string, model: string, opts?: { apiKey?: string; endpoint?: string }): ModelClient factory function from the existing inline client creation logic. Used by both daemon startup and runtime /model switching.

3. Command Parser (src/frontends/tui/commands.ts)

  • Extend Command type: { type: 'model'; name?: string; providerModel?: string }
  • Parse /model <tier> <provider/model> — split on space to get tier + provider/model
  • Parse provider/model string: split on first / to get provider and model name
  • Update autocompletion to suggest available providers after tier name
  • Update tooltips

4. Daemon Wiring (src/daemon/index.ts)

Handle the new command variant:

  1. Receive { type: 'model', name: 'default', providerModel: 'github-copilot/claude-sonnet-4-5' }
  2. Parse provider and model from providerModel
  3. Call createClientFromProvider(provider, model) to instantiate client
  4. Call router.setClient('default', client, 'github-copilot/claude-sonnet-4-5')
  5. Respond with confirmation message

5. Provider Name Mapping

Map short provider names to client constructors:

Provider Name Client Class
anthropic AnthropicClient
openai OpenAIClient
github / github-copilot GitHubModelsClient
gemini GeminiClient
bedrock BedrockClient
ollama OllamaClient
llamacpp LlamaCppClient

Files Changed

File Change
src/models/router.ts Add setClient(), getLabel(), label tracking
src/models/router.test.ts Tests for new methods
src/frontends/tui/commands.ts Extended parser, completions, tooltips
src/frontends/tui/commands.test.ts Tests for new parsing
src/daemon/index.ts Extract factory, wire new command handler