feat: add opencode support#762
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds 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. ChangesOpenCode Provider Integration
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 liftPer-session ownership validation is missing for abort-session action.
The
abort-sessionhandler (lines 159-187) routesprovider=opencodetoabortOpenCodeSessionwithout 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 winPass
context?.projectPathto OpenCode model catalog fetch for consistency with agent.js.Line 195 fetches the OpenCode model catalog without a
cwdoption, whileserver/routes/agent.jsline 944 passes{ cwd: finalProjectPath }. Thecwdparameter affects OpenCode CLI detection—the service executesopencode modelswithin the specified directory. Sincecontext?.projectPathis 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/modelcommand.🤖 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 valueSingle-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 valueConsider using a more descriptive label than "email".
Returning
${providerId} credentialsas thecredentialSourceor keepnullwhen 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 winConsider parallel model catalog fetching for better performance.
Lines 942-944 fetch three provider model catalogs sequentially using separate
awaitstatements. If these calls are independent, consider usingPromise.allto 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 valueRedundant import:
spawnfromchild_processis unused on Windows.Line 1 imports
spawnfromchild_process, but on Windows (line 10) the code usescrossSpawninstead. Thespawnimport 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 winExit 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.isProviderInstalledcheck (line 182) will still detect the missing CLI, the initial conditioncode === 127may never match on Windows.Consider checking for platform-specific exit codes or relying solely on the
isProviderInstalledcheck 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
📒 Files selected for processing (53)
server/claude-sdk.jsserver/index.jsserver/modules/projects/services/project-management.service.tsserver/modules/projects/services/projects-with-sessions-fetch.service.tsserver/modules/providers/README.mdserver/modules/providers/list/codex/codex-skills.provider.tsserver/modules/providers/list/opencode/opencode-auth.provider.tsserver/modules/providers/list/opencode/opencode-mcp.provider.tsserver/modules/providers/list/opencode/opencode-session-synchronizer.provider.tsserver/modules/providers/list/opencode/opencode-sessions.provider.tsserver/modules/providers/list/opencode/opencode-skills.provider.tsserver/modules/providers/list/opencode/opencode.provider.tsserver/modules/providers/provider.registry.tsserver/modules/providers/provider.routes.tsserver/modules/providers/services/provider-models.service.tsserver/modules/providers/services/session-synchronizer.service.tsserver/modules/providers/services/sessions-watcher.service.tsserver/modules/providers/tests/mcp.test.tsserver/modules/providers/tests/opencode-sessions.test.tsserver/modules/providers/tests/skills.test.tsserver/modules/websocket/services/chat-websocket.service.tsserver/modules/websocket/services/shell-websocket.service.tsserver/opencode-cli.jsserver/routes/agent.jsserver/routes/commands.jsserver/routes/cursor.jsserver/shared/types.tsserver/shared/utils.tsshared/modelConstants.jssrc/components/chat/hooks/useChatComposerState.tssrc/components/chat/hooks/useChatProviderState.tssrc/components/chat/view/ChatInterface.tsxsrc/components/chat/view/subcomponents/ChatMessagesPane.tsxsrc/components/chat/view/subcomponents/ClaudeStatus.tsxsrc/components/chat/view/subcomponents/MessageComponent.tsxsrc/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsxsrc/components/command-palette/sources/useSessionsSource.tssrc/components/llm-logo-provider/OpenCodeLogo.tsxsrc/components/llm-logo-provider/SessionProviderLogo.tsxsrc/components/mcp/constants.tssrc/components/provider-auth/types.tssrc/components/provider-auth/view/ProviderLoginModal.tsxsrc/components/settings/constants/constants.tssrc/components/settings/view/tabs/agents-settings/AgentListItem.tsxsrc/components/settings/view/tabs/agents-settings/AgentsSettingsTab.tsxsrc/components/settings/view/tabs/agents-settings/sections/AgentSelectorSection.tsxsrc/components/settings/view/tabs/agents-settings/sections/content/AccountContent.tsxsrc/components/sidebar/types/types.tssrc/components/sidebar/utils/utils.tssrc/hooks/useProjectsState.tssrc/i18n/locales/en/chat.jsonsrc/i18n/locales/en/settings.jsonsrc/types/app.ts
7111bed to
f36c5b6
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/components/chat/hooks/useChatRealtimeHandlers.ts (1)
232-258: ⚡ Quick winRemove leftover
console.logdebug statements.Lines 239–240 look like debugging artifacts that will spam the browser console on every
session_createdevent. 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
📒 Files selected for processing (12)
server/modules/database/index.tsserver/modules/providers/list/opencode/opencode-sessions.provider.tsserver/modules/providers/services/provider-models.service.tsserver/modules/providers/tests/opencode-sessions.test.tsserver/modules/providers/tests/provider-models.service.test.tsserver/routes/commands.jsserver/routes/tests/commands.test.jssrc/components/chat/hooks/useChatComposerState.tssrc/components/chat/hooks/useChatRealtimeHandlers.tssrc/components/chat/hooks/useChatSessionState.tssrc/components/chat/view/ChatInterface.tsxsrc/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
There was a problem hiding this comment.
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 winDon't silently price non-Claude providers with Claude rates.
geminiandopencodefall through topricingByProvider.claude, so/costreturns 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
📒 Files selected for processing (1)
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.
There was a problem hiding this comment.
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 winDon't price unsupported providers with Claude's rate card.
/costnow acceptsgeminiandopencode, 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
📒 Files selected for processing (34)
server/claude-sdk.jsserver/cursor-cli.jsserver/gemini-cli.jsserver/modules/providers/list/claude/claude-models.provider.tsserver/modules/providers/list/claude/claude.provider.tsserver/modules/providers/list/codex/codex-models.provider.tsserver/modules/providers/list/codex/codex.provider.tsserver/modules/providers/list/cursor/cursor-models.provider.tsserver/modules/providers/list/cursor/cursor-sessions.provider.tsserver/modules/providers/list/cursor/cursor.provider.tsserver/modules/providers/list/gemini/gemini-models.provider.tsserver/modules/providers/list/gemini/gemini.provider.tsserver/modules/providers/list/opencode/opencode-models.provider.tsserver/modules/providers/list/opencode/opencode.provider.tsserver/modules/providers/provider.routes.tsserver/modules/providers/services/provider-models.service.tsserver/modules/providers/shared/base/abstract.provider.tsserver/modules/providers/tests/provider-models.service.test.tsserver/openai-codex.jsserver/opencode-cli.jsserver/routes/agent.jsserver/routes/commands.jsserver/routes/cursor.jsserver/routes/tests/commands.test.jsserver/shared/interfaces.tsserver/shared/types.tsserver/shared/utils.tssrc/components/chat/hooks/useChatComposerState.tssrc/components/chat/hooks/useChatProviderState.tssrc/components/chat/view/ChatInterface.tsxsrc/components/chat/view/subcomponents/ChatMessagesPane.tsxsrc/components/chat/view/subcomponents/CommandResultModal.tsxsrc/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsxsrc/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
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.
Summary by CodeRabbit
New Features
Bug Fixes / Behavior
Documentation
Tests