Skip to content

fix(cli): harden provider command detection and routing - #157

Merged
AssafMiron merged 6 commits into
mainfrom
fix/cli-provider-command-detection
May 9, 2026
Merged

AssafMiron merged 6 commits into
mainfrom
fix/cli-provider-command-detection

Conversation

@aviatam

@aviatam aviatam commented May 8, 2026 •

Copy link
Copy Markdown
Collaborator

This branch hardens CLI provider command detection and routing across Codex, Claude, and Gemini, adds Windows-safe CLI spawning, and updates the related tests.

Summary by CodeRabbit

  • New Features

    • Option to pick which AI provider to use for chats; UI stays in sync with external provider switches and forwards selection to the backend.
    • More robust detection and resolution of installed AI CLI tools, returning resolved executable paths and versions.
  • Bug Fixes

    • Preserve and display a previously selected provider even if not detected.
    • Reject unsupported explicit provider choices early with an error.
  • Tests

    • Added tests for CLI resolution, detection, and provider factory behavior.

Review Change Stack

Review Change Stack

@aviatam
aviatam requested a review from AssafMiron as a code owner May 8, 2026 19:58
@coderabbitai

coderabbitai Bot commented May 8, 2026 •

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a410791e-254d-4d38-b553-f9eda836f22f

📥 Commits

Reviewing files that changed from the base of the PR and between 3603ac8 and e06ba63.

📒 Files selected for processing (2)
  • node-backend/lib/orchestrator.mjs
  • node-backend/lib/providers/gemini.mjs
🚧 Files skipped from review as they are similar to previous changes (2)
  • node-backend/lib/orchestrator.mjs
  • node-backend/lib/providers/gemini.mjs

📝 Walkthrough

Walkthrough

This PR adds cross-platform CLI discovery and resolution (including path/version), a spawnCli helper for safe child execution, updates Claude/Gemini/OpenAI providers to use it, threads explicit providerType from UI through API to the orchestrator, and adds tests verifying CLI resolution and provider factory behavior.

Changes

CLI Resolution and Provider Type Selection

Layer / File(s) Summary
CLI Detection & Execution Infrastructure
node-backend/lib/system.mjs
Enumerates PATH and extra bin locations, validates executables, extracts CLI versions, changes checkCli() to return { installed,in_path,path,version }, and adds resolveCliCommand(...commands) and updated getAppConfig fields.
Spawn Helper & Base Provider
node-backend/lib/providers/base.mjs
Adds quoteCmdArg() and exported spawnCli(spawn, command, args, options) to handle Windows .cmd/.bat shims via ComSpec and safe argument quoting; keeps AIProvider scaffolding.
Provider Implementations
node-backend/lib/providers/claude.mjs, node-backend/lib/providers/gemini.mjs, node-backend/lib/providers/openai.mjs
Providers now default config objects, adopt spawnCli() for subprocesses, send prompts via stdin/headless flags, conditionally include --model, change auth checks to prefer API keys or installed CLI detection, and report configured models (or default).
Service Factory & Orchestrator
node-backend/lib/ai.mjs, node-backend/lib/orchestrator.mjs
AIService.createProvider() defaults settings and rewrites config.command to resolved CLI paths via withDetectedCommand; runAgentLoop() prioritizes explicit providerType, returns an error for unsupported explicit providers, and derives activeProvider from the created provider instance.
Backend Detection & Routing
node-backend/server.mjs
OpenAI detection now uses resolveCliCommand('codex','openai'); the chat endpoint forwards providerType from the request body into the orchestrator.
API Layer
src/api/runtime.ts, src/api/server.ts
runtimeApi.sendMessage() and chatApi.sendMessage() accept optional providerType and include it in the backend payload; runtimeApi.switchProvider awaits persistence and dispatches productos:provider-switched in browser contexts.
UI Message Sending
src/components/workspace/ChatPanel.tsx
Normalizes/preserves configured activeProvider (removes duplicate prefix), listens for external provider-switch events, uses textToSend for enrichment and workflow detection, and passes activeProvider when sending messages.
Test Coverage
node-backend/tests/parity.test.mjs, node-backend/tests/services/settings-service.test.mjs
New tests create temporary CLI executables, verify checkCli/resolveCliCommand return path/version and preferring codex over openai, and assert the provider factory resolves local CLI commands.

🎯 4 (Complex) | ⏱️ ~75 minutes

🐰 The CLI commands find their way,
Through PATH and ComSpec they play,
Providers now chosen with care,
Cross-platform spawning through the air,
Safe execution lights the day! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(cli): harden provider command detection and routing' accurately reflects the main changes: enhanced CLI command detection for providers (Gemini, Claude, OpenAI) and improved routing logic in the orchestrator.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/cli-provider-command-detection

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

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

Inline comments:
In `@node-backend/lib/ai.mjs`:
- Around line 13-16: The helper withDetectedCommand currently probes fallback
CLI commands even when the user provided an explicit config.command; change it
so that if config.command is present you immediately return config unchanged (do
not call resolveCliCommand), and only call resolveCliCommand with the fallback
commands (the rest of the variadic commands) when config.command is absent;
update logic in withDetectedCommand to build candidates only from ...commands
and set config.command = detected.path only when config.command was originally
undefined and detected.installed is true.

In `@node-backend/lib/orchestrator.mjs`:
- Around line 34-40: The code lets providerType override saved settings without
validation, which can cause typos to silently fall back to HostedAPI; update the
logic around activeProvider to validate providerType before using it: if
providerType is present, call a validator (or AIService.getSupportedProviders)
to ensure it's a known provider and throw or return an error for unsupported
IDs; alternatively call AIService.createProvider with a candidate and then use
provider.type (or provider.id) to normalize activeProvider from the instantiated
provider instead of trusting the raw providerType; reference the variables
activeProvider, providerType, settings and the call
AIService.createProvider(...) as the locations to implement this
validation/normalization and error handling.

In `@node-backend/lib/providers/gemini.mjs`:
- Around line 70-78: In checkAuthentication, don't unconditionally return true;
instead only allow the CLI fallback when the Gemini CLI is positively detected
(e.g., add or call a detection helper like hasGeminiCLI or run a quick `gemini
--version` probe) and otherwise return false so preflight fails when neither an
API key (this.config.apiKeySecretId / this.secrets['GEMINI_API_KEY'] /
this.secrets['GOOGLE_API_KEY']) nor a confirmed CLI login is present; update the
method (checkAuthentication) to gate the current fallback on that CLI detection
and return false when detection fails so setup issues surface before chat().

In `@node-backend/tests/parity.test.mjs`:
- Around line 57-77: The test mutates PATH but doesn't isolate
HOME/PROJECTS_DIR; update the test around AIService.createProvider to set
process.env.HOME and process.env.PROJECTS_DIR to temporary directories (created
via fs.mkdtemp) before calling AIService.createProvider('geminiCli', ...) and
restore the original values in the finally block (alongside resetting PATH and
cleaning temp dirs); ensure any created temp dirs are removed and that
provider.config.command assertion remains unchanged.

In `@node-backend/tests/services/settings-service.test.mjs`:
- Around line 9-20: Tests sandbox only APP_DATA_DIR and PATH but not HOME and
PROJECTS_DIR, so the suite can still touch real user data; update the
beforeEach/afterEach in this test file to create temp directories (e.g., using
fs.mkdtemp like tempAppData) for HOME and PROJECTS_DIR, stash their original
values (add originalHome and originalProjectsDir alongside originalPath), set
process.env.HOME and process.env.PROJECTS_DIR to those temp dirs in beforeEach,
and in afterEach delete the temp dirs, delete the test env vars and restore
originalHome, originalProjectsDir and originalPath so the environment is fully
isolated for the settings-service.test.mjs suite.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2668e467-a430-4262-872d-ba16267b6568

📥 Commits

Reviewing files that changed from the base of the PR and between 43a0796 and db1764d.

📒 Files selected for processing (13)
  • node-backend/lib/ai.mjs
  • node-backend/lib/orchestrator.mjs
  • node-backend/lib/providers/base.mjs
  • node-backend/lib/providers/claude.mjs
  • node-backend/lib/providers/gemini.mjs
  • node-backend/lib/providers/openai.mjs
  • node-backend/lib/system.mjs
  • node-backend/server.mjs
  • node-backend/tests/parity.test.mjs
  • node-backend/tests/services/settings-service.test.mjs
  • src/api/runtime.ts
  • src/api/server.ts
  • src/components/workspace/ChatPanel.tsx

Comment thread node-backend/lib/ai.mjs
Comment thread node-backend/lib/orchestrator.mjs Outdated
Comment thread node-backend/lib/providers/gemini.mjs Outdated
Comment thread node-backend/tests/parity.test.mjs Outdated
Comment thread node-backend/tests/services/settings-service.test.mjs Outdated
AssafMiron
AssafMiron previously approved these changes May 8, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/api/runtime.ts (1)

543-546: 💤 Low value

productOS: event namespace differs from the existing productos: convention.

All other window events in this file use the productos: prefix (e.g. 'productos:settings-changed'), but the new event uses 'productOS:provider-switched' (capital O, capital S). Both currently work because the dispatch and the listener in ChatPanel.tsx use the same literal, but the inconsistency will confuse future contributors.

♻️ Proposed alignment
-      window.dispatchEvent(new CustomEvent('productOS:provider-switched', { detail: { providerType } }));
+      window.dispatchEvent(new CustomEvent('productos:provider-switched', { detail: { providerType } }));

Corresponding change in ChatPanel.tsx lines 256-257:

-    window.addEventListener('productOS:provider-switched', handleProviderSwitched);
-    return () => window.removeEventListener('productOS:provider-switched', handleProviderSwitched);
+    window.addEventListener('productos:provider-switched', handleProviderSwitched);
+    return () => window.removeEventListener('productos:provider-switched', handleProviderSwitched);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/runtime.ts` around lines 543 - 546, The new window event uses an
inconsistent namespace "productOS:provider-switched" versus the project's
"productos:" convention; update the dispatch to use the existing namespace so
listeners match project style. Change the CustomEvent name in the dispatch
inside the method that calls saveGlobalSettings (the code that creates new
CustomEvent('productOS:provider-switched', { detail: { providerType } })) to use
the "productos:" prefix (e.g., 'productos:provider-switched'), and ensure any
related listener references (such as the handler in ChatPanel.tsx) are using the
same 'productos:provider-switched' string so dispatch and listeners remain
consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/api/runtime.ts`:
- Around line 543-546: The new window event uses an inconsistent namespace
"productOS:provider-switched" versus the project's "productos:" convention;
update the dispatch to use the existing namespace so listeners match project
style. Change the CustomEvent name in the dispatch inside the method that calls
saveGlobalSettings (the code that creates new
CustomEvent('productOS:provider-switched', { detail: { providerType } })) to use
the "productos:" prefix (e.g., 'productos:provider-switched'), and ensure any
related listener references (such as the handler in ChatPanel.tsx) are using the
same 'productos:provider-switched' string so dispatch and listeners remain
consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ae8a09e8-c825-4f79-ae9c-77ae36a887e4

📥 Commits

Reviewing files that changed from the base of the PR and between db1764d and c24cd39.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • src/api/runtime.ts
  • src/components/workspace/ChatPanel.tsx

AssafMiron
AssafMiron previously approved these changes May 8, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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

Inline comments:
In `@node-backend/lib/providers/gemini.mjs`:
- Around line 71-81: checkAuthentication() returning cli.installed can falsely
indicate usable authentication; add a runtime fallback in the chat() method to
detect when the Gemini CLI call fails due to missing auth and surface a clear
error instructing the user to run "gemini auth login" or provide an API key.
Specifically, inside chat() (and any helper that invokes the CLI), catch CLI
invocation errors or parse CLI stderr for authentication-related messages, and
convert them into a user-friendly error that mentions running "gemini auth
login" or setting the apiKeySecretId/GOOGLE_API_KEY/GEMINI_API_KEY; keep
existing checkCli/checkAuthentication() behavior but ensure chat() fails fast
with the clearer guidance when the CLI is present but not logged in.
- Line 93: The metadata() implementation currently sets models to ['default']
when no config is present; replace that fallback so models is either an empty
array or a real Gemini model identifier—e.g., compute a value like const model =
this.config.model_alias || this.config.modelAlias || this.config.model || null
and then set models to model ? [model] : [] (or use a concrete default like
'gemini-2.0-flash' if your app requires a specific fallback) so API/UI consumers
never receive the misleading 'default' identifier.
- Around line 15-20: Add a clarifying inline comment in gemini.mjs next to the
configuredModel check (the block that pushes '--model' onto args) explaining why
the 'pro' value is excluded (e.g., because the Gemini CLI already defaults to
'pro' or to avoid overriding the CLI/account-compatible default), mirroring the
note pattern used in claude.mjs; update the comment near the const args /
configuredModel check to mention the '--model' flag, the 'pro' sentinel value,
and that omitting it preserves the CLI/account default behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0ad22681-6af1-4069-855b-d95c115f6876

📥 Commits

Reviewing files that changed from the base of the PR and between c24cd39 and 3603ac8.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • node-backend/lib/ai.mjs
  • node-backend/lib/orchestrator.mjs
  • node-backend/lib/providers/gemini.mjs
  • node-backend/tests/parity.test.mjs
  • node-backend/tests/services/settings-service.test.mjs
  • src/api/runtime.ts
  • src/components/workspace/ChatPanel.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/api/runtime.ts
  • node-backend/lib/orchestrator.mjs
  • node-backend/tests/parity.test.mjs
  • node-backend/lib/ai.mjs
  • node-backend/tests/services/settings-service.test.mjs
  • src/components/workspace/ChatPanel.tsx

Comment thread node-backend/lib/providers/gemini.mjs
Comment thread node-backend/lib/providers/gemini.mjs
Comment thread node-backend/lib/providers/gemini.mjs Outdated
AssafMiron
AssafMiron previously approved these changes May 9, 2026
@AssafMiron
AssafMiron merged commit d33d11b into main May 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants