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

[](https://github.com/Gitlawb/openclaude/actions/workflows/pr-checks.yml)
[](https://github.com/Gitlawb/openclaude/tags)
[](https://www.npmjs.com/package/@gitlawb/openclaude)
[](https://github.com/Gitlawb/openclaude/discussions)
[](https://discord.gg/k68zFR6AcB)
[](https://x.com/gitlawb)
[](SECURITY.md)
[](LICENSE)

OpenClaude is also mirrored to GitLawb:
[gitlawb.com/node/repos/z6MkqDnb/openclaude](https://gitlawb.com/node/repos/z6MkqDnb/openclaude)

[Quick Start](#quick-start) | [Setup Guides](#setup-guides) | [Providers](#supported-providers) | [Development](#development) | [VS Code Extension](#vs-code-extension) | [Partners](#partners) | [Community](#community)

## Partners



|  |  |  |  |  |
| --- | --- | --- | --- | --- |
| [**GitLawb**](https://gitlawb.com) | [**Bankr.bot**](https://bankr.bot) | [**Atomic Chat**](https://atomic.chat/) | [**Xiaomi MiMo**](https://mimo.mi.com) | [**Atlas Cloud**](https://www.atlascloud.ai/) |
|  |  |  |  |  |
| [**AI/ML API**](https://aimlapi.com/) | [**Novita AI**](https://novita.ai/) | [**ApiSmart**](https://www.apismart.ai) | [**Concentrate**](https://concentrate.ai/) | [**Exa**](https://exa.ai/) |



## 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](#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](https://aur.archlinux.org/packages/openclaude):
```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](docs/advanced-setup.md#ollama-context-length) for
verification with `ollama ps`.

## Setup Guides

Beginner-friendly guides:

- [Non-Technical Setup](docs/non-technical-setup.md)
- [Windows Quick Start](docs/quick-start-windows.md)
- [macOS / Linux Quick Start](docs/quick-start-mac-linux.md)

Advanced and source-build guides:

- [Advanced Setup](docs/advanced-setup.md)
- [Smart Auto-Routing](docs/smart-routing.md)
- [Agent Routing and Step Limits](docs/agent-routing.md)
- [Headless gRPC Server](docs/grpc-server.md)
- [Repo Map (codebase intelligence)](docs/repo-map.md)
- [Android Install](ANDROID_INSTALL.md)

## 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](docs/aimlapi-setup.md)) | 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](docs/repo-map.md) for details.
- **A companion with signature moves**: A truecolor pixel-art hero who lives beside your prompt and reacts when you work. See below.

## Meet Your Buddy

Run `/buddy` to hatch a companion — a truecolor pixel-art hero who stands
beside your prompt, idles, blinks, and fires their signature move every time
you submit a message:

```
/buddy                  hatch (first run) or pet your companion
/buddy set robinhood    the green archer — arrow shot on every Enter
/buddy set kaio         gold-haired warrior — charges a full-width energy wave
/buddy set strawhat     stretchy punch that snaps back
/buddy set merlin       twinkling sparkle stream
/buddy set kage         spinning shuriken
/buddy set ember        dragon fire with a real heat gradient
/buddy set corsair      cannonball with smoke trail
/buddy name Robin       rename your companion
/buddy set random       back to your rolled 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](docs/agent-routing.md#github-copilot-sub-agent-optimization) 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](docs/agent-routing.md) 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](https://firecrawl.dev) 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](https://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](docs/grpc-server.md) 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`](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](vscode-extension/openclaude-vscode/README.md).

## Security

If you believe you found a security issue, see [SECURITY.md](SECURITY.md).

## Community

- Use [GitHub Discussions](https://github.com/Gitlawb/openclaude/discussions) for Q&A, ideas, and community conversation
- Use [GitHub Issues](https://github.com/Gitlawb/openclaude/issues) for confirmed bugs and actionable feature work
- Join the [Discord](https://discord.gg/k68zFR6AcB) to chat with the community in real time
- Follow [@gitlawb on X](https://x.com/gitlawb) for updates and announcements

## Contributing

Contributions are welcome. For larger changes, open an issue first so the
scope is clear before implementation. See [Development](#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](LICENSE) for details.

## License

MIT for OpenClaude contributors' modifications; the derived Claude Code remains Anthropic's. [See more](LICENSE).

---

## 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](https://www.kimi.com/code/docs/en/kimi-code/models.html)
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`.