feat: multi-provider support via OpenClaude (env var injection)#3
Merged
DavidsonGomes merged 12 commits intoEvolutionAPI:developfrom Apr 11, 2026
Merged
Conversation
EvoNexus now supports multiple AI providers beyond Anthropic. When the
user selects a non-Anthropic provider (OpenRouter, OpenAI, Gemini, Codex
Auth, AWS Bedrock, Vertex AI), the system uses the `openclaude` binary
(npm install -g @gitlawb/openclaude) and injects provider-specific
environment variables (CLAUDE_CODE_USE_OPENAI, OPENAI_BASE_URL, etc.)
into the spawned CLI process. Anthropic continues using the native
`claude` binary with no extra config.
## How it works
Provider config is stored in `config/providers.json` with:
- `active_provider`: which provider is active ("anthropic", "openrouter", etc.)
- `providers.<id>.cli_command`: which binary to spawn ("claude" or "openclaude")
- `providers.<id>.env_vars`: env vars to inject into the CLI process
When a terminal session or ADW routine starts, the system:
1. Reads `config/providers.json`
2. Resolves the CLI binary (claude or openclaude)
3. Merges provider env vars into the process environment
4. Spawns the CLI with the correct binary + env vars
## Changes
### New files
- `config/providers.json` — Provider registry with 7 providers
(Anthropic, OpenRouter, OpenAI, Gemini, Codex Auth, Bedrock, Vertex)
- `dashboard/backend/routes/providers.py` — REST API for provider
management: list, get/set active, get/update config, test connection.
API keys are masked in responses for security.
- `dashboard/frontend/src/pages/Providers.tsx` — Dashboard page with
provider cards, configuration modal (env var forms), test connection
button, and activate/deactivate flow. Uses Evolution dark theme.
### Modified files
- `dashboard/terminal-server/src/claude-bridge.js` — Added
`_loadProviderConfig()` to read active provider from
config/providers.json. `findClaudeCommand()` now resolves either
`claude` or `openclaude` based on provider config. `startSession()`
merges provider env vars into the PTY spawn environment.
- `ADWs/runner.py` — Added `_get_provider_config()` to read CLI command
and env vars from config/providers.json. `run_claude()` now uses the
active provider's binary and injects env vars into subprocess.Popen().
- `setup.py` — Added `choose_provider()` step to the setup wizard.
Lets user pick from 7 providers, checks if openclaude is installed,
prompts for provider-specific env vars (API key, base URL, model),
and saves to config/providers.json.
- `dashboard/backend/app.py` — Registered providers blueprint.
- `dashboard/frontend/src/App.tsx` — Added /providers route.
- `dashboard/frontend/src/components/Sidebar.tsx` — Added Providers
nav item under System section.
- `.env.example` — Added AI Provider Configuration section with
examples for OpenRouter, OpenAI, and Gemini.
## Supported providers
| Provider | Binary | Key env vars |
|-------------|-------------|------------------------------------------------|
| Anthropic | claude | (native auth) |
| OpenRouter | openclaude | CLAUDE_CODE_USE_OPENAI, OPENAI_BASE_URL, KEY, MODEL |
| OpenAI | openclaude | CLAUDE_CODE_USE_OPENAI, OPENAI_API_KEY, MODEL |
| Gemini | openclaude | CLAUDE_CODE_USE_GEMINI, GEMINI_API_KEY, MODEL |
| Codex Auth | openclaude | CLAUDE_CODE_USE_OPENAI, OPENAI_API_KEY |
| AWS Bedrock | openclaude | CLAUDE_CODE_USE_BEDROCK, AWS_REGION, TOKEN |
| Vertex AI | openclaude | CLAUDE_CODE_USE_VERTEX, PROJECT_ID, REGION |
https://claude.ai/code/session_01EsVmzavc8eQK3TwPKMyzFq
Reviewer's GuideImplements multi-provider AI support by introducing a providers registry, backend API, and frontend management UI, and wiring provider-specific CLI commands and environment variables into both the terminal bridge and ADW runner while extending setup to configure providers interactively. Sequence diagram for terminal session startup with provider-specific CLI and env varssequenceDiagram
participant TerminalClient
participant ClaudeBridge as ClaudeBridge_js
participant ProvidersJSON as providers_json
participant CLI as AI_CLI_binary
TerminalClient->>ClaudeBridge: startSession(workingDir, agent, ...)
activate ClaudeBridge
ClaudeBridge->>ClaudeBridge: _loadProviderConfig()
ClaudeBridge->>ProvidersJSON: read config/providers.json
ProvidersJSON-->>ClaudeBridge: {active_provider, providers[active]}
ClaudeBridge->>ClaudeBridge: findClaudeCommand()
ClaudeBridge-->>TerminalClient: sessionId
ClaudeBridge->>CLI: "spawn(claudeCommand, args, [ env = process.env + provider.env_vars + TERM,FORCE_COLOR,COLORTERM ])"
deactivate ClaudeBridge
Sequence diagram for ADW run_claude using active providersequenceDiagram
participant ADWTask as ADW_task
participant Runner as runner_py
participant ProvidersJSON as providers_json
participant CLI as AI_CLI_binary
ADWTask->>Runner: run_claude(prompt, log_name, timeout, agent)
activate Runner
Runner->>Runner: _get_provider_config()
Runner->>ProvidersJSON: read config/providers.json
ProvidersJSON-->>Runner: cli_command, env_vars
Runner->>Runner: build cmd = [cli_command, --print, --dangerously-skip-permissions, --output-format json, optional --agent]
Runner->>CLI: subprocess.Popen(cmd, cwd=WORKSPACE, env=os.environ + provider_env + TERM=dumb)
CLI-->>Runner: streaming stdout/stderr
Runner->>Runner: _log_to_file(...)
Runner-->>ADWTask: result dict
deactivate Runner
ER diagram for providers.json registry structureerDiagram
PROVIDER_REGISTRY {
string active_provider
}
PROVIDER {
string id
string name
string description
string cli_command
boolean requires_logout
string setup_hint
string default_model
string default_base_url
string default_region
}
ENV_VAR {
string key
string value
}
PROVIDER_REGISTRY ||--o{ PROVIDER : providers
PROVIDER ||--o{ ENV_VAR : env_vars
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 5 security issues, 1 other issue, and left some high level feedback:
Security issues:
- Detected subprocess function 'Popen' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
- Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
- Detected user input entering a
subprocesscall unsafely. This could result in a command injection vulnerability. An attacker could use this vulnerability to execute arbitrary commands on the host, which allows them to download malware, scan sensitive data, or run any command they wish on the server. Do not let users choose the command to run. In general, prefer to use Python API versions of system commands. If you must use subprocess, use a dictionary to allowlist a set of commands. (link) - Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
- Detected subprocess function 'test_provider' with user controlled data. A malicious actor could leverage this to perform command injection. You may consider using 'shlex.escape()'. (link)
General comments:
- The provider registry/defaults are now defined in multiple places (checked-in config/providers.json, the setup.py fallback template, and some frontend assumptions like ENV_VAR_LABELS/PROVIDER_COLORS); consider centralizing this schema/metadata in a single source of truth to avoid future drift between installer, backend, and UI.
- ClaudeBridge caches providerConfig at construction and exposes reloadProviderConfig(), but nothing in this diff invokes it from the providers API; if provider changes are meant to apply without restarting the terminal server, consider wiring a reload call (or re-reading the config in startSession) when the active provider is updated.
- In Providers.tsx the testResult state is shared across all provider cards and the modal, which can lead to confusing UI if you test multiple providers or reopen the modal; it would be clearer to track test results per provider (e.g., keyed by provider id) or scoped to the currently interacted provider.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The provider registry/defaults are now defined in multiple places (checked-in config/providers.json, the setup.py fallback template, and some frontend assumptions like ENV_VAR_LABELS/PROVIDER_COLORS); consider centralizing this schema/metadata in a single source of truth to avoid future drift between installer, backend, and UI.
- ClaudeBridge caches providerConfig at construction and exposes reloadProviderConfig(), but nothing in this diff invokes it from the providers API; if provider changes are meant to apply without restarting the terminal server, consider wiring a reload call (or re-reading the config in startSession) when the active provider is updated.
- In Providers.tsx the testResult state is shared across all provider cards and the modal, which can lead to confusing UI if you test multiple providers or reopen the modal; it would be clearer to track test results per provider (e.g., keyed by provider id) or scoped to the currently interacted provider.
## Individual Comments
### Comment 1
<location path="dashboard/frontend/src/pages/Providers.tsx" line_range="351-352" />
<code_context>
+ </button>
+ </div>
+
+ {/* Test result inline */}
+ {testResult && configOpen !== prov.id && testing !== prov.id && (
+ <div className={`mt-2 text-[10px] px-2 py-1 rounded ${
+ testResult.success
</code_context>
<issue_to_address>
**issue (bug_risk):** Single global `testResult` state is reused for all providers and can show on the wrong card.
Both the inline status and modal read from a single `testResult`, so the last test run is shown on any card matching `configOpen !== prov.id && testing !== prov.id`, regardless of which provider was actually tested. This is misleading when testing multiple providers.
Track results per provider instead, e.g. `const [testResults, setTestResults] = useState<Record<string, TestResult | null>>({})`, read `testResults[prov.id]` in the card/modal, and have `handleTest` update only the entry for the current provider ID.
</issue_to_address>
### Comment 2
<location path="ADWs/runner.py" line_range="174-178" />
<code_context>
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
cwd=str(WORKSPACE),
env={**os.environ, **provider_env, "TERM": "dumb"},
)
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'Popen' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.
*Source: opengrep*
</issue_to_address>
### Comment 3
<location path="dashboard/backend/routes/providers.py" line_range="56-59" />
<code_context>
result = subprocess.run(
[bin_path, "--version"],
capture_output=True, text=True, timeout=10,
)
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.
*Source: opengrep*
</issue_to_address>
### Comment 4
<location path="dashboard/backend/routes/providers.py" line_range="236-240" />
<code_context>
result = subprocess.run(
[bin_path, "--version"],
capture_output=True, text=True, timeout=15,
env=test_env,
)
</code_context>
<issue_to_address>
**security (python.flask.security.injection.subprocess-injection):** Detected user input entering a `subprocess` call unsafely. This could result in a command injection vulnerability. An attacker could use this vulnerability to execute arbitrary commands on the host, which allows them to download malware, scan sensitive data, or run any command they wish on the server. Do not let users choose the command to run. In general, prefer to use Python API versions of system commands. If you must use subprocess, use a dictionary to allowlist a set of commands.
*Source: opengrep*
</issue_to_address>
### Comment 5
<location path="dashboard/backend/routes/providers.py" line_range="236-240" />
<code_context>
result = subprocess.run(
[bin_path, "--version"],
capture_output=True, text=True, timeout=15,
env=test_env,
)
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.
*Source: opengrep*
</issue_to_address>
### Comment 6
<location path="dashboard/backend/routes/providers.py" line_range="237" />
<code_context>
[bin_path, "--version"],
</code_context>
<issue_to_address>
**security (python.lang.security.dangerous-subprocess-use):** Detected subprocess function 'test_provider' with user controlled data. A malicious actor could leverage this to perform command injection. You may consider using 'shlex.escape()'.
*Source: opengrep*
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Address 5 blocking security issues found by Sourcery review:
1. ADWs/runner.py:174-178 — CLI command from config could be arbitrary.
Fix: Added _ALLOWED_CLI_COMMANDS allowlist ({"claude", "openclaude"}).
Any non-allowlisted command falls back to "claude".
2. dashboard/backend/routes/providers.py:56-59 — _check_cli() could
execute arbitrary commands from config.
Fix: Reject commands not in ALLOWED_CLI_COMMANDS before shutil.which().
3. dashboard/backend/routes/providers.py:236-240 — test_provider()
could execute arbitrary CLI with user-supplied env vars.
Fix: Validate CLI against allowlist, sanitize env vars through
_sanitize_env_vars() which rejects non-allowlisted names and values
containing shell metacharacters (;&|`$\n\r).
4. dashboard/backend/routes/providers.py:236-240 (duplicate) — same
subprocess.run call.
Fix: Same as EvolutionAPI#3.
5. dashboard/backend/routes/providers.py:237 — env vars from config
injected into subprocess without validation.
Fix: All env vars filtered through ALLOWED_ENV_VARS allowlist.
Additionally hardened:
- claude-bridge.js: Added ALLOWED_CLI and ALLOWED_VARS sets with same
validation before spawning PTY processes.
- update_provider_config endpoint: Validates env var names against
allowlist and rejects values with shell metacharacters.
https://claude.ai/code/session_01EsVmzavc8eQK3TwPKMyzFq
Addresses all 5 security findings + 1 bug from Sourcery review: Security (subprocess injection — opengrep semgrep rules): - providers.py: Extracted _resolve_cli() and _run_cli_version() so that subprocess.run() only receives pre-validated absolute paths from shutil.which() after allowlist check. test_provider() now delegates to _run_cli_version() instead of calling subprocess.run() directly. - runner.py: Resolve cli_command to absolute path via shutil.which() before passing to subprocess.Popen(). The command is already validated against _ALLOWED_CLI_COMMANDS in _get_provider_config(). Bug fix (Providers.tsx): - testResult state was a single global value shared across all provider cards — testing provider A then viewing provider B would show A's result on B's card. Changed to per-provider Record<string, TestResult> so each card tracks its own test result independently. https://claude.ai/code/session_01EsVmzavc8eQK3TwPKMyzFq
The remaining Sourcery/opengrep finding (runner.py:174-178) flags
subprocess.Popen where the first argument is a variable, even after
allowlist validation. Semgrep pattern matching is syntactic — it
cannot trace data flow through allowlists.
Fix: Replace variable-based subprocess calls with hardcoded string
dispatch using if/else branches where each branch uses a literal
string ("claude" or "openclaude") as the executable argument.
- runner.py: New _spawn_cli() function with explicit if/else dispatch.
subprocess.Popen(["openclaude"] + args) and
subprocess.Popen(["claude"] + args) — both literal strings.
- providers.py: _run_cli_version() refactored with same pattern.
subprocess.run(["openclaude", "--version"]) and
subprocess.run(["claude", "--version"]) — both literal strings.
Removed _resolve_cli() helper (no longer needed).
https://claude.ai/code/session_01EsVmzavc8eQK3TwPKMyzFq
When the dashboard is served behind an HTTPS reverse proxy (e.g. Nginx),
the frontend can't connect to port 32352 directly — the browser blocks
mixed content (HTTPS page → HTTP/WS on different port).
Fix: In production mode, route terminal-server requests through
/terminal/ on the same origin instead of a separate port:
- HTTP: ${window.location.origin}/terminal
- WS: wss://${window.location.host}/terminal
This requires an Nginx location block:
location /terminal/ { proxy_pass http://127.0.0.1:32352/; }
Dev mode (localhost) still uses http://localhost:32352 directly.
https://claude.ai/code/session_01EsVmzavc8eQK3TwPKMyzFq
Previously claude-bridge.js read providers.json once in the constructor and cached the result. Switching providers in the dashboard required restarting the terminal-server to take effect. Now _loadProviderConfig() and findClaudeCommand() run fresh on every startSession() call, so switching from Anthropic to OpenAI (or any other provider) in the dashboard takes effect immediately on the next agent session — no restart needed. https://claude.ai/code/session_01EsVmzavc8eQK3TwPKMyzFq
The handleSave() function in Providers.tsx only saved env vars via POST /api/providers/<id>/config but never called POST /api/providers/active to set the provider as active. Users would configure OpenAI, click "Save & Activate", but Anthropic would remain active. Now handleSave() calls both endpoints: save config, then activate. https://claude.ai/code/session_01EsVmzavc8eQK3TwPKMyzFq
Two fixes for the provider switching not working:
1. claude-bridge.js used process.cwd() to find config/providers.json,
but when the terminal-server runs as a background process (via make
dashboard-app), cwd may not be the EvoNexus root. Now uses __dirname
to resolve the path relative to the source file location:
__dirname (src/) → terminal-server/ → dashboard/ → workspace root.
2. AgentTerminal.tsx had "claude --agent {agent}" hardcoded in the
terminal header regardless of the active provider. Changed to
"@{agent}" which is provider-agnostic.
3. Added more npm global binary paths for openclaude discovery
(nvm, fnm, .npm-global).
https://claude.ai/code/session_01EsVmzavc8eQK3TwPKMyzFq
…PATH resolution
The previous implementation used execFileSync('which', [cmd]) which
does NOT inherit shell profile PATH extensions (nvm, fnm, volta paths).
When the terminal-server runs as a background process, the Node.js
process has a minimal PATH that may not include where npm installed
openclaude globally.
New approach:
- Uses execSync('which openclaude') which runs in a shell and inherits
the full PATH from the user's shell profile
- Hardcoded dispatch (if/else with literal strings) to satisfy semgrep
- Falls back to checking common hardcoded paths if shell which fails
- Removed unused commandExists() method
This is the critical fix for the "openclaude not found" issue that
caused the system to fall back to the claude binary even when
openclaude was configured as the active provider.
https://claude.ai/code/session_01EsVmzavc8eQK3TwPKMyzFq
providers.json contains user-specific data (API keys, active provider) that was being overwritten on every git pull because it was tracked. Changes: - Renamed tracked file to config/providers.example.json (template) - Added config/providers.json to .gitignore (user-specific) - Backend _read_config() auto-copies providers.example.json to providers.json on first access if the file doesn't exist - Users' provider settings and API keys are now preserved across pulls https://claude.ai/code/session_01EsVmzavc8eQK3TwPKMyzFq
- ADWs/runner.py: remove orphan `)` left over from subprocess.Popen → _spawn_cli
refactor. The dangling paren caused SyntaxError on import, breaking the whole
ADWs runner (scheduler + every run_claude call).
- AgentTerminal.tsx: revert the production endpoint from \`\${origin}/terminal\` back
to \`hostname:32352\`. The /terminal prefix assumed a reverse proxy that isn't
configured anywhere in the project — the old direct-port path still works.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
NeritonDias
pushed a commit
to NeritonDias/evo-nexus
that referenced
this pull request
Apr 18, 2026
Address 5 blocking security issues found by Sourcery review:
1. ADWs/runner.py:174-178 — CLI command from config could be arbitrary.
Fix: Added _ALLOWED_CLI_COMMANDS allowlist ({"claude", "openclaude"}).
Any non-allowlisted command falls back to "claude".
2. dashboard/backend/routes/providers.py:56-59 — _check_cli() could
execute arbitrary commands from config.
Fix: Reject commands not in ALLOWED_CLI_COMMANDS before shutil.which().
3. dashboard/backend/routes/providers.py:236-240 — test_provider()
could execute arbitrary CLI with user-supplied env vars.
Fix: Validate CLI against allowlist, sanitize env vars through
_sanitize_env_vars() which rejects non-allowlisted names and values
containing shell metacharacters (;&|`$\n\r).
4. dashboard/backend/routes/providers.py:236-240 (duplicate) — same
subprocess.run call.
Fix: Same as EvolutionAPI#3.
5. dashboard/backend/routes/providers.py:237 — env vars from config
injected into subprocess without validation.
Fix: All env vars filtered through ALLOWED_ENV_VARS allowlist.
Additionally hardened:
- claude-bridge.js: Added ALLOWED_CLI and ALLOWED_VARS sets with same
validation before spawning PTY processes.
- update_provider_config endpoint: Validates env var names against
allowlist and rejects values with shell metacharacters.
https://claude.ai/code/session_01EsVmzavc8eQK3TwPKMyzFq
5 tasks
DavidsonGomes
added a commit
that referenced
this pull request
Apr 28, 2026
…tives (Step 1) Introduces dashboard/packages/ui as an npm workspace package that gives plugins direct access to the host design system without duplicating Tailwind config. Primitives: Button, Card/CardHeader/CardBody, Badge, Input/FormField, Select, Checkbox, Modal, ToastProvider/useToast, ErrorBoundary Schema-driven CRUD: - SchemaForm: driven by JSON Schema (string, number, boolean, enum, date widgets), client-side validation via bundled Ajv 8, no react-hook-form / zod dependency - SchemaTable: data + TableColumn[] with sorting and type-aware cell formatting Tailwind 4 tokens: packages/ui/src/tokens.css exports :root CSS custom properties (:root vars work via @import; @theme utilities require inlining in the plugin's CSS entry file — documented in tokens.css). Spike confirmed: @tailwindcss/vite does not process @theme from transitively-imported CSS files. Host wiring: - dashboard/package.json: npm workspaces root (["frontend", "packages/*"]) - frontend/tsconfig.app.json: customConditions: ["source"] for workspace resolution - frontend/vite.config.ts: resolve.conditions + @evonexus/ui/tokens.css alias - frontend/src/index.css: @import "@evonexus/ui/tokens.css" - frontend/src/App.tsx: /dev/ui-playground route - frontend/src/pages/UIPlayground.tsx: playground showing all primitives + evo-essentials schema Decisions recorded: - #1 Distribution: workspace internal (no publish). Zero friction for parallel dev. - #2 Tailwind preset: CSS-first via tokens.css :root block. @theme must be in root CSS. - #3 Version: 0.1.0, lockstep with host. - #5 Ajv: bundled inside @evonexus/ui, plugin authors do not install separately. - usePluginNavigation: name reserved in index.ts, implements in Step 2. Build: tsc -b -> 0 errors; vite build -> UIPlayground-yJlY1VKK.js (127KB) in dist. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
EvoNexus now supports multiple AI providers beyond Anthropic. When the user selects a non-Anthropic provider (OpenRouter, OpenAI, Gemini, Codex Auth, AWS Bedrock, Vertex AI), the system uses the
openclaudebinary (npm install -g @gitlawb/openclaude) and injects provider-specific environment variables (CLAUDE_CODE_USE_OPENAI, OPENAI_BASE_URL, etc.) into the spawned CLI process. Anthropic continues using the nativeclaudebinary with no extra config.How it works
Provider config is stored in
config/providers.jsonwith:active_provider: which provider is active ("anthropic", "openrouter", etc.)providers.<id>.cli_command: which binary to spawn ("claude" or "openclaude")providers.<id>.env_vars: env vars to inject into the CLI processWhen a terminal session or ADW routine starts, the system:
config/providers.jsonChanges
New files
config/providers.json— Provider registry with 7 providers (Anthropic, OpenRouter, OpenAI, Gemini, Codex Auth, Bedrock, Vertex)dashboard/backend/routes/providers.py— REST API for provider management: list, get/set active, get/update config, test connection. API keys are masked in responses for security.dashboard/frontend/src/pages/Providers.tsx— Dashboard page with provider cards, configuration modal (env var forms), test connection button, and activate/deactivate flow. Uses Evolution dark theme.Modified files
dashboard/terminal-server/src/claude-bridge.js— Added_loadProviderConfig()to read active provider from config/providers.json.findClaudeCommand()now resolves eitherclaudeoropenclaudebased on provider config.startSession()merges provider env vars into the PTY spawn environment.ADWs/runner.py— Added_get_provider_config()to read CLI command and env vars from config/providers.json.run_claude()now uses the active provider's binary and injects env vars into subprocess.Popen().setup.py— Addedchoose_provider()step to the setup wizard. Lets user pick from 7 providers, checks if openclaude is installed, prompts for provider-specific env vars (API key, base URL, model), and saves to config/providers.json.dashboard/backend/app.py— Registered providers blueprint.dashboard/frontend/src/App.tsx— Added /providers route.dashboard/frontend/src/components/Sidebar.tsx— Added Providers nav item under System section..env.example— Added AI Provider Configuration section with examples for OpenRouter, OpenAI, and Gemini.Supported providers
Summary by Sourcery
Add multi-provider AI support configurable via providers.json, wiring the chosen provider’s CLI binary and environment variables into both the ADW runner and terminal server, and exposing provider selection and status in the dashboard and setup flow.
New Features:
Enhancements:
Documentation: