Skip to content

[FEATURE]: Dynamic Model Routing via Plugin Hook - Update Active Model State #18644

@v1truv1us

Description

@v1truv1us
  • I have verified this feature I'm about to request hasn't been suggested before.

Feature: Dynamic Model Routing via Plugin Hook - Update Active Model State

What it is

A plugin hook that allows plugins to dynamically change the active model for the entire OpenCode session. When a plugin overrides the model, it should update OpenCode's active model state, so that all subsequent agents and tasks using inherited model (no explicit model pin) will use the new model.

Current behavior

Proposed behavior

Key distinction from current chat.message behavior

  • Current: Plugin can mutate output.message.model, but it only affects the user message and subsequent assistant/subagent for that turn
  • Proposed: Plugin can change the active model, affecting all future turns and the UI state

Benefits

  1. True active model inheritance - Fixes [Bug] Subagent spawned via Task tool uses global config model instead of inheriting parent session's active model #17870 where subagents ignore parent's active model
  2. UI consistency - Model picker always reflects the actual model being used
  3. Dynamic routing - Route tasks to appropriate models based on content/complexity
  4. Cost optimization - Use cheaper models for simple tasks
  5. Failover - Automatically switch providers on rate limits or errors
  6. A/B testing - Blind model evaluation with proper UI feedback ([FEATURE]: chat.model hook for blind LLM benchmarking feat(plugin) #16932)

Current Status

Partially implemented. The existing chat.message hook allows plugins to mutate output.message.model, but this only affects the current turn's chain (user → assistant → subagent). It does NOT update OpenCode's active model state or UI, and future turns revert to the old model.

Test coverage exists at packages/opencode/test/session/chat-message-model-routing.test.ts.

This is insufficient because:

This request is to:

  1. When a plugin changes the model, update OpenCode's active model state
  2. Update the UI/model picker to reflect the new active model
  3. Ensure all future turns use the new active model
  4. Subagents inherit correctly across all turns

Problem

Describe the problem or limitation that this feature would solve.

Related issues:


Proposed Solution

Extend the chat.message hook (or add a dedicated chat.model hook) to update OpenCode's active model state when a plugin changes the model.

"chat.model"?: (
  input: {
    sessionID: string
    agent: string
    proposedModel?: { providerID: string; modelID: string }
  },
  output: {
    model?: { providerID: string; modelID: string }
  }
) => Promise<void>

Hook semantics:

  • Fires when a plugin wants to change the active model
  • When output.model is set, OpenCode updates its active model state
  • The UI/model picker reflects the new active model
  • All subsequent turns use the new active model by default
  • Subagents launched via Task tool inherit the new active model (unless pinned)

Resolution order:

  1. Plugin chat.model / chat.message model mutation (updates active model)
  2. Explicit agent.model pin in agent configuration
  3. Parent assistant model (subagent inheritance)
  4. Default model resolution

Effect on active model:

  • When a plugin returns a different model, OpenCode should update the active model
  • This affects future turns until another model change occurs
  • Subagents without explicit model inherit the active model
  • The model picker UI should display the active model

Use Cases

  1. Dynamic model routing based on prompt content

    • Route coding tasks to specialized code models
    • Route debugging tasks to models with better reasoning
    • Route simple queries to faster/cheaper models
  2. Model fallback/failover

    • Detect rate limits or errors and switch to backup provider
    • Handle provider-specific outages gracefully
  3. A/B testing and benchmarking

  4. Enterprise model governance

    • Enforce policy-based model selection (e.g., cost limits, compliance)
    • Audit trail of model selection decisions
  5. Context-aware routing

    • Analyze file types, project size, or task complexity
    • Select appropriate model tier dynamically

Implementation Notes

What exists:

  • chat.message hook allows plugins to mutate output.message.model
  • Model inheritance chain works (user → assistant → subagent)
  • Test coverage at packages/opencode/test/session/chat-message-model-routing.test.ts

What needs to be added:

  • Update OpenCode's active model state when plugin changes model
  • Update the UI/model picker to reflect the new active model
  • Ensure future turns use the new active model by default

Key change needed:
In prompt.ts where chat.message is triggered, when output.message.model is mutated, also update the session's active model. This should propagate to the UI so the model picker reflects the change.

Files involved:

  • packages/plugin/src/index.ts - Hook type definition
  • packages/opencode/src/session/prompt.ts - Update active model when hook mutates model
  • packages/opencode/src/tool/task.ts - Already respects parent assistant model (lines 108-111)
  • UI components - Display active model from session state

Complementary Approaches

  1. Add model parameter to Task tool

Questions for Discussion

  1. Hook timing: Should the hook fire before or after the user message is persisted?
  2. UI update: How should the model picker UI respond when a plugin changes the active model? (animate? flash? silent update?)
  3. Validation: How should model validation failures be handled when updating active model?
  4. Override mechanism: Should users be able to opt out of plugin model changes? (e.g., config flag)
  5. Dedicated hook vs extend existing: Should we add a dedicated chat.model hook or extend chat.message?
  6. Persistence: Should the active model change persist across session restarts?

Metadata

Metadata

Assignees

Labels

coreAnything pertaining to core functionality of the application (opencode server stuff)

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions