Repository navigation
Conversation
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 <[email protected]>
- 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
There was a problem hiding this comment.
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+modelVariantonSession.Info(SQLite columns + migration) and expose aSession.setModel()API. - Trigger session model updates after saving a user message when
message.modelis present (enabling plugin-driven routing viachat.messagemutation). - Add
PATCH /session/:sessionID/modelendpoint 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.
| 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" |
There was a problem hiding this comment.
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).
| 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) | ||
| } |
There was a problem hiding this comment.
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).
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
639c78f to
f51bc9e
Compare
|
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! |
|
Automated PR Cleanup Thank you for contributing to opencode. Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions. This PR was closed because it matched the following cleanup criteria:
PRs created within the last month are not affected by this cleanup. If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate. Thanks again for taking the time to contribute. |
Issue for this PR
Closes #18793
Type of change
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 formodelandmodelVariant, aPATCH /session/:sessionID/modelroute, and TUI sync from session state. Thechat.messagehook remains the integration point for plugins: if a plugin mutatesoutput.message.model, that model is saved on the user message and then persisted onto the session.How did you verify your code works?
Session.setModel()persistence and variant persistenceupstream/devScreenshots / recordings
No UI recording. The UI change is limited to syncing the selected model/variant from persisted session state.
Checklist