Skip to content

Conversation

@stefandevo
Copy link
Collaborator

@stefandevo stefandevo commented Jan 11, 2026

Summary

  • Dynamic model discovery: Fetches available models from opencode models CLI command and caches them in the Zustand store
  • Provider authentication detection: Shows connected providers via opencode auth list CLI command in the settings UI
  • Model routing: Routes dynamic OpenCode models (e.g., github-copilot/gpt-4o, google/gemini-2.5-pro) to the OpenCode provider
  • Provider-based grouping: Groups models by provider in both settings and model selector dropdown (e.g., "GitHub Copilot", "Z.AI Coding Plan", "Google AI")
  • Dynamic icons: Shows appropriate icons for dynamic models based on the underlying model type (GPT, Claude, Gemini, etc.)
  • Fallback support: Unknown providers are automatically supported with generic icons and formatted display names

Changes

  • opencode-provider.ts: Enhanced with model discovery and improved event normalization
  • opencode-models.ts: New route for fetching dynamic models and providers
  • provider-icon.tsx: Added dynamic model icon detection
  • phase-model-selector.tsx: Added provider-based grouping in dropdown
  • opencode-model-configuration.tsx: Added dynamic model configuration UI
  • opencode-cli-status.tsx: Shows connected dynamic providers
  • resolver.ts: Recognizes dynamic provider/model format
  • provider-utils.ts: Enhanced isOpencodeModel to detect dynamic models

Test plan

  • Connect a provider via opencode auth login (e.g., GitHub Copilot, Google AI)
  • Verify models appear in Settings > Providers > OpenCode tab
  • Verify models appear grouped by provider in the model dropdown
  • Select a dynamic model and verify it works for enhancement
  • Verify unknown providers show with fallback icons and names
CleanShot 2026-01-12 at 00 36 56@2x CleanShot 2026-01-12 at 00 37 33@2x CleanShot 2026-01-12 at 00 37 53@2x CleanShot 2026-01-12 at 00 38 35@2x CleanShot 2026-01-12 at 00 39 09@2x

Summary by CodeRabbit

  • New Features
    • Dynamic discovery, background refresh, caching and manual refresh/clear for OpenCode models and providers exposed in setup UI and API
    • Per-provider grouping, provider-aware model selection, and provider icons (including OpenRouter) in model pickers and settings
  • Bug Fixes / UX
    • Clearer CLI/authentication status UI showing connected providers vs direct auth; improved prompt handling and normalized provider error streams
  • Chores
    • Persisted settings for enabled dynamic models and new store bindings for dynamic providers/models

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 11, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

Adds dynamic OpenCode model/provider discovery via the OpenCode CLI, with parsing, caching, refresh and provider-auth listing; server API routes to expose/refresh/clear caches; UI wiring for listing/toggling dynamic models and providers; provider-based execution path and related types, store migrations, and tests.

Changes

Cohort / File(s) Summary
OpenCode provider core
apps/server/src/providers/opencode-provider.ts
New OpenCodeModelInfo/OpenCodeProviderInfo types; CLI fetch & parse helpers (fetchModelsFromCli, parseModelsOutput, modelInfoToDefinition); model/provider caching (cachedModels, modelsCacheExpiry, MODEL_CACHE_DURATION_MS) and refresh workflow (refreshModels, getAvailableModels, clearModelCache, hasCachedModels); provider auth discovery (fetchAuthenticatedProviders, parseProvidersOutput); event normalization, opencodeLogger, prompt/stdin handling, and helper exports (formatProviderName, formatModelDisplayName, inferModelTier, modelSupportsVision).
Server API routes
apps/server/src/routes/setup/index.ts, apps/server/src/routes/setup/routes/opencode-models.ts
New routes: GET /api/setup/opencode/models, POST /api/setup/opencode/models/refresh, GET /api/setup/opencode/providers, POST /api/setup/opencode/cache/clear; singleton provider, handlers use refreshModels/getAvailableModels/fetchAuthenticatedProviders/clearModelCache, unified error handling.
Provider execution & routing
apps/server/src/routes/enhance-prompt/routes/enhance.ts
Replaced Cursor-specific flow with executeWithProvider; added isOpencodeModel branch to use provider execution for OpenCode; consolidated provider message/error handling and final result accumulation.
UI API client & icons
apps/ui/src/lib/http-api-client.ts, apps/ui/src/components/ui/provider-icon.tsx
HttpApiClient methods added: getOpencodeModels, refreshOpencodeModels, getOpencodeProviders, clearOpencodeCache; new openrouter provider icon and dynamic model→icon resolution updates.
Settings UI & model config
apps/ui/src/components/views/settings-view/*
apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx, opencode-settings-tab.tsx, opencode-model-configuration.tsx, phase-model-selector.tsx
CLI status shows provider auth and provider badges; settings tab loads/caches dynamic providers/models and exposes refresh; model configuration and model selector integrate dynamic models grouped by provider, toggles for enabling dynamic models, loading states and toasts.
Store & settings sync/migration
apps/ui/src/store/app-store.ts, apps/ui/src/hooks/use-settings-migration.ts, apps/ui/src/hooks/use-settings-sync.ts
AppState additions: dynamicOpencodeModels, enabledDynamicModelIds, cachedOpencodeProviders; actions: setDynamicOpencodeModels, toggleDynamicModel, setCachedOpencodeProviders, setEnabledDynamicModelIds; migration/sync sanitize and persist opencode fields including enabledDynamicModelIds.
Types & utilities
libs/types/src/opencode-models.ts, libs/types/src/provider-utils.ts, libs/types/src/settings.ts, libs/model-resolver/src/resolver.ts
Narrowed OpencodeModel/Provider unions (removed many Bedrock IDs, default points to opencode free-tier model); isOpencodeModel extended to detect provider/model dynamic format; added enabledDynamicModelIds to GlobalSettings.
Tests
apps/server/tests/unit/providers/opencode-provider.test.ts, libs/prompts/tests/enhancement.test.ts
Updated provider tests for new defaults and parseModelsOutput assertions; enhancement modes test now includes ux-reviewer.
Misc UI copy
apps/ui/src/components/views/setup-view/steps/*
Minor copy edits replacing "AWS Bedrock" with "connected providers".

Sequence Diagram(s)

sequenceDiagram
    participant UI as Settings UI
    participant Store as App Store
    participant API as HttpApiClient
    participant Server as Server Routes
    participant Provider as OpencodeProvider
    participant CLI as OpenCode CLI

    UI->>Store: mount -> request opencode models
    Store->>API: getOpencodeModels(refresh=false)
    API->>Server: GET /api/setup/opencode/models
    Server->>Provider: getAvailableModels()
    alt cache valid
        Provider-->>Server: return cached models (cached=true)
    else cache missing/expired or refresh
        Provider->>CLI: spawn CLI fetch (stream/JSON)
        CLI-->>Provider: stream output
        Provider->>Provider: parseModelsOutput & modelInfoToDefinition
        Provider->>Provider: update cache
        Provider-->>Server: return fresh models (cached=false)
    end
    Server-->>API: { models, cached, count }
    API-->>Store: setDynamicOpencodeModels(models)
    Store-->>UI: render models grouped by provider
    UI->>Store: toggleDynamicModel(id, enabled)
    Store->>API: sync settings update / optional refresh
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Possibly related PRs

  • feat: bulk delete in backlog mass select #413 — modifies related server/UI flows for bulk-delete; surfaced by vector similarity (may touch related route and UI wiring).
    (Only PRs with strong code-level relation were considered; list limited to most relevant.)

Suggested labels

Enhancement, Testers-Requested, Do Not Merge

Poem

🐰 Hopping through CLI streams at night,

I sniff new models, glowing bright.
Cache them, group them, badges in a row,
Providers lined up, ready to show.
🥕🔎✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and concisely describes the primary objective: adding dynamic model discovery and routing for the OpenCode provider, which is the main focus across the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @stefandevo, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the OpenCode provider's capabilities by introducing dynamic model discovery and management. It allows the application to automatically fetch and display AI models and authenticated providers directly from the OpenCode CLI, providing users with a more flexible and up-to-date selection of tools. The user interface has been updated to intelligently group these dynamic models by provider and assign relevant icons, improving usability. Additionally, the underlying event processing for OpenCode has been made more resilient, and the project's development dependencies have undergone a substantial cleanup.

Highlights

  • Dynamic Model Discovery: The application can now dynamically discover and fetch available AI models from the OpenCode CLI using the opencode models command, rather than relying solely on hardcoded definitions.
  • Provider Authentication Detection: The settings UI now displays connected OpenCode providers by querying the opencode auth list CLI command, showing which external AI services are authenticated.
  • Model Routing and Grouping: Dynamic OpenCode models (e.g., 'github-copilot/gpt-4o', 'google/gemini-2.5-pro') are now correctly routed to the OpenCode provider and grouped by their respective providers in both the settings and model selector dropdowns for improved organization.
  • Dynamic Icons and Fallback Support: Appropriate icons are now displayed for dynamic models based on their underlying model type (e.g., GPT, Claude, Gemini), with robust fallback support for unknown providers using generic icons and formatted display names.
  • Enhanced OpenCode Event Normalization: The OpenCode provider's event processing has been improved to handle various stream event types more robustly, including refined error message extraction and clearer mapping to internal message structures.
  • Dependency Cleanup: A significant number of unused or transitive development dependencies, particularly related to Next.js, Electron build tools, and image processing libraries, have been removed from package-lock.json, streamlining the project.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces dynamic model discovery for the OpenCode provider, which is a significant feature enhancement. The implementation is comprehensive, covering server-side logic for fetching and caching models, API routes, and extensive UI updates to display and manage these dynamic models. The code is well-structured, but there are a few key areas for improvement, particularly regarding server performance and code duplication. My main feedback focuses on replacing synchronous file operations with asynchronous alternatives on the server to prevent blocking the event loop, which is critical for a responsive application. I've also identified what appears to be an unused UI component that should be removed.

@stefandevo
Copy link
Collaborator Author

@coderabbitai can you review this PR

@coderabbitai
Copy link

coderabbitai bot commented Jan 11, 2026

@stefandevo: I'll review the changes in this PR for you.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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: 6

🤖 Fix all issues with AI agents
In @apps/server/src/routes/setup/routes/opencode-models.ts:
- Around line 124-126: The catch blocks in the refresh handler and
createGetOpencodeProvidersHandler are logging the same error twice via
logger.error(...) and logError(...); remove the duplicate by keeping only the
preferred logging call (use logError(error, 'Refresh OpenCode models failed')
and logError(error, 'Get OpenCode providers failed') for consistent
structured/telemetry logging) and delete the corresponding logger.error(...)
calls so each catch logs once.

In @apps/ui/src/components/ui/provider-icon.tsx:
- Around line 445-497: The dynamic model parsing branch incorrectly handles
strings with multiple slashes (e.g., "a/b/c") because it uses
modelStr.includes('/') and modelStr.split('/')[1]; update the logic in the
dynamic branch to only treat strings with exactly one slash as OpenCode-style
provider/model pairs (e.g., use a check like indexOf('/') !== -1 && indexOf('/')
=== lastIndexOf('/') or a regex /^[^/]+\/[^/]+$/), then destructure safely once
(const [providerName, modelName] = modelStr.split('/', 2)) and keep the existing
pattern checks against modelName/providerName; if the string does not have
exactly one slash, skip this branch and fall back to the normal detection flow.

In
@apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx:
- Around line 240-254: The selected OpenCode model always uses the generic
OpenCodeIcon; instead resolve the icon dynamically using getProviderIconForModel
for both static and dynamic cases. Update the opencodeModel branch to set icon:
getProviderIconForModel(opencodeModel.id) instead of OpenCodeIcon, and update
the dynamicModel branch to set icon: getProviderIconForModel(dynamicModel.id)
instead of OpenCodeIcon so the trigger shows provider-specific icons for models
like google/gemini-*.
- Around line 298-318: The merge logic currently filters out dynamic models that
collide with static ones, making static take precedence; to fix, build
dynamicModelOptions from dynamicOpencodeModels, create a map keyed by id, then
produce the merged array by iterating OPENCODE_MODELS and replacing any static
entry with the dynamic map value when present (so dynamic overrides but keeps
static ordering), and finally append any dynamicModelOptions whose ids were not
present in OPENCODE_MODELS so their relative order is preserved; update the
allOpencodeModels useMemo to use this replace-then-append strategy (referencing
allOpencodeModels, OPENCODE_MODELS, dynamicOpencodeModels, and
dynamicModelOptions).

In @apps/ui/src/store/app-store.ts:
- Around line 1953-1965: The static OpenCode settings enabledOpencodeModels and
opencodeDefaultModel are missing from the SETTINGS_FIELDS_TO_SYNC list in
use-settings-sync.ts; add those two keys to SETTINGS_FIELDS_TO_SYNC so they are
persisted like enabledCursorModels/cursorDefaultModel. For dynamic models,
decide whether enabledDynamicModelIds should be session-only or persisted: if
you want persistence, add enabledDynamicModelIds to the GlobalSettings interface
and include it in SETTINGS_FIELDS_TO_SYNC; otherwise explicitly leave it out and
add a code comment in setDynamicOpencodeModels (and the GlobalSettings
definition) noting that enabledDynamicModelIds is intentionally session-scoped.
🧹 Nitpick comments (9)
libs/types/src/provider-utils.ts (1)

90-137: Tighten provider/model detection to avoid over-matching arbitrary strings.

Right now any non-URL string with a single / will route to OpenCode (e.g., accidental foo/bar values). If that’s intended, fine; otherwise consider requiring exactly one slash via index checks (and/or whitelisting provider ids elsewhere) to reduce false positives.

Proposed tweak (exactly one slash, fewer allocations)
-  if (model.includes('/') && !model.includes('://')) {
-    const parts = model.split('/');
-    // Valid dynamic model format: provider/model-name (exactly 2 parts)
-    if (parts.length === 2 && parts[0].length > 0 && parts[1].length > 0) {
-      return true;
-    }
-  }
+  if (!model.includes('://')) {
+    const first = model.indexOf('/');
+    if (first !== -1 && first === model.lastIndexOf('/') && first > 0 && first < model.length - 1) {
+      return true;
+    }
+  }
apps/ui/src/components/views/settings-view/providers/opencode-dynamic-providers.tsx (2)

20-25: Avoid duplicating OpenCodeProviderInfo shape in UI (type drift risk).
Consider exporting/importing a single OpenCodeProviderInfo type (e.g., from @automaker/types) instead of re-declaring it here. Also PROVIDER_CONFIG.icon is unused.

Possible direction
-interface OpenCodeProviderInfo {
-  id: string;
-  name: string;
-  authenticated: boolean;
-  authMethod?: 'oauth' | 'api_key';
-}
+// Prefer a shared type (example):
+// import type { OpenCodeProviderInfo } from '@automaker/types';
-const PROVIDER_CONFIG: Record<string, { icon: string; displayName: string }> = {
+const PROVIDER_CONFIG: Record<string, { displayName: string }> = {
   copilot: { icon: '', displayName: 'GitHub Copilot' },
   anthropic: { icon: '', displayName: 'Anthropic' },
   openai: { icon: '', displayName: 'OpenAI' },
   google: { icon: '', displayName: 'Google' },
   'amazon-bedrock': { icon: '', displayName: 'AWS Bedrock' },
   azure: { icon: '', displayName: 'Azure OpenAI' },
   ollama: { icon: '', displayName: 'Ollama' },
   lmstudio: { icon: '', displayName: 'LM Studio' },
   opencode: { icon: '', displayName: 'OpenCode' },
 };

Also applies to: 27-51


62-85: Consider user feedback on initial model fetch failures (silent failure).
fetchModels() logs errors but doesn’t surface a toast; if the UI depends on these models, a lightweight toast (or inline “failed to load”) may reduce confusion.

Also applies to: 117-145

apps/ui/src/lib/http-api-client.ts (1)

1439-1499: Prefer shared types for OpenCode model/provider payloads to prevent drift.

These new methods inline models[] and providers[] shapes; this will eventually diverge from server responses and @automaker/types (ModelDefinition, plus whatever provider-info type you standardize on).

Direction (sketch)
-    getOpencodeModels: (
-      refresh?: boolean
-    ): Promise<{
-      success: boolean;
-      models?: Array<{
-        id: string;
-        name: string;
-        modelString: string;
-        provider: string;
-        description: string;
-        supportsTools: boolean;
-        supportsVision: boolean;
-        tier: string;
-        default?: boolean;
-      }>;
+    getOpencodeModels: (
+      refresh?: boolean
+    ): Promise<{
+      success: boolean;
+      models?: ModelDefinition[];
       count?: number;
       cached?: boolean;
       error?: string;
     }> => this.get(`/api/setup/opencode/models${refresh ? '?refresh=true' : ''}`),
apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx (1)

372-412: Provider grouping looks good; consider typing renderOpencodeModelItem as ModelOption.

You’re now passing dynamic ModelOptions through grouped rendering; renderOpencodeModelItem is typed as (typeof OPENCODE_MODELS)[0], which can become brittle if OPENCODE_MODELS’ element type ever diverges from ModelOption.

Also applies to: 1077-1082

apps/ui/src/store/app-store.ts (1)

574-583: Make enabledDynamicModelIds updates idempotent + consider reconciling on refresh.

toggleDynamicModel(..., true) appends without checking existence, so duplicates are possible. Also, when models refresh, old enabled IDs may reference removed models.

Proposed idempotent toggle
-  toggleDynamicModel: (modelId, enabled) =>
-    set((state) => ({
-      enabledDynamicModelIds: enabled
-        ? [...state.enabledDynamicModelIds, modelId]
-        : state.enabledDynamicModelIds.filter((id) => id !== modelId),
-    })),
+  toggleDynamicModel: (modelId, enabled) =>
+    set((state) => {
+      const cur = state.enabledDynamicModelIds;
+      if (enabled) {
+        return cur.includes(modelId) ? state : { enabledDynamicModelIds: [...cur, modelId] };
+      }
+      return { enabledDynamicModelIds: cur.filter((id) => id !== modelId) };
+    }),

Also applies to: 1953-1973

apps/server/src/providers/opencode-provider.ts (1)

725-776: Synchronous execFileSync blocks the event loop during CLI operations.

While execFileSync is acceptable for quick CLI operations, consider using async execFile from child_process/promises to avoid blocking, especially since timeouts are set to 30 seconds which could freeze the server.

This is a recommended improvement for future iterations - the current implementation will work but may impact server responsiveness during model discovery.

apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx (1)

235-235: Unnecessary type assertion on providers prop.

The type assertion as OpenCodeProviderInfo[] suggests a potential type mismatch between the store and component. If cachedOpencodeProviders is already typed as OpenCodeProviderInfo[] in the store, this cast is unnecessary.

Consider ensuring the store's cachedOpencodeProviders is correctly typed to avoid the need for this assertion.

apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx (1)

8-13: Duplicate OpenCodeProviderInfo interface should be shared.

This interface is duplicated from apps/server/src/providers/opencode-provider.ts. Consider moving it to @automaker/types to maintain a single source of truth and ensure type consistency between server and client.

// In @automaker/types
export interface OpenCodeProviderInfo {
  id: string;
  name: string;
  authenticated: boolean;
  authMethod?: 'oauth' | 'api_key';
}

Then import from the shared package in both locations.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43fc3de and 887c1b1.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (14)
  • apps/server/src/providers/opencode-provider.ts
  • apps/server/src/routes/enhance-prompt/routes/enhance.ts
  • apps/server/src/routes/setup/index.ts
  • apps/server/src/routes/setup/routes/opencode-models.ts
  • apps/ui/src/components/ui/provider-icon.tsx
  • apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/ui/src/components/views/settings-view/providers/opencode-dynamic-providers.tsx
  • apps/ui/src/components/views/settings-view/providers/opencode-model-configuration.tsx
  • apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx
  • apps/ui/src/lib/http-api-client.ts
  • apps/ui/src/store/app-store.ts
  • libs/model-resolver/src/resolver.ts
  • libs/types/src/provider-utils.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • libs/types/src/provider-utils.ts
  • apps/ui/src/components/ui/provider-icon.tsx
  • apps/server/src/routes/setup/routes/opencode-models.ts
  • apps/ui/src/components/views/settings-view/providers/opencode-dynamic-providers.tsx
  • libs/model-resolver/src/resolver.ts
  • apps/ui/src/components/views/settings-view/providers/opencode-model-configuration.tsx
  • apps/server/src/routes/enhance-prompt/routes/enhance.ts
  • apps/ui/src/lib/http-api-client.ts
  • apps/server/src/providers/opencode-provider.ts
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/server/src/routes/setup/index.ts
  • apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx
  • apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • libs/types/src/provider-utils.ts
  • apps/ui/src/components/ui/provider-icon.tsx
  • apps/server/src/routes/setup/routes/opencode-models.ts
  • apps/ui/src/components/views/settings-view/providers/opencode-dynamic-providers.tsx
  • libs/model-resolver/src/resolver.ts
  • apps/ui/src/components/views/settings-view/providers/opencode-model-configuration.tsx
  • apps/server/src/routes/enhance-prompt/routes/enhance.ts
  • apps/ui/src/lib/http-api-client.ts
  • apps/server/src/providers/opencode-provider.ts
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/server/src/routes/setup/index.ts
  • apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx
  • apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx
apps/server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket

Files:

  • apps/server/src/routes/setup/routes/opencode-models.ts
  • apps/server/src/routes/enhance-prompt/routes/enhance.ts
  • apps/server/src/providers/opencode-provider.ts
  • apps/server/src/routes/setup/index.ts
🧠 Learnings (2)
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Applies to **/*.{ts,tsx} : Use `resolveModelString()` from automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Applied to files:

  • apps/ui/src/components/ui/provider-icon.tsx
  • libs/model-resolver/src/resolver.ts
  • apps/server/src/routes/enhance-prompt/routes/enhance.ts
  • apps/server/src/providers/opencode-provider.ts
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.

Applied to files:

  • apps/ui/src/components/ui/provider-icon.tsx
  • apps/ui/src/components/views/settings-view/providers/opencode-dynamic-providers.tsx
  • apps/ui/src/components/views/settings-view/providers/opencode-model-configuration.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx
  • apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx
🧬 Code graph analysis (8)
apps/server/src/routes/setup/routes/opencode-models.ts (2)
apps/server/src/providers/opencode-provider.ts (2)
  • OpencodeProvider (228-1201)
  • OpenCodeProviderInfo (64-73)
apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx (1)
  • OpenCodeProviderInfo (8-13)
apps/ui/src/components/views/settings-view/providers/opencode-dynamic-providers.tsx (4)
apps/server/src/providers/opencode-provider.ts (1)
  • OpenCodeProviderInfo (64-73)
apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx (1)
  • OpenCodeProviderInfo (8-13)
apps/ui/src/lib/electron.ts (1)
  • getElectronAPI (908-917)
apps/ui/src/lib/utils.ts (1)
  • cn (6-8)
apps/ui/src/components/views/settings-view/providers/opencode-model-configuration.tsx (3)
libs/types/src/index.ts (1)
  • ModelDefinition (15-15)
apps/server/src/providers/index.ts (1)
  • ModelDefinition (18-18)
apps/ui/src/components/ui/provider-icon.tsx (4)
  • GeminiIcon (158-160)
  • OpenAIIcon (150-152)
  • AnthropicIcon (146-148)
  • GrokIcon (162-164)
apps/server/src/routes/enhance-prompt/routes/enhance.ts (1)
libs/types/src/provider-utils.ts (1)
  • isOpencodeModel (103-138)
apps/ui/src/store/app-store.ts (2)
apps/ui/src/types/electron.d.ts (1)
  • ModelDefinition (1029-1037)
libs/types/src/provider.ts (1)
  • ModelDefinition (222-235)
apps/server/src/routes/setup/index.ts (1)
apps/server/src/routes/setup/routes/opencode-models.ts (4)
  • createGetOpencodeModelsHandler (61-103)
  • createRefreshOpencodeModelsHandler (110-133)
  • createGetOpencodeProvidersHandler (141-166)
  • createClearOpencodeCacheHandler (173-191)
apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx (1)
apps/server/src/providers/opencode-provider.ts (1)
  • OpenCodeProviderInfo (64-73)
apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx (3)
apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx (2)
  • OpencodeAuthStatus (44-51)
  • OpenCodeProviderInfo (8-13)
apps/ui/src/lib/electron.ts (1)
  • getElectronAPI (908-917)
apps/server/src/providers/opencode-provider.ts (1)
  • OpenCodeProviderInfo (64-73)
🪛 Biome (2.1.2)
apps/server/src/providers/opencode-provider.ts

[error] 795-795: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)


[error] 1051-1051: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)

🔇 Additional comments (18)
libs/model-resolver/src/resolver.ts (1)

72-81: No issues found. All four documented OpenCode model formats are correctly handled by isOpencodeModel() in provider-utils.ts:

  • opencode-* prefix (line 107)
  • opencode/* prefix (line 119)
  • amazon-bedrock/* prefix (line 119)
  • provider/model-name format with proper validation (lines 129-135)

The comment documentation in resolver.ts is accurate.

apps/ui/src/components/views/settings-view/providers/opencode-dynamic-providers.tsx (1)

87-96: File does not exist. The path apps/ui/src/components/views/settings-view/providers/opencode-dynamic-providers.tsx does not exist in the repository. Additionally, eslint-plugin-react-hooks is neither installed nor configured in the ESLint setup, so exhaustive-deps enforcement is not active.

If this review is intended for a different file (such as opencode-settings-tab.tsx), please clarify the correct path and lines.

Likely an incorrect or invalid review comment.

apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx (1)

1-19: The model resolution pipeline is correctly implemented. Settings store ModelAlias values (like 'sonnet', 'opus', 'haiku') in the UI, which is the proper design. Server-side routes explicitly call resolvePhaseModel() before API usage—this function internally uses resolveModelString() to resolve aliases to canonical model IDs. Examples include auto-mode-service.ts, generate-suggestions.ts, and describe-file.ts, all of which call resolvePhaseModel(phaseModelEntry) at the execution boundary. No additional resolution is needed in PhaseModelSelector.

apps/server/src/providers/opencode-provider.ts (3)

793-796: Static analysis false positive - ANSI escape code regex is correct.

The regex /\x1b\[[0-9;]*m/g correctly matches ANSI escape sequences. The \x1b (escape character, ASCII 27) is intentional for stripping terminal color codes from CLI output. The static analysis warning about "control characters in regex" can be safely ignored here.


828-844: Model tier and default assignment logic looks correct.

The modelInfoToDefinition function properly converts CLI output to ModelDefinition with inferred tier, vision support, and a sensible default selection for Claude Sonnet 4.


1023-1089: Provider parsing implementation handles CLI output format well.

The parseProvidersOutput function correctly parses the opencode auth list CLI output format with proper handling of ANSI codes and provider name mapping. The fallback for unknown providers (line 1075) is a good defensive approach.

apps/server/src/routes/enhance-prompt/routes/enhance.ts (2)

101-133: Provider-based execution properly handles errors and accumulates text.

The executeWithProvider function correctly:

  • Throws on provider errors with the error message
  • Accumulates text from assistant messages
  • Handles final result messages appropriately

221-227: OpenCode routing integration is correct.

The new branch correctly identifies OpenCode models (both static and dynamic with provider/model format) and routes them through the provider-based execution path with a combined system/user prompt.

apps/server/src/routes/setup/index.ts (1)

74-79: OpenCode model discovery routes properly registered.

The new routes follow consistent naming patterns and are logically grouped with other OpenCode CLI routes. The API design is RESTful with appropriate HTTP methods (GET for retrieval, POST for mutations).

apps/ui/src/components/views/settings-view/providers/opencode-model-configuration.tsx (3)

79-106: Dynamic provider configuration with sensible defaults.

The DYNAMIC_PROVIDER_CONFIG provides good coverage for known providers with appropriate icons. The fallback in getDynamicProviderConfig correctly formats unknown provider IDs into title case.


164-189: Provider sorting implementation is well-designed.

The sorting logic correctly:

  1. Filters out 'opencode' to avoid duplication with static models
  2. Prioritizes known providers by their position in dynamicProviderOrder
  3. Falls back to alphabetical sorting for unknown providers

319-392: Dynamic models section renders correctly with loading state.

The conditional rendering handles edge cases properly:

  • Shows section when there are providers OR when loading (to show spinner)
  • Displays "Discovering..." indicator during loading
  • Each provider group has its own section with appropriate icon and badge
apps/server/src/routes/setup/routes/opencode-models.ts (2)

21-29: Singleton provider pattern is appropriate for caching.

Using a module-level singleton ensures the model cache is shared across all route handlers, which is the intended behavior for performance optimization.


61-102: Get models handler correctly implements cache-aware fetching.

The handler properly:

  • Supports ?refresh=true query param for force refresh
  • Automatically triggers refresh when only default models are cached
  • Returns cache status in the response
apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx (2)

81-119: Two-phase loading strategy provides good UX.

The implementation correctly separates fast CLI status detection (Phase 1) from slower dynamic model discovery (Phase 2). The eslint-disable comment is appropriate since the effect should only re-run when CLI status changes, not when store setters change.


205-217: Dynamic model toggle handler follows established patterns.

The handleDynamicModelToggle callback correctly mirrors the static model toggle implementation with proper loading state management and error handling.

apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx (2)

259-298: Authentication status display correctly handles multiple scenarios.

The conditional rendering properly distinguishes between:

  1. Direct authentication with a specific method
  2. Authentication via connected providers
  3. Not authenticated state with helpful guidance

300-336: Dynamic providers connected section provides clear visibility.

The new section effectively communicates:

  • Number of connected providers
  • Individual provider badges with auth method
  • Guidance for adding more providers

The use of semantic color (emerald for success) is consistent with the rest of the component.

Comment on lines 298 to 387
// Combine static and dynamic OpenCode models
const allOpencodeModels: ModelOption[] = useMemo(() => {
// Start with static models
const staticModels = [...OPENCODE_MODELS];

// Add dynamic models (convert ModelDefinition to ModelOption)
const dynamicModelOptions: ModelOption[] = dynamicOpencodeModels.map((model) => ({
id: model.id,
label: model.name,
description: model.description,
badge: model.tier === 'premium' ? 'Premium' : model.tier === 'basic' ? 'Free' : undefined,
provider: 'opencode' as const,
}));

// Merge, avoiding duplicates (dynamic takes precedence for same ID)
const staticIds = new Set(staticModels.map((m) => m.id));
const uniqueDynamic = dynamicModelOptions.filter((m) => !staticIds.has(m.id));

return [...staticModels, ...uniqueDynamic];
}, [dynamicOpencodeModels]);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Bug: “dynamic takes precedence” merge doesn’t actually override static models.

You build staticIds and then filter out dynamic IDs already in static, which makes static take precedence (opposite of the comment/intent).

Proposed fix (replace static entries when IDs collide, keep stable ordering)
   const allOpencodeModels: ModelOption[] = useMemo(() => {
     // Start with static models
     const staticModels = [...OPENCODE_MODELS];

@@
-    // Merge, avoiding duplicates (dynamic takes precedence for same ID)
-    const staticIds = new Set(staticModels.map((m) => m.id));
-    const uniqueDynamic = dynamicModelOptions.filter((m) => !staticIds.has(m.id));
-
-    return [...staticModels, ...uniqueDynamic];
+    // Merge with override (dynamic takes precedence for same ID), preserving static order
+    const byId = new Map<string, ModelOption>();
+    for (const m of staticModels) byId.set(m.id, m);
+    for (const m of dynamicModelOptions) byId.set(m.id, m);
+
+    const ordered: ModelOption[] = [];
+    const seen = new Set<string>();
+    for (const m of staticModels) {
+      ordered.push(byId.get(m.id)!);
+      seen.add(m.id);
+    }
+    for (const m of dynamicModelOptions) {
+      if (!seen.has(m.id)) ordered.push(byId.get(m.id)!);
+    }
+    return ordered;
   }, [dynamicOpencodeModels]);
🤖 Prompt for AI Agents
In
@apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
around lines 298 - 318, The merge logic currently filters out dynamic models
that collide with static ones, making static take precedence; to fix, build
dynamicModelOptions from dynamicOpencodeModels, create a map keyed by id, then
produce the merged array by iterating OPENCODE_MODELS and replacing any static
entry with the dynamic map value when present (so dynamic overrides but keeps
static ordering), and finally append any dynamicModelOptions whose ids were not
present in OPENCODE_MODELS so their relative order is preserved; update the
allOpencodeModels useMemo to use this replace-then-append strategy (referencing
allOpencodeModels, OPENCODE_MODELS, dynamicOpencodeModels, and
dynamicModelOptions).

Comment on lines 1953 to 2057
setDynamicOpencodeModels: (models) => {
// When setting dynamic models, auto-enable all of them if enabledDynamicModelIds is empty
const currentEnabled = get().enabledDynamicModelIds;
const newModelIds = models.map((m) => m.id);

// If no models were previously enabled, enable all new ones
if (currentEnabled.length === 0) {
set({ dynamicOpencodeModels: models, enabledDynamicModelIds: newModelIds });
} else {
// Keep existing enabled state, just update the models list
set({ dynamicOpencodeModels: models });
}
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's search for syncSettingsToServer usage to understand the persistence pattern
rg "syncSettingsToServer" --type ts --type tsx -B 2 -A 2

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

# Search for enabledDynamicModelIds to see where it's used and if there's persistence logic
rg "enabledDynamicModelIds" --type ts --type tsx -B 2 -A 2

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

# Search for OpenCode settings persistence patterns
rg "opencode|OpenCode" --type ts --type tsx -l | head -20

Repository: AutoMaker-Org/automaker

Length of output: 94


🏁 Script executed:

# Look for persist or storage related patterns in app-store.ts
cat -n apps/ui/src/store/app-store.ts | grep -i "persist\|storage\|sync" | head -30

Repository: AutoMaker-Org/automaker

Length of output: 2092


🏁 Script executed:

# Check the app-store.ts file structure around the setDynamicOpencodeModels function
sed -n '1940,1975p' apps/ui/src/store/app-store.ts

Repository: AutoMaker-Org/automaker

Length of output: 1619


🏁 Script executed:

# Find and examine use-settings-sync.ts
find . -name "use-settings-sync.ts" -o -name "use-settings-migration.ts"

Repository: AutoMaker-Org/automaker

Length of output: 153


🏁 Script executed:

# Search for references to enabledDynamicModelIds across the codebase
rg "enabledDynamicModelIds" -i --max-count 20

Repository: AutoMaker-Org/automaker

Length of output: 1743


🏁 Script executed:

# Check what settings are being synced - look at the settings sync implementation
fd "settings" -i | grep -E "\.(ts|tsx)$" | head -20

Repository: AutoMaker-Org/automaker

Length of output: 1426


🏁 Script executed:

# Look for OpenCode-related persistence or sync patterns
rg "opencode|OpenCode" -i -A 3 -B 3 --max-count 30

Repository: AutoMaker-Org/automaker

Length of output: 50380


🏁 Script executed:

# Examine use-settings-sync.ts to understand persistence strategy
cat -n apps/ui/src/hooks/use-settings-sync.ts | head -200

Repository: AutoMaker-Org/automaker

Length of output: 8362


🏁 Script executed:

# Search for what fields are included in settings sync
rg "enabledDynamic|dynamicOpencode" --max-count 20

Repository: AutoMaker-Org/automaker

Length of output: 3007


🏁 Script executed:

# Check if enabledDynamicModelIds is in the Settings type definition
cat -n libs/types/src/settings.ts | grep -A 5 -B 5 "enabledDynamic\|dynamicOpencode"

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check if there are any comments explaining why OpenCode settings aren't synced
rg "enabledOpencodeModels|opencodeDefaultModel" -B 5 -A 5 --type ts

Repository: AutoMaker-Org/automaker

Length of output: 14749


🏁 Script executed:

# Search for OpenCode settings in use-settings-sync.ts or related files
rg "opencode" apps/ui/src/hooks/ -i -A 3 -B 3

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

# Check if enabledDynamicModelIds is used anywhere with persistence concerns
rg "enabledDynamicModelIds" -B 3 -A 3 --type ts

Repository: AutoMaker-Org/automaker

Length of output: 7462


Add static OpenCode settings (enabledOpencodeModels, opencodeDefaultModel) to the sync list, and clarify whether dynamic model selections should persist.

Both enabledDynamicModelIds and the static OpenCode settings are currently not persisted:

  • enabledDynamicModelIds is session-only (not in GlobalSettings interface), which appears intentional for dynamically discovered models.
  • However, enabledOpencodeModels and opencodeDefaultModel are defined in GlobalSettings but missing from SETTINGS_FIELDS_TO_SYNC in use-settings-sync.ts—compare to enabledCursorModels and cursorDefaultModel which are synced. Add static OpenCode settings to the sync list if persistence is intended. For dynamic models, decide whether enabledDynamicModelIds should also be synced or remain session-only.
🤖 Prompt for AI Agents
In @apps/ui/src/store/app-store.ts around lines 1953 - 1965, The static OpenCode
settings enabledOpencodeModels and opencodeDefaultModel are missing from the
SETTINGS_FIELDS_TO_SYNC list in use-settings-sync.ts; add those two keys to
SETTINGS_FIELDS_TO_SYNC so they are persisted like
enabledCursorModels/cursorDefaultModel. For dynamic models, decide whether
enabledDynamicModelIds should be session-only or persisted: if you want
persistence, add enabledDynamicModelIds to the GlobalSettings interface and
include it in SETTINGS_FIELDS_TO_SYNC; otherwise explicitly leave it out and add
a code comment in setDynamicOpencodeModels (and the GlobalSettings definition)
noting that enabledDynamicModelIds is intentionally session-scoped.

@stefandevo
Copy link
Collaborator Author

The other test failures (common.test.ts and execFile mock issues) are pre-existing issues and not related to this PR.

@stefandevo
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 11, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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 (5)
apps/ui/src/hooks/use-settings-migration.ts (1)

135-173: Persisted OpenCode fields will likely be dropped during migration sync (buildSettingsUpdateFromStore omission).
You added enabledOpencodeModels/opencodeDefaultModel to localStorage parsing (Line 155-156), but buildSettingsUpdateFromStore() (Line 578+) is used to build the payload for api.settings.updateGlobal(...) during migration (needsSync path, Line 451+) and for syncSettingsToServer(); it currently does not include these two fields, so they may be lost on first migration and on any code path calling syncSettingsToServer().

Proposed fix (add fields to buildSettingsUpdateFromStore)
 function buildSettingsUpdateFromStore(): Record<string, unknown> {
   const state = useAppStore.getState();
   const setupState = useSetupStore.getState();
   return {
@@
     enabledCursorModels: state.enabledCursorModels,
     cursorDefaultModel: state.cursorDefaultModel,
+    enabledOpencodeModels: state.enabledOpencodeModels,
+    opencodeDefaultModel: state.opencodeDefaultModel,
     autoLoadClaudeMd: state.autoLoadClaudeMd,
     skipSandboxWarning: state.skipSandboxWarning,
@@
   };
 }
apps/ui/src/hooks/use-settings-sync.ts (1)

29-64: Good: new OpenCode settings are now included in the synced field set. (Line 47-48)
One gap: refreshSettingsFromServer() doesn’t set enabledOpencodeModels / opencodeDefaultModel, so a manual refresh won’t pick up those server-side changes (it’ll keep stale store values due to partial setState).

Proposed fix (extend refreshSettingsFromServer setState)
     useAppStore.setState({
@@
       enabledCursorModels: serverSettings.enabledCursorModels,
       cursorDefaultModel: serverSettings.cursorDefaultModel,
+      enabledOpencodeModels: serverSettings.enabledOpencodeModels,
+      opencodeDefaultModel: serverSettings.opencodeDefaultModel,
       autoLoadClaudeMd: serverSettings.autoLoadClaudeMd ?? false,
@@
     });
apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx (3)

298-412: Dynamic models are merged but not filtered by “enabled” state.
allOpencodeModels includes all dynamicOpencodeModels (Line 298-318) and groupedOpencodeModels groups whatever is in opencode (Line 373-412), but this component does not consult enabledDynamicModelIds (store) to hide disabled dynamic models. That likely makes the “enable/disable” UI ineffective for the selector.

Proposed fix (filter dynamic models using enabledDynamicModelIds)
   const {
@@
     dynamicOpencodeModels,
+    enabledDynamicModelIds,
   } = useAppStore();
@@
   const allOpencodeModels: ModelOption[] = useMemo(() => {
@@
-    const dynamicModelOptions: ModelOption[] = dynamicOpencodeModels.map((model) => ({
+    const enabledSet = new Set(enabledDynamicModelIds);
+    const dynamicModelOptions: ModelOption[] = dynamicOpencodeModels
+      .filter((model) => enabledSet.has(model.id))
+      .map((model) => ({
         id: model.id,
         label: model.name,
         description: model.description,
         badge: model.tier === 'premium' ? 'Premium' : model.tier === 'basic' ? 'Free' : undefined,
         provider: 'opencode' as const,
-    }));
+      }));
@@
-  }, [dynamicOpencodeModels]);
+  }, [dynamicOpencodeModels, enabledDynamicModelIds]);

595-654: Selecting a dynamic OpenCode model may be incorrectly cast to OpencodeModelId.
renderOpencodeModelItem calls onChange({ model: model.id as OpencodeModelId }) (Line 608-609). For dynamic IDs like github-copilot/gpt-4o, that cast is suspicious and could either (a) break type safety or (b) hide a runtime routing bug if downstream assumes opencode/... only.

If PhaseModelEntry['model'] is intended to accept arbitrary dynamic strings, prefer as PhaseModelEntry['model'] (or just model.id if typed) and avoid narrowing to OpencodeModelId.

Also applies to: 1078-1083


92-263: PhaseModelEntry type does not support OpenCode models — add OpencodeModelId to the model union.

Line 608 casts dynamic model IDs as OpencodeModelId, but PhaseModelEntry.model (libs/types/src/settings.ts:118) is typed as ModelAlias | CursorModelId | CodexModelId and does not include OpencodeModelId. This type incompatibility will cause issues at runtime and should be resolved by updating the PhaseModelEntry.model union to include OpencodeModelId | string to support both static and dynamic OpenCode model IDs.

🤖 Fix all issues with AI agents
In @apps/server/src/providers/opencode-provider.ts:
- Around line 832-855: The modelIdRegex currently rejects model names with colon
version specifiers; update the regex in modelIdRegex (used to validate lines) to
include the colon character in the model-name character class (e.g. change the
right-hand class to include ':' so it matches patterns like
provider/name:version), then keep the existing split('/') logic and
modelInfoToDefinition usage unchanged; do not remove or alter the intentional
ANSI escape stripping using /\x1b\[[0-9;]*m/g as that is correct.

In @apps/server/src/routes/setup/routes/opencode-models.ts:
- Around line 140-164: The handler createGetOpencodeProvidersHandler builds and
returns an unnecessary authenticated field; remove the authenticated variable
and the authenticated property from the response object so the JSON only
contains success and providers, and delete the filtering call
providers.filter((p) => p.authenticated); also update the ProvidersResponse
type/interface (if defined) to remove the authenticated field so the response
shape matches what the UI expects.
🧹 Nitpick comments (10)
libs/prompts/tests/enhancement.test.ts (1)

402-412: Test coverage for 'ux-reviewer' mode is incomplete.

The assertions correctly verify that 'ux-reviewer' exists as a mode, but the test file lacks dedicated tests for this new mode:

  1. No import or test for UX_REVIEWER_SYSTEM_PROMPT and UX_REVIEWER_EXAMPLES constants (lines 21-47 and 51-111)
  2. No getEnhancementPrompt('ux-reviewer') test (lines 113-175)
  3. No getSystemPrompt('ux-reviewer') test (lines 177-197)
  4. No getExamples('ux-reviewer') test (lines 199-223)
  5. Line 334's modes array excludes 'ux-reviewer' from buildUserPrompt iteration

The integration test at lines 444-463 does provide indirect coverage since it iterates over all modes from getAvailableEnhancementModes(). However, adding explicit tests would improve coverage consistency.

Suggested additions for complete test coverage

Add 'ux-reviewer' to the modes array at line 334:

 describe('all modes', () => {
   it('should work for all valid enhancement modes', () => {
-    const modes: Array<'improve' | 'technical' | 'simplify' | 'acceptance'> = [
+    const modes: Array<'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer'> = [
       'improve',
       'technical',
       'simplify',
       'acceptance',
+      'ux-reviewer',
     ];

Consider adding dedicated tests for 'ux-reviewer' in the getEnhancementPrompt, getSystemPrompt, and getExamples describe blocks to match the pattern used for other modes.

apps/ui/src/components/ui/provider-icon.tsx (1)

393-530: Dynamic provider/model detection: solid guardrails; consider centralizing parsing + alias normalization.
The “exactly one slash and not a URL” check (Line 448-505) is a good safety filter.

Two follow-ups:

  1. This file now embeds model-family detection logic (glm/claude/gpt/gemini/…) that will likely be duplicated elsewhere (selectors, provider display). Consider extracting a shared helper (e.g., @automaker/model-resolver or a small @automaker/types utility) so UI/server stay consistent.
  2. Per repo guidance, consider normalizing aliases via resolveModelString() before string matching (e.g., sonnet/opus inputs) to avoid mismatches if upstream starts passing aliases here. Based on learnings, use resolveModelString() from @automaker/model-resolver.
apps/server/src/routes/setup/routes/opencode-models.ts (2)

61-103: GET models handler is reasonable; minor simplification + avoid unused imports.
cachedModels local is currently unused beyond assignment (Line 75-84). Also createLogger/logger are unused in this module (Line 17-20).


1-189: Server guideline: consider emitting operation events via createEventEmitter().
This file is under apps/server/src/** and currently doesn’t use createEventEmitter() for these operations. If your server convention expects WebSocket-streamed ops/telemetry for setup actions, align these endpoints with that pattern. As per coding guidelines, use createEventEmitter() from lib/events.ts.

apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx (1)

1-29: Repo guideline: consider resolveModelString() usage where aliases are handled.
This component works with aliases (e.g., sonnet, opus) and full model strings; per guidance, consider normalizing via resolveModelString() from @automaker/model-resolver at boundaries where you compare/serialize models. Based on learnings, use resolveModelString() from @automaker/model-resolver.

apps/ui/src/store/app-store.ts (2)

1956-1979: enabledDynamicModelIds maintenance: dedupe + prune stale IDs.

  • toggleDynamicModel can introduce duplicates when called repeatedly with enabled=true.
  • setDynamicOpencodeModels keeps enabledDynamicModelIds as-is when non-empty, even if refreshed models removed some IDs; you can end up with stale enabled IDs.
Proposed fix (dedupe + prune)
   setDynamicOpencodeModels: (models) => {
@@
     const currentEnabled = get().enabledDynamicModelIds;
     const newModelIds = models.map((m) => m.id);
+    const newModelIdSet = new Set(newModelIds);
 
     // If no models were previously enabled, enable all new ones
     if (currentEnabled.length === 0) {
       set({ dynamicOpencodeModels: models, enabledDynamicModelIds: newModelIds });
     } else {
-      // Keep existing enabled state, just update the models list
-      set({ dynamicOpencodeModels: models });
+      // Prune enabled IDs that no longer exist in refreshed model list
+      const prunedEnabled = currentEnabled.filter((id) => newModelIdSet.has(id));
+      set({ dynamicOpencodeModels: models, enabledDynamicModelIds: prunedEnabled });
     }
   },
@@
-  toggleDynamicModel: (modelId, enabled) =>
-    set((state) => ({
-      enabledDynamicModelIds: enabled
-        ? [...state.enabledDynamicModelIds, modelId]
-        : state.enabledDynamicModelIds.filter((id) => id !== modelId),
-    })),
+  toggleDynamicModel: (modelId, enabled) =>
+    set((state) => {
+      const current = state.enabledDynamicModelIds;
+      if (enabled) {
+        return current.includes(modelId)
+          ? { enabledDynamicModelIds: current }
+          : { enabledDynamicModelIds: [...current, modelId] };
+      }
+      return { enabledDynamicModelIds: current.filter((id) => id !== modelId) };
+    }),

973-978: Type hygiene: prefer the shared OpenCodeProviderInfo authMethod union.
cachedOpencodeProviders uses authMethod?: string (Line 585-586) while the server type is authMethod?: 'oauth' | 'api_key'. Keeping this as the narrower union will prevent UI drift.

Also applies to: 581-586

apps/server/src/providers/opencode-provider.ts (3)

326-331: Consider using resolveModelString() for model alias support.

Per coding guidelines, resolveModelString() from @automaker/model-resolver should be used to convert model aliases (e.g., "haiku", "sonnet", "opus") to full model names. Currently, only the provider prefix is stripped, but aliases won't be resolved.

♻️ Proposed fix
+import { resolveModelString } from '@automaker/model-resolver';
 // ... in buildCliArgs
     if (options.model) {
-      const model = stripProviderPrefix(options.model);
+      const strippedModel = stripProviderPrefix(options.model);
+      const model = resolveModelString(strippedModel);
       args.push('--model', model);
     }

Based on learnings, this ensures aliases like "sonnet" are properly resolved to full model identifiers.


464-471: Type assertion for undeclared result property.

Line 469 uses a type assertion to access result property which isn't defined in OpenCodePart. If the OpenCode CLI returns this field in step_finish events, consider adding it to the interface for type safety.

♻️ Add result to OpenCodePart interface
 interface OpenCodePart {
   id?: string;
   sessionID?: string;
   messageID?: string;
   type: string;
   text?: string;
   reason?: string;
   error?: string;
   name?: string;
   args?: unknown;
   call_id?: string;
   output?: string;
+  result?: string;
   tokens?: {
     input?: number;
     output?: number;
     reasoning?: number;
   };
 }

885-937: Consider consolidating provider name mappings.

Both formatProviderName (lines 886-897) and formatModelDisplayName (lines 922-933) maintain separate provider-to-display-name mappings with overlapping entries. Consider extracting a shared constant to reduce duplication and ensure consistency.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 887c1b1 and ef5e3c7.

📒 Files selected for processing (8)
  • apps/server/src/providers/opencode-provider.ts
  • apps/server/src/routes/setup/routes/opencode-models.ts
  • apps/ui/src/components/ui/provider-icon.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/ui/src/hooks/use-settings-sync.ts
  • apps/ui/src/store/app-store.ts
  • libs/prompts/tests/enhancement.test.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • libs/prompts/tests/enhancement.test.ts
  • apps/server/src/routes/setup/routes/opencode-models.ts
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/hooks/use-settings-sync.ts
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/server/src/providers/opencode-provider.ts
  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/ui/src/components/ui/provider-icon.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • libs/prompts/tests/enhancement.test.ts
  • apps/server/src/routes/setup/routes/opencode-models.ts
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/hooks/use-settings-sync.ts
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/server/src/providers/opencode-provider.ts
  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/ui/src/components/ui/provider-icon.tsx
apps/server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket

Files:

  • apps/server/src/routes/setup/routes/opencode-models.ts
  • apps/server/src/providers/opencode-provider.ts
🧠 Learnings (2)
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Applies to **/*.{ts,tsx} : Use `resolveModelString()` from automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Applied to files:

  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/server/src/providers/opencode-provider.ts
  • apps/ui/src/components/ui/provider-icon.tsx
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.

Applied to files:

  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/ui/src/components/ui/provider-icon.tsx
🧬 Code graph analysis (2)
apps/server/src/routes/setup/routes/opencode-models.ts (1)
apps/server/src/providers/opencode-provider.ts (2)
  • OpencodeProvider (240-1231)
  • OpenCodeProviderInfo (67-76)
apps/ui/src/store/app-store.ts (2)
apps/ui/src/types/electron.d.ts (1)
  • ModelDefinition (1029-1037)
libs/types/src/provider.ts (1)
  • ModelDefinition (222-235)
🪛 Biome (2.1.2)
apps/server/src/providers/opencode-provider.ts

[error] 839-839: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)


[error] 1081-1081: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)

🔇 Additional comments (9)
apps/ui/src/hooks/use-settings-migration.ts (1)

498-565: Hydration of OpenCode settings looks correct.
Using ?? current.enabledOpencodeModels / ?? current.opencodeDefaultModel (Line 544-545) is consistent with the rest of the hydration defaults.

apps/ui/src/store/app-store.ts (1)

573-587: State additions look coherent; comment about “session-only” needs alignment with persisted fields.
The new dynamic fields (Line 579-586) are explicitly session-only, while static OpenCode fields are persisted elsewhere—this is coherent, but ensure UI components consistently treat only enabledOpencodeModels/opencodeDefaultModel as persisted and enabledDynamicModelIds as session-only.

Also applies to: 1182-1187

apps/server/src/routes/setup/routes/opencode-models.ts (1)

21-29: Singleton provider caching is safe for Electron (local/single-user) deployments, but requires architectural clarification for web deployments.

The module-level singleton (lines 21–29) shares cache across all requests in a single server process. This is fine for Electron mode (inherently single-user), but for web deployments clarify whether multiple users/tenants share one instance. If they do, the shared provider and cache could leak models or authentication state across users. The OpencodeProvider has internal cache expiry logic (modelsCacheExpiry), which provides some safety, but explicit per-user/per-session isolation would be more robust.

apps/server/src/providers/opencode-provider.ts (6)

13-33: LGTM!

Imports correctly use shared packages from @automaker/* as per coding guidelines. Good use of promisify to convert callback-based execFile to async.


722-739: LGTM!

Good use of Promise-based deduplication pattern to avoid multiple concurrent refresh operations. The finally block ensures proper cleanup of refreshPromise and isRefreshing state.


770-815: LGTM!

Good use of async execFileAsync with appropriate timeout (30s for model fetch). The three execution strategies (NPX, WSL, direct CLI) are handled correctly with proper logging.


1075-1115: LGTM!

The provider parsing logic correctly handles the OpenCode CLI output format. The static analysis warning about \x1b on line 1081 is a false positive—this is the standard approach for stripping ANSI escape sequences from terminal output.


532-557: LGTM!

Comprehensive error extraction handling multiple formats (string, object with nested data.message, message, or name properties). Good defensive coding with fallback to "Unknown error".


975-982: Heuristic-based vision detection is acceptable but may need updates.

The vision model detection relies on substring matching against known model families. This is a reasonable approach for dynamic discovery, but be aware it may need periodic updates as new model families are added or if text-only variants exist (e.g., some Claude models may not support vision in certain configurations).

Comment on lines +140 to +164
export function createGetOpencodeProvidersHandler() {
return async (_req: Request, res: Response): Promise<void> => {
try {
const provider = getProvider();
const providers = await provider.fetchAuthenticatedProviders();

// Filter to only authenticated providers
const authenticated = providers.filter((p) => p.authenticated);

const response: ProvidersResponse = {
success: true,
providers,
authenticated,
};

res.json(response);
} catch (error) {
logError(error, 'Get OpenCode providers failed');
res.status(500).json({
success: false,
error: getErrorMessage(error),
} as ProvidersResponse);
}
};
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find UI client/consumers of /api/setup/opencode/providers and confirm expected response shape.
rg -n "/api/setup/opencode/providers|getOpencodeProviders" -S apps/ui libs || true

Repository: AutoMaker-Org/automaker

Length of output: 833


🏁 Script executed:

#!/bin/bash
# Check the expected response type in the API client
sed -n '1470,1500p' apps/ui/src/lib/http-api-client.ts

Repository: AutoMaker-Org/automaker

Length of output: 910


🏁 Script executed:

#!/bin/bash
# Check how the providersResult is used in the component
sed -n '90,160p' apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx

Repository: AutoMaker-Org/automaker

Length of output: 2856


Remove unused authenticated field from response.
The UI only consumes the providers field (lines 99, 150 of opencode-settings-tab.tsx) and never accesses authenticated. This redundant field should be removed from the response to avoid sending unnecessary data.

🤖 Prompt for AI Agents
In @apps/server/src/routes/setup/routes/opencode-models.ts around lines 140 -
164, The handler createGetOpencodeProvidersHandler builds and returns an
unnecessary authenticated field; remove the authenticated variable and the
authenticated property from the response object so the JSON only contains
success and providers, and delete the filtering call providers.filter((p) =>
p.authenticated); also update the ProvidersResponse type/interface (if defined)
to remove the authenticated field so the response shape matches what the UI
expects.

@DhanushSantosh
Copy link
Collaborator

@coderabbitai review this pr now

@coderabbitai
Copy link

coderabbitai bot commented Jan 12, 2026

@DhanushSantosh: I'll review the changes in this PR for you now.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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

🤖 Fix all issues with AI agents
In @apps/server/src/providers/opencode-provider.ts:
- Around line 786-819: The test extracts parseModelsOutput and calls it unbound,
causing this to be undefined when the method invokes this.modelInfoToDefinition;
update the test to either call parseModelsOutput on the provider instance (e.g.,
provider.parseModelsOutput(output)) or bind the method before calling (e.g.,
provider.parseModelsOutput.bind(provider)), so that parseModelsOutput has the
correct this context when calling modelInfoToDefinition.

In @apps/server/tests/unit/providers/opencode-provider.test.ts:
- Around line 106-122: The test pulls out parseModelsOutput unbound which loses
the provider instance context so calls to this.modelInfoToDefinition() fail; fix
by binding the method to the provider before invoking (e.g., use
provider.parseModelsOutput.bind(provider) or call
provider.parseModelsOutput(...) directly) so parseModelsOutput retains its this
and modelInfoToDefinition resolves correctly; locate the test where
parseModelsOutput is extracted and replace the unbound reference with a bound
function or direct method call.

In @apps/ui/src/hooks/use-settings-sync.ts:
- Around line 47-49: refreshSettingsFromServer is not restoring the new OpenCode
fields added to SETTINGS_FIELDS_TO_SYNC; update refreshSettingsFromServer to
read and apply enabledOpencodeModels, opencodeDefaultModel, and
enabledDynamicModelIds from the server response into the local settings
(matching the GlobalSettings types) alongside the other synced fields so these
values are not lost during sync.
🧹 Nitpick comments (6)
apps/server/src/providers/opencode-provider.ts (1)

750-764: Consider adding timeout handling for CLI execution.

The execFileAsync call has a 30-second timeout, which is good. However, the error handling could be more specific about timeout vs. other failures for better debugging.

♻️ Optional: More specific error handling
     } catch (error) {
-      opencodeLogger.error(`Failed to fetch models from CLI: ${error}`);
+      const errorMessage = error instanceof Error ? error.message : String(error);
+      const isTimeout = errorMessage.includes('ETIMEDOUT') || errorMessage.includes('timed out');
+      opencodeLogger.error(`Failed to fetch models from CLI: ${errorMessage}${isTimeout ? ' (timeout)' : ''}`);
       return [];
     }
apps/ui/src/components/ui/provider-icon.tsx (1)

476-482: Consider adding 'haiku' to the Claude model detection patterns.

The pattern matching for Anthropic models checks for claude, sonnet, and opus, but omits haiku. Dynamic models like provider/claude-3-haiku would not match this pattern and would fall through to a potentially incorrect icon.

♻️ Suggested fix
         if (
           modelName.includes('claude') ||
           modelName.includes('sonnet') ||
-          modelName.includes('opus')
+          modelName.includes('opus') ||
+          modelName.includes('haiku')
         ) {
           return 'anthropic';
         }
apps/ui/src/components/views/settings-view/providers/opencode-model-configuration.tsx (1)

614-631: Consider: "Select all" toggles all provider models, not just filtered ones.

When a search filter is active, clicking "Select all" for a provider toggles all models for that provider (models.map), not just the filtered models (filteredModels). This may be unexpected behavior when users have an active search filter.

If the intent is to toggle only visible (filtered) models:

♻️ Suggested change
                       {models.length > 0 && (
                         <div className={OPENCODE_SELECT_ALL_CONTAINER_CLASS}>
                           <Checkbox
                             checked={getSelectionState(
-                              models.map((model) => model.id),
+                              filteredModels.map((model) => model.id),
                               enabledDynamicModelIds
                             )}
                             onCheckedChange={(checked) =>
                               toggleProviderDynamicModels(
-                                models.map((model) => model.id),
+                                filteredModels.map((model) => model.id),
                                 checked
                               )
                             }
                             disabled={isSaving}
                           />
apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx (1)

85-123: Consider adding cleanup/abort handling for the async effect.

The effect runs an async operation but doesn't handle component unmount scenarios. If the component unmounts while loadDynamicContent is running, it could cause state updates on an unmounted component.

♻️ Suggested fix with AbortController or mounted flag
   useEffect(() => {
+    let isMounted = true;
     const loadDynamicContent = async () => {
       const api = getElectronAPI();
       const isInstalled = cliStatus?.success && cliStatus?.status === 'installed';
 
       if (!isInstalled || !api?.setup) return;
 
       // Skip if already have cached data
       const needsProviders = cachedOpencodeProviders.length === 0;
       const needsModels = dynamicOpencodeModels.length === 0;
 
       if (!needsProviders && !needsModels) return;
 
       setIsLoadingDynamicModels(true);
       try {
         // Load providers if needed
         if (needsProviders && api.setup.getOpencodeProviders) {
           const providersResult = await api.setup.getOpencodeProviders();
-          if (providersResult.success && providersResult.providers) {
+          if (isMounted && providersResult.success && providersResult.providers) {
             setCachedOpencodeProviders(providersResult.providers);
           }
         }
 
         // Load models if needed
         if (needsModels && api.setup.getOpencodeModels) {
           const modelsResult = await api.setup.getOpencodeModels();
-          if (modelsResult.success && modelsResult.models) {
+          if (isMounted && modelsResult.success && modelsResult.models) {
             setDynamicOpencodeModels(modelsResult.models);
           }
         }
       } catch (error) {
         logger.error('Failed to load dynamic content:', error);
       } finally {
-        setIsLoadingDynamicModels(false);
+        if (isMounted) setIsLoadingDynamicModels(false);
       }
     };
     loadDynamicContent();
+    return () => { isMounted = false; };
   }, [cliStatus?.success, cliStatus?.status]); // eslint-disable-line react-hooks/exhaustive-deps
apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx (1)

8-13: Move OpenCodeProviderInfo to @automaker/types and import it from both the UI and server.

This interface is duplicated in apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx and apps/server/src/providers/opencode-provider.ts. Both define identical shapes. Add it to libs/types/src/opencode-models.ts and update both locations to import from @automaker/types to ensure type consistency and prevent drift across the codebase.

apps/ui/src/store/app-store.ts (1)

1958-1972: Redundant ternary expression.

The ternary on line 1970 is unnecessary. If currentEnabled is empty, then filteredEnabled (which filters from currentEnabled) will also be empty. The expression can be simplified:

-    const nextEnabled = currentEnabled.length === 0 ? [] : filteredEnabled;
+    const nextEnabled = filteredEnabled;

The filtering logic is otherwise correct—it preserves user-enabled model IDs while removing stale references to models that no longer exist.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef5e3c7 and c1dda85.

📒 Files selected for processing (14)
  • apps/server/src/providers/opencode-provider.ts
  • apps/server/tests/unit/providers/opencode-provider.test.ts
  • apps/ui/src/components/ui/provider-icon.tsx
  • apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/ui/src/components/views/settings-view/providers/opencode-model-configuration.tsx
  • apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx
  • apps/ui/src/components/views/setup-view/steps/opencode-setup-step.tsx
  • apps/ui/src/components/views/setup-view/steps/providers-setup-step.tsx
  • apps/ui/src/hooks/use-settings-migration.ts
  • apps/ui/src/hooks/use-settings-sync.ts
  • apps/ui/src/store/app-store.ts
  • libs/types/src/opencode-models.ts
  • libs/types/src/settings.ts
✅ Files skipped from review due to trivial changes (2)
  • apps/ui/src/components/views/setup-view/steps/opencode-setup-step.tsx
  • apps/ui/src/components/views/setup-view/steps/providers-setup-step.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/ui/src/hooks/use-settings-migration.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • libs/types/src/settings.ts
  • apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx
  • apps/server/tests/unit/providers/opencode-provider.test.ts
  • apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/server/src/providers/opencode-provider.ts
  • apps/ui/src/components/views/settings-view/providers/opencode-model-configuration.tsx
  • apps/ui/src/hooks/use-settings-sync.ts
  • apps/ui/src/store/app-store.ts
  • libs/types/src/opencode-models.ts
  • apps/ui/src/components/ui/provider-icon.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • libs/types/src/settings.ts
  • apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx
  • apps/server/tests/unit/providers/opencode-provider.test.ts
  • apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/server/src/providers/opencode-provider.ts
  • apps/ui/src/components/views/settings-view/providers/opencode-model-configuration.tsx
  • apps/ui/src/hooks/use-settings-sync.ts
  • apps/ui/src/store/app-store.ts
  • libs/types/src/opencode-models.ts
  • apps/ui/src/components/ui/provider-icon.tsx
apps/server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket

Files:

  • apps/server/src/providers/opencode-provider.ts
🧠 Learnings (2)
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.

Applied to files:

  • apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx
  • apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/ui/src/components/views/settings-view/providers/opencode-model-configuration.tsx
  • apps/ui/src/components/ui/provider-icon.tsx
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Applies to **/*.{ts,tsx} : Use `resolveModelString()` from automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Applied to files:

  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
  • apps/server/src/providers/opencode-provider.ts
  • apps/ui/src/components/ui/provider-icon.tsx
🧬 Code graph analysis (4)
apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx (3)
apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx (2)
  • OpencodeAuthStatus (45-52)
  • OpenCodeProviderInfo (8-13)
apps/ui/src/lib/electron.ts (1)
  • getElectronAPI (908-917)
apps/server/src/providers/opencode-provider.ts (1)
  • OpenCodeProviderInfo (67-76)
apps/server/tests/unit/providers/opencode-provider.test.ts (2)
apps/server/src/providers/opencode-provider.ts (1)
  • parseModelsOutput (776-819)
libs/types/src/provider.ts (1)
  • ModelDefinition (222-235)
apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx (1)
apps/server/src/providers/opencode-provider.ts (1)
  • OpenCodeProviderInfo (67-76)
apps/ui/src/store/app-store.ts (3)
libs/types/src/opencode-models.ts (2)
  • OpencodeModelId (5-11)
  • DEFAULT_OPENCODE_MODEL (107-107)
apps/ui/src/types/electron.d.ts (1)
  • ModelDefinition (1029-1037)
libs/types/src/provider.ts (1)
  • ModelDefinition (222-235)
🪛 Biome (2.1.2)
apps/server/src/providers/opencode-provider.ts

[error] 788-788: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)


[error] 1044-1044: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)

🪛 GitHub Actions: Test Suite
apps/server/src/providers/opencode-provider.ts

[error] 808-809: TypeError: Cannot read properties of undefined (reading 'modelInfoToDefinition') while parsing provider output. Ensure modelInfoToDefinition is defined before use in parseModelsOutput.

🪛 GitHub Check: test
apps/server/src/providers/opencode-provider.ts

[failure] 808-808: [server] tests/unit/providers/opencode-provider.test.ts > opencode-provider.ts > parseModelsOutput > should parse nested provider model IDs
TypeError: Cannot read properties of undefined (reading 'modelInfoToDefinition')
❯ parseModelsOutput src/providers/opencode-provider.ts:808:16
❯ tests/unit/providers/opencode-provider.test.ts:113:22

🔇 Additional comments (33)
libs/types/src/opencode-models.ts (2)

5-16: Clean type simplification for dynamic model support.

The reduction of OpencodeModelId to free-tier models and OpencodeProvider to a single 'opencode' value aligns well with the PR's dynamic model discovery approach. Static types now represent the fallback/free tier, while dynamic models are handled at runtime.


105-107: Default model updated appropriately.

The default model change to 'opencode/big-pickle' is consistent with the simplified free-tier model set.

apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx (6)

49-97: Well-organized provider configuration constants.

The provider label maps, ordering arrays, and section configuration provide a clean foundation for the dynamic model grouping UI. The separation between static and dynamic provider labels is clear.


99-121: Helper functions for provider formatting.

The helper functions handle provider key formatting and section determination cleanly. One minor observation: formatProviderLabel correctly handles hyphen-separated words but could produce unexpected results for edge cases like empty strings or providers with consecutive hyphens.


308-322: Dynamic model resolution with appropriate icon handling.

The current model resolution now correctly checks both static and dynamic OpenCode models, using getProviderIconForModel for provider-specific icons. This ensures consistent icon display across all OpenCode model sources.


366-386: Proper static/dynamic model merging with duplicate prevention.

The merging logic correctly prioritizes static models over dynamic ones when IDs overlap, using a Set for efficient lookup. The comment noting that IDs don't overlap in practice is helpful context.


441-527: Comprehensive provider-based grouping implementation.

The opencodeSections memo implements a robust grouping system with:

  • Provider resolution from multiple sources (static, dynamic, ID parsing)
  • Priority-based sorting for dynamic providers
  • Section-aware group labeling

The logic is well-structured and handles the complexity of multi-source model aggregation.


1193-1219: Provider-grouped rendering with proper section hierarchy.

The rendering correctly uses Fragment for section iteration and applies conditional styling for dynamic sections. The nested group structure provides clear visual hierarchy in the UI.

apps/server/src/providers/opencode-provider.ts (5)

788-788: Biome false positive: ANSI escape code regex is intentional.

The regex /\x1b\[[0-9;]*m/g correctly strips ANSI escape codes from CLI output. The \x1b (ESC character, hex 1B) is the standard ANSI escape sequence prefix. This is intentional and necessary for parsing colorized terminal output.


671-712: Robust refresh workflow with deduplication.

The refreshModels and doRefreshModels implementation correctly handles:

  • Concurrent refresh deduplication via isRefreshing flag and shared promise
  • Graceful fallback to cached/default models on failure
  • Proper cleanup of refreshPromise in finally block

582-599: Background refresh with stale-while-revalidate pattern.

The getAvailableModels implementation uses a sensible caching strategy: return cached models immediately (even if stale) while triggering a background refresh. This provides good UX by avoiding blocking on network calls.


904-932: Model tier inference logic is reasonable.

The tier inference based on model ID substrings covers the major model families appropriately. The fallback to 'standard' for unrecognized models is sensible.


1015-1082: Provider parsing handles formatted CLI output well.

The parsing of opencode auth list output correctly:

  • Identifies authenticated providers via the symbol
  • Strips ANSI codes for clean parsing
  • Maps provider names to canonical IDs
  • Extracts auth method from the last word

The provider ID mapping is comprehensive for common providers.

apps/server/tests/unit/providers/opencode-provider.test.ts (2)

54-86: Test expectations updated correctly for new model set.

The updated tests correctly reflect:

  • 5 models instead of 10
  • Big Pickle as the new default
  • GLM 4.7 Free and MiniMax M2.1 Free as free-tier models

1239-1239: Default model assertion updated correctly.

The assertion now expects 'opencode/big-pickle' as the single default model, consistent with the provider implementation.

libs/types/src/settings.ts (1)

404-406: LGTM!

The new enabledDynamicModelIds field follows the established pattern of other enabled model arrays (e.g., enabledCursorModels, enabledOpencodeModels). The empty array default aligns with the documented semantic that "empty means all discovered".

apps/ui/src/components/ui/provider-icon.tsx (2)

409-412: LGTM!

The OpenRouter check is correctly placed before Bedrock checks since openrouter/ is a distinct prefix that should be handled separately.


161-163: LGTM!

The OpenRouterIcon component follows the established pattern used by other provider icon exports.

apps/ui/src/components/views/settings-view/providers/opencode-model-configuration.tsx (4)

39-51: LGTM!

The extended props interface cleanly adds dynamic model support while maintaining backward compatibility with optional props.


156-165: LGTM!

The getSelectionState function correctly implements the three-state checkbox logic (all/none/indeterminate) with proper empty array handling.


270-288: LGTM!

The effect correctly handles both initial filter setup and stale filter recovery when the provider list changes, using a ref to prevent unwanted re-initialization after user interaction.


317-349: LGTM!

The toggle functions efficiently filter out already-toggled models before calling the callbacks, preventing redundant state updates.

apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx (2)

125-180: LGTM with note on dependency design.

The signature-based deduplication (providerRefreshSignatureRef) correctly prevents redundant refresh calls even though isLoadingDynamicModels is in the dependency array. The early return at line 131 combined with the signature check at line 156 ensures no infinite loops.


266-278: LGTM!

The handleDynamicModelToggle callback follows the same pattern as handleModelToggle with appropriate error handling and saving state management.

apps/ui/src/components/views/settings-view/cli-status/opencode-cli-status.tsx (3)

18-36: LGTM!

The display configuration handles both copilot and github-copilot variants, and the fallback chain (PROVIDER_DISPLAY[id] || name || id) ensures unknown providers display gracefully.


260-299: LGTM!

The authentication status logic correctly prioritizes:

  1. Direct authentication with a specific method
  2. Provider-based authentication (shows count)
  3. Free tier messaging with helpful guidance

The conditional rendering is clear and handles all cases appropriately.


301-337: LGTM!

The Dynamic Providers Connected section provides useful visibility into authenticated providers with:

  • Provider count in the header
  • Individual badges with display names and auth method indicators
  • Helpful guidance for adding more providers
apps/ui/src/store/app-store.ts (6)

26-27: LGTM!

Import correctly uses the shared @automaker/types package per coding guidelines. The ModelDefinition type from libs/types/src/provider.ts with provider: string is appropriate for dynamic multi-provider models.


37-38: LGTM!

Constants are well-defined and consistently reused for bedrock filtering logic in both setDynamicOpencodeModels and setCachedOpencodeProviders.


576-588: LGTM!

State definitions appropriately capture session-only dynamic data. The inline comments clearly document that these fields are intentionally not persisted since they depend on CLI authentication state.


975-980: LGTM!

Action signatures are consistent with state definitions and follow the established patterns in the store.


1185-1188: LGTM!

Initial state values correctly default to empty arrays, appropriate for session-only data that will be populated from CLI discovery.


1974-1985: LGTM!

toggleDynamicModel follows the established toggle pattern used by toggleCursorModel and toggleCodexModel. setCachedOpencodeProviders consistently filters out bedrock, matching the filtering in setDynamicOpencodeModels.

@DhanushSantosh
Copy link
Collaborator

@coderabbitai recheck pr.

@coderabbitai
Copy link

coderabbitai bot commented Jan 12, 2026

@DhanushSantosh: I'll recheck the changes in this PR for you.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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: 0

🧹 Nitpick comments (1)
apps/ui/src/hooks/use-settings-sync.ts (1)

434-438: Consider extracting the repeated expression.

The expression serverSettings.opencodeDefaultModel ?? currentAppState.opencodeDefaultModel is evaluated twice. Extracting it to a variable improves readability.

♻️ Suggested refactor
 const validOpencodeModelIds = new Set(getAllOpencodeModelIds());
 const incomingEnabledOpencodeModels =
   serverSettings.enabledOpencodeModels ?? currentAppState.enabledOpencodeModels;
-const sanitizedOpencodeDefaultModel = validOpencodeModelIds.has(
-  serverSettings.opencodeDefaultModel ?? currentAppState.opencodeDefaultModel
-)
-  ? (serverSettings.opencodeDefaultModel ?? currentAppState.opencodeDefaultModel)
-  : DEFAULT_OPENCODE_MODEL;
+const incomingOpencodeDefaultModel =
+  serverSettings.opencodeDefaultModel ?? currentAppState.opencodeDefaultModel;
+const sanitizedOpencodeDefaultModel = validOpencodeModelIds.has(incomingOpencodeDefaultModel)
+  ? incomingOpencodeDefaultModel
+  : DEFAULT_OPENCODE_MODEL;
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c1dda85 and e19e1f0.

📒 Files selected for processing (2)
  • apps/server/tests/unit/providers/opencode-provider.test.ts
  • apps/ui/src/hooks/use-settings-sync.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • apps/server/tests/unit/providers/opencode-provider.test.ts
  • apps/ui/src/hooks/use-settings-sync.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • apps/server/tests/unit/providers/opencode-provider.test.ts
  • apps/ui/src/hooks/use-settings-sync.ts
🧬 Code graph analysis (2)
apps/server/tests/unit/providers/opencode-provider.test.ts (2)
apps/server/src/providers/opencode-provider.ts (1)
  • parseModelsOutput (776-819)
libs/types/src/provider.ts (1)
  • ModelDefinition (222-235)
apps/ui/src/hooks/use-settings-sync.ts (1)
libs/types/src/opencode-models.ts (2)
  • getAllOpencodeModelIds (119-121)
  • DEFAULT_OPENCODE_MODEL (107-107)
🔇 Additional comments (10)
apps/server/tests/unit/providers/opencode-provider.test.ts (7)

6-6: LGTM!

The import correctly uses the shared package @automaker/types as per coding guidelines, and the ModelDefinition type is properly used to type the parseModelsOutput result in the new test.


54-56: LGTM!

Test expectation correctly updated to reflect the new model count of 5.


59-67: LGTM!

Comprehensive test for the new default model with appropriate assertions for all key properties.


70-77: LGTM!

New test correctly validates the GLM 4.7 Free model properties including the basic tier.


79-86: LGTM!

Test correctly validates the MiniMax M2.1 Free model properties.


106-122: LGTM!

Good test coverage for the parseModelsOutput method, validating that nested provider model IDs (like openrouter/anthropic/claude-3.5-sonnet) are correctly parsed with the provider extracted from the first path segment and the full ID preserved in modelString.


1239-1239: LGTM!

Default model ID assertion correctly updated to match the new opencode/big-pickle default, consistent with other test updates in this file.

apps/ui/src/hooks/use-settings-sync.ts (3)

22-26: LGTM!

Imports correctly use the shared @automaker/types package as per coding guidelines.


51-53: LGTM!

New OpenCode settings fields are correctly added to the sync list, consistent with the PR's dynamic model discovery feature.


475-477: LGTM!

Sanitized values are correctly applied to the store, ensuring only valid model IDs are persisted.

stefandevo and others added 13 commits January 12, 2026 23:39
- Update isOpencodeModel() to detect dynamic models with provider/model format
  (e.g., github-copilot/gpt-4o, google/gemini-2.5-pro, zai-coding-plan/glm-4.7)
- Update resolveModelString() to recognize and pass through OpenCode models
- Update enhance route to route OpenCode models to OpenCode provider
- Fix OpenCode CLI command format: use --format json (not stream-json)
- Remove unsupported -q and - flags from CLI arguments
- Update normalizeEvent() to handle actual OpenCode JSON event format
- Add dynamic model configuration UI with provider grouping
- Cache providers and models in app store for snappier navigation
- Show authenticated providers in OpenCode CLI status card

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update error event interface to handle nested error objects with
  name/data/message structure from OpenCode CLI
- Extract meaningful error messages from provider errors in normalizeEvent
- Add error type handling in executeWithProvider to throw errors with
  actual provider messages instead of returning empty response

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add icon detection for dynamic OpenCode provider models (provider/model format)
- Support zai-coding-plan, github-copilot, google, xai, and other providers
- Detect model type from name (glm, claude, gpt, gemini, grok, etc.)
- Fix React.useMemo → useMemo to resolve "React is not defined" error

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Convert execFileSync to async execFile in fetchModelsFromCli and
  fetchAuthenticatedProviders to avoid blocking the event loop
- Remove unused opencode-dynamic-providers.tsx component
- Use regex for more robust model ID validation in parseModelsOutput

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Test expected 4 enhancement modes but there are now 5 after adding
the ux-reviewer mode.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace busy-wait loop in refreshModels with Promise-based approach
- Remove duplicate error logging in opencode-models.ts handlers
- Fix multi-slash parsing in provider-icon.tsx (only handle exactly one slash)
- Use dynamic icon resolution for selected OpenCode model in trigger
- Fix misleading comment about merge precedence (static takes precedence)
- Add enabledOpencodeModels and opencodeDefaultModel to settings sync
- Add clarifying comments about session-only dynamic model settings

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add enabledOpencodeModels and opencodeDefaultModel to the settings
migration to ensure they are properly persisted like Cursor settings.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants