0xfandomandGitHub db01038d5c feat(model-picker): surface inactive provider profiles in /model (#1119 piece 2) (#1164)
* feat(model-picker): surface inactive provider profiles in /model

When a user configures multiple providerProfiles (Kimi + Z.AI + OpenRouter
+ SambaNova in the #1119 repro, but the pattern fits any multi-provider
setup), switching the main session between them currently requires
round-tripping through /provider — /model only shows the active
profile's models.

Make /model the single switcher:

- ModelOption gains an optional `switchToProfileId`. Existing options
  leave it unset and behave exactly as today.
- `getInactiveProviderProfileOptions` enumerates every configured
  profile that isn't the active one and emits a picker entry per model,
  labelled `<model> · <profile.name>` so the user can see the choice
  changes providers, not just models.
- Each option's `value` is encoded with `__switch_profile__:<id>:<model>`
  so the picker's plain-string `value` channel stays the source of truth
  and same-named models under different base URLs (`gpt-4o` on multiple
  OpenAI-compatible endpoints) stay disambiguated.
- /model's handleSelect detects the prefix, calls
  `setActiveProviderProfile` (same path /provider uses — applies env,
  persists active profile, refreshes startup file), then sets
  `mainLoopModel` to the bare model string.

Only surfaces inactive options when `CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED`
is set, so users who haven't opted into the multi-profile workflow at all
don't see the affordance.

Tests cover round-trip encoding (including OpenRouter-style colon-bearing
model strings), the active-filter, the multi-model explosion, and that
`getModelOptions()` 3P path includes the inactive options only when the
profile env is applied. Combined invocation with the rest of
`src/utils/model/` + `src/commands/model/` + `src/utils/providerProfiles.test.ts`
runs clean to guard against mock-leak (per the 2026-04-30 lesson —
spreads `import * as actual` for every `mock.module` factory).

Refs #1119

* fix(model-picker): run fast-mode cleanup on cross-profile switch

The new switch-profile branch returned before reaching the fast-mode
reconciliation, so a user with fastMode latched on Anthropic Opus could
switch to an OpenAI profile and silently keep fastMode on even though
the new model can't support it. Extract the cleanup into a pure helper
`reconcileFastModeForSwitch` and call it from both branches.

Refs #1119.

* fix(model-picker): decode cross-profile values before effort/display lookup

Inactive-profile entries encode the picker value as
`__switch_profile__:<profileId>:<model>`, but `resolveOptionModel`
forwarded the raw string straight to `parseUserSpecifiedModel`. For a
reasoning-capable cross-profile entry such as `gpt-5.4`,
`modelSupportsEffort()` then saw the prefixed string and reported
"Effort not supported", and `handleSelect` dropped the toggled effort
even when the underlying model accepts it.

Run `parseSwitchProfileValue` first; when it matches, hand the bare
target model to `parseUserSpecifiedModel` so effort capability,
default-effort lookup, and display-name resolution all key off the real
model id.

* fix(model-picker): include inactive profiles on local OpenAI-compatible scope

The inactive-profile compute lived after the
`getAdditionalModelOptionsCacheScope()?.startsWith('openai:')` early
return, so users with a local OpenAI-compatible profile active (Ollama,
lm-studio, any localhost endpoint) never saw the cross-profile switcher
in `/model`. They still had to round-trip through `/provider` to change
profile.

Hoist `profileEnvApplied`, the active-profile lookup, and
`getInactiveProviderProfileOptions(activeProfileId)` above the early
return, and append `inactiveProfileOptions` to the local-OpenAI branch
return value. Other branches (Claude.AI, MiMo, MiniMax, ant) were
already either irrelevant or have their own gating.

Test: new regression in modelOptions.crossProfile.test.ts pins
`getAdditionalModelOptionsCacheScope` to an `openai:` value and confirms
the inactive profile still surfaces with a parseable
`__switch_profile__` value.

* fix(model-picker): apply the allowlist to the decoded cross-profile model

filterModelOptionsByAllowlist evaluated cross-profile options by their encoded
__switch_profile__:<id>:<model> value, so an availableModels allowlist that
permits the bare target (e.g. glm-5.1) dropped every inactive-profile entry.
Check the allowlist against parseSwitchProfileValue(value)?.model ?? value, and
cover both the allowed and denied cases.

* fix(model-picker): only surface cross-profile switch options on the /model path

The inactive-profile entries come from the shared getModelOptions() list, but
only the /model command's onSelect decodes __switch_profile__ values and
activates the target profile. The prompt hotkey and Settings pickers wrote the
encoded value straight to mainLoopModel, sending an invalid model string.

Gate these options behind a new allowProfileSwitch prop that only the /model
command sets; inline pickers no longer surface an option they cannot honor.
Also apply the org allowlist to the decoded target model in the /model select
handler.

* test(model-picker): drop flaky cross-profile allowlist case

The decoded-allowlist assertion drove the org allowlist through the shared
session settings cache, which is racy across bun's single-process run and could
leak availableModels into sibling suites (the providerConfig cache-scope tests
went red in CI). The decode itself is a one-line guard already exercised by the
parseSwitchProfileValue round-trip coverage, so remove the unreliable case
rather than ship CI flake.

Also snapshot the real provider/auth modules before mocking so each harness
call rebuilds its mock from a clean base instead of a previous test's overrides
(bun live-repoints the imported namespace to the active mock).

* test(model-picker): stop cross-profile mocks leaking into provider suites

The cross-profile tests mock.module'd ../providerProfiles, ./providers,
../auth and ../../services/api/providerConfig per test. bun's mock.module is
process-wide and mock.restore() does not undo it, so these persisted into later
files — most damagingly the providerConfig mock, which replaced the module with
a single-function stub and stripped resolveProviderRequest /
getAdditionalModelOptionsCacheScope from providerConfig.local's suite (now
adjacent after the rebase onto #1706).

Install each mock once at module load, keep the full export surface, and gate
the overrides on module-level flags cleared in beforeEach/afterEach so the
persisted mocks are transparent passthroughs for every other suite. Same
pattern as the cross-spawn / install-surfaces leak fixes.

* fix(model): reconcile fast mode before activating the switched profile

In the cross-profile /model switch path, reconcileFastModeForSwitch ran after
setActiveProviderProfile. The reconciler gates on isFastModeEnabled(), which
reads the *active* provider — so once the target profile is activated it
reflects the new (fast-mode-less) provider and short-circuits to 'unchanged',
leaving fastMode latched on for a model that can't use it.

Compute the reconciliation before activating the profile, so it evaluates
against the source provider and correctly returns 'off' for an unsupported
target. Add a command-level regression test that drives handleSelect with a
__switch_profile__ value while setActiveProviderProfile flips the fast-mode
state, and asserts fastMode is set to false (it fails if the call order
regresses).

* fix(model): re-check fast mode after activating a switched profile

The pre-activation reconcile gates on the source provider, so its 'on' result
is stale when the target provider cannot run fast mode even though the target
model name passes the source-side support check (e.g. a third-party shim
exposing a claude-opus-* model). Re-evaluate isFastModeEnabled / supported /
available after setActiveProviderProfile and force fastMode off when it is no
longer genuinely supported. Add a command-level regression test for that path
and wrap the cross-profile test cleanup in try/finally so a failing assertion
still unmounts the Ink instance (jatmn review, #1119).

* test(model-picker): cover cross-profile allowlist with isolated settings

Re-add the regression dropped in 06a0c80: filterModelOptionsByAllowlist must
evaluate the allowlist against the decoded target model, not the encoded
__switch_profile__ wrapper. Uses this suite's per-test settings cache (reset in
afterEach) instead of the shared cache that made the earlier version flaky
(jatmn review, #1119).

* test(model-picker): make the cross-profile allowlist test leak-proof

The new allowlist test drove availableModels through setSessionSettingsCache,
but sibling suites (ModelPicker, ProviderManager, ...) mock.module both
settings.js (getSettings_DEPRECATED) and modelAllowlist.js (isModelAllowed)
process-wide, so in the full sequential run the leaked stubs defeated the cache
and the denied option was not filtered (smoke-and-tests red on the full suite,
green in isolation).

Drive the allowlist deterministically from this suite instead: install-once,
gated, passthrough mocks of getSettings_DEPRECATED (the filter gate) and
isModelAllowed (the per-option check), both keyed off a single
activeSettingsOverride and cleared in afterEach. Same gated-passthrough pattern
as the suite's existing providerConfig/providers/auth/profiles mocks and the
agent.test.ts allowlist approach.

* fix(model): keep cross-profile switch options out of the SDK models list

getModelOptions() now returns inactive-profile entries encoded as
__switch_profile__:<id>:<model>. print.ts mapped those straight into the
ModelInfo list returned to SDK/web callers, exposing UI-only values that
are not selectable model ids. Filter them with parseSwitchProfileValue
before building modelInfos. Add ModelPicker coverage for the
allowProfileSwitch filter (hidden inline, shown when allowed) and
document cross-profile /model switching in the provider-profile docs.

* test(model-picker): prove cross-profile switch options never reach SDK models

Extract selectSdkModelOptions as the single gate the SDK modelInfos
builder runs every getModelOptions() entry through, and cover it directly:
an encoded __switch_profile__:<id>:<model> option is dropped while real
model ids pass through. Fails if an inactive-profile affordance ever leaks
into the initialize.models response again (#1119).

* docs(model-picker): clarify the env gate for inactive-profile entries

The inactive-profile models only appear when the provider-profile env
workflow is active (CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED=1), not for
every multi-profile setup. Spell that out and restore the local-only
`--provider ollama` guidance that was folded into the paragraph.

* fix(model-picker): gate SDK option filter on switchToProfileId marker

selectSdkModelOptions filtered on the encoded __switch_profile__ value
prefix, which also reserved that prefix for every custom model id. A real
configured model whose id starts with __switch_profile__: would vanish
from the SDK models response and non-switching pickers. Key the gate on
the explicit switchToProfileId marker, which only synthesized switch
options carry, and add the collision regression.

Refs #1119

* fix(model-picker): reuse switch confirmation for cross-profile selections

The cross-profile branch built its own "Switched to" message and returned
before the regular path appended the selected effort and the
"Billed as extra usage" notice, hiding cost-impacting feedback when a
reasoning/extra-usage target was chosen through an inactive profile.
Append effort and the extra-usage check to the switch confirmation.

Refs #1119

* fix(model-picker): surface inactive profiles on the active Ollama path

The isOllamaProvider() early return ran before the inactive-profile
options were computed, so an active local Ollama profile saw only its own
models and lost the cross-profile switcher, forcing the /provider
round-trip this feature removes. Hoist the inactive-profile compute above
the Ollama branch and append it to the Ollama returns.

Refs #1119

* fix(model): surface inactive profiles on all provider branches; decode only real switch options

Two follow-ups to the #1119 unified /model switcher:

- inactiveProfileOptions was computed before the early-return branches but only
  appended on Ollama / local-scope / PAYG paths. The GitHub Copilot, NVIDIA NIM,
  MiniMax, Xiaomi MiMo, ant, and Claude-subscriber branches returned first, so a
  user with a saved profile active on any of those routes lost the cross-profile
  entries and had to round-trip through /provider. Append the (env-gated, so
  empty unless a profile is applied) inactive options on those branches too.

- filterModelOptionsByAllowlist decoded any value starting with
  `__switch_profile__:` via parseSwitchProfileValue, even a normal custom model
  id that merely shares that prefix, evaluating the allowlist against the wrong
  inner model. Gate the decode on the `switchToProfileId` marker (the type's
  documented contract) so non-switch ids are checked verbatim.

Extends the cross-profile harness with gated getAPIProvider / NVIDIA / subscriber
overrides and adds branch-append + verbatim-allowlist regressions (red-green).

* fix(model): key profile-switch handling on the marker across picker and command

The allowlist/SDK paths already used the switchToProfileId marker, but two
surfaces still keyed on the raw `__switch_profile__:` value prefix:

- ModelPicker's inline-picker filter hid any option whose value started with
  the prefix, so a real custom model id like `__switch_profile__:vendor:gpt-5.4`
  disappeared from prompt/settings pickers. It now filters on
  `switchToProfileId === undefined`.
- the /model command decoded parseSwitchProfileValue(model) for any prefixed
  string and tried to activate the encoded profile id, so selecting such a
  custom model activated a nonexistent profile instead of setting the literal
  model. It now only treats the value as a switch when the decoded profile id
  maps to a real configured provider profile — which every synthesized switch
  option does, and a prefix-colliding custom id does not.

Drops the now-unused SWITCH_PROFILE_VALUE_PREFIX import from ModelPicker. Adds a
picker regression (marked switch hidden, prefixed custom model stays visible) and
completes the cross-profile branch coverage (MiniMax, Xiaomi MiMo, ant) so every
branch that appends inactive-profile options is locked.

* test(model): register target profiles in cross-profile switch tests

The /model command now only treats a `__switch_profile__:` value as a switch
when its decoded profile id maps to a real configured provider profile. The
cross-profile switch tests set up setActiveProviderProfile but left the shared
getProviderProfiles mock empty, so the new guard classified their switch values
as literal models and the fast-mode / effort / extra-usage assertions no longer
ran. Register each test's target profile via getProviderProfiles so the switch
path executes as intended.

* fix(model): gate cross-profile switches on the selected option marker

Selecting a value that merely parses as `__switch_profile__:<profileId>:<model>`
activated the provider whenever <profileId> existed, so a literal custom model
id such as `__switch_profile__:profile_openai:gpt-5-mini` wrongly switched the
active provider instead of being applied verbatim.

Thread the picked option's `switchToProfileId` marker from ModelPicker.onSelect
(selectOptions already carries it) and only activate a profile when the marker
matches the decoded id. The effort/display resolver had the same gap — it
decoded every prefixed value; gate it on a genuine marker-backed switch option
too. Add a regression asserting a marker-less prefixed id is applied literally.

* test(model): cover Max/Team Premium and empty-catalog switch-append paths

The cross-profile branch-coverage suite exercised the populated-catalog returns
but not the Max/Team Premium subscriber early return nor the empty-catalog
fallbacks (NVIDIA/MiniMax/Xiaomi), which are the same paths that previously
dropped the inactive-profile switch options. Lock them so every changed return
that appends `...inactiveProfileOptions` is covered.

* fix(model): keep inactive-profile switch options in /model discovery overrides

The interactive /model command passes an optionsOverride into ModelPicker for
descriptor-backed and legacy OpenAI-compatible discovery contexts, built from
mergeActiveProfileModelOptions which only merges the ACTIVE profile's route
models. Because the picker renders optionsOverride ?? getModelOptions(), the
inactive-profile switch entries getModelOptions() appends never reached those
paths, so the unified switcher vanished for provider-profile routes
(OpenRouter/Kimi/MiniMax, refreshed local profiles). Re-append the same
inactive-profile switch options (allowlist-filtered on the decoded target) to
any override list before handing it to the picker.

* fix(model): base the switch marker on the presented option, treat ties as ambiguous

The picker derived switchToProfileId with selectOptions.find(value===...), and
the effort/display resolver decoded when any getModelOptions() entry with the
same value carried the marker. If a literal custom model id collided with an
encoded switch value, the literal could borrow a different same-value option's
marker and wrongly activate a provider. Add resolveSelectedSwitchProfileId,
which keys on the actual presented option and treats duplicate-value matches as
ambiguous (no switch), and route both the onSelect marker and the decode
decision through it.
2026-07-07 21:53:29 +08:00
2026-04-30 18:22:01 +08:00
2026-04-30 18:22:01 +08:00
2026-04-03 09:40:17 +08:00

OpenClaude

OpenClaude is an open-source coding-agent CLI for cloud and local model providers.

Use OpenAI-compatible APIs, Gemini, GitHub Models, Codex OAuth, Codex, Ollama, Atomic Chat, and other supported backends while keeping one terminal-first workflow: prompts, tools, agents, MCP, slash commands, and streaming output.

PR Checks Release Discussions Discord X Security Policy License

OpenClaude is also mirrored to GitLawb: gitlawb.com/node/repos/z6MkqDnb/openclaude

Quick Start | Setup Guides | Providers | Source Build | VS Code Extension | Sponsors | Community

Sponsors

GitLawb logo Bankr.bot logo Atomic Chat logo Xiaomi MiMo logo Atlas Cloud logo
GitLawb Bankr.bot Atomic Chat Xiaomi MiMo Atlas Cloud

Star History

Star History Chart

Why OpenClaude

  • Use one CLI across cloud APIs and local model backends
  • Save provider profiles inside the app with /provider
  • Run with OpenAI-compatible services, Gemini, GitHub Models, Codex OAuth, Codex, Ollama, Atomic Chat, and other supported providers
  • Keep coding-agent workflows in one place: bash, file tools, grep, glob, agents, tasks, MCP, and web tools
  • Use the bundled VS Code extension for launch integration and theme support

Quick Start

Install

OpenClaude requires Node.js >=22.0.0 for npm installs and runtime. Bun is only needed for source builds and local development.

npm install -g @gitlawb/openclaude@latest

If you're on Arch Linux, you can install OpenClaude from the community-maintained AUR package:

paru -S openclaude

If the install later reports ripgrep not found, install ripgrep system-wide and confirm rg --version works in the same terminal before starting OpenClaude.

Verify / troubleshoot installed version:

openclaude --version
npm view @gitlawb/openclaude dist-tags
npm install -g @gitlawb/openclaude@latest

Start

openclaude

Inside OpenClaude:

  • run /provider for guided provider setup and saved profiles
  • run /onboard-github for GitHub Models onboarding

Note: OpenClaude does not automatically load project .env files. We recommend using the /provider command for setup, which saves provider profiles and credentials in .openclaude-profile.json. If you prefer environment variables, export them explicitly or run openclaude --provider-env-file .env for provider/setup variables. Export runtime/debug knobs from your shell or launcher.

Resume or fork a conversation

Resume an existing conversation by session ID, or continue the most recent conversation in the current directory:

openclaude --resume <session-id>
openclaude --continue

Add --fork-session to branch the conversation history into a new session ID instead of reusing the original transcript:

openclaude --resume <session-id> --fork-session
openclaude --continue --fork-session

Forking is conversation branching only. It does not create filesystem isolation, copy your working tree, or create a git worktree branch.

Background sessions

Run long non-interactive prompts detached from the current terminal:

openclaude --bg "fix failing tests"
openclaude --bg --name auth-refactor "refactor auth middleware"
openclaude ps
openclaude logs auth-refactor
openclaude logs auth-refactor -f
openclaude kill auth-refactor

Background sessions are local child processes. OpenClaude does not start a daemon or network service, and permission/provider/model/settings flags are passed to the child process the same way they are for a foreground --print run. Session metadata and logs are stored under the resolved OpenClaude config directory, usually ~/.openclaude/bg-sessions/; OPENCLAUDE_CONFIG_DIR can point OpenClaude somewhere else. CLAUDE_CONFIG_DIR is ignored for OpenClaude background-session storage. Session names can be reused after older sessions reach a terminal state; use the session ID to inspect older logs with the same name.

openclaude attach <id-or-name> currently reports the matching session and points to openclaude logs <id> -f; full terminal reattach is not implemented for local background sessions yet.

OpenClaude config cutover

OpenClaude stores its own config under ~/.openclaude and ~/.openclaude.json by default. It does not read ~/.claude, project .claude/ directories, or CLAUDE_CONFIG_DIR; new users can start with an empty OpenClaude config and do not need Claude Code installed.

If you previously used OpenClaude with .claude paths, migrate intentionally: copy only the settings, commands, agents, skills, scheduled tasks, or other files you personally created for OpenClaude into the matching .openclaude location. Do not blanket-copy .claude, and do not copy Claude Code credentials or auth files. For provider authentication, prefer running OpenClaude's provider setup again or exporting provider-specific environment variables.

Fastest OpenAI setup

macOS / Linux:

export CLAUDE_CODE_USE_OPENAI=1
export OPENAI_API_KEY=sk-your-key-here
export OPENAI_MODEL=gpt-4o

openclaude

Windows PowerShell:

$env:CLAUDE_CODE_USE_OPENAI="1"
$env:OPENAI_API_KEY="sk-your-key-here"
$env:OPENAI_MODEL="gpt-4o"

openclaude

Fastest local Ollama setup

macOS / Linux:

export CLAUDE_CODE_USE_OPENAI=1
export OPENAI_BASE_URL=http://localhost:11434/v1
export OPENAI_MODEL=qwen2.5-coder:7b

openclaude

Windows PowerShell:

$env:CLAUDE_CODE_USE_OPENAI="1"
$env:OPENAI_BASE_URL="http://localhost:11434/v1"
$env:OPENAI_MODEL="qwen2.5-coder:7b"

openclaude

For Ollama, OpenClaude uses Ollama's native chat API and requests a 32768-token context window on each chat request so same-session history is not silently truncated by Ollama's OpenAI-compatible shim. Set OPENCLAUDE_OLLAMA_NUM_CTX or OLLAMA_CONTEXT_LENGTH if you need a different request-level context size. See Advanced Setup for verification with ollama ps.

Setup Guides

Beginner-friendly guides:

Advanced and source-build guides:

Supported Providers

Provider Setup Path Notes
OpenAI-compatible /provider or env vars Works with OpenAI, OpenRouter, DeepSeek, Groq, Mistral, LM Studio, and other compatible /v1 servers
Z.AI GLM Coding Plan /provider or OpenAI-compatible env vars Uses OPENAI_API_KEY at https://api.z.ai/api/coding/paas/v4 and defaults to glm-5.2
AI/ML API /provider or AIMLAPI_API_KEY (setup guide) Uses https://api.aimlapi.com/v1, auto-detects the OpenAI-compatible route from AIMLAPI_API_KEY, sends OpenClaude attribution headers, and discovers chat-capable models from the public /models catalog
Hicap /provider or OpenAI-compatible env vars Uses api-key auth, discovers models from unauthenticated /models, and supports Responses mode for gpt- models
Fireworks AI /provider or env vars First-class provider with 276 curated models (DeepSeek, Qwen, Llama, Gemma, and more); uses FIREWORKS_API_KEY
ClinePass /provider or env vars AI model gateway with usage limits (5hr, weekly, monthly); uses CLINE_API_KEY at https://api.cline.bot/api/v1
Gemini /provider or env vars Supports API key only
GitHub Models /onboard-github Interactive onboarding with saved credentials
Codex OAuth /provider Opens ChatGPT sign-in in your browser and stores Codex credentials securely
Codex /provider Uses existing Codex CLI auth, OpenClaude secure storage, or env credentials
Gitlawb Opengateway Startup default, /provider, or env vars Smart gateway at https://opengateway.gitlawb.com/v1; requires an API key from https://gitlawb.com/opengateway/keys and routes Xiaomi MiMo and GMI Cloud partner models by OPENAI_MODEL
OpenCode Zen /provider or env vars Pay-as-you-go AI gateway (48 models); uses OPENCODE_API_KEY via https://opencode.ai/zen/v1; shared key with OpenCode Go
OpenCode Go /provider or env vars $10/mo subscription for open models (13 models); uses OPENCODE_API_KEY via https://opencode.ai/zen/go/v1; shared key with OpenCode Zen
Xiaomi MiMo /provider or env vars OpenAI-compatible API at https://mimo.mi.com; uses MIMO_API_KEY and defaults to mimo-v2.5-pro
NEAR AI /provider or env vars Unified gateway (Claude, GPT, Gemini + TEE open models); uses NEARAI_API_KEY at https://cloud-api.near.ai/v1
Ollama /provider or env vars Local inference with no API key
Atomic Chat /provider, env vars, or bun run dev:atomic-chat Local Model Provider; auto-detects loaded models
Bedrock / Vertex / Foundry env vars Anthropic-family cloud routes; Vertex is for Claude on Vertex AI, not arbitrary Model Garden models

What Works

  • Tool-driven coding workflows: Bash, file read/write/edit, grep, glob, agents, tasks, MCP, and slash commands
  • Streaming responses: Real-time token output and tool progress
  • Tool calling: Multi-step tool loops with model calls, tool execution, and follow-up responses
  • Images: URL and base64 image inputs for providers that support vision
  • Provider profiles: Guided setup plus saved user-level provider profile support
  • Local and remote model backends: Cloud APIs, local servers, and Apple Silicon local inference
  • Codebase intelligence (repo map): Structural map of the repository ranked by PageRank importance, auto-injected into context when the REPO_MAP flag is enabled or the REPO_MAP environment variable is set. Inspect with /repomap (2048-token default). See docs/repo-map.md for details.

Provider Notes

OpenClaude supports multiple providers, but behavior is not identical across all of them.

  • Anthropic-specific features may not exist on other providers
  • Tool quality depends heavily on the selected model
  • Smaller local models can struggle with long multi-step tool flows
  • Some providers impose lower output caps than the CLI defaults, and OpenClaude adapts where possible
  • AI/ML API uses the OpenAI-compatible route, defaults to gpt-4o, and only surfaces chat-capable models from its public catalog
  • Gitlawb Opengateway is the fresh-install startup default and requires an API key from https://gitlawb.com/opengateway/keys. It uses one OpenAI-compatible base URL; switch between mimo-* and google/gemini-3.1-flash-lite-preview with /model, and do not pin the base URL to /v1/xiaomi-mimo.
  • Z.AI GLM Coding Plan uses https://api.z.ai/api/coding/paas/v4 with glm-5.2 by default. Use glm-5.2?reasoning=high for enhanced reasoning, glm-5.2?reasoning=xhigh to request Z.AI reasoning_effort=max, or glm-5.2?thinking=disabled for faster direct answers.
  • Xiaomi MiMo uses api-key header auth on the direct OpenAI-compatible route and currently does not support /usage reporting in OpenClaude

GitHub Copilot sub-agent optimization

When CLAUDE_CODE_USE_GITHUB=1, OpenClaude serializes sub-agent execution to reduce GitHub Copilot Premium Request consumption. Default behavior is GITHUB_COPILOT_MAX_SUBAGENTS=1 (synchronous, one sub-agent at a time). Tuning vars (all optional):

Var Effect
GITHUB_COPILOT_MAX_SUBAGENTS=0 Suppress sub-agents entirely (sub-agents throw an error).
GITHUB_COPILOT_MAX_SUBAGENTS=1 Force synchronous execution. Default.
GITHUB_COPILOT_MAX_SUBAGENTS=2..10 Parsed/clamped but not enforced differently from =1 (any positive cap = synchronous).
GITHUB_COPILOT_ALLOW_SUBAGENTS=1 Re-enable parallel/background sub-agents, overriding the cap.
GITHUB_COPILOT_FORCE_SYNC_SUBAGENTS=1 Force synchronous execution regardless of cap.
GITHUB_COPILOT_OPTIMIZATION_DISABLED=1 Disable all of the above; sub-agents run as before this feature.

The is_async field reported in the tengu_agent_tool_selected event and the agent metadata now reflects the final execution mode (i.e., false when synchronous is forced). See .env.example for the full descriptions.

For best results, use models with strong tool/function calling support.

Agent step limits

Custom agents can define maxSteps as a positive integer to cap how many tool-use steps a sub-agent may execute. When the limit is reached, OpenClaude stops additional tool calls and asks the sub-agent for a concise final summary covering completed work, findings, remaining tasks, and whether another run is needed. Omitting maxSteps, or setting it to an invalid value such as 0 or malformed input, preserves the default unlimited behavior.

---
name: bounded-researcher
description: Use for focused research with bounded tool use
maxSteps: 8
---

You are a focused research agent.

Agent Routing

OpenClaude can route different agents to different models through settings-based routing. This is useful for cost optimization or splitting work by model strength.

Add to ~/.openclaude.json:

{
  "agentModels": {
    "deepseek-v4-flash": {
      "base_url": "https://api.deepseek.com/v1",
      "api_key": "sk-your-key"
    },
    "zai-default": {
      "model": "glm-5.2",
      "base_url": "https://api.z.ai/api/coding/paas/v4",
      "api_key": "sk-your-key"
    },
    "gpt-4o": {
      "base_url": "https://api.openai.com/v1",
      "api_key": "sk-your-key"
    }
  },
  "agentRouting": {
    "Explore": "deepseek-v4-flash",
    "Plan": "gpt-4o",
    "general-purpose": "gpt-4o",
    "frontend-dev": "zai-default",
    "default": "gpt-4o"
  }
}

When no routing match is found, the global provider remains the fallback.

agentRouting values and explicit Agent tool model overrides match keys in agentModels. By default, that key is also the model string sent to the provider. Set agentModels.<key>.model when you want a local route key such as zai-default to call a different provider model name such as glm-5.2.

Note: /provider changes the global/parent provider for your current session. agentModels and agentRouting are specifically for configuring per-agent provider overrides while keeping the parent session unchanged.

Note: api_key values in settings.json are stored in plaintext. Keep this file private and do not commit it to version control.

Model-only routes (same provider): Omit base_url and api_key to run an agent on a different model using your current provider's endpoint and key — no credential duplication:

{
  "agentModels": {
    "mini": { "model": "gpt-5-mini" }
  },
  "agentRouting": {
    "verification": "mini"
  }
}

Built-in agents are routable by their type name. Useful keys: verification (the read-only auditor that runs before completion), Explore, and Plan. For example, "agentRouting": { "verification": "mini" } runs the verifier on gpt-5-mini while your main session stays on its model. Absent any entry, the verifier inherits the main-loop model.

Web Search and Fetch

By default, WebSearch works on non-Anthropic models using DuckDuckGo. This gives GPT-4o, DeepSeek, Gemini, Ollama, and other OpenAI-compatible providers a free web search path out of the box.

Note: DuckDuckGo fallback works by scraping search results and may be rate-limited, blocked, or subject to DuckDuckGo's Terms of Service. If you want a more reliable supported option, configure Firecrawl.

For Anthropic-native backends and Codex responses, OpenClaude keeps the native provider web search behavior.

WebFetch works, but its basic HTTP plus HTML-to-markdown path can still fail on JavaScript-rendered sites or sites that block plain HTTP requests.

Set a Firecrawl API key if you want Firecrawl-powered search/fetch behavior:

export FIRECRAWL_API_KEY=your-key-here

With Firecrawl enabled:

  • WebSearch can use Firecrawl's search API while DuckDuckGo remains the default free path for non-Claude models
  • WebFetch uses Firecrawl's scrape endpoint instead of raw HTTP, handling JS-rendered pages correctly

Free tier at firecrawl.dev includes 500 credits. The key is optional.


Headless gRPC Server

OpenClaude can be run as a headless gRPC service, allowing you to integrate its agentic capabilities (tools, bash, file editing) into other applications, CI/CD pipelines, or custom user interfaces. The server uses bidirectional streaming to send real-time text chunks, tool calls, and request permissions for sensitive commands.

1. Start the gRPC Server

Start the core engine as a gRPC service on localhost:50051:

npm run dev:grpc

Configuration

Variable Default Description
GRPC_PORT 50051 Port the gRPC server listens on
GRPC_HOST localhost Bind address. Use 0.0.0.0 to expose on all interfaces (not recommended without authentication)

2. Run the Test CLI Client

We provide a lightweight CLI client that communicates exclusively over gRPC. It acts just like the main interactive CLI, rendering colors, streaming tokens, and prompting you for tool permissions (y/n) via the gRPC action_required event.

In a separate terminal, run:

npm run dev:grpc:cli

Note: The gRPC definitions are located in src/proto/openclaude.proto. You can use this file to generate clients in Python, Go, Rust, or any other language.


Source Build And Local Development

Use Node.js >=22.0.0 and Bun 1.3.13 or newer for source builds.

bun install
bun run build
node dist/cli.mjs

Helpful commands:

  • bun run dev
  • bun test
  • bun run test:coverage
  • bun run security:pr-scan -- --base origin/main
  • bun run smoke
  • bun run doctor:runtime
  • bun run verify:privacy
  • focused bun test ... runs for the areas you touch

Testing And Coverage

OpenClaude uses Bun's built-in test runner for unit tests.

Run the full unit suite:

bun test

Generate unit test coverage:

bun run test:coverage

Open the visual coverage report:

open coverage/index.html

If you already have coverage/lcov.info and only want to rebuild the UI:

bun run test:coverage:ui

Use focused test runs when you only touch one area:

  • bun run test:provider
  • bun run test:provider-recommendation
  • bun test path/to/file.test.ts

Recommended contributor validation before opening a PR:

  • bun run build
  • bun run smoke
  • bun run test:coverage for broader unit coverage when your change affects shared runtime or provider logic
  • focused bun test ... runs for the files and flows you changed

Coverage output is written to coverage/lcov.info, and OpenClaude also generates a git-activity-style heatmap at coverage/index.html.

Repository Structure

  • src/ - core CLI/runtime
  • scripts/ - build, verification, and maintenance scripts
  • docs/ - setup, contributor, and project documentation
  • vscode-extension/openclaude-vscode/ - VS Code extension
  • .github/ - repo automation, templates, and CI configuration
  • bin/ - CLI launcher entrypoints

VS Code Extension

The repo includes a VS Code extension in vscode-extension/openclaude-vscode for OpenClaude launch integration, provider-aware Control Center, in-editor chat, theme support, and optional Microsoft Foundry / Azure OpenAI configuration (endpoint, API version, deployment, API key via Secret Storage) injected into launched terminals. See that folders README.

Security

If you believe you found a security issue, see SECURITY.md.

Community

Contributing

Contributions are welcome.

For larger changes, open an issue first so the scope is clear before implementation. Helpful validation commands include:

  • bun run build
  • bun run test:coverage
  • bun run smoke
  • focused bun test ... runs for files and flows you changed

Disclaimer

OpenClaude is an independent community project and is not affiliated with, endorsed by, or sponsored by Anthropic.

OpenClaude originated from the Claude Code codebase and has since been substantially modified to support multiple providers and open use. "Claude" and "Claude Code" are trademarks of Anthropic PBC. See LICENSE for details.

License

MIT for OpenClaude contributors' modifications; the derived Claude Code remains Anthropic's. See more.

S
Description
runs anywhere. uses anything
Readme
83 MiB
Languages
TypeScript 99%
JavaScript 0.7%
Astro 0.2%