Skip to content

feat(opencode): session-level model switching API#22044

Open
v1truv1us wants to merge 7 commits intoanomalyco:devfrom
v1truv1us:feat/session-model-switching-v2
Open

feat(opencode): session-level model switching API#22044
v1truv1us wants to merge 7 commits intoanomalyco:devfrom
v1truv1us:feat/session-model-switching-v2

Conversation

@v1truv1us
Copy link
Copy Markdown

@v1truv1us v1truv1us commented Apr 11, 2026

Issue for this PR

Closes #18793

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

This adds session-level model persistence so plugins can change the active session model for future turns. The main change is Session.setModel(), plus session storage for model and modelVariant, a PATCH /session/:sessionID/model route, and TUI sync from session state. The chat.message hook remains the integration point for plugins: if a plugin mutates output.message.model, that model is saved on the user message and then persisted onto the session.

How did you verify your code works?

  • Added targeted tests for Session.setModel() persistence and variant persistence
  • Verified unknown models are handled without failing the session update
  • Will rerun the targeted test file after resolving the current merge with upstream/dev

Screenshots / recordings

No UI recording. The UI change is limited to syncing the selected model/variant from persisted session state.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

John Ferguson and others added 5 commits March 27, 2026 01:59
Adds first-class session model switching that allows plugins to dynamically
change the active model for a session. Model changes persist across turns,
update the UI, and are inherited by subagents.

Key changes:
- Add model/modelVariant fields to Session.Info with database migration
- Add Session.setModel() backend API for programmatic model changes
- Add PATCH /session/:sessionID/model HTTP route
- Chat.message hook mutations flow through Session.setModel()
- TUI syncs session model via reactive effect
- Session.fork copies model from original session
- Session.create inherits parent model for child sessions
- Add adversarial-review plugin example using model routing
- Add model-rewrite plugin for env var based model overrides
- Remove redundant ModelChanged event (use Event.Updated instead)
- Remove dead session.model hook type

Tests: 19 new tests for model routing and model-rewrite plugin
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- Remove hard-coded local plugin path from .opencode/opencode.jsonc
- Fix unreachable concurrent session guard in adversarial-review plugin
- Fix config load failure message (disabled -> running with defaults)
- Add model validation to Session.setModel (logs warning if not found)
- Sync modelVariant in TUI session model effect
Remove adversarial-review and model-rewrite example plugins along with
related config, specs, tests, and state files. These should live in a
dedicated plugins repository.
Merge dev's Effect architecture changes with session model switching feature.
Key resolutions:
- package.json: take dev's newer dependency versions
- session.sql.ts: keep model/model_variant columns, use dev's Permission type
- session/index.ts: integrate setModel into Effect layer with model inheritance
  in create/fork, convert setModel to Effect-based implementation
- session/prompt.ts: add model persistence via setModel after user message save
  in createUserMessage Effect, keep dev's Effect-based insertReminders
Copilot AI review requested due to automatic review settings April 11, 2026 14:19
@github-actions github-actions bot added the needs:compliance This means the issue will auto-close after 2 hours. label Apr 11, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a session-level “active model” concept to OpenCode so plugins and clients can switch which provider/model a session uses for future turns, with persistence and a small HTTP/TUI surface area.

Changes:

  • Persist model + modelVariant on Session.Info (SQLite columns + migration) and expose a Session.setModel() API.
  • Trigger session model updates after saving a user message when message.model is present (enabling plugin-driven routing via chat.message mutation).
  • Add PATCH /session/:sessionID/model endpoint and TUI syncing of model changes from session state.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/opencode/src/session/index.ts Adds model/modelVariant to Session mapping + introduces Session.setModel() with validation/warn behavior.
packages/opencode/src/session/session.sql.ts Extends session table schema with model (json text) and model_variant (text).
packages/opencode/migration/20260401004113_add_session_model/migration.sql Adds SQLite columns for session model + variant.
packages/opencode/migration/20260401004113_add_session_model/snapshot.json Updates migration snapshot to include the new columns.
packages/opencode/src/session/prompt.ts After persisting a user message, calls Session.setModel() if the message includes a model.
packages/opencode/src/server/routes/session.ts Adds PATCH /:sessionID/model route to update the session’s active model.
packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx Adds reactive syncing of local model selection from session state changes.
packages/opencode/test/session/set-model.test.ts Adds tests for persistence and “unknown model” behavior of Session.setModel().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +8 to +12
import { Config } from "../config/config"
import { Flag } from "../flag/flag"
import { Installation } from "../installation"
import { Provider } from "../provider/provider"
import { ModelID, ProviderID } from "../provider/schema"
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new relative imports for Provider/ProviderID/ModelID duplicate existing imports later in this file (via the @/ alias). This will cause TS "duplicate identifier"/conflicting import errors and can also result in two distinct module instances depending on TS path mapping. Remove one set and use a single import path consistently (prefer the existing @/ imports used elsewhere in this file).

Copilot uses AI. Check for mistakes.
Comment on lines +215 to +222
const modelKey = `${sessionModel.providerID}/${sessionModel.modelID}/${sessionVariant ?? ""}`
if (modelKey === lastSessionModelID) return

lastSessionModelID = modelKey
local.model.set(sessionModel)
if (sessionVariant !== undefined && sessionVariant !== null) {
local.model.variant.set(sessionVariant)
}
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the session model changes to a value with no modelVariant (null/undefined), this effect never clears the local variant, so the UI can keep showing/sending a stale variant from a previous model selection. Update the effect to explicitly clear/reset local.model.variant when sessionVariant is nullish (and consider handling the case where sessionModel becomes undefined if that’s possible).

Copilot uses AI. Check for mistakes.
@github-actions github-actions bot removed the needs:compliance This means the issue will auto-close after 2 hours. label Apr 11, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@v1truv1us v1truv1us force-pushed the feat/session-model-switching-v2 branch from 639c78f to f51bc9e Compare April 11, 2026 14:49
@v1truv1us
Copy link
Copy Markdown
Author

This is my first real open source contribution, so I hope this is helpful! If there is anything I can do to improve it, please let me know!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants