Skip to content

[FEATURE]: add chat.model plugin hook for pre-call model routing #18793

Closed as not planned

Description

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

Add a new plugin hook named chat.model that runs before an LLM call and can replace the active { providerID, modelID }.

Today plugins can modify params, headers, prompts, and tool behavior, but they cannot choose a different model at call time. That makes it hard to build plugin-driven routing patterns that feel like a good fit for OpenCode: cost-based switching, fallback to another model, per-tool routing, and review workflows that use multiple configured models without manually changing the session model.

I think this hook should stay minimal. It only needs the current call context: sessionID, agent, current model, provider context, current message, and step. It should return an optional replacement model and does not need full conversation history.

This seems related to fallback discussions like #7602, but broader: the goal is to expose model routing to plugins, not only add native fallback behavior. Since this changes core plugin infrastructure, I think it should start as a design discussion before implementation.

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Mar 23, 2026
  2. github-actions commented on Mar 23, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    Please review those issues and consider whether this is addressing something distinct before proceeding.

  3. v1truv1us commented on Mar 23, 2026

    @v1truv1us
    Author

    This is similar to the other issue, and from what I can see doing this would probably help fix it, but it's not the same problem

  4. LuisDuarte1 commented on Apr 11, 2026

    @LuisDuarte1

    Interested in implementing this. My use case is time-of-day routing to avoid provider degradation under peak load. The other use cases in this thread are compelling too — cost routing, fallback chains, per-tool selection, A/B testing — and this seems to cover most if not all of them.

    Why not chat.params?

    Provider, language model, and auth are resolved before chat.params runs (llm.ts:97-101). A chat.model hook before resolution avoids re-resolving and breaking changes.

    Proposed signature

    "chat.model"?: (
      input: {
        sessionID: string
        agent: string
        model: Model
        provider: ProviderContext
        message: UserMessage
      },
      output: {
        model: { providerID: string; modelID: string }
        persistent: boolean  // true = switch for session, false = this call only
      }
    ) => Promise<void>

    Follows existing pattern — caller pre-populates defaults, plugin mutates what it needs:

    // per-call routing
    "chat.model": async (input, output) => {
      if (isPeakHours()) output.model = { providerID: "openai", modelID: "gpt-4o" }
    }
    
    // persistent fallback
    "chat.model": async (input, output) => {
      if (this.failures > 3) {
        output.model = { providerID: "anthropic", modelID: "claude-sonnet-4-20250514" }
        output.persistent = true
      }
    }

    My only open question would be if the UI indicate when a plugin routes to a different model?

  5. v1truv1us commented on Apr 11, 2026

    @v1truv1us
    Author

    My thinking is that this should actually switch the model in the UI. That's how I envision in.

    My use case is for a tool/plugin that loops through review rounds and combines them to ensure the best output is provided. I've actually got a solution at least semi-implemented, I just haven't had a chance to make a PR for it

  6. assigned and unassigned on Apr 26, 2026
  7. sanchitmonga22 commented on Apr 28, 2026

    @sanchitmonga22

    Bumping this. The design conversation @v1truv1us asked for in the issue body hasn't really happened yet, and there are two open questions from the thread worth nailing down before implementation lands:

    1. persistent: boolean in LuisDuarte1's sketch: is the hook strictly per-call, or does it also switch the active session model (see also [FEATURE]: Plugin can change the active model and have it stick #18667)? The session-switching variant pulls in session state, UI/model picker, and subagent model inheritance.
    2. UI reflection: v1truv1us mentioned the picker should show the routed model. Only really applies if persistent is in scope.

    If the answer is per-call only, this is a small addition (one entry in Hooks + a dispatch site in LLM.run before the provider/language resolution). If persistent is in scope, it's a bigger surface.

    I have a per-call-only draft up at #24666; posted it before I'd properly read this thread. Wanted to surface that here and hear what scope you (@v1truv1us) and @LuisDuarte1 are landing on before I push it forward. Happy to keep it as input, drop it if someone's close to a complete one, or take the per-call subset if the UI-aware path is the focus.

  8. v1truv1us commented on May 3, 2026

    @v1truv1us
    Author

    @sanchitmonga22 #22044 I actually got this spun up. My use case is actually using this with a plugin and having that plugin actually run through a prompt with multiple agents - adversarial reviewing whatever it’s used on. It does a loop, switches the model, does a loop, switches model, etc

  9. removed
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  10. sanchitmonga22 commented on May 5, 2026

    @sanchitmonga22

    Cool! what do we need in order to get this merged in that case? @v1truv1us @thdxr

  11. v1truv1us commented on May 9, 2026

    @v1truv1us
    Author

    @sanchitmonga22 i have no idea, this is my first time contributing, so I'm still - hopefully - learning the process

  12. JGuck commented on Jun 3, 2026

    @JGuck

    I opened #30457.

    The PR keeps the scope intentionally small.

    This should cover the per-call routing use case discussed here, while leaving the larger persistent/session-level model switching behavior for a separate follow-up.

  13. github-actions commented on Aug 3, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions