Skip to content

feat: add opencode support#762

Merged
viper151 merged 15 commits into
mainfrom
feature/add-opencode-support
May 28, 2026
Merged

feat: add opencode support#762
viper151 merged 15 commits into
mainfrom
feature/add-opencode-support

Conversation

@blackmammoth
Copy link
Copy Markdown
Collaborator

@blackmammoth blackmammoth commented May 13, 2026

Summary by CodeRabbit

  • New Features

    • Full OpenCode provider added: CLI run/abort, live sessions, session history sync, MCP support, auth, logo, and terminal/websocket integration.
    • Provider model catalogs with caching, UI model selection, hard-refresh, and per-session model resolution for resuming sessions.
    • Command result modal (help/models/cost/status) and related chat/command UX updates.
  • Bug Fixes / Behavior

    • Resuming sessions now honors provider/session-specific model overrides.
  • Documentation

    • Provider guide updated with OpenCode implementation checklist.
  • Tests

    • New/expanded tests for OpenCode flows, MCP, skills, and model caching.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds OpenCode as a first-class provider, a provider-models cache/service, per-provider model adapters, CLI orchestration and resume-model resolution for CLIs, OpenCode session synchronization from SQLite, frontend provider-model catalogs and selection UI, and associated tests/docs.

Changes

OpenCode Provider Integration

Layer / File(s) Summary
Shared types, utils, provider-models service
src/types/app.ts, server/shared/types.ts, server/shared/utils.ts, server/modules/providers/services/provider-models.service.ts, shared/modelConstants.js
Adds opencode to provider union, provider model metadata/cache types, utilities for active-model caching and OpenCode DB path, and implements providerModelsService with in-memory TTL + persisted disk cache and resume-model resolution.
Provider contracts & registry
server/shared/interfaces.ts, server/modules/providers/shared/base/abstract.provider.ts, server/modules/providers/provider.registry.ts
Adds IProviderModels to IProvider, requires models on AbstractProvider, and registers OpenCodeProvider in the internal registry.
Provider model adapters
server/modules/providers/list/*/*-models.provider.ts
Adds per-provider model adapters and FALLBACK model sets for Claude, Cursor, Codex, Gemini, and OpenCode implementing getSupportedModels/getCurrentActiveModel/changeActiveModel.
OpenCode provider implementation
server/modules/providers/list/opencode/*
Adds OpenCode provider classes: auth, MCP (JSON/JSONC), skills discovery, sessions provider (live normalization + history/token usage), session synchronizer, and OpenCodeProvider wiring.
Opencode CLI & websocket wiring
server/opencode-cli.js, server/index.js, server/modules/websocket/services/*
Implements spawnOpenCode CLI orchestration with JSON parsing and session tracking; exports abort/active helpers; wires opencode command/abort/status handlers into websocket services and server startup.
CLI resume-model integration
server/claude-sdk.js, server/cursor-cli.js, server/gemini-cli.js, server/openai-codex.js, server/opencode-cli.js
Resolves resume/session-specific model via providerModelsService.resolveResumeModel before spawning CLI processes and switches default fallbacks to provider-specific FALLBACK models.
Routes and commands
server/modules/providers/provider.routes.ts, server/routes/agent.js, server/routes/commands.js, server/routes/cursor.js
Adds provider models GET and session active-model POST routes; updates /api/agent to support opencode and use providerModelsService for defaults; refactors commands to use provider catalogs and adds executeModelsCommand.
Project & session data, watcher, synchronizer
server/modules/projects/services/*, server/modules/providers/services/session-synchronizer.service.ts, server/modules/providers/services/sessions-watcher.service.ts
Adds opencodeSessions to API views and session-page types, includes opencode in bucketing and synchronization counts, and registers ~/.local/share/opencode watcher targeting opencode.db.
Frontend: provider model catalogs, hooks, UI
src/components/chat/hooks/*, src/components/chat/view/*, src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx, src/components/chat/view/subcomponents/CommandResultModal.tsx, src/components/llm-logo-provider/*
Loads per-provider model catalogs (including opencode), exposes opencodeModel/selectors, updates composer to dispatch opencode-command, replaces static constants with dynamic catalogs, adds CommandResultModal, and adds OpenCodeLogo and label mappings.
Settings, sidebar, session state
src/components/mcp/constants.ts, src/components/settings/*, src/components/sidebar/*, src/hooks/useProjectsState.ts
Adds opencode to MCP constants and settings, extends session view models with isOpenCodeSession, and threads opencodeSessions through projects/session merge, selection, deletion, and pagination logic.
Docs & tests
server/modules/providers/README.md, server/modules/providers/tests/*, server/routes/tests/*
Updates provider README for OpenCode; adds tests for provider-models caching/TTL/persistence/concurrency, OpenCode MCP, skills discovery, session sync/history normalization and token usage; adjusts existing tests for added provider coverage.

"🐰 I stitched OpenCode in with a hop and a cheer,
CLI friends arrive, session logs now appear.
Models and skills, SQLite trails in tow,
This rabbit celebrates the code's new glow!"

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/add-opencode-support

Copy link
Copy Markdown

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 OpenCode as a first-class LLM provider across the UI and server, including auth/status, model discovery, MCP + skills compatibility, session indexing/history, and WebSocket command execution.

Changes:

  • Extend provider unions/types and UI surfaces to include opencode (selection, labels, logos, sessions, settings).
  • Add backend OpenCode provider implementation (auth, MCP config, skills discovery, session sync + history from SQLite).
  • Introduce provider model catalog endpoint/service and update commands/agent/websocket flows to use it.

Reviewed changes

Copilot reviewed 53 out of 53 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/types/app.ts Add opencode provider + shared model-catalog types.
src/i18n/locales/en/settings.json Add OpenCode strings for settings/MCP descriptions.
src/i18n/locales/en/chat.json Add OpenCode strings for chat UI.
src/hooks/useProjectsState.ts Include OpenCode sessions in project state merge/selection/delete paths.
src/components/sidebar/utils/utils.ts Include OpenCode sessions in sidebar view models + sorting.
src/components/sidebar/types/types.ts Add isOpenCodeSession flag to session view model.
src/components/settings/view/tabs/agents-settings/sections/content/AccountContent.tsx Add OpenCode visuals + safer i18n fallback for descriptions.
src/components/settings/view/tabs/agents-settings/sections/AgentSelectorSection.tsx Add OpenCode label + selector dot color.
src/components/settings/view/tabs/agents-settings/AgentsSettingsTab.tsx Include OpenCode in visible agents + auth wiring.
src/components/settings/view/tabs/agents-settings/AgentListItem.tsx Add OpenCode list config + zinc color support.
src/components/settings/constants/constants.ts Add OpenCode to supported agent providers.
src/components/provider-auth/view/ProviderLoginModal.tsx Add OpenCode login command/title.
src/components/provider-auth/types.ts Add OpenCode auth status endpoints + initial state.
src/components/mcp/constants.ts Add OpenCode MCP name/scope/transport/button rules.
src/components/llm-logo-provider/SessionProviderLogo.tsx Route opencode to a dedicated logo component.
src/components/llm-logo-provider/OpenCodeLogo.tsx New OpenCode SVG logo component.
src/components/command-palette/sources/useSessionsSource.ts Include OpenCode sessions in palette session results.
src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx Switch provider-model lists to server-provided catalog + add OpenCode model handling.
src/components/chat/view/subcomponents/MessageComponent.tsx Add OpenCode message type label mapping.
src/components/chat/view/subcomponents/ClaudeStatus.tsx Add OpenCode provider label key for status header.
src/components/chat/view/subcomponents/ChatMessagesPane.tsx Thread OpenCode model + model catalog props into empty state.
src/components/chat/view/ChatInterface.tsx Thread OpenCode state + model catalog into chat UI; pass selectedProject to provider-state hook.
src/components/chat/hooks/useChatProviderState.ts Add OpenCode model state + load model catalogs from /api/providers/:provider/models.
src/components/chat/hooks/useChatComposerState.ts Send OpenCode commands over WS + include OpenCode model in command context/local settings.
shared/modelConstants.js Add OPENCODE model list + provider registry entry.
server/shared/utils.ts Add helpers for topmost git root, skill source de-dupe, timestamp normalization, JSON record parsing, OpenCode DB path.
server/shared/types.ts Extend provider union + add provider model catalog types.
server/routes/cursor.js Read cursor model constants from provider-models service.
server/routes/commands.js Use provider-models service for /model, /cost, /status builtins; add OpenCode.
server/routes/agent.js Add OpenCode support to /api/agent execution path.
server/opencode-cli.js New OpenCode CLI runner with JSON streaming + abort/active tracking.
server/modules/websocket/services/shell-websocket.service.ts Add OpenCode shell command construction + welcome text.
server/modules/websocket/services/chat-websocket.service.ts Add WS message type for OpenCode + abort/active wiring.
server/modules/providers/tests/skills.test.ts Add OpenCode skills discovery test coverage.
server/modules/providers/tests/opencode-sessions.test.ts New tests for OpenCode SQLite session sync + history/token usage.
server/modules/providers/tests/mcp.test.ts Add OpenCode MCP config tests + update global MCP count/expectations.
server/modules/providers/services/sessions-watcher.service.ts Watch OpenCode opencode.db for session sync triggers.
server/modules/providers/services/session-synchronizer.service.ts Track OpenCode sync counts.
server/modules/providers/services/provider-models.service.ts New provider models service incl. opencode models parsing/fallback.
server/modules/providers/README.md Document OpenCode provider facets + MCP/skills/sessions rules.
server/modules/providers/provider.routes.ts Add GET /api/providers/:provider/models endpoint.
server/modules/providers/provider.registry.ts Register OpenCode provider implementation.
server/modules/providers/list/opencode/opencode.provider.ts New OpenCode provider composition (auth/mcp/skills/sessions/sync).
server/modules/providers/list/opencode/opencode-skills.provider.ts New OpenCode skills root discovery (cwd→topmost git + compat dirs).
server/modules/providers/list/opencode/opencode-sessions.provider.ts New OpenCode history reader + live event normalization + token aggregation.
server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts New OpenCode SQLite session indexer (jsonl_path stays null).
server/modules/providers/list/opencode/opencode-mcp.provider.ts New OpenCode MCP config read/write (JSON/JSONC support).
server/modules/providers/list/opencode/opencode-auth.provider.ts New OpenCode install + credential detection.
server/modules/providers/list/codex/codex-skills.provider.ts Refactor to shared git-root + dedupe utilities.
server/modules/projects/services/projects-with-sessions-fetch.service.ts Include OpenCode sessions in project session buckets and API views.
server/modules/projects/services/project-management.service.ts Ensure empty project API view includes OpenCode sessions array.
server/index.js Wire OpenCode into websocket deps + mark token-usage endpoint unsupported for OpenCode.
server/claude-sdk.js Read Claude model constants from provider-models service.

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

Comment thread shared/modelConstants.js Outdated
Comment thread server/routes/commands.js Outdated
Comment thread src/components/chat/hooks/useChatProviderState.ts Outdated
Comment thread server/modules/providers/services/provider-models.service.ts Fixed
Comment thread server/modules/providers/services/provider-models.service.ts Fixed
Comment thread server/opencode-cli.js Fixed
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
server/modules/websocket/services/chat-websocket.service.ts (1)

159-187: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Per-session ownership validation is missing for abort-session action.

The abort-session handler (lines 159-187) routes provider=opencode to abortOpenCodeSession without verifying that the requesting user owns the session. Based on learnings, this is a known security gap that affects all providers on both WebSocket and HTTP polling transports and is tracked for a dedicated follow-up PR.

For context: the learning recommends adding explicit per-session ownership validation (verify that the initiating client owns the session, enforce authorization guards, and avoid cross-session actions), along with tests that simulate cross-session requests and ensure they are rejected.

Based on learnings: session-scoped actions (abort-session, check-session-status, get-pending-permissions, get-active-sessions) do not enforce per-session ownership checks. This is a known, intentional gap spanning both transports and is tracked for a dedicated follow-up PR.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/modules/websocket/services/chat-websocket.service.ts` around lines 159
- 187, The abort-session branch lacks per-session ownership checks before
calling provider abort functions; update the handler that reads provider via
readProvider and then calls
abortCursorSession/abortCodexSession/abortGeminiSession/abortOpenCodeSession/abortClaudeSDKSession
to first verify the requester owns the session (e.g. call a new or existing
dependency like dependencies.verifySessionOwnership(sessionId, requesterId) and
reject with success=false if verification fails), ensure the
createNormalizedMessage reply reflects the authorization failure, and add
unit/integration tests that simulate cross-session abort attempts to confirm
unauthorized requests are rejected.
server/routes/commands.js (1)

189-230: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Pass context?.projectPath to OpenCode model catalog fetch for consistency with agent.js.

Line 195 fetches the OpenCode model catalog without a cwd option, while server/routes/agent.js line 944 passes { cwd: finalProjectPath }. The cwd parameter affects OpenCode CLI detection—the service executes opencode models within the specified directory. Since context?.projectPath is available in this handler (as used elsewhere in commands.js), pass it to maintain consistency and ensure project-specific model availability is properly detected in the /model command.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/routes/commands.js` around lines 189 - 230, The /model route handler
calls providerModelsService.getProviderModels for several providers but calls
getProviderModels('opencode') without the project cwd; update the opencode call
in the '/model' handler so it forwards context?.projectPath (e.g., pass an
options object with cwd: context?.projectPath) to
providerModelsService.getProviderModels('opencode') like the agent.js usage,
ensuring the OpenCode CLI runs in the project directory and project-specific
models are detected.
🧹 Nitpick comments (5)
server/modules/providers/list/opencode/opencode-mcp.provider.ts (1)

55-60: 💤 Low value

Single-quote strings are not valid JSON.

Line 55 treats single quotes (') as valid string delimiters. Standard JSON only supports double quotes. While this might work if you're parsing relaxed JSONC, it could lead to confusion or unexpected behavior with non-conforming input.

If OpenCode config files genuinely support single-quoted strings, this is fine. Otherwise, consider removing single-quote support.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/modules/providers/list/opencode/opencode-mcp.provider.ts` around lines
55 - 60, The parser currently treats single quotes as string delimiters (if
(char === '"' || char === '\'')), which allows non-standard JSON; update the
logic in the string-handling block (the variables inString, quote, and char are
involved) to only treat double quotes as a string start (i.e., check char ===
'"' only), remove or adjust any downstream handling that relies on quote being
'\'' so single-quoted strings are no longer accepted unless the project
explicitly intends to support JSONC-like single quotes.
server/modules/providers/list/opencode/opencode-auth.provider.ts (1)

78-78: 💤 Low value

Consider using a more descriptive label than "email".

Returning ${providerId} credentials as the email field is semantically misleading since this isn't actually an email address. Consider using a dedicated field like credentialSource or keep email as null when the credential is provider-based rather than user-based.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/modules/providers/list/opencode/opencode-auth.provider.ts` at line 78,
The returned object is setting a non-email string into the email field (email:
`${providerId} credentials`), which is misleading; update the return shape to
either set email to null and add a new descriptive field (e.g., credentialSource
or providerCredentials) populated with `${providerId} credentials`, or replace
the email assignment directly with credentialSource: `${providerId}
credentials`; locate the assignment using providerId and the email property in
opencode-auth.provider.ts and ensure any callers or type definitions (e.g., the
function that builds the user/identity object) are updated to expect the new
field or nullable email.
server/routes/agent.js (1)

942-944: ⚡ Quick win

Consider parallel model catalog fetching for better performance.

Lines 942-944 fetch three provider model catalogs sequentially using separate await statements. If these calls are independent, consider using Promise.all to fetch them concurrently:

-    const codexModels = await providerModelsService.getProviderModels('codex');
-    const geminiModels = await providerModelsService.getProviderModels('gemini');
-    const opencodeModels = await providerModelsService.getProviderModels('opencode', { cwd: finalProjectPath });
+    const [codexModels, geminiModels, opencodeModels] = await Promise.all([
+      providerModelsService.getProviderModels('codex'),
+      providerModelsService.getProviderModels('gemini'),
+      providerModelsService.getProviderModels('opencode', { cwd: finalProjectPath }),
+    ]);

This would reduce latency in the agent endpoint startup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/routes/agent.js` around lines 942 - 944, The three independent
provider catalog fetches (calls to providerModelsService.getProviderModels for
'codex', 'gemini', and 'opencode') are performed sequentially, causing
unnecessary latency; change the code to run them concurrently using Promise.all
and then destructure the results into codexModels, geminiModels, and
opencodeModels (pass { cwd: finalProjectPath } for the opencode call) so the
agent endpoint startup waits for all three promises together instead of
one-by-one.
server/opencode-cli.js (2)

1-3: 💤 Low value

Redundant import: spawn from child_process is unused on Windows.

Line 1 imports spawn from child_process, but on Windows (line 10) the code uses crossSpawn instead. The spawn import is only used on non-Windows platforms. While this works, it creates a minor redundancy.

This is a common pattern in cross-platform Node.js code and is acceptable, but consider documenting the platform-specific usage or conditionally importing if strict tree-shaking is desired.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/opencode-cli.js` around lines 1 - 3, The import of spawn from
'child_process' is redundant because crossSpawn is used on Windows; remove the
top-level "import { spawn } from 'child_process';" and either (a) always use
crossSpawn in place of spawn, or (b) perform a conditional/late require for
spawn inside the non-Windows branch (e.g., require('child_process').spawn) so
the symbol "spawn" is only loaded when used; update any references to "spawn"
accordingly and add a brief comment near "crossSpawn" explaining the
platform-specific usage.

181-191: ⚡ Quick win

Exit code 127 detection is Unix-specific and may not detect missing CLI on Windows.

Exit code 127 conventionally means "command not found" on Unix-like systems, but Windows typically returns exit code 1 or 9009 for missing commands. While the fallback providerAuthService.isProviderInstalled check (line 182) will still detect the missing CLI, the initial condition code === 127 may never match on Windows.

Consider checking for platform-specific exit codes or relying solely on the isProviderInstalled check for all non-zero exits, rather than special-casing 127.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/opencode-cli.js` around lines 181 - 191, The code special-cases exit
code 127 (Unix "command not found") which misses Windows variants (1 or 9009);
update the logic in the exit handler around the variables code and
finalSessionId to either (a) detect platform-specific missing-command codes
using process.platform (e.g., treat 127 on POSIX and 1/9009 on win32 as missing)
or, simpler and safer, remove the 127 special-case and always call
providerAuthService.isProviderInstalled('opencode') when code is
non-zero/failed; if not installed, send the same
ws.send(createNormalizedMessage({... provider: 'opencode' ...})) error message.
Ensure you update the condition that currently reads code === 127 to the new
platform-aware or non-zero check so Windows is covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/modules/providers/list/opencode/opencode-auth.provider.ts`:
- Around line 72-74: The current credential detection in the hasCredential
computation can treat empty objects as valid (Boolean(readObjectRecord(value))
returns true for {}); update the predicate used in the some(...) check to ensure
objects are non-empty or contain expected credential fields: when
readObjectRecord(value) returns an object, verify Object.keys(obj).length > 0
(or explicitly check for required properties like apiKey/token) instead of just
casting to Boolean; keep the existing readOptionalString(value) check and only
treat the provider as authenticated if a non-empty string is present or the
object passes the non-empty/has-required-keys test.

In `@server/modules/providers/services/sessions-watcher.service.ts`:
- Around line 37-40: The OpenCode data directory is hardcoded to
path.join(os.homedir(), '.local', 'share', 'opencode') which breaks on Windows;
update platform-specific resolution in the three spots
(sessions-watcher.service.ts provider entry, opencode-auth.provider.ts, and
getOpenCodeDatabasePath in utils.ts) to detect process.platform === 'win32' and
use process.env.APPDATA || process.env.LOCALAPPDATA (joined with 'opencode') on
Windows, otherwise keep the existing XDG-style path for Unix, and ensure you
import/retain path and os usage consistently in each module.

In `@src/components/chat/hooks/useChatProviderState.ts`:
- Around line 70-86: The Promise.all call that fetches provider models (inside
useChatProviderState where providers.map is used and results is assigned) should
be changed to Promise.allSettled so a single provider failure doesn't drop all
catalogs; for each settled result, keep fulfilled values (extract the models
returned) and ignore/recover from rejections or invalid parses (treat them as
null) before assigning to state. Also apply the same allSettled pattern to the
similar fetch block around the later providers loop (the second providers.map at
lines ~100-105) and ensure you still validate body.success and body.data?.models
when extracting fulfilled values.

---

Outside diff comments:
In `@server/modules/websocket/services/chat-websocket.service.ts`:
- Around line 159-187: The abort-session branch lacks per-session ownership
checks before calling provider abort functions; update the handler that reads
provider via readProvider and then calls
abortCursorSession/abortCodexSession/abortGeminiSession/abortOpenCodeSession/abortClaudeSDKSession
to first verify the requester owns the session (e.g. call a new or existing
dependency like dependencies.verifySessionOwnership(sessionId, requesterId) and
reject with success=false if verification fails), ensure the
createNormalizedMessage reply reflects the authorization failure, and add
unit/integration tests that simulate cross-session abort attempts to confirm
unauthorized requests are rejected.

In `@server/routes/commands.js`:
- Around line 189-230: The /model route handler calls
providerModelsService.getProviderModels for several providers but calls
getProviderModels('opencode') without the project cwd; update the opencode call
in the '/model' handler so it forwards context?.projectPath (e.g., pass an
options object with cwd: context?.projectPath) to
providerModelsService.getProviderModels('opencode') like the agent.js usage,
ensuring the OpenCode CLI runs in the project directory and project-specific
models are detected.

---

Nitpick comments:
In `@server/modules/providers/list/opencode/opencode-auth.provider.ts`:
- Line 78: The returned object is setting a non-email string into the email
field (email: `${providerId} credentials`), which is misleading; update the
return shape to either set email to null and add a new descriptive field (e.g.,
credentialSource or providerCredentials) populated with `${providerId}
credentials`, or replace the email assignment directly with credentialSource:
`${providerId} credentials`; locate the assignment using providerId and the
email property in opencode-auth.provider.ts and ensure any callers or type
definitions (e.g., the function that builds the user/identity object) are
updated to expect the new field or nullable email.

In `@server/modules/providers/list/opencode/opencode-mcp.provider.ts`:
- Around line 55-60: The parser currently treats single quotes as string
delimiters (if (char === '"' || char === '\'')), which allows non-standard JSON;
update the logic in the string-handling block (the variables inString, quote,
and char are involved) to only treat double quotes as a string start (i.e.,
check char === '"' only), remove or adjust any downstream handling that relies
on quote being '\'' so single-quoted strings are no longer accepted unless the
project explicitly intends to support JSONC-like single quotes.

In `@server/opencode-cli.js`:
- Around line 1-3: The import of spawn from 'child_process' is redundant because
crossSpawn is used on Windows; remove the top-level "import { spawn } from
'child_process';" and either (a) always use crossSpawn in place of spawn, or (b)
perform a conditional/late require for spawn inside the non-Windows branch
(e.g., require('child_process').spawn) so the symbol "spawn" is only loaded when
used; update any references to "spawn" accordingly and add a brief comment near
"crossSpawn" explaining the platform-specific usage.
- Around line 181-191: The code special-cases exit code 127 (Unix "command not
found") which misses Windows variants (1 or 9009); update the logic in the exit
handler around the variables code and finalSessionId to either (a) detect
platform-specific missing-command codes using process.platform (e.g., treat 127
on POSIX and 1/9009 on win32 as missing) or, simpler and safer, remove the 127
special-case and always call providerAuthService.isProviderInstalled('opencode')
when code is non-zero/failed; if not installed, send the same
ws.send(createNormalizedMessage({... provider: 'opencode' ...})) error message.
Ensure you update the condition that currently reads code === 127 to the new
platform-aware or non-zero check so Windows is covered.

In `@server/routes/agent.js`:
- Around line 942-944: The three independent provider catalog fetches (calls to
providerModelsService.getProviderModels for 'codex', 'gemini', and 'opencode')
are performed sequentially, causing unnecessary latency; change the code to run
them concurrently using Promise.all and then destructure the results into
codexModels, geminiModels, and opencodeModels (pass { cwd: finalProjectPath }
for the opencode call) so the agent endpoint startup waits for all three
promises together instead of one-by-one.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5ff1315b-c958-48b2-a649-603f00a6cdba

📥 Commits

Reviewing files that changed from the base of the PR and between 10f721c and 421bdd2.

📒 Files selected for processing (53)
  • server/claude-sdk.js
  • server/index.js
  • server/modules/projects/services/project-management.service.ts
  • server/modules/projects/services/projects-with-sessions-fetch.service.ts
  • server/modules/providers/README.md
  • server/modules/providers/list/codex/codex-skills.provider.ts
  • server/modules/providers/list/opencode/opencode-auth.provider.ts
  • server/modules/providers/list/opencode/opencode-mcp.provider.ts
  • server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts
  • server/modules/providers/list/opencode/opencode-sessions.provider.ts
  • server/modules/providers/list/opencode/opencode-skills.provider.ts
  • server/modules/providers/list/opencode/opencode.provider.ts
  • server/modules/providers/provider.registry.ts
  • server/modules/providers/provider.routes.ts
  • server/modules/providers/services/provider-models.service.ts
  • server/modules/providers/services/session-synchronizer.service.ts
  • server/modules/providers/services/sessions-watcher.service.ts
  • server/modules/providers/tests/mcp.test.ts
  • server/modules/providers/tests/opencode-sessions.test.ts
  • server/modules/providers/tests/skills.test.ts
  • server/modules/websocket/services/chat-websocket.service.ts
  • server/modules/websocket/services/shell-websocket.service.ts
  • server/opencode-cli.js
  • server/routes/agent.js
  • server/routes/commands.js
  • server/routes/cursor.js
  • server/shared/types.ts
  • server/shared/utils.ts
  • shared/modelConstants.js
  • src/components/chat/hooks/useChatComposerState.ts
  • src/components/chat/hooks/useChatProviderState.ts
  • src/components/chat/view/ChatInterface.tsx
  • src/components/chat/view/subcomponents/ChatMessagesPane.tsx
  • src/components/chat/view/subcomponents/ClaudeStatus.tsx
  • src/components/chat/view/subcomponents/MessageComponent.tsx
  • src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx
  • src/components/command-palette/sources/useSessionsSource.ts
  • src/components/llm-logo-provider/OpenCodeLogo.tsx
  • src/components/llm-logo-provider/SessionProviderLogo.tsx
  • src/components/mcp/constants.ts
  • src/components/provider-auth/types.ts
  • src/components/provider-auth/view/ProviderLoginModal.tsx
  • src/components/settings/constants/constants.ts
  • src/components/settings/view/tabs/agents-settings/AgentListItem.tsx
  • src/components/settings/view/tabs/agents-settings/AgentsSettingsTab.tsx
  • src/components/settings/view/tabs/agents-settings/sections/AgentSelectorSection.tsx
  • src/components/settings/view/tabs/agents-settings/sections/content/AccountContent.tsx
  • src/components/sidebar/types/types.ts
  • src/components/sidebar/utils/utils.ts
  • src/hooks/useProjectsState.ts
  • src/i18n/locales/en/chat.json
  • src/i18n/locales/en/settings.json
  • src/types/app.ts

Comment thread server/modules/providers/list/opencode/opencode-auth.provider.ts
Comment thread server/modules/providers/services/sessions-watcher.service.ts
Comment thread src/components/chat/hooks/useChatProviderState.ts Outdated
@blackmammoth blackmammoth marked this pull request as draft May 13, 2026 15:44
Comment thread server/modules/providers/services/provider-models.service.ts Fixed
Comment thread server/opencode-cli.js Fixed
@blackmammoth blackmammoth force-pushed the feature/add-opencode-support branch from 7111bed to f36c5b6 Compare May 14, 2026 12:02
@blackmammoth blackmammoth marked this pull request as ready for review May 14, 2026 12:03
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/components/chat/hooks/useChatRealtimeHandlers.ts (1)

232-258: ⚡ Quick win

Remove leftover console.log debug statements.

Lines 239–240 look like debugging artifacts that will spam the browser console on every session_created event. The same information is already implied by the surrounding state updates and can be moved to a structured logger if telemetry is actually desired.

🧹 Suggested cleanup
         if (!currentSessionId) {
-          console.log('Session created with ID:', newSessionId);
-          console.log('Existing session ID:', currentSessionId);
           setCurrentSessionId(newSessionId);
           setPendingPermissionRequests((prev) =>
             prev.map((r) => (r.sessionId ? r : { ...r, sessionId: newSessionId })),
           );
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/chat/hooks/useChatRealtimeHandlers.ts` around lines 232 - 258,
Remove the two console.log debug statements inside the 'session_created' case in
useChatRealtimeHandlers (the console.log calls that print newSessionId and
currentSessionId); instead either delete them or replace them with a structured
telemetry/log call if needed, keeping the rest of the logic that uses
currentSessionId, setCurrentSessionId, setPendingPermissionRequests,
pendingViewSessionRef, onSessionActive/onSessionProcessing, setIsLoading,
setCanAbortSession, setClaudeStatus and onNavigateToSession unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/routes/commands.js`:
- Around line 241-245: Normalize and validate the provider before calling
providerModelsService.getProviderModels: read context?.provider, coerce to a
normalized form (e.g., toLowerCase and trim), fall back to 'claude' if empty or
invalid, and verify against the set of supported providers (or use a
providerModelsService.normalize/validate helper if available) so
getProviderModels always receives a valid provider; then call
providerModelsService.getProviderModels(normalizedProvider, { cwd: workspace })
so workspace cwd options are respected and use catalog.DEFAULT for model
fallback (the existing model selection logic using context?.model ||
catalog.DEFAULT remains). Apply the same normalization and cwd fix in the other
handler block referenced (lines around 343-356 for /cost and /status).
- Around line 76-78: The message constructed in the commands.js route is
misleading because it says "Switching to model: ${args[0]}" even though the
handler only reports model availability/state; update the message logic to
accurately reflect reporting rather than performing a switch—e.g., when
args.length > 0 use a neutral phrase like "Requested model: ${args[0]}
(reporting availability)" or "Model info for: ${args[0]}", and when args is
empty keep "Current model: ${currentModel}"; modify the ternary that builds
message (the `message` variable using `args` and `currentModel`) to use the
corrected wording so the route no longer claims it switched models.

---

Nitpick comments:
In `@src/components/chat/hooks/useChatRealtimeHandlers.ts`:
- Around line 232-258: Remove the two console.log debug statements inside the
'session_created' case in useChatRealtimeHandlers (the console.log calls that
print newSessionId and currentSessionId); instead either delete them or replace
them with a structured telemetry/log call if needed, keeping the rest of the
logic that uses currentSessionId, setCurrentSessionId,
setPendingPermissionRequests, pendingViewSessionRef,
onSessionActive/onSessionProcessing, setIsLoading, setCanAbortSession,
setClaudeStatus and onNavigateToSession unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3fa232f4-a8e3-476d-b47f-e2c9e1f0784a

📥 Commits

Reviewing files that changed from the base of the PR and between 421bdd2 and f36c5b6.

📒 Files selected for processing (12)
  • server/modules/database/index.ts
  • server/modules/providers/list/opencode/opencode-sessions.provider.ts
  • server/modules/providers/services/provider-models.service.ts
  • server/modules/providers/tests/opencode-sessions.test.ts
  • server/modules/providers/tests/provider-models.service.test.ts
  • server/routes/commands.js
  • server/routes/tests/commands.test.js
  • src/components/chat/hooks/useChatComposerState.ts
  • src/components/chat/hooks/useChatRealtimeHandlers.ts
  • src/components/chat/hooks/useChatSessionState.ts
  • src/components/chat/view/ChatInterface.tsx
  • src/components/chat/view/subcomponents/CommandResultModal.tsx
✅ Files skipped from review due to trivial changes (1)
  • server/modules/database/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • server/modules/providers/tests/opencode-sessions.test.ts
  • server/modules/providers/list/opencode/opencode-sessions.provider.ts

Comment thread server/routes/commands.js Outdated
Comment thread server/routes/commands.js Outdated
Comment thread server/routes/commands.js Dismissed
Comment thread server/routes/commands.js Dismissed
Comment thread server/routes/commands.js Dismissed
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/routes/commands.js (1)

302-312: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't silently price non-Claude providers with Claude rates.

gemini and opencode fall through to pricingByProvider.claude, so /cost returns concrete USD numbers for the wrong provider. If you don't have a verified rate table here, it's safer to mark pricing as unavailable than to report a Claude estimate.

Also applies to: 328-335

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/routes/commands.js` around lines 302 - 312, The code currently
defaults any unknown provider (e.g., "gemini", "opencode") to
pricingByProvider.claude which produces incorrect USD estimates; update the
/cost logic so that when pricingByProvider[provider] is undefined you do NOT
assign rates = pricingByProvider.claude but instead treat rates as missing and
return a clear "pricing unavailable" response (or include null/N/A for
inputCost/outputCost/totalCost) rather than concrete numbers; change the logic
around the symbols pricingByProvider, provider, rates, inputCost, outputCost,
and totalCost to check existence first and handle the unknown-provider branch
explicitly (apply the same fix to the other pricing block referenced around the
later occurrence of these same variables).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/routes/commands.js`:
- Around line 58-61: The currentModel selection uses context.model verbatim and
can end up choosing a model that doesn't belong to the resolved provider; update
the logic wherever currentModel is derived (e.g., the currentModel assignment
and similar spots) to validate that context.model exists in catalog.OPTIONS for
the provider resolved by readModelProvider(), and if it does not, fall back to
catalog.DEFAULT; specifically, obtain the normalized provider via
readModelProvider(context.provider || context.modelProvider), check
catalog.OPTIONS[normalizedProvider] includes context.model, and only then use
context.model, otherwise use catalog.DEFAULT to ensure model and provider stay
consistent.
- Around line 541-559: The current allowlist uses a lexical resolvedPath and can
be bypassed via symlinks; instead call the async realpath (fs.realpath /
fs.promises.realpath) on commandPath and use that canonical path in the isUnder
check (replace resolvedPath with the real path); also canonicalize userBase and
projectBase (realpath) before comparing so comparisons are against actual
filesystem locations, and handle realpath errors (missing file -> return 404,
other errors -> 500/403) before calling fs.readFile. Reference: resolvedPath,
userBase, projectBase, isUnder, and fs.readFile.

---

Outside diff comments:
In `@server/routes/commands.js`:
- Around line 302-312: The code currently defaults any unknown provider (e.g.,
"gemini", "opencode") to pricingByProvider.claude which produces incorrect USD
estimates; update the /cost logic so that when pricingByProvider[provider] is
undefined you do NOT assign rates = pricingByProvider.claude but instead treat
rates as missing and return a clear "pricing unavailable" response (or include
null/N/A for inputCost/outputCost/totalCost) rather than concrete numbers;
change the logic around the symbols pricingByProvider, provider, rates,
inputCost, outputCost, and totalCost to check existence first and handle the
unknown-provider branch explicitly (apply the same fix to the other pricing
block referenced around the later occurrence of these same variables).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b0042abe-670b-4a9f-bc73-347ca68ee854

📥 Commits

Reviewing files that changed from the base of the PR and between f36c5b6 and ffaef39.

📒 Files selected for processing (1)
  • server/routes/commands.js

Comment thread server/routes/commands.js Outdated
Comment thread server/routes/commands.js
Provider model selection had outgrown a single hardcoded service.

The old service mixed shared caching with provider catalogs and CLI lookup details.

That made stale model lists more likely as providers changed on separate schedules.

Move model discovery behind each provider so lookup lives next to the integration.

The shared service now focuses on provider resolution, caching, persistence, and dedupe.

Return cache metadata and add bypassCache because model availability changes outside the app.

The UI and /models command can show freshness and let users force a provider refresh.

Surface model descriptions while keeping fallback catalogs for unavailable CLIs or SDKs.
The model inventory command was showing a mix of catalog defaults and
composer-local state instead of the model that is actually active for a
real provider session. That made /models, /cost, and /status
misleading once a session had already started, especially for providers
whose effective runtime model can differ from the optimistic model value
held in the UI.

Introduce an explicit getCurrentActiveModel() contract on
IProviderModels so model resolution lives next to each provider's
catalog logic and uses the provider-native source of truth:

- Claude reads the init event from a resumed stream-json run
- Codex reads model from ~/.codex/config.toml
- Cursor reads lastUsedModel from the chat store.db
- OpenCode reads the persisted session model from opencode.db
- Gemini intentionally returns its default because the CLI does not
  provide a reliable active-session lookup

Keep the returned shape intentionally minimal ({ model }). The goal is
to expose only what downstream command consumers need and avoid leaking
provider-specific metadata into a shared transport shape that would
create extra UI coupling and future cleanup cost.

Also make command behavior session-aware: when there is no concrete
session id, do not spawn provider processes or inspect provider session
storage just to answer /models, /cost, or /status. In a new-session
view the correct answer is simply the provider default, and doing more
work there adds latency and unnecessary side effects for no user value.

As part of this, centralize two supporting concerns:

- add a shared helper for building the default current-model result from
  a provider catalog so fallbacks stay aligned with DEFAULT
- move leaf-directory validation into shared utils so Cursor session
  readers and model lookup code enforce the same path-safety rule

Tests were expanded to cover both the new service delegation path and
the sessionless command behavior, while keeping cache-sensitive tests
isolated from persisted host cache state.

Why this change:
- command output should reflect the model actually driving a session
- new-session views should stay fast and side-effect free
- provider-specific active-model lookup should not be scattered across
  routes or UI code
- fallback behavior should be explicit, consistent, and limited to the
  provider default when no true active model can be resolved
Model selection was acting like a provider-level preference.

That made resumed sessions drift back to a default or request-time model.

Users expect /models changes made inside a conversation to affect that session.

Store explicit session choices in app-owned ~/.cloudcli state.

This avoids editing provider transcripts or native provider config.

Resolve the effective model before launching each provider runtime.

Claude, Cursor, Codex, Gemini, and OpenCode now honor stored resume choices.

Expose a backend active-model change endpoint for existing sessions.

The models modal can now distinguish default changes from session overrides.

It also shows when a selected model will apply on the next response.

For Claude, stop probing active model state by resuming with a dummy prompt.

Read the indexed JSONL transcript from the end instead.

This preserves provider history while honoring /model stdout or model fields.

Add service tests for adapter delegation and resume-model precedence.

The tests keep cache state, override state, and requested fallback separate.
Comment thread server/opencode-cli.js Dismissed
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/routes/commands.js (1)

305-311: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Don't price unsupported providers with Claude's rate card.

/cost now accepts gemini and opencode, but this fallback silently estimates both with Claude pricing. That makes the reported USD totals wrong for those providers. Either add explicit rates for every supported provider or return a "pricing unavailable" response instead of defaulting to Claude.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/routes/commands.js` around lines 305 - 311, The current fallback
assigns Claude rates to unsupported providers via the pricingByProvider and
rates variables, causing incorrect cost estimates for new providers (e.g.,
"gemini" and "opencode"); update the pricingByProvider map to include explicit
rate entries for every supported provider (add "gemini" and "opencode" objects
with input/output USD per million-token rates) or, alternatively, change the
lookup logic that computes rates (the code that sets rates =
pricingByProvider[provider] || pricingByProvider.claude) to detect missing
entries and return a clear "pricing unavailable" response (or 400) instead of
defaulting to Claude so unsupported providers are not silently mispriced.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/claude-sdk.js`:
- Around line 495-505: The call to
providerModelsService.resolveResumeModel('claude', sessionId, options.model) can
throw and currently aborts the entire run; wrap that resolution in a try/catch
and on error (or if it returns falsy) fall back to using options.model (the
caller/default) when building sdkOptions for mapCliOptionsToSDK so sdkOptions
uses resolvedModel only if resolution succeeded; reference
providerModelsService.resolveResumeModel, resolvedModel, options.model, and
mapCliOptionsToSDK to locate and update the logic.

In `@server/cursor-cli.js`:
- Around line 30-33: In spawnCursor, avoid using an async Promise executor: call
await providerModelsService.resolveResumeModel('cursor', sessionId, model)
before constructing the new Promise so any rejection is handled by the outer
async function (or explicitly try/catch there) and then pass the resolvedModel
into the Promise body; update references to resolvedModel inside the Promise
(and remove the async from the Promise executor) so the outer Promise settles
correctly on errors from resolveResumeModel.

In `@server/gemini-cli.js`:
- Around line 124-128: Wrap the call to providerModelsService.resolveResumeModel
in a try/catch inside spawnGemini so failures are handled defensively: catch
exceptions from resolveResumeModel (the resolvedModel variable) and log a
non-fatal warning/trace but allow spawnGemini to continue so CLI error
messaging/notifications still run; when falling back to a default model (the
fallback logic referenced near the existing model selection), ensure you
preserve the user's requested model intent by preferring options.model when
present and only using the provider-derived default if options.model is absent
or empty.

In `@server/modules/providers/list/cursor/cursor-models.provider.ts`:
- Around line 239-243: The code always hex-decodes non-empty strings into
metadataText which corrupts plain JSON; update the logic around metadataText
(the ternary that checks Buffer.isBuffer and row?.value) to only hex-decode when
the string is actually hex (e.g., matches /^[0-9a-fA-F]+$/ after trim);
otherwise treat the trimmed string as raw JSON text. Locate metadataText in
cursor-models.provider.ts and change the branch that decodes via
Buffer.from(...,'hex') to first test for a hex-only string before decoding so
lastUsedModel and session lookups recover correctly.

In `@server/openai-codex.js`:
- Around line 206-210: The call to
providerModelsService.resolveResumeModel('codex', sessionId, model) is performed
before the try block so any thrown error bypasses the function's normalized
error handling; wrap or move the resolveResumeModel call into the existing try
(or add a small try/catch around it) so failures are handled and
notifications/errors are normalized, and when setting threadOptions.model use a
local fallback (e.g., use the original model value if resolveResumeModel fails
or returns falsy) so outages in resolveResumeModel cannot overwrite
threadOptions.model with undefined.

In `@server/opencode-cli.js`:
- Around line 159-197: The close handler on opencodeProcess currently treats
code === null as a failure; change the opencodeProcess.on('close', ...) callback
to accept the second argument (signal) and treat signal === 'SIGTERM' (or use an
aborted flag set by abortOpenCodeSession()) as a user cancellation: skip the
provider-install check, send a normalized cancellation/completion message for
the session (using the same sessionId logic), call notifyTerminalState with a
cancellation status, resolve the promise (not reject), and return early; apply
the same change to the other close handler instance used later (the one
referenced in the comment).

In `@server/routes/commands.js`:
- Around line 567-576: The current replacements use raw replacement strings
(argsString and arg) which allow special sequences like $&/$1/$$ to be
interpreted by String.replace; change both replacements to use function
replacers so values are inserted literally: for the $ARGUMENTS replacement
replace processedContent = processedContent.replace(/\$ARGUMENTS/g, () =>
argsString) and for the positional loop use processedContent.replace(new
RegExp(`\\${placeholder}\\b`, "g"), () => arg) so the replacement callback
returns the literal argument value; update occurrences around the variables
argsString, processedContent, placeholder and the args.forEach callback
accordingly.

In `@server/shared/utils.ts`:
- Around line 971-972: getOpenCodeDatabasePath currently hardcodes the Linux XDG
path; update it to resolve per-platform data directories by checking
process.platform and environment variables: use process.env.XDG_DATA_HOME
(fallback to path.join(os.homedir(), '.local', 'share')) for Linux, use
path.join(os.homedir(), 'Library', 'Application Support') for darwin, and
process.env.APPDATA (fallback to path.join(os.homedir(), 'AppData', 'Roaming'))
for win32, then return path.join(dataDir, 'opencode', 'opencode.db'); implement
this logic inside the getOpenCodeDatabasePath function so session lookups work
across macOS/Windows/custom XDG roots.
- Around line 615-636: Currently when normalizedModel is falsy the function
returns without removing a previously persisted override; update the branch that
checks (!normalizedSessionId || !normalizedModel) to load the cache via
readProviderSessionActiveModelChangeCacheFile (using filePath from
options.filePath ?? getProviderSessionActiveModelChangesPath()), compute the key
with buildProviderSessionActiveModelChangeKey(provider, normalizedSessionId),
delete that key from the cache if present, write the cache back with
writeProviderSessionActiveModelChangeCacheFile, and then return with provider,
sessionId, supported:true, changed:true, model:null (or changed:false only if no
entry existed) so readProviderSessionActiveModelChange() no longer reports a
cleared override.

In `@src/components/chat/view/subcomponents/CommandResultModal.tsx`:
- Around line 327-338: handleSelectModel updates UI only for 'session' scope;
when onSelectProviderModel returns scope: 'default' you must also update the
component's displayed current model so the "Active model" card and selection
badges reflect the new default. After handling the result for scope ===
'default' (where you currently call setSelectionNotice and
setPendingSessionModel(null)), also call the state updater that controls the
displayed model (e.g., setCurrentModel or the equivalent prop/state that holds
currentModel/currentModelDisplayed) with result.model so the UI re-renders to
show the new default model.

---

Outside diff comments:
In `@server/routes/commands.js`:
- Around line 305-311: The current fallback assigns Claude rates to unsupported
providers via the pricingByProvider and rates variables, causing incorrect cost
estimates for new providers (e.g., "gemini" and "opencode"); update the
pricingByProvider map to include explicit rate entries for every supported
provider (add "gemini" and "opencode" objects with input/output USD per
million-token rates) or, alternatively, change the lookup logic that computes
rates (the code that sets rates = pricingByProvider[provider] ||
pricingByProvider.claude) to detect missing entries and return a clear "pricing
unavailable" response (or 400) instead of defaulting to Claude so unsupported
providers are not silently mispriced.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 73e498db-001d-4c84-987b-c3e0981f92d8

📥 Commits

Reviewing files that changed from the base of the PR and between ffaef39 and 9aa9270.

📒 Files selected for processing (34)
  • server/claude-sdk.js
  • server/cursor-cli.js
  • server/gemini-cli.js
  • server/modules/providers/list/claude/claude-models.provider.ts
  • server/modules/providers/list/claude/claude.provider.ts
  • server/modules/providers/list/codex/codex-models.provider.ts
  • server/modules/providers/list/codex/codex.provider.ts
  • server/modules/providers/list/cursor/cursor-models.provider.ts
  • server/modules/providers/list/cursor/cursor-sessions.provider.ts
  • server/modules/providers/list/cursor/cursor.provider.ts
  • server/modules/providers/list/gemini/gemini-models.provider.ts
  • server/modules/providers/list/gemini/gemini.provider.ts
  • server/modules/providers/list/opencode/opencode-models.provider.ts
  • server/modules/providers/list/opencode/opencode.provider.ts
  • server/modules/providers/provider.routes.ts
  • server/modules/providers/services/provider-models.service.ts
  • server/modules/providers/shared/base/abstract.provider.ts
  • server/modules/providers/tests/provider-models.service.test.ts
  • server/openai-codex.js
  • server/opencode-cli.js
  • server/routes/agent.js
  • server/routes/commands.js
  • server/routes/cursor.js
  • server/routes/tests/commands.test.js
  • server/shared/interfaces.ts
  • server/shared/types.ts
  • server/shared/utils.ts
  • src/components/chat/hooks/useChatComposerState.ts
  • src/components/chat/hooks/useChatProviderState.ts
  • src/components/chat/view/ChatInterface.tsx
  • src/components/chat/view/subcomponents/ChatMessagesPane.tsx
  • src/components/chat/view/subcomponents/CommandResultModal.tsx
  • src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx
  • src/types/app.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/types/app.ts
  • server/routes/cursor.js
  • server/modules/providers/list/opencode/opencode.provider.ts
  • server/routes/agent.js
  • src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx
  • src/components/chat/view/ChatInterface.tsx
  • src/components/chat/hooks/useChatComposerState.ts

Comment thread server/claude-sdk.js
Comment thread server/cursor-cli.js
Comment thread server/gemini-cli.js
Comment thread server/modules/providers/list/cursor/cursor-models.provider.ts
Comment thread server/openai-codex.js
Comment thread server/opencode-cli.js
Comment thread server/routes/commands.js
Comment thread server/shared/utils.ts
Comment thread server/shared/utils.ts
Comment thread src/components/chat/view/subcomponents/CommandResultModal.tsx
@blackmammoth blackmammoth linked an issue May 18, 2026 that may be closed by this pull request
OpenCode emits the real session id asynchronously on its first JSON output. The runner
registered that id from a helper that could not see the spawned process because
the process reference was scoped inside the model-resolution callback. That
ReferenceError was swallowed by the generic JSON parse fallback, so the client
never received session_created. Without that event, a new OpenCode chat stayed
on / and the assistant stream was not attached to the new session view.

Keep the process reference in the outer spawn scope so registration can update
the active-process map and websocket writer as soon as OpenCode announces the
session id. Split JSON parsing from event processing so malformed non-JSON
output can still stream as raw text, while registration or adapter failures are
surfaced as real errors instead of being hidden as assistant content.

Add a fake opencode executable regression test to lock in the expected lifecycle
ordering: session_created must be sent before live assistant messages, and the
same session id must carry through stream_end and complete.
OpenCode is now a supported chat provider, but first-run onboarding still only offered
Claude, Cursor, Codex, and Gemini. That made OpenCode harder to discover and
forced users to finish setup before finding the provider in settings or chat.
Adding it to onboarding keeps first-run setup aligned with the providers the
application already supports elsewhere.

The model refresh control was also doing too much visual work. In the new chat
model picker, the previous Hard Refresh label looked like the dialog heading,
which made the primary task unclear. Users open that dialog to choose a model;
refreshing catalogs is only a secondary maintenance action for stale cached
provider model lists.

Rename and reposition the refresh affordance so the model picker reads as a
model picker first. The copy now explains why catalogs are cached, when a refresh
is useful, and that the refresh checks every provider. The /models modal gets the
same clarification so both model-selection surfaces describe the cache behavior
consistently.
OpenCode returns provider-prefixed ids directly from the CLI. Passing those ids through as
labels made the model picker hard to scan: users saw values like
anthropic/claude-3-5-sonnet-20241022 or lowercased, hyphen-split text instead
of readable model names.

Keep the exact OpenCode id as the option value because that is what the CLI
expects, but derive a presentation label for the frontend. The formatter is
intentionally generic rather than a catalog of known providers. It handles common
identifier structure such as provider/model, hyphen-delimited words, v-prefixed
versions, adjacent numeric version tokens, and 8-digit date suffixes.

This keeps OpenCode usable as its model list expands across many upstream
providers without requiring code changes for every new provider or model family.
The description keeps the raw provider-prefixed id visible so users can still
confirm the precise model being selected.
The model catalog is no longer a frontend/backend runtime contract.

Keeping it under shared made ownership misleading. It implied the catalog was
application code shared by runtime consumers, even though it now only supports
README links and public API documentation.

Move the catalog into public so it lives beside the docs surfaces that need it.
This gives the API docs a stable, served module and gives README readers a
linkable source without suggesting frontend or backend runtime dependency.

Render the API docs model list from the exported provider registry instead of a
hardcoded Claude/Cursor/Codex subset. That keeps Gemini and OpenCode visible and
makes future provider documentation changes flow through one docs-specific file.

Update README links, provider maintenance notes, and package files so published
artifacts include the standalone docs page and model catalog without relying on
the old shared path.
Keep the provider empty state focused on the setup action users need there:

choosing a model.

The refresh control, cache timestamp, and refresh explanation made the dialog feel

like a cache-management surface.

That extra action is out of place in the empty state, where the goal is to start

a chat with the selected provider and model.

Remove the refresh-specific UI from ProviderSelectionEmptyState and drop the

now-unused refresh/cache props from the ChatMessagesPane pass-through.

Refresh behavior remains available in the dedicated command result flow.
@viper151 viper151 merged commit 374e9de into main May 28, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

4 participants