File: README.md
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.
OpenClaude is also mirrored to GitLawb:
gitlawb.com/node/repos/z6MkqDnb/openclaude
Quick Start | Setup Guides | Providers | Development | VS Code Extension | Partners | Community
Partners
| GitLawb | Bankr.bot | Atomic Chat | Xiaomi MiMo | Atlas Cloud |
| AI/ML API | Novita AI | ApiSmart | Concentrate | Exa |
Why OpenClaude
- One CLI across cloud APIs and local model backends โ no per-provider tooling
- Guided provider setup and saved profiles with
/provider - Coding-agent workflows in one place: bash, file tools, grep, glob, agents, tasks, MCP, and web tools
- A bundled VS Code extension for launch integration and theme support
- A pixel-art hero companion who fires an arrow every time you press Enter (really โ see Meet your buddy)
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 @gitlawb/openclaude@latestIf you're on Arch Linux, you can install OpenClaude from the community-maintained AUR package:
paru -S openclaudeIf 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 @gitlawb/openclaude@latestStart
openclaudeInside OpenClaude:
- run
/providerfor guided provider setup and saved profiles - run
/onboard-githubfor GitHub Models onboarding
Note: OpenClaude does not automatically load project
.envfiles. We recommend using the/providercommand for setup, which saves provider profiles and credentials in.openclaude-profile.json. If you prefer environment variables, export them explicitly or runopenclaude --provider-env-file .envfor 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 --continueAdd --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-sessionForking 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-refactorBackground 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. A naturally finished session is recorded as exited when its process
returns zero and failed when it returns nonzero or handles a termination
signal. stale remains the conservative result when the process disappears
without an observed outcome; an explicit successful openclaude kill is
recorded as killed, and killed takes precedence over a natural exited orfailed outcome for the same process. Terminal outcomes are stored separately
under bg-sessions/terminal/; deleting that directory makes finished sessions
fall back to liveness-derived status. OpenClaude does not infer POSIX signal
names on Windows.
Unobservable force termination, host crashes, and power loss remain stale on
every platform.
openclaude attach currently reports the matching session and
points to openclaude logs -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, orCLAUDE_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
openclaudeWindows PowerShell:
$env:CLAUDE_CODE_USE_OPENAI="1"
$env:OPENAI_API_KEY="sk-your-key-here"
$env:OPENAI_MODEL="gpt-4o"
openclaudeFastest 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
openclaudeWindows PowerShell:
$env:CLAUDE_CODE_USE_OPENAI="1"
$env:OPENAI_BASE_URL="http://localhost:11434/v1"
$env:OPENAI_MODEL="qwen2.5-coder:7b"
openclaudeFor 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:
- Advanced Setup
- Smart Auto-Routing
- Agent Routing and Step Limits
- Headless gRPC Server
- Repo Map (codebase intelligence)
- Android Install
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 |
| Concentrate | /provider or CONCENTRATE_API_KEY |
Unified OpenAI-compatible gateway at https://api.concentrate.ai/v1; defaults to deepseek-v4-flash and auto-discovers the chat model catalog |
| ApiSmart | /provider or APISMART_API_KEY |
Uses https://gw.apismart.ai/v1, defaults to DEEPSEEK_V4_FLASH, and supports optional APISMART_MODEL plus authenticated model discovery |
| 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 |
| LongCat | /provider or env vars |
Meituan LongCat OpenAI-compatible API at https://api.longcat.chat/openai/v1; uses LONGCAT_API_KEY and defaults to LongCat-2.0 |
| 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 |
| Cloudflare Workers AI | /provider or env vars |
OpenAI-compatible API at https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/ai/v1; uses CLOUDFLARE_API_TOKEN. Replace <ACCOUNT_ID> with your Cloudflare account id. |
| 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_MAPflag is enabled or theREPO_MAPenvironment variable is set. Inspect with/repomap(2048-token default). See docs/repo-map.md for details. - A companion with signature moves: A truecolor pixel-art hero who lives beside your prompt and reacts when you work. See below.
Meet Your Buddy
Run /buddy to hatch a companion โ a truecolor pixel-art hero who stands
beside your prompt, idles, blinks, and fires their signature move every time
you submit a message:
/buddy hatch (first run) or pet your companion
/buddy set robinhood the green archer โ arrow shot on every Enter
/buddy set kaio gold-haired warrior โ charges a full-width energy wave
/buddy set strawhat stretchy punch that snaps back
/buddy set merlin twinkling sparkle stream
/buddy set kage spinning shuriken
/buddy set ember dragon fire with a real heat gradient
/buddy set corsair cannonball with smoke trail
/buddy name Robin rename your companion
/buddy set random back to your rolled heroCompanions respect prefersReducedMotion, degrade gracefully to line art in
low-color terminals, and can be silenced with /buddy mute. Requires a
terminal at least 100 columns wide for the full sprite.
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-*andgoogle/gemini-3.1-flash-lite-previewwith/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/v4withglm-5.2by default. GLM-5.3 is selectable asglm-5.3; useglm-5.3?reasoning=low,glm-5.3?reasoning=high, orglm-5.3?reasoning=xhighto request its documented low, high, or maximum effort. The existing GLM-5.2 query controls remain supported. - Xiaomi MiMo uses
api-keyheader auth on the direct OpenAI-compatible route and currently does not support/usagereporting in OpenClaude - GitHub Copilot serializes sub-agent execution by default to reduce Premium Request consumption โ see Agent Routing and Step Limits for tuning
For best results, use models with strong tool/function calling support.
Agents
Route different agents to different models (cost optimization, splitting work
by model strength), cap sub-agent tool steps with maxSteps, and tune GitHub
Copilot sub-agent behavior. Configured via settings, agent frontmatter, and
environment variables:
- per-agent provider/model overrides via
agentModels+agentRoutingin~/.openclaude/settings.json - model-only routes that reuse your current provider's credentials
- built-in agents (
ExploreandPlan[feature-gated],verification[feature-gated: requiresVERIFICATION_AGENT+tengu_hive_evidence],code-reviewer[requires diff inline]) routable by type name
See Agent Routing and Step Limits for the full guide.
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-hereWith Firecrawl enabled:
WebSearchcan use Firecrawl's search API while DuckDuckGo remains the default free path for non-Claude modelsWebFetchuses 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 run as a headless gRPC service with bidirectional streaming โ
integrate its agentic capabilities into other applications, CI/CD pipelines,
or custom UIs. Start it with npm run dev:grpc; a test CLI client ships with
the repo. See Headless gRPC Server for configuration
and client generation from src/proto/openclaude.proto.
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.mjsDay-to-day commands:
bun run devโ build and launch from sourcebun testโ full unit suite (Bun's built-in runner)bun test path/to/file.test.tsโ focused runs for the areas you touchbun run test:coverageโ coverage tocoverage/lcov.infoplus a visual report atcoverage/index.html(bun run test:coverage:uirebuilds just the UI)bun run smokeโ smoke checksbun run doctor:runtime,bun run verify:privacy,bun run security:pr-scan -- --base origin/main
Focused suites: bun run test:provider, bun run test:provider-recommendation.
To benchmark the launcher module compile cache, build the CLI and run:
bun run build
bun run benchmark:startupThe benchmark requires Node >=22.8.0, where the compile-cache API was added;
the built OpenClaude launcher continues to support the declared Node >=22.0.0
runtime range.
The benchmark defaults to 30 separate-process warm runs and 10 isolated
empty-cache runs. It reports the median, IQR, MAD, first cache-populating run,
first warm-up, Node/OS/CPU details, bundle size, and commit. Direct bundle
timings are included only as a secondary diagnostic; the full launcher result
is the decision signal. Usebun run benchmark:startup -- --warm-runs 40 --cold-runs 10 to request a
larger sample set. The benchmark records results without enforcing a timing
threshold in CI.
OpenClaude leaves Node's standard compile-cache controls authoritative. SetNODE_DISABLE_COMPILE_CACHE=1 to disable the optimization, including for V8
coverage runs that require uncached compilation.
Recommended validation before opening a PR:
bun run buildbun run smokebun run test:coveragewhen your change affects shared runtime or provider logic- focused
bun test ...runs for the files and flows you changed
Repository Structure
src/- core CLI/runtimescripts/- build, verification, and maintenance scriptsdocs/- setup, contributor, and project documentationvscode-extension/openclaude-vscode/- VS Code extension.github/- repo automation, templates, and CI configurationbin/- 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 folder's README.
Security
If you believe you found a security issue, see SECURITY.md.
Community
- Use GitHub Discussions for Q&A, ideas, and community conversation
- Use GitHub Issues for confirmed bugs and actionable feature work
- Join the Discord to chat with the community in real time
- Follow @gitlawb on X for updates and announcements
Contributing
Contributions are welcome. For larger changes, open an issue first so the
scope is clear before implementation. See Development for the
build, test, and pre-PR validation commands.
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.
File: docs/architecture/integrations.md
Integrations Architecture
Purpose
OpenClaude's provider system is now descriptor-first:
- descriptors under
src/integrations/define vendors, gateways, brands,
shared model metadata, validation hints, discovery strategy, and supported
transport capabilities; - registry helpers load those descriptors and expose route/model lookups;
- runtime metadata bridges descriptor state into request execution without
reintroducing broad hand-maintained provider switches.
This note captures the post-Phase-3 architecture plus the remaining
constraints and known exceptions that are still expected to exist.
Companion docs:
docs/integrations/overview.md
Contributor-facing map of the integration docs set and the authoring rules.docs/integrations/glossary.md
Standard terminology for vendors, gateways, routes, models, brands, and
anthropic proxies.
Source of truth
The primary source of truth now lives in these layers:
src/integrations/descriptors.ts
Defines the descriptor shapes.src/integrations/index.tsandregistry.ts
Load and expose the registered vendors, gateways, brands, and models.src/integrations/routeMetadata.ts
Resolves route labels/defaults and maps active env state onto route ids.src/integrations/runtimeMetadata.ts
Derives request-time OpenAI-shim behavior from the active route plus the
selected model/catalog entry.- Discovery, validation, and provider-profile helpers
Consume descriptor metadata instead of owning their own provider lists.
In other words: descriptor metadata should decide which route exists and what
it supports; runtime code should execute that metadata, not replace it with a
parallel provider matrix.
Metadata, routing, and transport
These concerns are related, but they are not interchangeable:
- metadata
Descriptor files declare labels, defaults, catalogs, validation hints,
discovery policy, and capability flags. - routing
Route-resolution helpers decide which descriptor is active and which runtime
path should receive the request. - transport
Runtime code such as native Anthropic handlers, Gemini handling, andopenaiShim.tsperforms the actual request shaping and execution.
The rule of thumb is:
- descriptors own what a route is and what it says it supports;
- routing helpers own how current config/env state maps onto that route;
- transport code owns how requests are executed for the active route.
If a future change needs a new label, default model, setup hint, discovery
policy, or request-shaping flag, it probably belongs in descriptor/runtime
metadata. If it changes the actual HTTP/API contract, it probably belongs in
transport code.
Gateway routing contract
For gateway descriptors, transportConfig.kind is the routing contract.
- use
transportConfig.kindto decide whether a route is local,
OpenAI-compatible, Anthropic-proxy, Bedrock, Vertex, or another supported
transport family; - do not use gateway
categoryto choose runtime routing behavior.
Gateway category is optional display/grouping metadata only:
localhelps group routes like Ollama or LM Studio in UI/docs;hostedhelps describe remote first-party or managed endpoints;aggregatinghelps describe routes that expose mixed third-party catalogs.
That category is useful for contributor understanding, but runtime selection
must continue to key off transportConfig.kind.
Descriptor authoring pattern
Normal descriptor files should follow the define* + default-export pattern:
import { defineGateway, defineCatalog } from '../define.js'
const catalog = defineCatalog({
source: 'static',
models: [
{
id: 'acme-fast',
apiName: 'acme/fast',
modelDescriptorId: 'acme-fast',
},
],
})
export default defineGateway({
id: 'acme',
label: 'Acme AI',
category: 'hosted',
defaultBaseUrl: 'https://api.acme.example/v1',
defaultModel: 'acme/fast',
setup: {
requiresAuth: true,
authMode: 'api-key',
credentialEnvVars: ['ACME_API_KEY'],
},
transportConfig: {
kind: 'openai-compatible',
openaiShim: {
supportsApiFormatSelection: false,
supportsAuthHeaders: true,
},
},
catalog,
})Contributors should not call registerGateway, registerVendor,registerModel, or other registry functions directly from normal descriptor
files. Registration is loader-owned:
- the descriptor file defines typed data;
bun run integrations:generatederivessrc/integrations/generated/integrationArtifacts.generated.ts;src/integrations/index.tsloads and registers that generated descriptor
inventory;- registry helpers expose the loaded data to the rest of the app.
That keeps onboarding additive and prevents descriptor files from turning back
into distributed registration logic.
Compatibility layer
The repo still has a few intentionally named compatibility bridges because the
public env/config contract is not descriptor-native yet:
src/integrations/compatibility.ts
is a thin derived view over the generated preset manifest and maps legacy
provider preset names onto descriptor-backed route ids;src/integrations/profileResolver.ts
keeps stored/sanitized provider ids compatible with descriptor routes;src/utils/model/providers.ts
preservesAPIProvider/LegacyAPIProviderfor older callers;src/utils/providerFlag.ts
still writes the env-facing provider contract even though it now reads shared
descriptor metadata.
When contributor docs say "compatibility layer," they mean these env/preset/
legacy-name bridges rather than the descriptor registry itself.
Preset ordering for /provider flows is also derived. The generated manifest
pins anthropic first, sorts the remaining preset-participating routes by
preset description using standard alphanumeric sorting, and always keepscustom at the bottom automatically.
Current constraints
The architecture is descriptor-first, but not descriptor-only yet. A few
compatibility surfaces still exist because public/runtime contracts are still
env-centric.
Temporary compatibility bridges
These are expected to shrink in later work, but they are still correct today:
src/integrations/routeMetadata.tsresolveActiveRouteIdFromEnv()still honorsCLAUDE_CODE_USE_*flags and
OpenAI-compatible env fallback because bootstrap and saved-profile flows are
still env-driven.src/utils/providerFlag.ts--providerstill writes the legacy env contract directly, even though it
now reads descriptor defaults where possible.src/utils/model/providers.tsLegacyAPIProvider/APIProviderremain as the compatibility surface for
older callers, including env-only MiniMax and NVIDIA NIM recovery.src/commands/provider/provider.tsx
Current/saved-provider summaries still read provider-specific env/profile
fields directly.src/components/StartupScreen.ts
Startup banner labels are still derived from active env state and heuristics.
These bridges are not evidence that the descriptor migration failed; they are
evidence that the public env/config contract has not been redesigned yet.
Intentional long-term runtime exceptions
Some provider-specific behavior is real protocol or capability divergence and
should remain explicit unless the external API changes.
- GitHub is a dual-mode route.
Claude models can use Anthropic-native message format, while Copilot/Models
traffic still uses OpenAI/Codex-style transport behavior. - Mistral is not just "generic OpenAI-compatible".
It still requires dedicated env selection and request shaping. - Azure OpenAI and Bankr have distinct auth/header contracts.
Azure usesapi-keyand deployment URLs; Bankr usesX-API-Key.
Descriptor-backed gateways can usedefaultAuthHeaderfor route-owned
default auth headers. - Gemini still has provider-specific credential handling and thought-signature
behavior at the shim boundary. - DeepSeek and Moonshot/Kimi still need route-specific
reasoning_content,max_tokens, andstoreshaping. - Bedrock, Vertex, and Foundry stay on dedicated Anthropic-family SDK/auth
flows rather than the generic OpenAI-compatible transport. - Native web search is only valid on native Anthropic-family paths
(firstParty,vertex,foundry) and the separate Codex path. - MiniMax keeps dedicated
/usageexecution logic because its usage endpoints
are not the same as the generic vendor path. - Conversation recovery must preserve Anthropic-native thinking blocks for
native transports while stripping them for OpenAI-compatible routes.
Known exceptions
As of Phase 3 completion, the main known exception categories are:
githubmistralbedrockvertexfoundry- env-only MiniMax fallback
- env-only NVIDIA NIM fallback
- Bankr auth/header aliasing
- Azure deployment/auth request shaping
- MiniMax dedicated
/usage - native web-search gating
- Anthropic-native thinking preservation during conversation recovery
These are not all the same kind of exception:
- some are long-term protocol differences;
- some are temporary env/config bridges;
- some are hybrid compatibility shims at the transport boundary.
Any future cleanup should preserve that distinction.
What Phase 3 finished
Phase 3 removed stale metadata/naming/env duplication, but it intentionally did
not force every provider down one synthetic execution path.
Completed in Phase 3:
- removed metadata-only dead switches;
- renamed compatibility surfaces so they read as compatibility bridges rather
than descriptor-native routing; - consolidated env shaping between startup/profile helpers and config-backed
profile activation; - moved eligible OpenAI-shim base URL/model selection under route/runtime
metadata; - recorded the remaining exceptions instead of leaving them implicit.
Follow-on guidance
If a future change touches provider-specific runtime behavior:
- first decide whether the branch is a real external API contract difference or
only a compatibility bridge; - if it is a real contract difference, keep it explicit and document it;
- if it is a compatibility bridge, prefer moving the decision closer to
descriptor/runtime metadata rather than cloning more env-specific logic; - do not remove a documented exception just because it looks repetitive; remove
it only when equivalent behavior is proven by tests.
For the detailed post-Phase-3 inventory, seeplan/phase-3d-final-audit.md.
File: docs/integrations/how-to/add-anthropic-proxy.md
How To Add an Anthropic Proxy
What an anthropic proxy is
An anthropic proxy is a third-party route that accepts Anthropic-native
requests through a non-Anthropic endpoint and env contract.
It is a distinct descriptor type because the transport contract is different
from an OpenAI-compatible gateway:
- request/response shape stays Anthropic-native;
- auth and base URL env vars are Anthropic-proxy specific;
- routing should stay on the Anthropic-family transport path, not the generic
OpenAI-compatible shim path.
Even if the repo has not started shipping concrete anthropic proxy descriptors
yet, this is the contract future contributors should follow.
When to add an anthropic proxy
Add an anthropic proxy descriptor when:
- the upstream accepts Anthropic-native requests;
- the route is not simply another OpenAI-compatible endpoint;
- the route needs Anthropic-style auth/base URL handling through its own env
variable contract.
Do not use an anthropic proxy descriptor when the route is actually
OpenAI-compatible. In that case, use a gateway or direct-vendor descriptor with
the appropriate transportConfig.kind.
Step-by-step
- Create the descriptor file under
src/integrations/anthropicProxies/. - Use
defineAnthropicProxy(...). - Set the proxy identity fields.
Includeid,label,classification: 'anthropic-proxy',defaultBaseUrl, anddefaultModel. - Fill the setup metadata.
Addsetup.requiresAuth,setup.authMode, andsetup.credentialEnvVars. - Fill
envVarConfig.
This is the Anthropic-proxy-specific env contract. - Set
transportConfig.kind: 'anthropic-proxy'. - Add capabilities and optional catalog/usage/validation metadata as needed.
- Run
bun run integrations:generateso the generated loader picks up the
new descriptor.
Authoring rules
Anthropic proxy examples should:
- use
defineAnthropicProxy; - default-export the descriptor;
- keep registration out of the file;
- make the proxy env contract explicit through
envVarConfig; - keep the route on Anthropic-family transport behavior.
Do not treat an anthropic proxy as "just another gateway with a different
header." The transport contract is different.
Anthropic-specific env var contract
envVarConfig tells the rest of the system which env vars control the proxy's
auth and routing.
The descriptor contract is:
envVarConfig: {
authTokenEnvVar: string
baseUrlEnvVar: string
modelEnvVar?: string
}That means the proxy should explicitly declare:
- which env var contains the auth token;
- which env var contains the Anthropic-proxy base URL;
- optionally which env var overrides the model.
This is different from the OpenAI-compatible OPENAI_* contract.
Example: anthropic proxy using Anthropic-native auth and base URL config
import { defineAnthropicProxy } from '../define.js'
export default defineAnthropicProxy({
id: 'acme-anthropic-proxy',
label: 'Acme Anthropic Proxy',
classification: 'anthropic-proxy',
defaultBaseUrl: 'https://anthropic-proxy.acme.example',
defaultModel: 'claude-sonnet-4-5',
requiredEnvVars: ['ACME_ANTHROPIC_PROXY_TOKEN'],
setup: {
requiresAuth: true,
authMode: 'token',
credentialEnvVars: ['ACME_ANTHROPIC_PROXY_TOKEN'],
setupPrompt: 'Paste your Acme Anthropic proxy token.',
},
envVarConfig: {
authTokenEnvVar: 'ACME_ANTHROPIC_PROXY_TOKEN',
baseUrlEnvVar: 'ACME_ANTHROPIC_PROXY_BASE_URL',
modelEnvVar: 'ACME_ANTHROPIC_PROXY_MODEL',
},
capabilities: {
supportsStreaming: true,
supportsVision: true,
supportsFunctionCalling: true,
supportsJsonMode: true,
supportsReasoning: true,
},
transportConfig: {
kind: 'anthropic-proxy',
},
usage: {
supported: false,
},
})Why this is the right shape:
- the route keeps the Anthropic-native contract instead of pretending to be
OpenAI-compatible; - auth/base URL/model env wiring is explicit;
- the descriptor default-exports typed data and leaves registration to the
loader; - the transport family is encoded through
transportConfig.kind.
How anthropic proxies differ from OpenAI-compatible gateways
Anthropic proxies:
- use
defineAnthropicProxy; - use
classification: 'anthropic-proxy'; - use
transportConfig.kind: 'anthropic-proxy'; - keep Anthropic-native auth/base-URL env contracts in
envVarConfig; - should continue down Anthropic-family routing/transport behavior.
OpenAI-compatible gateways:
- use
defineGateway; - use
transportConfig.kind: 'openai-compatible'orlocal; - rely on OpenAI-compatible request/response shaping;
- do not use
envVarConfigfor Anthropic-native auth/base-URL wiring.
If the upstream expects OpenAI-compatible JSON bodies, it is not an anthropic
proxy even if it can reach Claude-family models.
Current repo note
The src/integrations/anthropicProxies/ directory is already part of the
generated loader flow, even though the repo does not currently ship any live
anthropic-proxy descriptors. That means contributors can add one through the
same descriptor-plus-regeneration workflow used for vendors and gateways.
What not to do
Avoid these patterns:
- documenting an Anthropic-native route as an OpenAI-compatible gateway;
- hiding the env contract instead of declaring it in
envVarConfig; - calling registry mutation helpers directly from the descriptor file;
- flattening the route into a generic transport kind when the external API
contract is actually Anthropic-native.
Verification checklist
Before calling an anthropic-proxy doc update complete:
- the example uses
defineAnthropicProxy; - the env contract is explicit through
envVarConfig; - the guide explains Anthropic-native auth/base URL expectations;
- the guide explains how the proxy differs from an OpenAI-compatible gateway;
- the transport family stays encoded as
transportConfig.kind: 'anthropic-proxy'.
File: docs/integrations/how-to/add-gateway.md
How To Add a Gateway
When to add a gateway
Add a gateway descriptor when the route hosts, proxies, or aggregates models
behind its own endpoint contract.
Typical gateway cases:
- a hosted OpenAI-compatible route with its own base URL and auth;
- a local route such as Ollama or LM Studio;
- an aggregating route that mixes third-party brands/models;
- a route that needs discovery metadata, discovery caching, or readiness
probing.
Step-by-step
- Choose the file layout.
Usesrc/integrations/gateways/.tsfor the descriptor. Addsrc/integrations/gateways/.models.tsonly when the catalog/discovery
details are large enough to deserve a companion file. - Pick the transport family.
transportConfig.kindis the routing contract. - Pick a
category.categoryis optional grouping/display metadata only. It must not drive
runtime routing. - Define setup and startup metadata.
Gateways often need readiness or auto-detection hints instartup. - Choose the catalog strategy.
Usestatic,dynamic, orhybrid. - Decide whether the gateway needs discovery cache TTL, refresh mode, and
manual refresh. - For OpenAI-compatible or local routes, add any required static headers,
decide whether users may edit API mode and header-related settings throughtransportConfig.openaiShim.supportsApiFormatSelectionandtransportConfig.openaiShim.supportsAuthHeaders, and usetransportConfig.openaiShim.ui.show*flags to choose which auth-header,
auth-value, or custom-header prompts appear for tighter built-in preset
flows. - If the gateway should appear in preset-driven
/providerflows, add apresetblock on the descriptor. - Run
bun run integrations:generateso the generated loader and preset
manifest stay in sync.
Authoring rules
Normal gateway examples should:
- use
defineGatewayanddefineCatalog; - default-export the gateway descriptor;
- default-export the catalog from any companion
*.models.tsfile; - avoid
registerGateway(...)in contributor-authored examples; - avoid removed legacy fields such as
targetVendorId,isOpenAICompatible, or routing-oriented gatewayclassification.
The routing decision belongs to transportConfig.kind, not to category.
Reasoning controls in mixed catalogs
Gateway catalogs often mix models with different reasoning APIs. Keepcapabilities.supportsReasoning as descriptive capability metadata unless
the exact gateway route and model ID have been probed.
Add /effort-controllable reasoning metadata per catalog entry, not at the
gateway level. If a gateway accepts one upstream model's reasoning_effort
but rejects another model's field, each entry must say so explicitly. Seedocs/integrations/reasoning-effort.md before adding or changing reasoning
controls.
Generated loader and preset manifest
Normal gateway onboarding is additive now:
- add or edit the descriptor file;
- add a
presetblock only if the route should be user-facing in preset
flows; - run
bun run integrations:generate; - let
src/integrations/generated/integrationArtifacts.generated.tsfeed the
loader, compatibility mapping, preset typing, and provider UI metadata.
Preset ordering is not configured manually. The generated manifest pinsanthropic first, sorts the remaining preset-participating routes by preset
description using standard alphanumeric sorting, and always pins custom to
the bottom automatically.
For gateway presets, set preset.vendorId so compatibility/profile helpers
know which vendor contract the gateway belongs to.
One-file example: hosted gateway with only first-party models
This is the simplest hosted OpenAI-compatible gateway pattern.
import { defineCatalog, defineGateway } from '../define.js'
const catalog = defineCatalog({
source: 'static',
models: [
{
id: 'acme-hosted-fast',
apiName: 'acme-hosted-fast',
label: 'Acme Hosted Fast',
modelDescriptorId: 'acme-hosted-fast',
},
{
id: 'acme-hosted-pro',
apiName: 'acme-hosted-pro',
label: 'Acme Hosted Pro',
modelDescriptorId: 'acme-hosted-pro',
capabilities: {
supportsReasoning: true,
},
notes: 'Practical input limit is lower than the full context window.',
},
],
})
export default defineGateway({
id: 'acme-hosted',
label: 'Acme Hosted',
category: 'hosted',
defaultBaseUrl: 'https://gateway.acme.example/v1',
defaultModel: 'acme-hosted-fast',
supportsModelRouting: true,
setup: {
requiresAuth: true,
authMode: 'api-key',
credentialEnvVars: ['ACME_HOSTED_API_KEY'],
},
transportConfig: {
kind: 'openai-compatible',
openaiShim: {
headers: {
'X-Acme-Client': 'openclaude',
},
supportsApiFormatSelection: false,
supportsAuthHeaders: true,
ui: {
showAuthHeader: false,
showAuthHeaderValue: false,
showCustomHeaders: true,
},
// Optional: use a non-Authorization default auth header.
defaultAuthHeader: { name: 'api-key', scheme: 'raw' },
// Optional: restrict Responses API mode to model ids with these prefixes.
responsesApiModelPrefixes: ['gpt-'],
maxTokensField: 'max_completion_tokens',
},
},
preset: {
id: 'acme-hosted',
vendorId: 'openai',
apiKeyEnvVars: ['ACME_HOSTED_API_KEY'],
},
catalog,
usage: {
supported: false,
},
})What this example covers:
- one-file descriptor authoring;
- hosted OpenAI-compatible routing;
- required static custom headers;
- API mode editing disabled for a fixed hosted gateway;
- route-owned auth with only regular custom-header prompts shown in the preset UI;
- route-owned default auth header and Responses API model-prefix rules;
- a static catalog;
- a gateway with only its own hosted models;
- different reasoning/context/input/output behavior across models;
- route defaults declared once through
defaultModel.
Transport family examples
Hosted OpenAI-compatible gateway
Use transportConfig.kind: 'openai-compatible' when the route speaks an
OpenAI-compatible request/response contract.
transportConfig: {
kind: 'openai-compatible',
openaiShim: {
supportsApiFormatSelection: false,
supportsAuthHeaders: false,
},
}Local gateway
Use transportConfig.kind: 'local' for routes such as Ollama or LM Studio.
transportConfig: {
kind: 'local',
openaiShim: {
supportsApiFormatSelection: false,
supportsAuthHeaders: true,
maxTokensField: 'max_tokens',
},
}Anthropic-proxy transport family
If you truly have a gateway-shaped route that accepts Anthropic-native traffic,
the routing contract still comes from transportConfig.kind.
transportConfig: {
kind: 'anthropic-proxy',
}In most cases, a real Anthropic-native third-party route should eventually be
documented through the dedicated anthropic-proxy guide. The key
point here is that the transport family belongs in transportConfig.kind, not
in a gateway-specific compatibility flag.
Local dynamic discovery example
This is the common local gateway shape.
import { defineGateway } from '../define.js'
export default defineGateway({
id: 'acme-local',
label: 'Acme Local',
category: 'local',
defaultBaseUrl: 'http://localhost:11434/v1',
defaultModel: 'acme-local:latest',
supportsModelRouting: true,
setup: {
requiresAuth: false,
authMode: 'none',
},
startup: {
autoDetectable: true,
probeReadiness: 'openai-compatible-models',
},
transportConfig: {
kind: 'local',
openaiShim: {
supportsApiFormatSelection: false,
supportsAuthHeaders: true,
maxTokensField: 'max_tokens',
},
},
catalog: {
source: 'dynamic',
discovery: {
kind: 'openai-compatible',
// Set requiresAuth: false when /models is public even if inference needs auth.
requiresAuth: false,
},
discoveryCacheTtl: '1d',
discoveryRefreshMode: 'startup',
allowManualRefresh: true,
},
usage: {
supported: false,
},
})What this example covers:
transportConfig.kind: 'local';catalog.source: 'dynamic';- a local readiness/discovery flow;
max_tokensfor a local/legacy-compatible token field;- a
startuprefresh mode example.
Two-file example: hybrid gateway with discovery cache
Use a companion *.models.ts file when the catalog or discovery rules are too
large to keep inline.
src/integrations/gateways/galaxy.models.ts
import { defineCatalog } from '../define.js'
export default defineCatalog({
source: 'hybrid',
discovery: {
kind: 'openai-compatible',
},
discoveryCacheTtl: '1h',
discoveryRefreshMode: 'background-if-stale',
allowManualRefresh: true,
models: [
{
id: 'galaxy-curated-default',
apiName: 'galaxy/gpt-5-mini',
label: 'GPT-5 Mini (via Galaxy)',
modelDescriptorId: 'gpt-5-mini',
},
{
id: 'galaxy-curated-reasoner',
apiName: 'galaxy/deepseek-r1',
label: 'DeepSeek R1 (via Galaxy)',
modelDescriptorId: 'deepseek-reasoner',
capabilities: {
supportsReasoning: true,
},
notes: 'Practical input limit is 192k tokens on this route.',
transportOverrides: {
openaiShim: {
preserveReasoningContent: true,
requireReasoningContentOnAssistantMessages: true,
reasoningContentFallback: '',
},
},
},
],
})src/integrations/gateways/galaxy.ts
import { defineGateway } from '../define.js'
import catalog from './galaxy.models.js'
export default defineGateway({
id: 'galaxy',
label: 'Galaxy Gateway',
category: 'aggregating',
defaultBaseUrl: 'https://api.galaxy.example/v1',
defaultModel: 'galaxy/gpt-5-mini',
supportsModelRouting: true,
setup: {
requiresAuth: true,
authMode: 'api-key',
credentialEnvVars: ['GALAXY_API_KEY'],
},
startup: {
probeReadiness: 'openai-compatible-models',
},
transportConfig: {
kind: 'openai-compatible',
openaiShim: {
supportsApiFormatSelection: false,
supportsAuthHeaders: true,
maxTokensField: 'max_completion_tokens',
},
},
catalog,
usage: {
supported: false,
},
})What this example covers:
- a two-file gateway pattern;
catalog.source: 'hybrid';- human-readable discovery cache TTL;
background-if-stalerefresh;- manual refresh enabled;
- stale cache fallback by design through the shared discovery cache service;
- a mixed catalog of hosted third-party models;
- different reasoning/context/input/output behavior across entries.
Because allowManualRefresh is enabled, this is the right pattern for routes
that should support /model refresh and in-picker refresh. The shared
discovery cache keeps curated entries visible while refreshes fail or become
stale.
`providerModelMap` in mixed gateway catalogs
If the gateway exposes a shared model under a route-specific API name, point
the gateway catalog entry at a shared model descriptor and use that model
descriptor's providerModelMap to record route-specific names.
Minimal pattern:
import { defineModel } from '../define.js'
export default [
defineModel({
id: 'deepseek-reasoner',
label: 'DeepSeek Reasoner',
vendorId: 'deepseek',
classification: ['chat', 'reasoning'],
defaultModel: 'deepseek-reasoner',
providerModelMap: {
galaxy: 'galaxy/deepseek-r1',
openrouter: 'deepseek/deepseek-r1',
},
capabilities: {
supportsReasoning: true,
},
}),
]The gateway still owns route availability. providerModelMap only helps shared
model metadata stay reusable across multiple routes.
Static vs dynamic vs hybrid
Use:
static
when discovery is unavailable or unnecessary;dynamic
when the route should rely entirely on runtime discovery;hybrid
when you need curated entries plus discovered models.
Typical choices:
static
stable hosted routes with a small fixed catalog;dynamic
local routes or provider catalogs that change frequently;hybrid
aggregators where curated defaults should stay visible even while discovery
fills in the rest.
Discovery cache TTL examples
Use human-readable TTLs in discoveryCacheTtl:
30m
fast-changing catalogs where freshness matters;1h
moderately active hosted routes;1d
stable hosted or local routes where churn is low.
Discovery refresh mode examples
Use discoveryRefreshMode to match the operational shape of the route:
manual
flaky or rate-limited providers where refresh should happen only on demand;on-open
routes where the picker should always try for a fresh list;background-if-stale
the normal hosted-gateway choice when cached models should appear immediately;startup
fast local routes where startup probing is cheap and useful.
`max_tokens` vs `max_completion_tokens`
OpenAI-compatible APIs do not all accept the same max-token field.
Use openaiShim.maxTokensField: 'max_tokens' when:
- the route is local or legacy-shaped;
- the provider rejects
max_completion_tokens; - the provider is Z.AI-style or otherwise strict about the older field;
- the route matches Moonshot/DeepSeek/local compatibility behavior.
Use openaiShim.maxTokensField: 'max_completion_tokens' when:
- the route expects the newer OpenAI/Azure-style contract;
- the provider rejects
max_tokens; - you want the route to stay aligned with newer hosted OpenAI-compatible APIs.
Strict-route example:
transportConfig: {
kind: 'openai-compatible',
openaiShim: {
supportsApiFormatSelection: false,
supportsAuthHeaders: false,
maxTokensField: 'max_tokens',
},
}Hosted modern-route example:
transportConfig: {
kind: 'openai-compatible',
openaiShim: {
supportsApiFormatSelection: false,
supportsAuthHeaders: false,
maxTokensField: 'max_completion_tokens',
},
}Custom headers
For OpenAI-compatible or local routes, required static headers belong intransportConfig.openaiShim.headers.
Optional user-editable API mode, auth header, auth-value, and custom-header
fields should be allowed only when the route really supports them:
transportConfig: {
kind: 'openai-compatible',
openaiShim: {
headers: {
'X-Acme-Client': 'openclaude',
},
supportsApiFormatSelection: false,
supportsAuthHeaders: true,
ui: {
showAuthHeader: false,
showAuthHeaderValue: false,
showCustomHeaders: true,
},
},
}Do not use custom headers as a substitute for transport-family selection.
Set these flags explicitly. supportsAuthHeaders enables header customization
in general, including auth header prompts and arbitrary custom headers.
When it is false, /provider add and /provider edit should only expose the
route's normal credential fields. When it is true, theopenaiShim.ui.showAuthHeader, showAuthHeaderValue, andshowCustomHeaders flags decide which header-related prompts are visible.
When supportsApiFormatSelection is false, /provider add and/provider edit should not expose the API mode picker.
Use:
supportsApiFormatSelection: true
for broad custom gateways where users may need to choose the API surface.supportsApiFormatSelection: false
for fixed hosted or local routes where the descriptor owns the API contract.supportsAuthHeaders: true
for gateways that support any user-configurable header behavior, including
auth header names, auth header values, or arbitrary custom headers.supportsAuthHeaders: false
for gateways that require a fixed auth contract and should only collect the
configured credential.ui.showAuthHeader: false
when the route has descriptor-owned auth and the preset flow should not ask
users for an auth header name. Pair this withdefaultAuthHeaderwhen the
descriptor should route the collected API key to a nonstandard auth header.ui.showAuthHeaderValue: false
when the preset flow should collect only the header name and reuse the API key
as the header value.ui.showCustomHeaders: false
when the route supports gateway header behavior but the built-in preset should
not expose arbitrary extra headers.
Presets and user-facing gateway onboarding
Most runtime/UI surfaces now consume generated descriptor-backed metadata, so a
normal gateway addition should not require broad switch editing.
Only add preset metadata when the gateway is supposed to appear as a preset
or explicit selectable route.
preset: {
id: 'acme-hosted',
vendorId: 'openai',
apiKeyEnvVars: ['ACME_HOSTED_API_KEY'],
}Then regenerate:
bun run integrations:generateThat keeps src/integrations/index.ts, src/integrations/compatibility.ts,src/integrations/providerUiMetadata.ts, and the generated preset-id type in
sync without hand-editing them.
What not to do
Avoid these patterns:
registerGateway(...)in the descriptor file;targetVendorId,isOpenAICompatible, or routing-oriented gatewayclassification;- using
categoryto make runtime routing decisions; - placing large discovery/cached-catalog logic inline when a companion
*.models.tsfile would be clearer; - treating every gateway as if it exposes every shared model.
Verification checklist
Before calling the gateway guide complete:
- the descriptor lives under
src/integrations/gateways/; - one-file and two-file patterns are both covered where useful;
- the gateway declares only the model subset it actually offers;
- the route default is declared once through
defaultModel; transportConfig.kindis the routing contract;categoryis treated as grouping/display metadata only;- any discovery route includes the right cache TTL, refresh mode, and manual
refresh behavior; - API mode, auth/header, and token-field behavior are explicit where required;
- user-facing preset participation is expressed through descriptor
preset
metadata and regenerated artifacts rather than handwritten follow-through.
File: docs/integrations/how-to/add-model.md
How To Add a Model
When to add a model descriptor
Add a shared model descriptor when the metadata is useful across more than one
route or when the model deserves a stable glossary/index entry of its own.
Good reasons to add a model descriptor:
- the model appears across multiple vendor or gateway catalogs;
- the model has stable capabilities, context, or output limits worth reusing;
- route-specific catalogs should be able to reference one shared model id;
- you want
providerModelMapto document route-specific API names for the same
conceptual model.
Do add or update a shared model descriptor when the model's context window,
output limit, or capabilities need to be available to runtime model lookup. Do
not use model descriptors as route availability lists. Route-owned catalogs are
still the source of truth for where a model is offered.
Step-by-step
- Pick the model file.
Use an existing family file undersrc/integrations/models/when the model
belongs to a current family such asgpt,claude, ordeepseek. - Decide whether the model also needs a brand descriptor.
Add or update a brand descriptor only when shared model-family identity is
useful across multiple model descriptors. - Add the
defineModel(...)entry.
Includeid,label,vendorId,classification,defaultModel, and
capabilities. - Add optional shared metadata.
IncludebrandId,contextWindow,maxOutputTokens, andcacheConfig
when the data is stable enough to be reused. SetruntimeMetadataScope: 'catalog'when verified limits and capabilities
should apply only on route catalogs that explicitly reference the
descriptor. - Add
providerModelMaponly when the same model needs route-specific API
names across multiple catalogs. - Update route-owned catalogs only if the model should be offered by those
routes.
Authoring rules
Model descriptor files should:
- use
defineModel; - default-export model descriptors, typically as an array for a family file;
- act as glossary/index metadata and optional route enrichment;
- avoid encoding gateway availability as if every route automatically exposes
the shared model.
Shared runtime metadata uses the legacy global model-name fallback by default.
Use runtimeMetadataScope: 'catalog' for a model whose verified limits and
capabilities belong to specific routes; that metadata then applies only when a
route catalog entry names the descriptor through modelDescriptorId.
Normal contributor-facing examples should not call registerModel(...)
directly.
Shared model descriptors vs route catalogs
The important boundary is:
- shared model descriptors answer what the model is;
- route-owned catalogs answer where the model is offered.
That is why gateway or direct-vendor onboarding should not normally require
editing multiple shared model files. In the common path:
- add or update the route descriptor/catalog first;
- add a shared model descriptor only if the metadata is reusable beyond that
one route; - let the route catalog continue to own the offered subset.
Reasoning and `/effort` metadata
Use classification: ['reasoning'] and capabilities.supportsReasoning to
describe that a model is known to reason or think. Those fields do not by
themselves enable /effort request mutation.
Only add reasoning metadata when the model's exact control surface has been
verified for the route that will call it, including accepted levels, rejected
levels, and any thinking-disable format. For the full checklist, seedocs/integrations/reasoning-effort.md.
When to add a brand descriptor
Add or update a brand descriptor when:
- multiple related models share a recognizable family identity;
- shared defaults or capability guidance are useful across that family;
- the docs/UI benefit from grouping those models under one brand.
Skip the brand descriptor when:
- the model is one-off or route-local;
- the shared family metadata would not actually be reused;
- the route catalog is enough on its own.
Example: model attached to a canonical vendor only
This is the simplest pattern: one model, one canonical vendor, no shared
route-name aliases needed.
import { defineModel } from '../define.js'
export default [
defineModel({
id: 'acme-chat',
label: 'Acme Chat',
vendorId: 'acme',
classification: ['chat', 'coding'],
defaultModel: 'acme-chat',
capabilities: {
supportsStreaming: true,
supportsFunctionCalling: true,
supportsJsonMode: true,
supportsReasoning: false,
},
contextWindow: 128_000,
maxOutputTokens: 8_192,
}),
]Use this when:
- the model belongs to one canonical vendor;
- the route does not need multiple route-specific API names recorded in the
shared model metadata; - the descriptor is mainly reusable metadata, not a route-availability table.
Example: shared model across multiple routes with `providerModelMap`
Use providerModelMap when the same conceptual model appears on multiple
routes under different API names.
import { defineModel } from '../define.js'
export default [
defineModel({
id: 'deepseek-reasoner',
label: 'DeepSeek Reasoner',
brandId: 'deepseek',
vendorId: 'deepseek',
classification: ['chat', 'reasoning', 'coding'],
defaultModel: 'deepseek-reasoner',
providerModelMap: {
deepseek: 'deepseek-reasoner',
openrouter: 'deepseek/deepseek-r1',
galaxy: 'galaxy/deepseek-r1',
},
capabilities: {
supportsStreaming: true,
supportsFunctionCalling: true,
supportsJsonMode: true,
supportsReasoning: true,
},
contextWindow: 128_000,
maxOutputTokens: 8_192,
}),
]What providerModelMap is for:
- recording route-specific API names for the same model;
- helping route catalogs reference one shared descriptor while still using the
route's real API name; - keeping shared metadata reusable across direct vendors and gateways.
What providerModelMap is not for:
- declaring route availability by itself;
- replacing the route-owned catalog;
- assuming every route in the map is automatically enabled.
Model lookup and fallback behavior
Model lookup should prefer:
- route-owned catalog metadata;
- shared model-descriptor enrichment when a catalog entry references
modelDescriptorId; - global shared model descriptors under
src/integrations/models/for legacy
and custom OpenAI-compatible model names; - documented user overrides from
src/utils/model/openaiContextWindows.tsโ
theCLAUDE_CODE_OPENAI_CONTEXT_WINDOWS/CLAUDE_CODE_OPENAI_MAX_OUTPUT_TOKENS
env vars and themodelLimitsmap insettings.json(see themodelLimitssection indocs/advanced-setup.mdfor key matching and
precedence).
openaiContextWindows.ts is compatibility glue for user-provided overrides
(env vars and the settings.json modelLimits map). It should not grow a
second built-in model table. Built-in model limits belong in model descriptor
files.
A descriptor with runtimeMetadataScope: 'catalog' is intentionally excluded
from global name-only lookups. Its limits and capabilities are available only
through an explicit route catalog entry, preventing one vendor's verified
contract from leaking onto an uncataloged gateway model with the same API name.
What not to do
Avoid these patterns:
- turning shared model files into the default place to list every route's
offered subset; - assuming a shared model descriptor means every gateway supports it;
- using
providerModelMapas a substitute for route catalogs; - adding a brand descriptor when no shared family metadata is actually useful;
- calling
registerModel(...)from contributor-authored examples. - adding built-in model limits to
src/utils/model/openaiContextWindows.ts
instead ofsrc/integrations/models/.
Verification checklist
Before calling a model doc update complete:
- the example uses
defineModel; - the example makes clear that shared model descriptors are glossary/index
metadata plus optional route enrichment; providerModelMapis shown only as a route-name mapping tool;- the doc explains when a brand descriptor is useful;
- the doc explains that global lookup reads
src/integrations/models/, whilesrc/utils/model/openaiContextWindows.tsonly preserves env overrides; - the doc explains why normal gateway/direct-vendor onboarding should not
require editing multiple shared model files.
File: docs/integrations/how-to/add-usage-support.md
How To Add `/usage` Support
What `/usage` resolves today
The descriptor-era /usage flow is centered on getUsageDescriptor() insrc/commands/usage/index.ts.
That resolver:
- finds the active vendor or gateway descriptor;
- reads the descriptor's
usagemetadata; - follows
delegateToVendorId/delegateToGatewayIdif present; - returns the final resolved usage target plus a
supportedflag.
Current implementation note:
- descriptor metadata already owns the support/delegation decision;
- the current resolver is still vendor/gateway-focused, with the
firstParty
compatibility id mapped to theanthropicvendor; src/components/Settings/Usage.tsxstill has concrete UI branches for the
currently supported runtime paths (Anthropic,MiniMax, and the separateCodexpath);- the descriptor schema already includes
fetchModuleandparseModule, but
those fields are still a contract for supported integrations rather than a
fully generic module-loader pipeline in the current implementation.
That means new docs should describe both the descriptor contract and the
current runtime reality.
The `usage` field
The descriptor schema exposes these usage fields:
usage?: {
supported: boolean
delegateToVendorId?: string
delegateToGatewayId?: string
fetchModule?: string
parseModule?: string
ui?: {
showResetCountdown?: boolean
compactProgressBar?: boolean
fallbackMessage?: string
}
silentlyIgnore?: boolean
}The same UsageMetadata shape can be attached to:
VendorDescriptorGatewayDescriptorAnthropicProxyDescriptor
What each field means:
supported
Whether the route has real/usagesupport.delegateToVendorId
Use the linked vendor's usage behavior instead of defining separate gateway
behavior.delegateToGatewayId
Use another gateway's usage behavior.fetchModule
The module that should fetch raw usage data when a module-backed runtime is
added or expanded.parseModule
The module that should normalize raw usage data into the UI/runtime shape.ui
Presentation hints for the usage UI.silentlyIgnore
Reserved for cases where unsupported usage should avoid noisy user-facing
errors.
Anthropic proxy note
Anthropic proxy descriptors can declare the same usage field as vendors and
gateways.
Authoring rule:
- use the same explicit
supported, delegation, fallback, anduirules you
would use on any other descriptor; - do not assume usage support is inherited automatically just because the proxy
speaks an Anthropic-compatible transport; - keep unsupported proxies explicit with
usage: { supported: false }until a
real usage path exists.
Current runtime note:
- the descriptor schema supports this metadata today;
- the active
/usageresolver insrc/commands/usage/index.tscurrently
resolves vendor and gateway targets, plus thefirstPartycompatibility id; - document anthropic-proxy usage metadata as part of the descriptor contract,
but do not describe it as a separately routed/usagesurface in this
branch unless that resolver is expanded.
When `/usage` belongs on the vendor descriptor
Put usage support on the vendor descriptor when:
- the vendor is the canonical owner of the usage API;
- direct vendor sessions should resolve to that usage behavior;
- gateways serving the same upstream should generally inherit the vendor's
usage behavior rather than redefining it.
This is the normal pattern for first-party or direct vendors.
Current real examples in the implementation:
anthropicminimax
When a gateway should delegate usage to a linked vendor
Use gateway delegation when:
- the gateway does not have its own separate usage API;
- the correct usage information comes from the underlying vendor;
- the route should resolve to the vendor's usage label/behavior after
delegation.
In descriptor terms:
usage: {
supported: true,
delegateToVendorId: 'anthropic',
}This lets the gateway stay explicit about support while avoiding duplicated
vendor usage logic.
When a gateway should define its own usage handling
Give a gateway its own usage handling when:
- the gateway exposes its own usage/quota API;
- the numbers are not equivalent to the underlying vendor's usage view;
- UI text or refresh behavior must follow the gateway's own limits.
In that case the gateway keeps its own usage block instead of delegating:
usage: {
supported: true,
fetchModule: './usage/fetchGatewayUsage.js',
parseModule: './usage/parseGatewayUsage.js',
ui: {
compactProgressBar: true,
showResetCountdown: true,
},
}Current implementation note:
- the branch already understands
supportedand delegation throughgetUsageDescriptor(); - if you add a truly new gateway-owned usage API, you will also need the
runtime/UI follow-through in the usage settings surface until a more generic
module-backed pipeline is introduced.
Required fetch/parse module structure
The descriptor contract already reserves fetchModule and parseModule for
module-backed usage integrations.
Recommended structure:
fetchModule
- performs the network call or SDK call;
- handles auth and endpoint specifics for that provider/gateway;
- returns raw usage payloads without UI-specific formatting.
parseModule
- receives the raw usage payload;
- normalizes it into the shape the usage UI expects;
- keeps provider-specific quirks out of the higher-level usage resolver;
- should be deterministic and easy to test with fixtures.
Recommended file layout:
src/
services/
api/
usage/
fetchAcmeUsage.ts
parseAcmeUsage.tsRecommended contract split:
- keep transport/auth/API calling in the fetch module;
- keep response-shape normalization in the parse module;
- keep descriptor wiring in the descriptor's
usagefield; - keep route selection in
getUsageDescriptor().
Worked example: vendor with native usage API
This is the simplest supported vendor pattern.
import { defineVendor } from '../define.js'
export default defineVendor({
id: 'acme',
label: 'Acme AI',
classification: 'openai-compatible',
defaultBaseUrl: 'https://api.acme.example/v1',
defaultModel: 'acme-chat',
setup: {
requiresAuth: true,
authMode: 'api-key',
credentialEnvVars: ['ACME_API_KEY'],
},
transportConfig: {
kind: 'openai-compatible',
openaiShim: {
supportsApiFormatSelection: false,
supportsAuthHeaders: false,
},
},
usage: {
supported: true,
fetchModule: './usage/fetchAcmeUsage.js',
parseModule: './usage/parseAcmeUsage.js',
ui: {
showResetCountdown: true,
},
},
})Use this pattern when the vendor really owns the usage endpoint and the route
should be the final resolved usage target.
Worked example: gateway delegating usage to a linked vendor
Use this when the gateway should appear supported, but the actual usage source
of truth is the linked vendor.
import { defineGateway } from '../define.js'
export default defineGateway({
id: 'acme-gateway',
label: 'Acme Gateway',
category: 'hosted',
defaultBaseUrl: 'https://gateway.acme.example/v1',
defaultModel: 'acme-chat',
supportsModelRouting: true,
setup: {
requiresAuth: true,
authMode: 'api-key',
credentialEnvVars: ['ACME_GATEWAY_API_KEY'],
},
transportConfig: {
kind: 'openai-compatible',
openaiShim: {
supportsApiFormatSelection: false,
supportsAuthHeaders: true,
},
},
usage: {
supported: true,
delegateToVendorId: 'acme',
},
})This keeps the gateway descriptor honest while preventing duplicated usage
fetch/parse logic.
Worked example: gateway with its own usage API
Use this when the gateway's quota or billing view is independent from the
linked model vendors.
import { defineGateway } from '../define.js'
export default defineGateway({
id: 'galaxy',
label: 'Galaxy Gateway',
category: 'aggregating',
defaultBaseUrl: 'https://api.galaxy.example/v1',
defaultModel: 'galaxy/gpt-5-mini',
supportsModelRouting: true,
setup: {
requiresAuth: true,
authMode: 'api-key',
credentialEnvVars: ['GALAXY_API_KEY'],
},
transportConfig: {
kind: 'openai-compatible',
openaiShim: {
supportsApiFormatSelection: false,
supportsAuthHeaders: true,
},
},
usage: {
supported: true,
fetchModule: './usage/fetchGalaxyUsage.js',
parseModule: './usage/parseGalaxyUsage.js',
ui: {
compactProgressBar: true,
showResetCountdown: true,
},
},
})This is the right shape for a gateway whose own account limits matter more than
the upstream vendor's usage accounting.
Worked example: unsupported-provider fallback
Be explicit when usage is unsupported.
import { defineVendor } from '../define.js'
export default defineVendor({
id: 'acme-unsupported',
label: 'Acme Unsupported',
classification: 'openai-compatible',
defaultBaseUrl: 'https://api.acme-unsupported.example/v1',
defaultModel: 'acme-basic',
setup: {
requiresAuth: true,
authMode: 'api-key',
credentialEnvVars: ['ACME_UNSUPPORTED_API_KEY'],
},
transportConfig: {
kind: 'openai-compatible',
openaiShim: {
supportsApiFormatSelection: false,
supportsAuthHeaders: false,
},
},
usage: {
supported: false,
ui: {
fallbackMessage: '/usage is not available for this provider.',
},
},
})The important part is that unsupported routes stay explicit and resolve to a
neutral fallback rather than silently disappearing.
Fallback behavior for unsupported providers
When usage is unsupported:
- keep
usage.supportedfalse; - let the resolver return an unsupported descriptor;
- let the UI render the unsupported-provider fallback;
- do not fake vendor support just to avoid the fallback state.
The current settings usage screen already resolves unsupported providers toUnsupportedUsage with the active provider label.
Current supported routes
As of the current implementation:
anthropicis supported;minimaxis supported;- most other vendor and gateway descriptors are explicitly unsupported;
codexstill uses its own direct UI path outside the descriptor-backed
vendor/gateway resolver.
That split is important when you update docs or runtime behavior.
What not to do
Avoid these patterns:
- adding gateway-specific usage logic when the vendor should be the source of
truth; - using delegation when the gateway actually has its own distinct usage API;
- hiding unsupported usage by omitting the
usagefield when the route should
explicitly report unsupported behavior; - calling registry mutation helpers directly in usage examples;
- treating
fetchModule/parseModuleas already fully generic runtime hooks
without also checking the current settings/runtime integration path.
Verification checklist
Before calling a /usage guide complete:
- the doc lives under
/docsas Markdown; - it documents the
usagefield for vendors, gateways, and anthropic proxies; - it distinguishes vendor-owned, delegated, and gateway-owned usage behavior;
- it explains the current unsupported fallback behavior;
- it includes vendor, gateway, and unsupported worked examples;
- examples use
define*helpers and default exports rather than direct
registry calls; - the guide accurately notes the current implementation boundary between
descriptor metadata and the still-concrete runtime/UI usage handlers.
File: docs/integrations/how-to/add-vendor.md
How To Add a Vendor
When to add a vendor
Add a vendor descriptor when the integration is the canonical API or
first-party model service for that provider.
Typical vendor cases:
- a direct OpenAI-compatible API with its own auth/base URL contract;
- a first-party model-serving endpoint that owns its own catalog;
- a vendor that should be selectable directly rather than only through a
gateway.
Use a gateway descriptor instead when the route primarily hosts, proxies, or
aggregates models behind a separate endpoint contract.
Step-by-step
- Pick the descriptor file path.
Usesrc/integrations/vendors/.ts. - Choose the transport family.
Common direct vendors usetransportConfig.kind: 'openai-compatible'.
Gemini-native and Anthropic-native routes keep their own transport kinds. - Define setup/auth metadata.
Fillsetup.requiresAuth,setup.authMode, andsetup.credentialEnvVars. - Set the route defaults.
AdddefaultBaseUrl,defaultModel, and any required env vars or
validation metadata. - For OpenAI-compatible vendors, set the
/providerUI capability flags intransportConfig.openaiShim.
UsesupportsApiFormatSelectionfor API mode editing andsupportsAuthHeadersfor auth/header editing. - Add a catalog if the vendor exposes models directly.
Put the vendor's offered model subset on the vendor descriptor itself. UsemodelDescriptorIdwhen an entry should inherit shared model metadata. - Add usage metadata if the vendor has real
/usagesupport.
If/usageis still unsupported, keep that explicit withusage: { supported: false }. - If the vendor should appear in preset-driven
/providerflows, add apresetblock on the descriptor. - Run
bun run integrations:generateso the generated loader and preset
manifest stay in sync.
Authoring rules
Normal vendor descriptor files should:
- use
defineVendoranddefineCatalog; - default-export the descriptor;
- keep registration out of the file;
- avoid direct
registerVendor(...)calls; - avoid extra
import typeboilerplate in contributor-facing patterns unless a
real type import is unavoidable.
Registration is loader-owned through the generated artifacts consumed bysrc/integrations/index.ts.
Generated loader and preset manifest
Normal vendor onboarding is additive now:
- add or edit the descriptor file;
- add a
presetblock only if the vendor should be user-facing in preset
flows; - run
bun run integrations:generate; - let
src/integrations/generated/integrationArtifacts.generated.tsfeed the
loader, compatibility mapping, preset typing, and provider UI metadata.
Preset ordering is derived automatically: anthropic is pinned first, middle
entries sort by preset description using standard alphanumeric sorting, andcustom is pinned last by the generated manifest. This ordering is not
configurable from descriptor files.
Example: standard API-key vendor with direct OpenAI-compatible routing
This is the common "direct hosted vendor" shape.
import { defineCatalog, defineVendor } from '../define.js'
const catalog = defineCatalog({
source: 'static',
models: [
{
id: 'acme-chat',
apiName: 'acme-chat',
label: 'Acme Chat',
modelDescriptorId: 'acme-chat',
},
],
})
export default defineVendor({
id: 'acme',
label: 'Acme AI',
classification: 'openai-compatible',
defaultBaseUrl: 'https://api.acme.example/v1',
defaultModel: 'acme-chat',
requiredEnvVars: ['ACME_API_KEY'],
setup: {
requiresAuth: true,
authMode: 'api-key',
credentialEnvVars: ['ACME_API_KEY'],
setupPrompt: 'Paste your Acme API key.',
},
transportConfig: {
kind: 'openai-compatible',
openaiShim: {
supportsApiFormatSelection: false,
supportsAuthHeaders: false,
},
},
preset: {
id: 'acme',
apiKeyEnvVars: ['ACME_API_KEY'],
},
catalog,
usage: {
supported: false,
},
})Why this is the right shape:
- the route is first-party and direct, so it is a vendor, not a gateway;
transportConfig.kindowns the transport choice;supportsApiFormatSelection: falsemeans/providershould not expose API
mode editing for this fixed direct-vendor route;supportsAuthHeaders: falsemeans/providershould only ask for the API
key, not custom auth-header fields;- the vendor owns its own catalog because it exposes models directly;
defaultModelon the vendor selects the default catalog entry;- the file default-exports one typed descriptor and leaves registration to the
loader.
Example: vendor with custom static headers
For OpenAI-compatible vendors, put fixed request headers intransportConfig.openaiShim.headers. Secrets still belong in credential env
vars or runtime auth handling.
import { defineVendor } from '../define.js'
export default defineVendor({
id: 'acme-labs',
label: 'Acme Labs',
classification: 'openai-compatible',
defaultBaseUrl: 'https://labs.acme.example/v1',
defaultModel: 'acme-research',
requiredEnvVars: ['ACME_LABS_API_KEY'],
setup: {
requiresAuth: true,
authMode: 'api-key',
credentialEnvVars: ['ACME_LABS_API_KEY'],
},
transportConfig: {
kind: 'openai-compatible',
openaiShim: {
headers: {
'X-Acme-Client': 'openclaude',
'X-Acme-Protocol': 'labs-v1',
},
supportsApiFormatSelection: false,
supportsAuthHeaders: false,
maxTokensField: 'max_completion_tokens',
},
},
usage: {
supported: false,
},
})Use this pattern when:
- the provider requires fixed non-secret headers on every request;
- the route still speaks an OpenAI-compatible body shape;
- the token-field contract needs to be explicit;
- users should not edit API mode or auth/header fields for this fixed vendor
route.
Example: vendor that owns a first-party model catalog
This is the OpenAI/DeepSeek-style pattern where the vendor serves multiple
first-party models directly.
import { defineCatalog, defineVendor } from '../define.js'
const catalog = defineCatalog({
source: 'static',
models: [
{
id: 'acme-fast',
apiName: 'acme-fast',
label: 'Acme Fast',
modelDescriptorId: 'acme-fast',
},
{
id: 'acme-reasoner',
apiName: 'acme-reasoner',
label: 'Acme Reasoner',
modelDescriptorId: 'acme-reasoner',
capabilities: {
supportsReasoning: true,
},
transportOverrides: {
openaiShim: {
preserveReasoningContent: true,
requireReasoningContentOnAssistantMessages: true,
reasoningContentFallback: '',
},
},
},
],
})
export default defineVendor({
id: 'acme-first-party',
label: 'Acme First-Party',
classification: 'openai-compatible',
defaultBaseUrl: 'https://api.acme-first-party.example/v1',
defaultModel: 'acme-fast',
setup: {
requiresAuth: true,
authMode: 'api-key',
credentialEnvVars: ['ACME_FIRST_PARTY_API_KEY'],
},
transportConfig: {
kind: 'openai-compatible',
openaiShim: {
supportsApiFormatSelection: false,
supportsAuthHeaders: false,
},
},
catalog,
usage: {
supported: false,
},
})Use this when the vendor really is the route that serves the models. Do not
move route availability into the shared model index by default. Put reusable
context windows, output limits, and cross-route capability metadata insrc/integrations/models/, then point catalog entries at those descriptors
with modelDescriptorId.
Reasoning controls
For direct vendors, record reasoning controls on the exact catalog model
entry or shared model descriptor only after the vendor API has been probed.capabilities.supportsReasoning means the model can reason; it does not
mean /effort should send reasoning_effort or any other control field.
If the vendor catalog contains both controllable and non-controllable
reasoning models, annotate each model separately. Seedocs/integrations/reasoning-effort.md for the metadata shape and audit
checklist.
OpenAI-compatible UI capability flags
For OpenAI-compatible vendors, be explicit about the provider editor surface:
supportsApiFormatSelection: false
for fixed vendor APIs where OpenClaude should choose the API surface.supportsApiFormatSelection: true
only when users should choose between compatible API modes such as chat
completions and responses.supportsAuthHeaders: false
when the route should only collect the configured credential env var/API key.supportsAuthHeaders: true
only when users should be able to edit custom auth/header fields in/provider addand/provider edit.
Most direct vendors should set both flags to false. Broad custom routes are
the usual place where both are true.
Presets and user-facing vendor onboarding
Most metadata-driven consumers now read generated descriptor-backed state, so a
normal vendor addition should not require broad switch editing.
Only add preset metadata when the vendor should appear as an explicit preset
or legacy-facing selectable route.
preset: {
id: 'acme',
apiKeyEnvVars: ['ACME_API_KEY'],
}Then regenerate:
bun run integrations:generateThat keeps src/integrations/index.ts, src/integrations/compatibility.ts,src/integrations/providerUiMetadata.ts, and the generated preset-id type in
sync without hand-editing them.
What not to do
Avoid these patterns in new vendor docs and examples:
registerVendor(...)inside the descriptor file;- direct registry mutation from contributor-authored descriptor files;
- inventing extra runtime routing fields when
transportConfig.kindalready
expresses the transport family; - pushing route-owned model availability into shared model files by default;
- treating the legacy word "provider" as precise when you really mean vendor,
gateway, route, or model.
Verification checklist
Before calling the vendor guide complete:
- the file lives under
src/integrations/vendors/; - the descriptor default-exports a
defineVendor(...)result; - any direct model-serving route owns the subset of models it actually exposes;
- the route default is declared once through
defaultModel; - the transport family is expressed through
transportConfig.kind; - OpenAI-compatible
/providerUI capabilities are explicit throughopenaiShim.supportsApiFormatSelectionandopenaiShim.supportsAuthHeaders; - auth/setup metadata and validation routing are explicit;
- user-facing preset participation is expressed through descriptor
preset
metadata and regenerated artifacts rather than handwritten follow-through.
File: docs/integrations/common-pitfalls.md
Common Pitfalls
Purpose
This is the short pre-PR checklist for descriptor-era integration work.
Use it after reading the architecture note, the relevant how-to guide, and the
reference samples.
Pitfall 1: Using the wrong descriptor type
Common mistake:
Modeling a route as a gateway or vendor just because it feels close enough.
Safer rule:
- use
VendorDescriptorwhen the route is the canonical direct vendor API; - use
GatewayDescriptorwhen the route hosts, proxies, or aggregates models
behind its own endpoint contract; - use
AnthropicProxyDescriptorwhen the route accepts Anthropic-native
traffic through its own Anthropic-style env contract; - use
ModelDescriptorfor shared model metadata, not route availability.
Pitfall 2: Treating `category` as the routing contract
Common mistake:
Using gateway category to decide runtime behavior.
Safer rule:transportConfig.kind is the routing contract. category is only optional
grouping/display metadata.
Pitfall 3: Reintroducing removed gateway fields
Common mistake:
Adding fields such as targetVendorId, isOpenAICompatible, or routing-style
gateway classification.
Safer rule:
Keep routing in transportConfig.kind. Use current descriptor fields fromsrc/integrations/descriptors.ts, not legacy examples from older branches or
notes.
Pitfall 4: Calling registry mutation helpers from descriptor files
Common mistake:
Using registerGateway(...), registerVendor(...), or registerModel(...)
inside contributor-authored descriptor files.
Safer rule:
Use the define* helpers from src/integrations/define.ts and default-export
the descriptor. Loader-owned registration stays in src/integrations/index.ts.
Pitfall 5: Putting route availability into shared model files
Common mistake:
Treating src/integrations/models/*.ts as the main place to say where a model
is available.
Safer rule:
Shared model descriptors answer what a model is. Route-owned catalogs answer
where it is offered.
Pitfall 6: Duplicating model defaults in catalog entries
Common mistake:
Marking catalog entries with per-model default or recommended flags after
the route already declares defaultModel.
Safer rule:
Declare the route's default once with defaultModel. UI recommendation labels
derive from that route default.
Pitfall 7: Forgetting `providerModelMap` boundaries
Common mistake:
Assuming providerModelMap enables a route automatically.
Safer rule:
Use providerModelMap only to record route-specific API names for the same
conceptual model. The route catalog still decides whether that route exposes
the model.
Pitfall 8: Omitting `openaiShim.maxTokensField` on strict routes
Common mistake:
Assuming every OpenAI-compatible route accepts the same max-token field.
Safer rule:
- use
max_completion_tokensfor newer hosted OpenAI-style contracts; - use
max_tokensfor local or legacy-shaped routes and other providers that
reject the newer field; - keep the choice explicit in
transportConfig.openaiShim.maxTokensFieldwhen
the route is strict.
Pitfall 9: Flattening real protocol differences
Common mistake:
Treating Bedrock, Vertex, Gemini, GitHub native Claude mode, or Mistral as if
they were all just generic OpenAI-compatible routes.
Safer rule:
If the external API contract is genuinely different, keep that difference
explicit. Descriptor-first does not mean protocol differences should be hidden.
Pitfall 10: Overstating `/usage` support
Common mistake:
Declaring usage support because the descriptor schema allows it, without
checking the active runtime/UI path.
Safer rule:
- use
usage.supported: trueonly when the route has real current support; - delegate from a gateway to a vendor only when the vendor is the true source
of usage data; - keep unsupported routes explicit with
usage: { supported: false }; - remember that the current resolver in
src/commands/usage/index.tsis still
vendor/gateway-focused, with the current settings UI still concrete for
Anthropic, MiniMax, and Codex.
Pitfall 11: Hiding discovery complexity in the descriptor file
Common mistake:
Packing a large hybrid catalog or complex discovery rules inline ingateways/.ts.
Safer rule:
Move large catalog or discovery-specific logic into a companiongateways/.models.ts file and keep the descriptor file small.
Pitfall 12: Rebuilding the old OpenAI context table
Common mistake:
Adding built-in context or output limits tosrc/utils/model/openaiContextWindows.ts.
Safer rule:
Put built-in model metadata in src/integrations/models/. KeepopenaiContextWindows.ts focused on documented user overrides โ theCLAUDE_CODE_OPENAI_CONTEXT_WINDOWS / CLAUDE_CODE_OPENAI_MAX_OUTPUT_TOKENS
env vars and the settings.json modelLimits map โ not a built-in table.
Pitfall 13: Forgetting the compatibility layer
Common mistake:
Changing descriptor metadata and assuming every public surface is already
descriptor-native.
Safer rule:
If the route should be user-facing in preset flows, add descriptor preset
metadata and regenerate the artifacts:
bun run integrations:generatesrc/integrations/generated/integrationArtifacts.generated.ts- env-facing flows that still preserve legacy names
Do not hand-edit:
src/integrations/compatibility.tssrc/integrations/profileResolver.tssrc/integrations/providerUiMetadata.ts- preset typing or preset ordering tables
Only touch remaining env-facing compatibility surfaces when the route truly
needs them.
Pitfall 14: Using stale repo paths in docs
Common mistake:
Pointing contributors at outdated files or command entrypoints.
Safer rule:
Use the current repo surfaces:
src/commands/usage/index.tsfor descriptor-backed/usageroutingsrc/components/Settings/Usage.tsxfor the current usage UI boundarysrc/integrations/routeMetadata.tsfor route/default/label helperssrc/integrations/runtimeMetadata.tsfor request-shaping metadatasrc/integrations/discoveryCache.tsandsrc/integrations/discoveryService.ts
for discovery caching and loading
Final check
Before opening or landing integration docs or descriptor changes:
- confirm the descriptor type is correct;
- confirm
transportConfig.kindis doing the routing work; - confirm examples use
define*helpers plus default exports; - confirm route catalogs own availability;
- confirm route defaults are declared once through
defaultModel; - confirm built-in model limits live in
src/integrations/models/; - confirm strict OpenAI-compatible routes specify the correct max-token field;
- confirm
/usagedocs match the actual current resolver/UI behavior; - confirm any illustrative sample is clearly marked as illustrative.
File: docs/integrations/glossary.md
Integrations Glossary
Brand
A shared model-family identity such as Claude, GPT, Kimi, DeepSeek, Llama, or
Qwen. Brands provide reusable family-level metadata and help related model
descriptors stay organized.
Catalog
The route-owned list of models that a vendor route or gateway route actually
offers. Catalog entries should name the route-facing API model and may point at
shared model metadata with modelDescriptorId. A catalog can be:
staticdynamichybrid
Catalog ownership lives with the route, not with the global model index. Route
defaults live on the descriptor's defaultModel, not as per-entry default or
recommended flags.
Category
Optional gateway display/grouping metadata:
localhostedaggregating
Category is descriptive only. It must not drive runtime routing.
Compatibility Layer
The set of env/preset/legacy-name bridges that keep older user config and older
callers working while the repo transitions to descriptor-backed metadata.
Examples include:
src/integrations/compatibility.tssrc/integrations/profileResolver.tssrc/utils/model/providers.tssrc/utils/providerFlag.ts
Descriptor
A typed metadata object defined under src/integrations/ through one of thedefine* helpers. Descriptors describe integrations; they are not runtime
executors.
Direct Vendor Route
A vendor that also acts as its own model-serving route, rather than only
existing behind a separate gateway.
Current examples include first-party or direct vendors such as OpenAI,
DeepSeek, Gemini, MiniMax, and Bankr.
Gateway
A route that hosts, proxies, or aggregates models behind its own endpoint and
transport contract.
Examples:
- Ollama
- LM Studio
- OpenRouter
- Together
- Groq
Loader-Owned Registration
The rule that descriptor files define typed data, whilesrc/integrations/index.ts is responsible for loading and registering that
data into the registry through generated descriptor artifacts. Normal
descriptor files should not call registry mutation helpers directly.
Metadata
The descriptive information about an integration, such as:
- label
- defaults
- setup/auth hints
- validation selection
- discovery policy
- catalog entries
- request-shaping flags
Metadata answers what a route is and what it supports.
Model Descriptor
A shared model metadata record under src/integrations/models/. Model
descriptors own reusable model identity, family metadata, capabilities, context
windows, output limits, cache behavior, and route-specific API aliases throughproviderModelMap.
Model descriptors do not declare route availability by themselves. Routes still
declare their offered subset in their catalogs.
Provider
Legacy umbrella term that historically mixed multiple concerns together.
Contributor docs should prefer more precise terms when possible:
- vendor
- gateway
- model
- brand
- anthropic proxy
- route
Route
The runtime-selectable integration surface that serves models.
In practice, a route may be:
- a gateway descriptor, or
- a direct vendor descriptor that exposes models itself.
Route-centric helpers resolve labels, defaults, transport kind, discovery, and
runtime metadata for the currently active integration.
Routing
The logic that maps presets, profile ids, env state, or base URLs onto the
active route and transport family.
Routing is not the same as metadata and not the same as transport execution.
Transport
The request-execution contract used to talk to an external API or local
runtime.
Examples:
- Anthropic-native
- Anthropic-proxy
- OpenAI-compatible
- local
- Gemini-native
- Bedrock
- Vertex
Transport code executes requests. It should consume descriptor/runtime metadata
rather than redefining the integration matrix itself.
`transportConfig.kind`
The routing contract field on a route descriptor. This is the authoritative
transport-family selector for gateways and other routes.
If runtime behavior differs because the underlying protocol differs,transportConfig.kind is the first field to inspect.
Vendor
The canonical API or first-party model service behind an integration.
Examples:
- Anthropic
- OpenAI
- Google/Gemini
- Moonshot
- DeepSeek
- MiniMax
- Bankr
Vendors own auth defaults, canonical base URLs, direct catalogs when
applicable, and vendor-specific metadata.
Anthropic Proxy
A distinct descriptor type for third-party endpoints that accept Anthropic-
native requests through a non-Anthropic endpoint and auth/base-URL contract.
An anthropic proxy is not the same thing as an OpenAI-compatible gateway.
File: docs/integrations/overview.md
Integrations Overview
Purpose
This folder is the contributor-facing documentation set for the descriptor-era
integration system.
Use it for:
- terminology and architecture rules;
- authoring rules for descriptor files;
- how-to guides for vendors, gateways, models, anthropic proxies, and
/usage; - reference samples that match the current implementation.
Documentation Structure
This is the current docs layout:
docs/
architecture/
integrations.md
integrations/
overview.md
glossary.md
reasoning-effort.md
how-to/
add-vendor.md
add-gateway.md
add-model.md
add-anthropic-proxy.md
add-usage-support.md
reference-samples.md
common-pitfalls.mdAll of the files listed above are part of the current contributor guide for the
descriptor-era integration system.
Reading Order
If you are onboarding to the integration system:
- Read
docs/architecture/integrations.mdfor the system boundaries. - Read
docs/integrations/glossary.mdfor the shared vocabulary. - Read
docs/integrations/reasoning-effort.mdbefore marking models as
reasoning-capable or/effort-controllable. - Use the how-to guides for the specific descriptor type you are adding.
- Use
docs/integrations/reference-samples.mdonce the architecture and the relevant how-to guide are clear. - Read
docs/integrations/common-pitfalls.mdbefore opening a docs or implementation PR for a new integration.
Core Rules
Metadata vs routing vs transport
Keep these concerns separate:
- metadata
Descriptor files declare labels, defaults, catalogs, setup requirements,
validation hints, and request-shaping metadata. - routing
Route/profile helpers map user config, presets, and env state onto the active
descriptor route. - transport
Runtime execution code actually performs the request using the active
transport family.
If a change is about what a route is, it likely belongs in descriptors. If it
is about how a request is executed against an external API contract, it likely
belongs in transport code.
`transportConfig.kind` is the routing contract
For gateways, transportConfig.kind is the field that tells runtime code which
transport family the route belongs to.
Examples:
'openai-compatible''local''anthropic-proxy''bedrock''vertex'
Do not use gateway category for routing decisions. category is optional
display/grouping metadata only.
`category` is descriptive, not executable
Gateway category exists to help people understand the route:
localhostedaggregating
It is valid to use category for docs, grouping, or display copy. It is not
valid to treat category as the transport selector.
OpenAI-compatible request shaping belongs in `openaiShim`
For OpenAI-compatible or local routes, keep request-shaping metadata intransportConfig.openaiShim.
Examples:
maxTokensFieldheaderssupportsApiFormatSelectionsupportsAuthHeaders
That matches the current runtime metadata flow insrc/integrations/runtimeMetadata.ts.
supportsApiFormatSelection and supportsAuthHeaders also control the
advanced /provider add and /provider edit fields for OpenAI-compatible
routes. Fixed direct vendors usually set both to false; broad custom routes
or gateways that intentionally accept user-supplied auth/header details set the
relevant flag to true.
Reasoning support is per model and per route
capabilities.supportsReasoning is descriptive. It says the model is known to
reason or think, but it does not by itself authorize /effort to add request
fields. Only add reasoning metadata when the exact route/model request shape,
accepted levels, and disable behavior have been verified. Seedocs/integrations/reasoning-effort.md.
Moonshot and Kimi K3 catalogs
The direct Moonshot API exposes Kimi K3 as kimi-k3, with a 1,048,576-token
context window, 32,768 maximum output tokens, and reasoning_effort levelslow, high, and max (defaulting to max). Kimi Code K3 uses the same
controls for two catalog selections: k3 provides the 1M Allegretto+ window, whilek3-256k keeps Moderato+ sessions within their 256K limit.
Kimi Code also lists kimi-for-coding-highspeed for eligible Allegretto+
subscriptions. Kimi documents
it as HighSpeed with approximately 6ร output speed and 3ร quota usage; plan
availability can vary. Retain the selected catalog ID in client-side routing so
its route-specific limits and capabilities are not lost when the outbound API
model is normalized.
Descriptor Authoring Pattern
Normal descriptor files should:
- use the
define*helpers fromsrc/integrations/define.ts; - default-export the descriptor object or model list;
- keep registration out of the descriptor file;
- keep route-owned catalogs with the route unless shared model metadata is
genuinely useful; - put built-in model limits and capabilities in
src/integrations/models/,
not in env-override compatibility helpers.
Typical helper usage:
defineVendordefineGatewaydefineCatalogdefineModeldefineBranddefineAnthropicProxy
Normal descriptor files should not:
- call
registerGateway,registerVendor,registerModel, or similar
registry functions directly; - import registry mutation helpers just to make a descriptor visible;
- turn simple route additions into scattered consumer edits.
Loader-Owned Registration
Registration is owned by src/integrations/index.ts.
That means the normal contributor workflow is:
- create or edit the descriptor file;
- keep the export typed through the appropriate
define*helper; - let the loader own registration;
- let registry consumers read the loaded descriptor state.
The loader may still be manually enumerated in some places today, but that is a
generated-artifact concern, not a descriptor-file concern.
Normal contributor flow for new preset-participating routes is:
- add or edit the descriptor file;
- add
presetmetadata only when the route should be user-facing; - add
preset.badgemetadata if the route should show a display tag (e.g.[FREE],[Sponsor]) in the preset picker โ this avoids
hard-coded badge logic insrc/components/ProviderManager.tsx; - run
bun run integrations:generate; - let the generated manifest feed the loader, compatibility mapping, preset
typing, and provider UI metadata.
Compatibility Layer
The descriptor system is the source of truth, but a compatibility layer still
exists for older env/config/public-callers.
Important compatibility surfaces include:
src/integrations/compatibility.ts
derived legacy preset name to descriptor-route mapping;src/integrations/profileResolver.ts
stored provider/profile id resolution;src/utils/model/providers.tsAPIProvider/LegacyAPIProvider;src/utils/providerFlag.ts
env-facing--providerbehavior.
Contributor docs should describe these as compatibility bridges, not as the
primary architecture.
Preset ordering pins gitlawb-opengateway first, derives the middle entries from preset
descriptions with standard alphanumeric sorting, and pins the custom presets
last: custom followed by custom-anthropic.