openclaude Documentation

Runs anywhere. uses anything

30,636 stars TypeScript TypeScript 1 file ยท ~26,540 tokens #ai#ai-agent#ai-tools#cli#coding
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy openclaude on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name openclaude --restart always gitlawb/openclaude:latest

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.

bash
npm install @gitlawb/openclaude@latest

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

bash
paru -S openclaude

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

Verify / troubleshoot installed version:

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

Start

bash
openclaude

Inside OpenClaude:

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

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

Resume or fork a conversation

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

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

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

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

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

Background sessions

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

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

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

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

Fastest OpenAI setup

macOS / Linux:

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

openclaude

Windows PowerShell:

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

openclaude

Fastest local Ollama setup

macOS / Linux:

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

openclaude

Windows PowerShell:

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

openclaude

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

Setup Guides

Beginner-friendly guides:

Advanced and source-build guides:

Supported Providers

Provider Setup Path Notes
OpenAI-compatible /provider or env vars Works with OpenAI, OpenRouter, DeepSeek, Groq, Mistral, LM Studio, and other compatible /v1 servers
Z.AI GLM Coding Plan /provider or OpenAI-compatible env vars Uses OPENAI_API_KEY at https://api.z.ai/api/coding/paas/v4 and defaults to glm-5.2
AI/ML API /provider or AIMLAPI_API_KEY (setup guide) Uses https://api.aimlapi.com/v1, auto-detects the OpenAI-compatible route from AIMLAPI_API_KEY, sends OpenClaude attribution headers, and discovers chat-capable models from the public /models catalog
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_MAP flag is enabled or the REPO_MAP environment variable is set. Inspect with /repomap (2048-token default). See docs/repo-map.md for details.
  • 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:

text
/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 hero

Companions 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-* and google/gemini-3.1-flash-lite-preview with /model, and do not pin the base URL to /v1/xiaomi-mimo.
  • Z.AI GLM Coding Plan uses https://api.z.ai/api/coding/paas/v4 with glm-5.2 by default. GLM-5.3 is selectable as glm-5.3; use glm-5.3?reasoning=low, glm-5.3?reasoning=high, or glm-5.3?reasoning=xhigh to request its documented low, high, or maximum effort. The existing GLM-5.2 query controls remain supported.
  • Xiaomi MiMo uses api-key header auth on the direct OpenAI-compatible route and currently does not support /usage reporting 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 + agentRouting in ~/.openclaude/settings.json
  • model-only routes that reuse your current provider's credentials
  • built-in agents (Explore and Plan [feature-gated], verification [feature-gated: requires VERIFICATION_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:

bash
export FIRECRAWL_API_KEY=your-key-here

With Firecrawl enabled:

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

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

Headless gRPC Server

OpenClaude can 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.

bash
bun install
bun run build
node dist/cli.mjs

Day-to-day commands:

  • bun run dev โ€” build and launch from source
  • bun test โ€” full unit suite (Bun's built-in runner)
  • bun test path/to/file.test.ts โ€” focused runs for the areas you touch
  • bun run test:coverage โ€” coverage to coverage/lcov.info plus a visual report at coverage/index.html (bun run test:coverage:ui rebuilds just the UI)
  • bun run smoke โ€” smoke checks
  • bun 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:

bash
bun run build
bun run benchmark:startup

The 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. Use
bun 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. Set
NODE_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 build
  • bun run smoke
  • bun run test:coverage when your change affects shared runtime or provider logic
  • focused bun test ... runs for the files and flows you changed

Repository Structure

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

VS Code Extension

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

Security

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

Community

Contributing

Contributions are welcome. For larger changes, open an issue first so the
scope is clear before implementation. 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:

  1. src/integrations/descriptors.ts
    Defines the descriptor shapes.
  2. src/integrations/index.ts and registry.ts
    Load and expose the registered vendors, gateways, brands, and models.
  3. src/integrations/routeMetadata.ts
    Resolves route labels/defaults and maps active env state onto route ids.
  4. src/integrations/runtimeMetadata.ts
    Derives request-time OpenAI-shim behavior from the active route plus the
    selected model/catalog entry.
  5. 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, and
    openaiShim.ts performs 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.kind to decide whether a route is local,
    OpenAI-compatible, Anthropic-proxy, Bedrock, Vertex, or another supported
    transport family;
  • do not use gateway category to choose runtime routing behavior.

Gateway category is optional display/grouping metadata only:

  • local helps group routes like Ollama or LM Studio in UI/docs;
  • hosted helps describe remote first-party or managed endpoints;
  • aggregating helps 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:

ts
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:generate derives
    src/integrations/generated/integrationArtifacts.generated.ts;
  • src/integrations/index.ts loads 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
    preserves APIProvider / LegacyAPIProvider for 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 keeps
custom 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.ts
    resolveActiveRouteIdFromEnv() still honors CLAUDE_CODE_USE_* flags and
    OpenAI-compatible env fallback because bootstrap and saved-profile flows are
    still env-driven.
  • src/utils/providerFlag.ts
    --provider still writes the legacy env contract directly, even though it
    now reads descriptor defaults where possible.
  • src/utils/model/providers.ts
    LegacyAPIProvider/APIProvider remain 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 uses api-key and deployment URLs; Bankr uses X-API-Key.
    Descriptor-backed gateways can use defaultAuthHeader for 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, and store shaping.
  • 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 /usage execution 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:

  • github
  • mistral
  • bedrock
  • vertex
  • foundry
  • 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, see
plan/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

  1. Create the descriptor file under src/integrations/anthropicProxies/.
  2. Use defineAnthropicProxy(...).
  3. Set the proxy identity fields.
    Include id, label, classification: 'anthropic-proxy',
    defaultBaseUrl, and defaultModel.
  4. Fill the setup metadata.
    Add setup.requiresAuth, setup.authMode, and
    setup.credentialEnvVars.
  5. Fill envVarConfig.
    This is the Anthropic-proxy-specific env contract.
  6. Set transportConfig.kind: 'anthropic-proxy'.
  7. Add capabilities and optional catalog/usage/validation metadata as needed.
  8. Run bun run integrations:generate so 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:

ts
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

ts
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' or local;
  • rely on OpenAI-compatible request/response shaping;
  • do not use envVarConfig for 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

  1. Choose the file layout.
    Use src/integrations/gateways/.ts for the descriptor. Add
    src/integrations/gateways/.models.ts only when the catalog/discovery
    details are large enough to deserve a companion file.
  2. Pick the transport family.
    transportConfig.kind is the routing contract.
  3. Pick a category.
    category is optional grouping/display metadata only. It must not drive
    runtime routing.
  4. Define setup and startup metadata.
    Gateways often need readiness or auto-detection hints in startup.
  5. Choose the catalog strategy.
    Use static, dynamic, or hybrid.
  6. Decide whether the gateway needs discovery cache TTL, refresh mode, and
    manual refresh.
  7. For OpenAI-compatible or local routes, add any required static headers,
    decide whether users may edit API mode and header-related settings through
    transportConfig.openaiShim.supportsApiFormatSelection and
    transportConfig.openaiShim.supportsAuthHeaders, and use
    transportConfig.openaiShim.ui.show* flags to choose which auth-header,
    auth-value, or custom-header prompts appear for tighter built-in preset
    flows.
  8. If the gateway should appear in preset-driven /provider flows, add a
    preset block on the descriptor.
  9. Run bun run integrations:generate so the generated loader and preset
    manifest stay in sync.

Authoring rules

Normal gateway examples should:

  • use defineGateway and defineCatalog;
  • default-export the gateway descriptor;
  • default-export the catalog from any companion *.models.ts file;
  • avoid registerGateway(...) in contributor-authored examples;
  • avoid removed legacy fields such as targetVendorId,
    isOpenAICompatible, or routing-oriented gateway classification.

The routing decision belongs to transportConfig.kind, not to category.

Reasoning controls in mixed catalogs

Gateway catalogs often mix models with different reasoning APIs. Keep
capabilities.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. See
docs/integrations/reasoning-effort.md before adding or changing reasoning
controls.

Generated loader and preset manifest

Normal gateway onboarding is additive now:

  1. add or edit the descriptor file;
  2. add a preset block only if the route should be user-facing in preset
    flows;
  3. run bun run integrations:generate;
  4. let src/integrations/generated/integrationArtifacts.generated.ts feed the
    loader, compatibility mapping, preset typing, and provider UI metadata.

Preset ordering is not configured manually. The generated manifest pins
anthropic 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.

ts
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.

ts
transportConfig: {
  kind: 'openai-compatible',
  openaiShim: {
    supportsApiFormatSelection: false,
    supportsAuthHeaders: false,
  },
}

Local gateway

Use transportConfig.kind: 'local' for routes such as Ollama or LM Studio.

ts
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.

ts
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.

ts
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_tokens for a local/legacy-compatible token field;
  • a startup refresh 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

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

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-stale refresh;
  • 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:

ts
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:

ts
transportConfig: {
  kind: 'openai-compatible',
  openaiShim: {
    supportsApiFormatSelection: false,
    supportsAuthHeaders: false,
    maxTokensField: 'max_tokens',
  },
}

Hosted modern-route example:

ts
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 in
transportConfig.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:

ts
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, the
openaiShim.ui.showAuthHeader, showAuthHeaderValue, and
showCustomHeaders 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 with defaultAuthHeader when 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.

ts
preset: {
  id: 'acme-hosted',
  vendorId: 'openai',
  apiKeyEnvVars: ['ACME_HOSTED_API_KEY'],
}

Then regenerate:

bash
bun run integrations:generate

That 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 gateway
    classification;
  • using category to make runtime routing decisions;
  • placing large discovery/cached-catalog logic inline when a companion
    *.models.ts file 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.kind is the routing contract;
  • category is 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 providerModelMap to 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

  1. Pick the model file.
    Use an existing family file under src/integrations/models/ when the model
    belongs to a current family such as gpt, claude, or deepseek.
  2. 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.
  3. Add the defineModel(...) entry.
    Include id, label, vendorId, classification, defaultModel, and
    capabilities.
  4. Add optional shared metadata.
    Include brandId, contextWindow, maxOutputTokens, and cacheConfig
    when the data is stable enough to be reused. Set
    runtimeMetadataScope: 'catalog' when verified limits and capabilities
    should apply only on route catalogs that explicitly reference the
    descriptor.
  5. Add providerModelMap only when the same model needs route-specific API
    names across multiple catalogs.
  6. 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, see
docs/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.

ts
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.

ts
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:

  1. route-owned catalog metadata;
  2. shared model-descriptor enrichment when a catalog entry references
    modelDescriptorId;
  3. global shared model descriptors under src/integrations/models/ for legacy
    and custom OpenAI-compatible model names;
  4. documented user overrides from src/utils/model/openaiContextWindows.ts โ€”
    the CLAUDE_CODE_OPENAI_CONTEXT_WINDOWS / CLAUDE_CODE_OPENAI_MAX_OUTPUT_TOKENS
    env vars and the modelLimits map in settings.json (see the
    modelLimits section in docs/advanced-setup.md for 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 providerModelMap as 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 of src/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;
  • providerModelMap is 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/, while
    src/utils/model/openaiContextWindows.ts only 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() in
src/commands/usage/index.ts.

That resolver:

  1. finds the active vendor or gateway descriptor;
  2. reads the descriptor's usage metadata;
  3. follows delegateToVendorId / delegateToGatewayId if present;
  4. returns the final resolved usage target plus a supported flag.

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 the anthropic vendor;
  • src/components/Settings/Usage.tsx still has concrete UI branches for the
    currently supported runtime paths (Anthropic, MiniMax, and the separate
    Codex path);
  • the descriptor schema already includes fetchModule and parseModule, 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:

ts
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:

  • VendorDescriptor
  • GatewayDescriptor
  • AnthropicProxyDescriptor

What each field means:

  • supported
    Whether the route has real /usage support.
  • 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, and ui rules 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 /usage resolver in src/commands/usage/index.ts currently
    resolves vendor and gateway targets, plus the firstParty compatibility id;
  • document anthropic-proxy usage metadata as part of the descriptor contract,
    but do not describe it as a separately routed /usage surface 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:

  • anthropic
  • minimax

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:

ts
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:

ts
usage: {
  supported: true,
  fetchModule: './usage/fetchGatewayUsage.js',
  parseModule: './usage/parseGatewayUsage.js',
  ui: {
    compactProgressBar: true,
    showResetCountdown: true,
  },
}

Current implementation note:

  • the branch already understands supported and delegation through
    getUsageDescriptor();
  • 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:

text
src/
  services/
    api/
      usage/
        fetchAcmeUsage.ts
        parseAcmeUsage.ts

Recommended 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 usage field;
  • keep route selection in getUsageDescriptor().

Worked example: vendor with native usage API

This is the simplest supported vendor pattern.

ts
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.

ts
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.

ts
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.

ts
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.supported false;
  • 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 to
UnsupportedUsage with the active provider label.

Current supported routes

As of the current implementation:

  • anthropic is supported;
  • minimax is supported;
  • most other vendor and gateway descriptors are explicitly unsupported;
  • codex still 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 usage field when the route should
    explicitly report unsupported behavior;
  • calling registry mutation helpers directly in usage examples;
  • treating fetchModule / parseModule as 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 /docs as Markdown;
  • it documents the usage field 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

  1. Pick the descriptor file path.
    Use src/integrations/vendors/.ts.
  2. Choose the transport family.
    Common direct vendors use transportConfig.kind: 'openai-compatible'.
    Gemini-native and Anthropic-native routes keep their own transport kinds.
  3. Define setup/auth metadata.
    Fill setup.requiresAuth, setup.authMode, and
    setup.credentialEnvVars.
  4. Set the route defaults.
    Add defaultBaseUrl, defaultModel, and any required env vars or
    validation metadata.
  5. For OpenAI-compatible vendors, set the /provider UI capability flags in
    transportConfig.openaiShim.
    Use supportsApiFormatSelection for API mode editing and
    supportsAuthHeaders for auth/header editing.
  6. Add a catalog if the vendor exposes models directly.
    Put the vendor's offered model subset on the vendor descriptor itself. Use
    modelDescriptorId when an entry should inherit shared model metadata.
  7. Add usage metadata if the vendor has real /usage support.
    If /usage is still unsupported, keep that explicit with
    usage: { supported: false }.
  8. If the vendor should appear in preset-driven /provider flows, add a
    preset block on the descriptor.
  9. Run bun run integrations:generate so the generated loader and preset
    manifest stay in sync.

Authoring rules

Normal vendor descriptor files should:

  • use defineVendor and defineCatalog;
  • default-export the descriptor;
  • keep registration out of the file;
  • avoid direct registerVendor(...) calls;
  • avoid extra import type boilerplate in contributor-facing patterns unless a
    real type import is unavoidable.

Registration is loader-owned through the generated artifacts consumed by
src/integrations/index.ts.

Generated loader and preset manifest

Normal vendor onboarding is additive now:

  1. add or edit the descriptor file;
  2. add a preset block only if the vendor should be user-facing in preset
    flows;
  3. run bun run integrations:generate;
  4. let src/integrations/generated/integrationArtifacts.generated.ts feed 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, and
custom 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.

ts
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.kind owns the transport choice;
  • supportsApiFormatSelection: false means /provider should not expose API
    mode editing for this fixed direct-vendor route;
  • supportsAuthHeaders: false means /provider should only ask for the API
    key, not custom auth-header fields;
  • the vendor owns its own catalog because it exposes models directly;
  • defaultModel on 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 in
transportConfig.openaiShim.headers. Secrets still belong in credential env
vars or runtime auth handling.

ts
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.

ts
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 in
src/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. See
docs/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 add and /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.

ts
preset: {
  id: 'acme',
  apiKeyEnvVars: ['ACME_API_KEY'],
}

Then regenerate:

bash
bun run integrations:generate

That 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.kind already
    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 /provider UI capabilities are explicit through
    openaiShim.supportsApiFormatSelection and openaiShim.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 VendorDescriptor when the route is the canonical direct vendor API;
  • use GatewayDescriptor when the route hosts, proxies, or aggregates models
    behind its own endpoint contract;
  • use AnthropicProxyDescriptor when the route accepts Anthropic-native
    traffic through its own Anthropic-style env contract;
  • use ModelDescriptor for 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 from
src/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_tokens for newer hosted OpenAI-style contracts;
  • use max_tokens for local or legacy-shaped routes and other providers that
    reject the newer field;
  • keep the choice explicit in transportConfig.openaiShim.maxTokensField when
    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: true only 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.ts is 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 in
gateways/.ts.

Safer rule:
Move large catalog or discovery-specific logic into a companion
gateways/.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 to
src/utils/model/openaiContextWindows.ts.

Safer rule:
Put built-in model metadata in src/integrations/models/. Keep
openaiContextWindows.ts focused on documented user overrides โ€” the
CLAUDE_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:generate
  • src/integrations/generated/integrationArtifacts.generated.ts
  • env-facing flows that still preserve legacy names

Do not hand-edit:

  • src/integrations/compatibility.ts
  • src/integrations/profileResolver.ts
  • src/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.ts for descriptor-backed /usage routing
  • src/components/Settings/Usage.tsx for the current usage UI boundary
  • src/integrations/routeMetadata.ts for route/default/label helpers
  • src/integrations/runtimeMetadata.ts for request-shaping metadata
  • src/integrations/discoveryCache.ts and src/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.kind is 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 /usage docs 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:

  • static
  • dynamic
  • hybrid

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:

  • local
  • hosted
  • aggregating

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.ts
  • src/integrations/profileResolver.ts
  • src/utils/model/providers.ts
  • src/utils/providerFlag.ts

Descriptor

A typed metadata object defined under src/integrations/ through one of the
define* 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, while
src/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 through
providerModelMap.

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:

text
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.md

All 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:

  1. Read docs/architecture/integrations.md for the system boundaries.
  2. Read docs/integrations/glossary.md for the shared vocabulary.
  3. Read docs/integrations/reasoning-effort.md before marking models as
    reasoning-capable or /effort-controllable.
  4. Use the how-to guides for the specific descriptor type you are adding.
  5. Use docs/integrations/reference-samples.md once the architecture and the relevant how-to guide are clear.
  6. Read docs/integrations/common-pitfalls.md before 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:

  • local
  • hosted
  • aggregating

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 in
transportConfig.openaiShim.

Examples:

  • maxTokensField
  • headers
  • supportsApiFormatSelection
  • supportsAuthHeaders

That matches the current runtime metadata flow in
src/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. See
docs/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 levels
low, high, and max (defaulting to max). Kimi Code K3 uses the same
controls for two catalog selections: k3 provides the 1M Allegretto+ window, while
k3-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 from src/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:

  • defineVendor
  • defineGateway
  • defineCatalog
  • defineModel
  • defineBrand
  • defineAnthropicProxy

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:

  1. create or edit the descriptor file;
  2. keep the export typed through the appropriate define* helper;
  3. let the loader own registration;
  4. 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:

  1. add or edit the descriptor file;
  2. add preset metadata only when the route should be user-facing;
  3. add preset.badge metadata if the route should show a display tag (e.g.
    [FREE], [Sponsor]) in the preset picker โ€” this avoids
    hard-coded badge logic in src/components/ProviderManager.tsx;
  4. run bun run integrations:generate;
  5. 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.ts
    APIProvider / LegacyAPIProvider;
  • src/utils/providerFlag.ts
    env-facing --provider behavior.

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.