agentleFS
Sign inSign up

shunt / wiki

pleaseai/shunt/wiki/llms-full.txt

Configuration exists to keep shunt generic: new upstreams are provider table entries, not code changes, and routing is a data problem expressed as exact model routes, prefix routes, and a default provider. Config::load merges built-in defaults, a TOML file, and SHUNT_ environment overrides before validate checks bind addresses, provider URLs, auth requirements, and provider references src/config.rs:185-194 src/config.rs:196-242.

llms.txt122 starsChanged 7 days ago
  • Reads credentials
# shunt full documentation

<doc title="Configuration" path="src/content/docs/01-getting-started/configuration.md">
## Overview

Configuration exists to keep shunt generic: new upstreams are provider table entries, not code changes, and routing is a data problem expressed as exact model routes, prefix routes, and a default provider. `Config::load` merges built-in defaults, a TOML file, and `SHUNT_` environment overrides before `validate` checks bind addresses, provider URLs, auth requirements, and provider references [src/config.rs:185-194](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L185-L194) [src/config.rs:196-242](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L196-L242).

| Area | Responsibility | Key file | Source |
|---|---|---|---|
| Built-in defaults | Seeds `anthropic`, `openai`, `codex`, and `xai` providers | `src/config.rs` | [src/config.rs:240-311](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L240-L311) |
| TOML example | Documents server, providers, routes, aliases | `shunt.toml.example` | [shunt.toml.example:1-134](https://github.com/chatbot-pf/shunt/blob/main/shunt.toml.example#L1-L134) |
| Runtime docs | Explains config precedence and Claude Code env vars | `docs/running.md` | [docs/running.md:40-159](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L40-L159) |
| Route resolver | Applies exact, prefix, default precedence | `src/routing.rs` | [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89) |
| Discovery | Serializes configured `[[models]]` entries | `src/discovery.rs` | [src/discovery.rs:17-30](https://github.com/chatbot-pf/shunt/blob/main/src/discovery.rs#L17-L30) |

## Configuration Layers

```mermaid
flowchart TB
    Defaults[Built-in Config default] --> Toml[shunt.toml or --config path]
    Toml --> Env[SHUNT_ environment overrides]
    Env --> Validate[Config::validate]
    Validate --> Runtime[Validated Config in AppState]
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class Defaults,Toml,Env,Validate,Runtime dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: src/config.rs:142, src/config.rs:185, src/config.rs:196, src/server.rs:13 -->

## Route Resolution

```mermaid
flowchart LR
    Model[Request model id] --> Exact{Exact route?}
    Exact -->|yes| ExactProvider[Route provider + optional upstream_model]
    Exact -->|no| Prefix{Prefix match?}
    Prefix -->|yes| PrefixProvider[Prefix provider]
    Prefix -->|no| DefaultProvider[server.default_provider]
    ExactProvider --> Route[Route]
    PrefixProvider --> Route
    DefaultProvider --> Route
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class Model,Exact,ExactProvider,Prefix,PrefixProvider,DefaultProvider,Route dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: src/routing.rs:48, src/routing.rs:49, src/routing.rs:60, src/routing.rs:65, shunt.toml.example:87 -->

## Route Entities

```mermaid
erDiagram
    CONFIG ||--|| SERVER_CONFIG : owns
    CONFIG ||--o{ PROVIDER_CONFIG : names
    CONFIG ||--o{ ROUTE_CONFIG : exact_routes
    CONFIG ||--o{ ROUTE_PREFIX_CONFIG : prefix_routes
    CONFIG ||--o{ MODEL_CONFIG : exposes
    PROVIDER_CONFIG ||--o{ ROUTE_CONFIG : selected_by
    PROVIDER_CONFIG ||--o{ ROUTE_PREFIX_CONFIG : selected_by
```
<!-- Sources: src/config.rs:9, src/config.rs:27, src/config.rs:89, src/config.rs:97, src/config.rs:103 -->

## Credential Strategy

| Auth mode | Who supplies credential | Outbound behavior | Source |
|---|---|---|---|
| `passthrough` | Claude Code request | Forward incoming credential unchanged | [src/auth/mod.rs:29-99](https://github.com/chatbot-pf/shunt/blob/main/src/auth/mod.rs#L29-L99) [src/adapters/anthropic.rs:66-89](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/anthropic.rs#L66-L89) |
| `api_key` | Environment variable named by `api_key_env`; OpenAI can fall back to Codex auth JSON | Inject provider key as Bearer or `x-api-key` | [src/auth/mod.rs:57-80](https://github.com/chatbot-pf/shunt/blob/main/src/auth/mod.rs#L57-L80) |
| `chatgpt_oauth` | `~/.codex/auth.json` from `codex login` | Refresh if near expiry, send Bearer + `chatgpt-account-id` | [src/auth/codex_auth.rs:34-63](https://github.com/chatbot-pf/shunt/blob/main/src/auth/codex_auth.rs#L34-L63) [src/adapters/responses.rs:180-188](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/responses.rs#L180-L188) |
| `xai_oauth` | `~/.shunt/xai-auth.json` from `shunt login xai` (SuperGrok / X Premium+) | Refresh with rotated-token persistence, send Bearer only; base_url must stay on an `x.ai` host | [src/auth/xai_auth.rs:87-121](https://github.com/chatbot-pf/shunt/blob/main/src/auth/xai_auth.rs#L87-L121) [src/config.rs:424-435](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L424-L435) |

## Discovery Sequence

```mermaid
sequenceDiagram
    autonumber
    participant CC as Claude Code
    participant S as shunt /v1/models
    participant CFG as Config.models
    CC->>S: GET /v1/models?limit=1000
    S->>CFG: iterate configured aliases
    CFG-->>S: ModelConfig entries
    S-->>CC: data array with id and display_name
```
<!-- Sources: src/discovery.rs:17, src/discovery.rs:19, src/config.rs:98, docs/running.md:257 -->

## Related Pages

| Page | Relationship |
|---|---|
| [Overview](./overview.md) | Explains why configuration controls routing |
| [Operations](./operations.md) | Shows how to run `shunt check` and connect Claude Code |
| [Routing and Configuration](../02-deep-dive/routing-and-configuration.md) | Deep implementation details |
| [Authentication](../02-deep-dive/authentication.md) | Credential resolution internals |

## References

- [src/config.rs:9-269](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L9-L269)
- [shunt.toml.example:1-134](https://github.com/chatbot-pf/shunt/blob/main/shunt.toml.example#L1-L134)
- [docs/running.md:40-159](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L40-L159)
- [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89)
- [src/discovery.rs:17-30](https://github.com/chatbot-pf/shunt/blob/main/src/discovery.rs#L17-L30)
</doc>

<doc title="xAI Grok Provider" path="src/content/docs/01-getting-started/xai-provider.md">
> **Experimental:** not yet verified against the live xAI API — implemented from the reference clients (Hermes, OpenCode) and unit-tested with mocked endpoints only.

## Overview

shunt ships a built-in `xai` provider that translates Anthropic Messages to xAI's OpenAI-Responses-shaped API at `https://api.x.ai/v1/responses`. It is reachable two ways: an **API key** (`XAI_API_KEY`, the default) or a **subscription OAuth** login that reuses a SuperGrok / X Premium+ plan with no separate API billing [src/config.rs:300-311](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L300-L311) [docs/m6-xai-provider.md](https://github.com/chatbot-pf/shunt/blob/main/docs/m6-xai-provider.md).

| Path | Credential | Cost model | Setup |
|---|---|---|---|
| API key (default) | `XAI_API_KEY` env var | Pay-per-token xAI API billing | `export XAI_API_KEY=...` + routes |
| Subscription OAuth | `~/.shunt/xai-auth.json`, written by `shunt login xai` | Included in SuperGrok / X Premium+ | `auth = "xai_oauth"` + one device-code login |

## API key setup

The built-in provider already defines `kind = "responses"`, `base_url = "https://api.x.ai/v1"`, and `api_key_env = "XAI_API_KEY"`, so a minimal `shunt.toml` only adds routes [src/config.rs:295-311](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L295-L311):

```toml
[[routes]]
model = "grok-build-0.1"    # flagship coding model
provider = "xai"

[[routes]]
model = "grok-4.3"
provider = "xai"
```

```bash
export XAI_API_KEY=xai-...
shunt run
```

## Subscription OAuth setup

Flip the provider's auth mode and log in once with the RFC 8628 device-code flow:

```toml
[providers.xai]
auth = "xai_oauth"
```

```bash
shunt login xai   # prints a verification URL + short code; approve in any browser
```

`shunt login xai` requests a device code from `https://auth.x.ai/oauth2/device/code`, prints the verification URL, and polls the token endpoint until the login is approved [src/auth/xai_login.rs:52-100](https://github.com/chatbot-pf/shunt/blob/main/src/auth/xai_login.rs#L52-L100). Because there is no loopback callback server, the flow works over SSH, in containers, and on headless VPS hosts. Credentials are written atomically at `0600` to `~/.shunt/xai-auth.json` (override with `SHUNT_XAI_AUTH_FILE`) [src/auth/mod.rs:119-129](https://github.com/chatbot-pf/shunt/blob/main/src/auth/mod.rs#L119-L129).

## Token lifecycle

| Behavior | Detail | Source |
|---|---|---|
| Expiry | Access-token JWT `exp` claim, 5-minute buffer | [src/auth/xai_auth.rs:87-121](https://github.com/chatbot-pf/shunt/blob/main/src/auth/xai_auth.rs#L87-L121) |
| Refresh-token rotation | xAI rotates the refresh token on every refresh; shunt persists the rotated pair | [src/auth/xai_auth.rs:106-120](https://github.com/chatbot-pf/shunt/blob/main/src/auth/xai_auth.rs#L106-L120) |
| Concurrent refresh | Process-wide single-flight mutex; waiters re-read the winner's rotated pair | [src/auth/xai_auth.rs:43-48](https://github.com/chatbot-pf/shunt/blob/main/src/auth/xai_auth.rs#L43-L48) |
| Refresh `403` | Subscription tier is not entitled to API access — re-login will not help; the error points at the `XAI_API_KEY` path | [src/auth/xai_auth.rs:233-246](https://github.com/chatbot-pf/shunt/blob/main/src/auth/xai_auth.rs#L233-L246) |
| Refresh `400`/`401` | Consumed or invalid refresh token — run `shunt login xai` again | [src/auth/xai_auth.rs:233-246](https://github.com/chatbot-pf/shunt/blob/main/src/auth/xai_auth.rs#L233-L246) |

## Safety and request shaping

- **Bearer-leak guard:** a provider with `auth = "xai_oauth"` must be `kind = "responses"`, use an https `base_url`, and stay on an `x.ai` host; anything else fails validation at boot, so the subscription bearer can never be sent off-origin or over plaintext [src/config.rs:424-435](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L424-L435).
- **Reasoning is opt-in:** several grok models reject `reasoning.effort` with a 400, so shunt sends a `reasoning` object only when an `effort` was explicitly chosen — configured on the route/provider, or sent per-request by the client (`output_config.effort`) [src/model/responses_request.rs:34-51](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses_request.rs#L34-L51).
- The xAI dialect also omits the `text` object and the `OpenAI-Beta` header, and always sends `store: false`; detection is table-driven from the provider's auth mode and base-URL host, never a hardcoded provider name [src/config.rs:489-505](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L489-L505) [src/adapters/responses.rs:214-240](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/responses.rs#L214-L240).

## Related Pages

| Page | Relation |
|---|---|
| [Configuration](./configuration.md) | Provider table keys and route syntax |
| [Authentication](../02-deep-dive/authentication.md) | All auth modes side by side |
| [Adapters and Translation](../02-deep-dive/adapters-and-translation.md) | Responses translation internals |

## Sources

- [src/auth/xai_auth.rs](https://github.com/chatbot-pf/shunt/blob/main/src/auth/xai_auth.rs)
- [src/auth/xai_login.rs](https://github.com/chatbot-pf/shunt/blob/main/src/auth/xai_login.rs)
- [src/config.rs:295-311](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L295-L311)
- [docs/m6-xai-provider.md](https://github.com/chatbot-pf/shunt/blob/main/docs/m6-xai-provider.md)
- [docs/running.md](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md)
</doc>

<doc title="Operations" path="src/content/docs/01-getting-started/operations.md">
## Overview

Operationally, shunt is a local Rust binary plus a TOML config. You build it with Cargo, validate the config with `shunt check`, run the gateway, then start Claude Code with `ANTHROPIC_BASE_URL` pointing at the gateway. Provider credentials stay in the shunt process environment or Codex/Claude credential files; Claude Code does not send OpenAI or ChatGPT credentials for mapped models [docs/running.md:1-461](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L1-L461) [src/auth/mod.rs:29-99](https://github.com/chatbot-pf/shunt/blob/main/src/auth/mod.rs#L29-L99).

| Operation | Command | Why it exists | Source |
|---|---|---|---|
| Debug build | `cargo build` | Compile the gateway while developing | [docs/running.md:26-37](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L26-L37) |
| Release build | `cargo build --release` | Produce `target/release/shunt` for daily use | [docs/running.md:31-34](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L31-L34) |
| Config check | `cargo run -- check` or `shunt check` | Validate before binding or connecting Claude Code | [src/main.rs:77-83](https://github.com/chatbot-pf/shunt/blob/main/src/main.rs#L77-L83) |
| Run | `cargo run -- run` or `shunt run` | Start Axum HTTP gateway | [src/main.rs:38-76](https://github.com/chatbot-pf/shunt/blob/main/src/main.rs#L38-L76) |
| Token helper | `shunt token` | Print Claude subscription token for `apiKeyHelper` | [src/main.rs:51-58](https://github.com/chatbot-pf/shunt/blob/main/src/main.rs#L51-L58) |
| CI validation | `cargo fmt`, `cargo clippy`, `cargo test` | Enforced before PR merge | [.github/workflows/ci.yml:1-42](https://github.com/chatbot-pf/shunt/blob/main/.github/workflows/ci.yml#L1-L42) |

## Command Lifecycle

```mermaid
flowchart TB
    Start[Clone repo] --> Build[cargo build --release]
    Build --> Config[cp shunt.toml.example shunt.toml]
    Config --> Check[shunt check]
    Check -->|ok| Run[shunt run]
    Check -->|error| Fix[Fix TOML or env]
    Fix --> Check
    Run --> Claude[Start Claude Code with ANTHROPIC_BASE_URL]
    Claude --> Verify[Run curl or model picker smoke]
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class Start,Build,Config,Check,Run,Fix,Claude,Verify dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: docs/running.md:26, docs/running.md:40, src/main.rs:77, src/main.rs:60, docs/running.md:189, docs/running.md:395 -->

## Connect Claude Code

```mermaid
sequenceDiagram
    autonumber
    participant Shell as Developer shell
    participant Shunt as shunt process
    participant Claude as Claude Code
    Shell->>Shunt: export provider credentials and run gateway
    Shell->>Claude: export ANTHROPIC_BASE_URL=http://127.0.0.1:3001
    Claude->>Shunt: HEAD / probe
    Claude->>Shunt: POST /v1/messages
    Shunt-->>Claude: Anthropic-shaped response
```
<!-- Sources: docs/running.md:163, docs/running.md:189, src/server.rs:21, src/server.rs:23 -->

## CI Pipeline

```mermaid
flowchart LR
    PR[Push or pull_request] --> Checkout[Checkout pinned SHA]
    Checkout --> Rust[Install stable Rust + rustfmt + clippy]
    Rust --> Cache[Cargo cache]
    Cache --> Fmt[cargo fmt --all --check]
    Fmt --> Clippy[cargo clippy --all-targets --all-features -- -D warnings]
    Clippy --> Test[cargo test --all-features --workspace]
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class PR,Checkout,Rust,Cache,Fmt,Clippy,Test dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: .github/workflows/ci.yml:1, .github/workflows/ci.yml:23, .github/workflows/ci.yml:27, .github/workflows/ci.yml:32, .github/workflows/ci.yml:35, .github/workflows/ci.yml:38, .github/workflows/ci.yml:41 -->

## Troubleshooting Table

| Symptom | Likely cause | Fix | Source |
|---|---|---|---|
| `config check failed` | Bad bind address, provider URL, missing env setting, or unknown provider reference | Run `shunt check` and follow typed error | [src/config.rs:196-242](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L196-L242) |
| `ChatGPT auth not found; run codex login` | `~/.codex/auth.json` is absent or unreadable | Run `codex login` | [src/auth/codex_auth.rs:34-63](https://github.com/chatbot-pf/shunt/blob/main/src/auth/codex_auth.rs#L34-L63) |
| Model absent in `/model` | `gpt-*` IDs are ignored by gateway discovery | Use `ANTHROPIC_CUSTOM_MODEL_OPTION` or a Claude-named alias | [docs/running.md:231-287](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L231-L287) |
| Claude passthrough fails | Gateway credential is dummy or missing | Use real Anthropic credential or `shunt token` helper | [docs/running.md:289-348](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L289-L348) |
| OpenAI/Codex model rejected | Upstream slug not entitled or unsupported | Use an entitled slug or `upstream_model` | [docs/running.md:244-252](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L244-L252) |

## Related Pages

| Page | Relationship |
|---|---|
| [Configuration](./configuration.md) | Defines the settings operations validate |
| [Authentication](../02-deep-dive/authentication.md) | Explains credential sources and refresh |
| [Testing and Quality](../02-deep-dive/testing-and-quality.md) | Expands on CI and test coverage |
| [Contributor Guide](../onboarding/contributor-guide.md) | Contributor workflow using these commands |

## References

- [docs/running.md:1-461](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L1-L461)
- [src/main.rs:38-76](https://github.com/chatbot-pf/shunt/blob/main/src/main.rs#L38-L76)
- [src/server.rs:13-25](https://github.com/chatbot-pf/shunt/blob/main/src/server.rs#L13-L25)
- [.github/workflows/ci.yml:1-42](https://github.com/chatbot-pf/shunt/blob/main/.github/workflows/ci.yml#L1-L42)
- [tests/passthrough.rs:72-247](https://github.com/chatbot-pf/shunt/blob/main/tests/passthrough.rs#L72-L247)
</doc>

<doc title="Overview" path="src/content/docs/01-getting-started/overview.md">
## Overview

`shunt` exists because Claude Code can talk to a first-class LLM gateway through `ANTHROPIC_BASE_URL`, but teams sometimes want only selected model IDs to run on another provider while the rest of the Claude Code session keeps its normal tools, skills, settings, and Anthropic pass-through behavior. The project implements that gateway as a Rust/Axum process that routes by the request `model` instead of trying to infer caller identity from prompts [README.md:1-60](https://github.com/chatbot-pf/shunt/blob/main/README.md#L1-L60) [docs/implementation-plan.md:46-71](https://github.com/chatbot-pf/shunt/blob/main/docs/implementation-plan.md#L46-L71).

| Component | Responsibility | Key file | Source |
|---|---|---|---|
| CLI process | Starts server, validates config, prints token helper output | `src/main.rs` | [src/main.rs:38-76](https://github.com/chatbot-pf/shunt/blob/main/src/main.rs#L38-L76) |
| Router | Exposes `HEAD /`, `GET /v1/models`, `POST /v1/messages`, and `POST /v1/messages/count_tokens` | `src/server.rs` | [src/server.rs:13-25](https://github.com/chatbot-pf/shunt/blob/main/src/server.rs#L13-L25) |
| Proxy handler | Buffers request, resolves route, dispatches adapter, logs latency | `src/proxy.rs` | [src/proxy.rs:19-126](https://github.com/chatbot-pf/shunt/blob/main/src/proxy.rs#L19-L126) |
| Route resolver | Applies exact, prefix, then default provider precedence | `src/routing.rs` | [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89) |
| Config | Defines built-in providers and validates routes/providers | `src/config.rs` | [src/config.rs:142-183](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L142-L183) |
| Translation | Converts Anthropic request/stream shapes to and from Responses | `src/model/responses_request.rs`, `src/model/responses.rs` | [src/model/responses_request.rs:4-280](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses_request.rs#L4-L280) [src/model/responses.rs:45-378](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses.rs#L45-L378) |

## Gateway Position

```mermaid
graph TB
    subgraph Client[Claude Code process]
      ModelPicker[/model picker]
      ToolLoop[Tool loop and skills]
    end
    subgraph Gateway[shunt]
      Server[Axum router]
      Resolver[Model route resolver]
      Adapter[Selected adapter]
    end
    subgraph Upstreams[Provider APIs]
      Anthropic[Anthropic-compatible Messages API]
      Responses[OpenAI Responses API]
    end
    ModelPicker --> ToolLoop --> Server --> Resolver --> Adapter
    Adapter --> Anthropic
    Adapter --> Responses
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class ModelPicker,ToolLoop,Server,Resolver,Adapter,Anthropic,Responses dark;
    style Client fill:#161b22,stroke:#30363d,color:#e6edf3;
    style Gateway fill:#161b22,stroke:#30363d,color:#e6edf3;
    style Upstreams fill:#161b22,stroke:#30363d,color:#e6edf3;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: README.md:14, src/server.rs:13, src/routing.rs:37, src/adapters/anthropic.rs:31, src/adapters/responses.rs:34 -->

## Request Lifecycle

```mermaid
sequenceDiagram
    autonumber
    participant CC as Claude Code
    participant Axum as shunt Router
    participant Proxy as proxy::post
    participant Routing as routing::resolve
    participant Adapter as Adapter
    participant Upstream as Provider API
    CC->>Axum: POST /v1/messages
    Axum->>Proxy: body + headers + OriginalUri
    Proxy->>Routing: parse JSON model field
    Routing-->>Proxy: Route(provider, adapter, upstream_model)
    Proxy->>Adapter: forward(state, route, uri, headers, body)
    Adapter->>Upstream: provider-specific request
    Upstream-->>Adapter: stream or JSON response
    Adapter-->>CC: Anthropic-shaped response
```
<!-- Sources: src/server.rs:19, src/proxy.rs:19, src/routing.rs:37, src/adapters/mod.rs:21, src/adapters/responses.rs:34 -->

## Runtime States

```mermaid
stateDiagram-v2
    [*] --> ConfigLoad
    ConfigLoad --> Listening: valid config
    ConfigLoad --> Failed: validation error
    Listening --> RequestBuffered: POST received
    RequestBuffered --> Routed: model parsed
    Routed --> AnthropicPath: AdapterKind::Anthropic
    Routed --> ResponsesPath: AdapterKind::Responses
    AnthropicPath --> StreamBack
    ResponsesPath --> TranslateBack
    StreamBack --> Listening
    TranslateBack --> Listening
    Failed --> [*]
```
<!-- Sources: src/main.rs:60, src/config.rs:196, src/proxy.rs:93, src/routing.rs:48, src/adapters/responses.rs:61 -->

## Why Model-Based Routing

| Approach | shunt decision | Reason | Source |
|---|---|---|---|
| Prompt fingerprinting | Not used | The request already contains the selected model ID, so prompt-shape coupling is unnecessary | [docs/implementation-plan.md:24-32](https://github.com/chatbot-pf/shunt/blob/main/docs/implementation-plan.md#L24-L32) |
| Global provider swap | Not the focus | Unmapped models must continue to pass through to Anthropic | [README.md:43-56](https://github.com/chatbot-pf/shunt/blob/main/README.md#L43-L56) |
| Per-model mapping | Primary mechanism | Claude Code lets users pick model IDs per context; shunt honors those IDs | [README.md:18-20](https://github.com/chatbot-pf/shunt/blob/main/README.md#L18-L20) |

## Related Pages

| Page | Relationship |
|---|---|
| [Configuration](./configuration.md) | Explains how model IDs map to providers |
| [Operations](./operations.md) | Turns the overview into runnable commands |
| [Architecture](../02-deep-dive/architecture.md) | Deep runtime component map |
| [Adapters and Translation](../02-deep-dive/adapters-and-translation.md) | Details the adapter implementation |

## References

- [README.md:1-60](https://github.com/chatbot-pf/shunt/blob/main/README.md#L1-L60)
- [src/main.rs:38-76](https://github.com/chatbot-pf/shunt/blob/main/src/main.rs#L38-L76)
- [src/server.rs:13-25](https://github.com/chatbot-pf/shunt/blob/main/src/server.rs#L13-L25)
- [src/proxy.rs:19-126](https://github.com/chatbot-pf/shunt/blob/main/src/proxy.rs#L19-L126)
- [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89)
</doc>

<doc title="Adapters and Translation" path="src/content/docs/02-deep-dive/adapters-and-translation.md">
## Overview

Adapters are the protocol boundary. The Anthropic adapter preserves the Anthropic Messages shape and streams upstream bytes back to Claude Code. The Responses adapter rebuilds the request for OpenAI's Responses API, sends provider-specific credentials, and converts Responses SSE events into Anthropic SSE events through a state machine [src/adapters/anthropic.rs:31-104](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/anthropic.rs#L31-L104) [src/adapters/responses.rs:34-213](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/responses.rs#L34-L213) [src/model/responses_request.rs:4-280](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses_request.rs#L4-L280) [src/model/responses.rs:45-378](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses.rs#L45-L378).

| Adapter | Provider kinds | Mutation level | Streaming behavior | Source |
|---|---|---|---|---|
| `AnthropicAdapter` | `kind = "anthropic"` | Header credential swap only for API-key providers; body is unchanged | Relays upstream `bytes_stream()` | [src/adapters/anthropic.rs:31-104](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/anthropic.rs#L31-L104) |
| `ResponsesAdapter` | `kind = "responses"` | Full request rebuild into Responses shape | Parses upstream SSE and emits Anthropic SSE | [src/adapters/responses.rs:34-213](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/responses.rs#L34-L213) |
| `translate_request` | Responses providers | Maps system/messages/tools/effort into Responses request | Always requests upstream streaming | [src/model/responses_request.rs:4-280](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses_request.rs#L4-L280) |
| `AnthropicSseMachine` | Responses providers | Converts response events into content blocks and final JSON | Supports stream and non-stream output | [src/model/responses.rs:62-113](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses.rs#L62-L113) |

## Adapter Dispatch

```mermaid
flowchart TB
    Route[Route adapter] --> Choice{AdapterKind}
    Choice -->|Anthropic| A[AnthropicAdapter.forward]
    Choice -->|Responses| R[ResponsesAdapter.forward]
    A --> AU[upstream_url + filtered headers]
    R --> TR[translate_request]
    R --> RB[request_builder + responses_url]
    R --> SM[AnthropicSseMachine]
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class Route,Choice,A,R,AU,TR,RB,SM dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: src/proxy.rs:113, src/adapters/anthropic.rs:18, src/adapters/responses.rs:21, src/model/responses_request.rs:4, src/model/responses.rs:24 -->

## Request Translation

```mermaid
flowchart LR
    AM[Anthropic Messages JSON] --> System[system to instructions]
    AM --> Messages[messages to input items]
    AM --> Tools[tools to function tools]
    AM --> Choice[tool_choice mapping]
    AM --> Effort[effort mapping]
    System --> OR[OpenAI Responses request]
    Messages --> OR
    Tools --> OR
    Choice --> OR
    Effort --> OR
    OR --> Stream[stream true and store false]
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class AM,System,Messages,Tools,Choice,Effort,OR,Stream dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: src/model/responses_request.rs:4, src/model/responses_request.rs:31, src/model/responses_request.rs:47, src/model/responses_request.rs:198, src/model/responses_request.rs:233, src/model/responses_request.rs:254 -->

## Streaming Conversion

```mermaid
sequenceDiagram
    autonumber
    participant Up as Responses upstream
    participant Parser as SseParser
    participant Machine as AnthropicSseMachine
    participant CC as Claude Code
    Up-->>Parser: response.created
    Parser->>Machine: ResponseEvent
    Machine-->>CC: message_start + ping
    Up-->>Parser: response.output_item.added
    Machine-->>CC: content_block_start
    Up-->>Parser: response.output_text.delta
    Machine-->>CC: content_block_delta
    Up-->>Parser: response.completed
    Machine-->>CC: message_delta + message_stop
```
<!-- Sources: src/adapters/responses.rs:68, src/adapters/responses.rs:226, src/model/responses.rs:62, src/model/responses.rs:115, src/model/responses.rs:206, src/model/responses.rs:273 -->

## SSE State Machine

```mermaid
stateDiagram-v2
    [*] --> Idle
    Idle --> Started: response.created
    Started --> TextOpen: message output item
    Started --> ToolOpen: function_call output item
    TextOpen --> TextOpen: output_text.delta
    ToolOpen --> ToolOpen: function_call_arguments.delta
    TextOpen --> Started: output_text.done
    ToolOpen --> Started: arguments.done
    Started --> Completed: response.completed
    Started --> Error: response.failed or error
    Completed --> [*]
    Error --> [*]
```
<!-- Sources: src/model/responses.rs:62, src/model/responses.rs:146, src/model/responses.rs:206, src/model/responses.rs:222, src/model/responses.rs:273 -->

## Translation Coverage

| Anthropic concept | Responses concept | Implementation | Test coverage | Source |
|---|---|---|---|---|
| `system` string/blocks | `instructions` | `instructions()` | Plain-text request test | [src/model/responses_request.rs:31-45](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses_request.rs#L31-L45) [tests/responses_translate.rs:25-50](https://github.com/chatbot-pf/shunt/blob/main/tests/responses_translate.rs#L25-L50) |
| User text blocks | `input_text` | `text_part()` | Multi-turn role test | [src/model/responses_request.rs:113-123](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses_request.rs#L113-L123) [tests/responses_translate.rs:52-71](https://github.com/chatbot-pf/shunt/blob/main/tests/responses_translate.rs#L52-L71) |
| Images | Data URL `input_image` | `image_part()` | Image translation test | [src/model/responses_request.rs:126-138](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses_request.rs#L126-L138) [tests/responses_translate.rs:98-119](https://github.com/chatbot-pf/shunt/blob/main/tests/responses_translate.rs#L98-L119) |
| Tool use | `function_call` | `tool_use_item()` | Tool call ID test | [src/model/responses_request.rs:140-148](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses_request.rs#L140-L148) [tests/responses_translate.rs:73-96](https://github.com/chatbot-pf/shunt/blob/main/tests/responses_translate.rs#L73-L96) |
| Reasoning effort | `reasoning.effort` | `effort()` | Thinking and override test | [src/model/responses_request.rs:254-280](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses_request.rs#L254-L280) [tests/responses_translate.rs:164-178](https://github.com/chatbot-pf/shunt/blob/main/tests/responses_translate.rs#L164-L178) |

## Error Mapping

Upstream errors on the `responses` path are re-shaped into the Anthropic error envelope by `map_error_value`, with the `error.type` derived from the upstream status (401 → `authentication_error`, 429 → `rate_limit_error`, 400 → `invalid_request_error`, other → `api_error`) and the upstream message preserved ([src/model/responses.rs:532-563](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses.rs#L532-L563)).

Context-overflow errors are the one case where the message itself is rewritten: Claude Code's automatic compact-and-retry matches the literal phrase `prompt is too long` and parses `N tokens > M maximum` to size the retry, so upstream phrasings (`context_length_exceeded`, "maximum context length is N tokens", "prompt token count of N exceeds the limit of M") are detected and rewritten to that shape, keeping the token counts when the upstream message carries them ([src/model/responses.rs:566-616](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses.rs#L566-L616), [tests/responses_translate.rs:368-417](https://github.com/chatbot-pf/shunt/blob/main/tests/responses_translate.rs#L368-L417)).

## Related Pages

| Page | Relationship |
|---|---|
| [Architecture](./architecture.md) | Shows adapter layer in context |
| [Routing and Configuration](./routing-and-configuration.md) | Explains how a provider selects an adapter |
| [Authentication](./authentication.md) | Explains credentials adapters consume |
| [Testing and Quality](./testing-and-quality.md) | Shows tests that lock translation behavior |

## References

- [src/adapters/anthropic.rs:31-104](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/anthropic.rs#L31-L104)
- [src/adapters/responses.rs:34-213](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/responses.rs#L34-L213)
- [src/model/responses_request.rs:4-280](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses_request.rs#L4-L280)
- [src/model/responses.rs:45-378](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses.rs#L45-L378)
- [tests/responses_translate.rs:25-287](https://github.com/chatbot-pf/shunt/blob/main/tests/responses_translate.rs#L25-L287)
</doc>

<doc title="Architecture" path="src/content/docs/02-deep-dive/architecture.md">
## Overview

The core architectural insight is that shunt is not a second agent runtime. It is a protocol-preserving gateway inserted at Claude Code's inference boundary. Because Claude Code already sends a `model` field, shunt can stay stateless for routing: parse the model, choose a provider, and hand the request to the adapter responsible for that provider's protocol [docs/implementation-plan.md:6-249](https://github.com/chatbot-pf/shunt/blob/main/docs/implementation-plan.md#L6-L249) [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89).

| Component | Responsibility | Key file | Source |
|---|---|---|---|
| `AppState` | Holds validated config and shared reqwest client | `src/server.rs` | [src/server.rs:13-25](https://github.com/chatbot-pf/shunt/blob/main/src/server.rs#L13-L25) |
| `proxy::post` | Entry handler for inference and count-token posts | `src/proxy.rs` | [src/proxy.rs:19-126](https://github.com/chatbot-pf/shunt/blob/main/src/proxy.rs#L19-L126) |
| `Route` | Carries provider, adapter kind, model, upstream model, effort | `src/routing.rs` | [src/routing.rs:23-31](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L23-L31) |
| `Adapter` trait | Hides provider protocol differences behind one async `forward` | `src/adapters/mod.rs` | [src/adapters/mod.rs:21-30](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/mod.rs#L21-L30) |
| `Credential` | Represents pass-through, API-key, and ChatGPT OAuth modes | `src/auth/mod.rs` | [src/auth/mod.rs:17-28](https://github.com/chatbot-pf/shunt/blob/main/src/auth/mod.rs#L17-L28) |
| `AnthropicSseMachine` | Converts Responses event streams into Anthropic event streams | `src/model/responses.rs` | [src/model/responses.rs:62-113](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses.rs#L62-L113) |

## System Architecture

```mermaid
graph TB
    subgraph Entry[Process entry]
      CLI[src/main.rs CLI]
      Config[Config load and validate]
    end
    subgraph HTTP[HTTP gateway]
      Router[server::build_router]
      Proxy[proxy::post]
      Discovery[discovery::get]
    end
    subgraph RoutingCore[Routing core]
      Resolver[routing::resolve_model]
      Route[Route]
    end
    subgraph AdapterLayer[Adapter layer]
      Anthropic[AnthropicAdapter]
      Responses[ResponsesAdapter]
    end
    subgraph Models[Translation model]
      Request[translate_request]
      SSE[AnthropicSseMachine]
    end
    CLI --> Config --> Router
    Router --> Proxy --> Resolver --> Route
    Router --> Discovery
    Route --> Anthropic
    Route --> Responses --> Request
    Responses --> SSE
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class CLI,Config,Router,Proxy,Discovery,Resolver,Route,Anthropic,Responses,Request,SSE dark;
    style Entry fill:#161b22,stroke:#30363d,color:#e6edf3;
    style HTTP fill:#161b22,stroke:#30363d,color:#e6edf3;
    style RoutingCore fill:#161b22,stroke:#30363d,color:#e6edf3;
    style AdapterLayer fill:#161b22,stroke:#30363d,color:#e6edf3;
    style Models fill:#161b22,stroke:#30363d,color:#e6edf3;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: src/main.rs:38, src/config.rs:185, src/server.rs:13, src/proxy.rs:19, src/routing.rs:48, src/adapters/mod.rs:21, src/model/responses.rs:24 -->

## Core Types

```mermaid
classDiagram
    class Config {
      ServerConfig server
      ProvidersConfig providers
      Vec_ModelConfig models
      Vec_RouteConfig routes
      Vec_RoutePrefixConfig route_prefixes
      load(path)
      validate()
    }
    class Route {
      String provider
      AdapterKind adapter
      String model
      String upstream_model
      Option_String effort
    }
    class Adapter {
      forward(state, route, uri, headers, body)
    }
    class AnthropicAdapter
    class ResponsesAdapter
    class Credential {
      Passthrough
      ApiKey
      ChatGptOAuth
    }
    Config --> Route
    Route --> Adapter
    Adapter <|.. AnthropicAdapter
    Adapter <|.. ResponsesAdapter
    ResponsesAdapter --> Credential
    AnthropicAdapter --> Credential
```
<!-- Sources: src/config.rs:9, src/routing.rs:23, src/adapters/mod.rs:21, src/adapters/anthropic.rs:18, src/adapters/responses.rs:21, src/auth/mod.rs:17 -->

## Request Sequence

```mermaid
sequenceDiagram
    autonumber
    participant Main as main::run
    participant Server as server::build_router
    participant Proxy as proxy::post
    participant Routing as routing::resolve
    participant Auth as resolve_credential
    participant Adapter as Adapter impl
    participant Model as Translation state
    Main->>Server: build router with Config
    Server->>Proxy: dispatch POST /v1/messages
    Proxy->>Routing: resolve(config, body bytes)
    Routing-->>Proxy: Route
    Proxy->>Adapter: forward(...)
    Adapter->>Auth: provider credential
    Adapter->>Model: translate or stream-convert if Responses
    Adapter-->>Proxy: status + response
```
<!-- Sources: src/main.rs:73, src/server.rs:19, src/proxy.rs:39, src/routing.rs:37, src/auth/mod.rs:29, src/adapters/responses.rs:43 -->

## State and Failure Paths

```mermaid
stateDiagram-v2
    [*] --> ValidatingConfig
    ValidatingConfig --> Serving: Config validate ok
    ValidatingConfig --> ConfigError: bad bind/provider/route
    Serving --> BodyBuffered: POST body within cap
    BodyBuffered --> RouteResolved: model JSON parsed
    BodyBuffered --> BadRequest: invalid model JSON
    RouteResolved --> CredentialResolved
    CredentialResolved --> UpstreamCall
    CredentialResolved --> AuthenticationError: missing provider credential
    UpstreamCall --> SuccessStream
    UpstreamCall --> MappedError: upstream error or transport failure
    SuccessStream --> Serving
    MappedError --> Serving
```
<!-- Sources: src/config.rs:196, src/proxy.rs:17, src/routing.rs:37, src/auth/mod.rs:82, src/adapters/responses.rs:128, src/error.rs:40 -->

## Architectural Invariants

| Invariant | Enforced by | Why it matters | Source |
|---|---|---|---|
| Request bodies are buffered only to choose a route | `to_bytes(..., 64 MiB)` before `routing::resolve` | Routing needs `model`, while responses must still stream | [src/proxy.rs:17-112](https://github.com/chatbot-pf/shunt/blob/main/src/proxy.rs#L17-L112) |
| Response streaming is preserved | `Body::from_stream` for both pass-through and Responses | Claude Code expects incremental SSE | [src/adapters/anthropic.rs:49-62](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/anthropic.rs#L49-L62) [src/adapters/responses.rs:68-111](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/responses.rs#L68-L111) |
| Route precedence is deterministic | `resolve_model` loops exact routes before prefixes before default | Prevents broad prefixes from shadowing explicit mappings | [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89) |
| Adapter selection follows provider kind | `ProviderKind` to `AdapterKind` conversion | Provider config controls protocol boundary | [src/routing.rs:14-21](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L14-L21) |
| Gateway-owned errors use Anthropic error envelope | `ShuntError` and `UpstreamError` `IntoResponse` | Claude Code can interpret failures consistently | [src/error.rs:7-91](https://github.com/chatbot-pf/shunt/blob/main/src/error.rs#L7-L91) |

## Related Pages

| Page | Relationship |
|---|---|
| [Routing and Configuration](./routing-and-configuration.md) | Deep dive into config and route resolver |
| [Adapters and Translation](./adapters-and-translation.md) | Deep dive into adapter internals |
| [Authentication](./authentication.md) | Credential resolution and refresh |
| [Testing and Quality](./testing-and-quality.md) | Tests that enforce the architecture |

## References

- [src/main.rs:38-76](https://github.com/chatbot-pf/shunt/blob/main/src/main.rs#L38-L76)
- [src/server.rs:13-25](https://github.com/chatbot-pf/shunt/blob/main/src/server.rs#L13-L25)
- [src/proxy.rs:19-126](https://github.com/chatbot-pf/shunt/blob/main/src/proxy.rs#L19-L126)
- [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89)
- [src/config.rs:9-269](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L9-L269)
- [src/adapters/responses.rs:34-213](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/responses.rs#L34-L213)
</doc>

<doc title="Authentication" path="src/content/docs/02-deep-dive/authentication.md">
## Overview

Authentication is intentionally route-scoped. Once routing selects a provider, shunt resolves the credential strategy declared by that provider: pass through the Claude Code credential, read an API key, or reuse and refresh ChatGPT/Codex OAuth tokens. That separation keeps Claude Code from needing provider-specific secrets for mapped models [src/auth/mod.rs:29-99](https://github.com/chatbot-pf/shunt/blob/main/src/auth/mod.rs#L29-L99) [src/auth/codex_auth.rs:34-63](https://github.com/chatbot-pf/shunt/blob/main/src/auth/codex_auth.rs#L34-L63) [src/auth/claude_auth.rs:27-92](https://github.com/chatbot-pf/shunt/blob/main/src/auth/claude_auth.rs#L27-L92).

| Credential mode | Used by | Secret source | Boundary | Source |
|---|---|---|---|---|
| `Passthrough` | Anthropic default provider | Inbound Claude Code headers | Forwarded unchanged | [src/auth/mod.rs:17-22](https://github.com/chatbot-pf/shunt/blob/main/src/auth/mod.rs#L17-L22) [src/adapters/anthropic.rs:66-89](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/anthropic.rs#L66-L89) |
| `ApiKey` | OpenAI and Anthropic-compatible gateways | Env var named by `api_key_env`, with OpenAI fallback to Codex auth JSON | Injected by shunt | [src/auth/mod.rs:57-80](https://github.com/chatbot-pf/shunt/blob/main/src/auth/mod.rs#L57-L80) |
| `ChatGptOAuth` | `codex` provider | `~/.codex/auth.json` | Refreshed and sent with account ID | [src/auth/codex_auth.rs:34-63](https://github.com/chatbot-pf/shunt/blob/main/src/auth/codex_auth.rs#L34-L63) |
| `XaiOauth` | `xai` provider (opt-in) | `~/.shunt/xai-auth.json` from `shunt login xai` (RFC 8628 device code) | Refreshed under a single-flight lock (xAI rotates the refresh token), sent as Bearer only; base_url validated to stay on `x.ai` | [src/auth/xai_auth.rs:87-121](https://github.com/chatbot-pf/shunt/blob/main/src/auth/xai_auth.rs#L87-L121) [src/auth/xai_login.rs:52-100](https://github.com/chatbot-pf/shunt/blob/main/src/auth/xai_login.rs#L52-L100) |
| Claude token helper | Claude Code gateway discovery/pass-through credential | `SHUNT_GATEWAY_TOKEN`, `CLAUDE_CODE_OAUTH_TOKEN`, or `~/.claude/.credentials.json` | Printed to stdout for `apiKeyHelper` | [src/auth/claude_auth.rs:27-92](https://github.com/chatbot-pf/shunt/blob/main/src/auth/claude_auth.rs#L27-L92) |

## Credential Resolution Flow

```mermaid
flowchart TB
    Route[Route provider] --> Provider[Config provider]
    Provider --> Auth{AuthMode}
    Auth -->|passthrough| Pass[Credential Passthrough]
    Auth -->|api_key| Env[Read api_key_env]
    Env -->|found| Api[Credential ApiKey]
    Env -->|OPENAI_API_KEY missing| CodexKey[Read OPENAI_API_KEY from codex auth]
    CodexKey --> Api
    Auth -->|chatgpt_oauth| Store[CodexAuthStore]
    Store --> Valid{Token valid beyond 5 min?}
    Valid -->|yes| Chat[Credential ChatGptOAuth]
    Valid -->|no| Refresh[Refresh token and atomic write-back]
    Refresh --> Chat
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class Route,Provider,Auth,Pass,Env,Api,CodexKey,Store,Valid,Refresh,Chat dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: src/auth/mod.rs:29, src/auth/mod.rs:57, src/auth/codex_auth.rs:34, src/auth/codex_auth.rs:125, src/auth/codex_auth.rs:207 -->

## Token Refresh Sequence

```mermaid
sequenceDiagram
    autonumber
    participant Adapter as ResponsesAdapter
    participant Auth as resolve_credential
    participant Store as CodexAuthStore
    participant File as codex auth json
    participant OAuth as auth.openai.com
    Adapter->>Auth: route provider codex
    Auth->>Store: get_valid_chatgpt()
    Store->>File: read auth JSON
    Store->>Store: check JWT expiry with buffer
    alt expired
      Store->>OAuth: refresh_token grant
      OAuth-->>Store: new access/refresh/id token
      Store->>File: atomic write-back 0600
    end
    Store-->>Auth: access_token + account_id
    Auth-->>Adapter: Credential ChatGptOAuth
```
<!-- Sources: src/adapters/responses.rs:51, src/auth/mod.rs:44, src/auth/codex_auth.rs:34, src/auth/codex_auth.rs:172, src/auth/codex_auth.rs:213 -->

## Trust Boundaries

```mermaid
graph LR
    CC[Claude Code credential] -->|passthrough only| Anthropic[Anthropic-compatible upstream]
    Env[Provider API key env] -->|injected by shunt| OpenAI[OpenAI-compatible upstream]
    CodexFile[Codex auth JSON] -->|read and refresh by shunt| ChatGPT[ChatGPT Codex backend]
    ClaudeFile[Claude credentials JSON] -->|optional token helper| CC
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class CC,Anthropic,Env,OpenAI,CodexFile,ChatGPT,ClaudeFile dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: src/adapters/anthropic.rs:66, src/auth/mod.rs:57, src/auth/codex_auth.rs:201, src/auth/claude_auth.rs:94 -->

## Credential States

```mermaid
stateDiagram-v2
    [*] --> Missing
    Missing --> Present: env or file exists
    Present --> Valid: expiry beyond buffer
    Present --> RefreshNeeded: within five-minute buffer
    RefreshNeeded --> Valid: refresh succeeds
    RefreshNeeded --> Error: refresh fails
    Valid --> Injected: adapter builds request
    Error --> AuthenticationError
```
<!-- Sources: src/auth/mod.rs:82, src/auth/codex_auth.rs:125, src/auth/codex_auth.rs:172, src/adapters/responses.rs:174, src/adapters/anthropic.rs:71 -->

## Related Pages

| Page | Relationship |
|---|---|
| [Configuration](../01-getting-started/configuration.md) | Shows user-facing auth modes |
| [Operations](../01-getting-started/operations.md) | Shows `codex login`, `OPENAI_API_KEY`, and `shunt token` usage |
| [Adapters and Translation](./adapters-and-translation.md) | Shows where credentials are injected |
| [Testing and Quality](./testing-and-quality.md) | Covers auth-related unit tests |

## References

- [src/auth/mod.rs:29-99](https://github.com/chatbot-pf/shunt/blob/main/src/auth/mod.rs#L29-L99)
- [src/auth/codex_auth.rs:34-63](https://github.com/chatbot-pf/shunt/blob/main/src/auth/codex_auth.rs#L34-L63)
- [src/auth/codex_auth.rs:103-129](https://github.com/chatbot-pf/shunt/blob/main/src/auth/codex_auth.rs#L103-L129)
- [src/auth/claude_auth.rs:27-92](https://github.com/chatbot-pf/shunt/blob/main/src/auth/claude_auth.rs#L27-L92)
- [src/adapters/anthropic.rs:66-89](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/anthropic.rs#L66-L89)
</doc>

<doc title="Routing and Configuration" path="src/content/docs/02-deep-dive/routing-and-configuration.md">
## Overview

Routing and configuration are deliberately simple: the config file describes providers and model mappings, and the resolver turns a request body's `model` string into a `Route`. The design favors data changes over code changes, which is why provider definitions are a `BTreeMap<String, ProviderConfig>` and why new Anthropic-compatible gateways can be added as TOML tables [src/config.rs:9-269](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L9-L269) [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89).

| Topic | Mechanism | Key file | Source |
|---|---|---|---|
| Config structure | `Config`, `ServerConfig`, `ProviderConfig`, route structs | `src/config.rs` | [src/config.rs:9-107](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L9-L107) |
| Defaults | Built-in `anthropic`, `openai`, `codex` providers | `src/config.rs` | [src/config.rs:142-183](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L142-L183) |
| Loading | Figment defaults to TOML to `SHUNT_` env | `src/config.rs` | [src/config.rs:185-194](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L185-L194) |
| Validation | URL, bind, auth, provider references | `src/config.rs` | [src/config.rs:196-242](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L196-L242) |
| Routing | Exact to prefix to default | `src/routing.rs` | [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89) |

## Config Entity Model

```mermaid
erDiagram
    SERVER_CONFIG {
      string bind
      string default_provider
    }
    PROVIDER_CONFIG {
      string kind
      string base_url
      string auth
      string api_key_env
      string api_key_header
      string effort
    }
    ROUTE_CONFIG {
      string model
      string provider
      string upstream_model
      string effort
    }
    ROUTE_PREFIX_CONFIG {
      string prefix
      string provider
    }
    MODEL_CONFIG {
      string id
      string display_name
    }
    CONFIG ||--|| SERVER_CONFIG : contains
    CONFIG ||--o{ PROVIDER_CONFIG : providers
    CONFIG ||--o{ ROUTE_CONFIG : routes
    CONFIG ||--o{ ROUTE_PREFIX_CONFIG : route_prefixes
    CONFIG ||--o{ MODEL_CONFIG : models
```
<!-- Sources: src/config.rs:9, src/config.rs:27, src/config.rs:89, src/config.rs:97, src/config.rs:103 -->

## Loading and Validation Flow

```mermaid
flowchart TB
    Defaults[Serialized defaults] --> File[TOML file]
    File --> Env[SHUNT_ env]
    Env --> Extract[Figment extract Config]
    Extract --> Bind[Validate bind address]
    Bind --> Urls[Validate provider base URLs]
    Urls --> Auth[Validate api_key_env]
    Auth --> Refs[Validate default/route/prefix provider refs]
    Refs --> Warn[Warn discovery model without route]
    Warn --> Ok[Validated Config]
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class Defaults,File,Env,Extract,Bind,Urls,Auth,Refs,Warn,Ok dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: src/config.rs:185, src/config.rs:196, src/config.rs:198, src/config.rs:200, src/config.rs:212, src/config.rs:233 -->

## Routing Sequence

```mermaid
sequenceDiagram
    autonumber
    participant Proxy as proxy::forward
    participant Routing as routing::resolve
    participant JSON as serde_json
    participant Config as Config
    Proxy->>Routing: body bytes
    Routing->>JSON: parse RoutingView with model
    JSON-->>Routing: model string
    Routing->>Config: scan exact routes
    Routing->>Config: scan prefix routes
    Routing->>Config: fallback default provider
    Routing-->>Proxy: Route with AdapterKind
```
<!-- Sources: src/proxy.rs:93, src/routing.rs:32, src/routing.rs:37, src/routing.rs:49, src/routing.rs:60, src/routing.rs:65 -->

## Route Decision Table

| Input condition | Selected provider | Upstream model | Effort source | Source |
|---|---|---|---|---|
| Exact `route.model == model` | `route.provider` | `route.upstream_model` or original model | Route override, then provider default | [src/routing.rs:49-58](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L49-L58) |
| Prefix `model.starts_with(prefix)` | Prefix provider | Original model | Provider default | [src/routing.rs:60-64](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L60-L64) |
| No match | `server.default_provider` | Original model | Provider default | [src/routing.rs:65-66](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L65-L66) |
| Unknown provider after validation | Fallback adapter is Anthropic | Original model | None/provider default if present | [src/routing.rs:75-82](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L75-L82) |

## Adapter Selection

```mermaid
flowchart LR
    ProviderKind[ProviderKind] -->|anthropic| AnthropicKind[AdapterKind Anthropic]
    ProviderKind -->|responses| ResponsesKind[AdapterKind Responses]
    AnthropicKind --> Pass[Pass-through adapter]
    ResponsesKind --> Translate[Responses adapter]
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class ProviderKind,AnthropicKind,ResponsesKind,Pass,Translate dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: src/config.rs:52, src/routing.rs:14, src/adapters/anthropic.rs:16, src/adapters/responses.rs:19 -->

## Related Pages

| Page | Relationship |
|---|---|
| [Configuration](../01-getting-started/configuration.md) | User-facing configuration reference |
| [Architecture](./architecture.md) | Shows route resolver in the full runtime |
| [Authentication](./authentication.md) | Explains how selected providers get credentials |
| [Adapters and Translation](./adapters-and-translation.md) | Explains what each adapter does after routing |

## References

- [src/config.rs:9-269](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L9-L269)
- [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89)
- [shunt.toml.example:1-134](https://github.com/chatbot-pf/shunt/blob/main/shunt.toml.example#L1-L134)
- [src/routing.rs:97-134](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L97-L134)
- [docs/running.md:40-159](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L40-L159)
</doc>

<doc title="Testing and Quality" path="src/content/docs/02-deep-dive/testing-and-quality.md">
## Overview

Testing exists because shunt's correctness is mostly protocol preservation. The dangerous failures are subtle: a beta header split incorrectly, an SSE stream buffered until the end, a tool call ID lost during translation, or an upstream error hidden behind a generic gateway error. The repository uses colocated unit tests plus Wiremock integration tests to lock those behaviors down [tests/passthrough.rs:72-247](https://github.com/chatbot-pf/shunt/blob/main/tests/passthrough.rs#L72-L247) [tests/responses_translate.rs:25-287](https://github.com/chatbot-pf/shunt/blob/main/tests/responses_translate.rs#L25-L287).

| Test layer | Scope | Representative file | Source |
|---|---|---|---|
| Unit tests | Config, routing, auth, adapters, translation helpers | `src/**` module tests | [src/routing.rs:97-134](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L97-L134) [src/config.rs:301-391](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L301-L391) |
| Integration tests | Axum gateway with mock upstream | `tests/passthrough.rs` | [tests/passthrough.rs:72-247](https://github.com/chatbot-pf/shunt/blob/main/tests/passthrough.rs#L72-L247) |
| Translation tests | Request conversion, SSE state machine, error mapping | `tests/responses_translate.rs` | [tests/responses_translate.rs:25-287](https://github.com/chatbot-pf/shunt/blob/main/tests/responses_translate.rs#L25-L287) |
| CI | Format, clippy with warnings denied, test suite | `.github/workflows/ci.yml` | [.github/workflows/ci.yml:1-42](https://github.com/chatbot-pf/shunt/blob/main/.github/workflows/ci.yml#L1-L42) |

## Test Architecture

```mermaid
graph TB
    subgraph Unit[Colocated unit tests]
      Config[config.rs tests]
      Routing[routing.rs tests]
      Auth[auth tests]
      Adapter[adapter tests]
    end
    subgraph Integration[tests]
      Pass[passthrough.rs]
      Translate[responses_translate.rs]
    end
    subgraph CI[GitHub Actions]
      Fmt[cargo fmt]
      Clippy[cargo clippy -D warnings]
      CargoTest[cargo test]
    end
    Unit --> CargoTest
    Integration --> CargoTest
    Fmt --> Clippy --> CargoTest
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class Config,Routing,Auth,Adapter,Pass,Translate,Fmt,Clippy,CargoTest dark;
    style Unit fill:#161b22,stroke:#30363d,color:#e6edf3;
    style Integration fill:#161b22,stroke:#30363d,color:#e6edf3;
    style CI fill:#161b22,stroke:#30363d,color:#e6edf3;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: src/routing.rs:91, src/config.rs:277, tests/passthrough.rs:72, tests/responses_translate.rs:25, .github/workflows/ci.yml:35 -->

## Pass-through Integration Flow

```mermaid
sequenceDiagram
    autonumber
    participant Test as Wiremock test
    participant Gateway as shunt gateway
    participant Mock as Mock upstream
    Test->>Mock: mount expected request matchers
    Test->>Gateway: POST /v1/messages or count_tokens
    Gateway->>Mock: forwarded request
    Mock-->>Gateway: configured status/body/stream
    Gateway-->>Test: response
    Test->>Mock: verify expected calls
```
<!-- Sources: tests/passthrough.rs:90, tests/passthrough.rs:96, tests/passthrough.rs:110, tests/passthrough.rs:119, tests/passthrough.rs:120 -->

## Translation Test Coverage

```mermaid
flowchart LR
    Input[Anthropic fixture] --> Translate[translate_request]
    Translate --> Assertions[JSON assertions]
    SSE[Responses SSE fixture] --> Parse[parse_sse_events]
    Parse --> Machine[AnthropicSseMachine]
    Machine --> EventAssertions[Event name and payload assertions]
    Error[OpenAI/Codex error shapes] --> Map[map_error_value]
    Map --> ErrorAssertions[Anthropic error assertions]
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class Input,Translate,Assertions,SSE,Parse,Machine,EventAssertions,Error,Map,ErrorAssertions dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: tests/responses_translate.rs:20, tests/responses_translate.rs:25, tests/responses_translate.rs:180, tests/responses_translate.rs:234, tests/responses_translate.rs:255 -->

## CI State Machine

```mermaid
stateDiagram-v2
    [*] --> Checkout
    Checkout --> RustToolchain
    RustToolchain --> CacheCargo
    CacheCargo --> FormatCheck
    FormatCheck --> Clippy
    Clippy --> Tests
    Tests --> Success
    FormatCheck --> Failed: formatting differs
    Clippy --> Failed: warning or lint
    Tests --> Failed: test failure
    Success --> [*]
```
<!-- Sources: .github/workflows/ci.yml:23, .github/workflows/ci.yml:27, .github/workflows/ci.yml:32, .github/workflows/ci.yml:35, .github/workflows/ci.yml:38, .github/workflows/ci.yml:41 -->

## Quality Gates

| Gate | Command | What it protects | Source |
|---|---|---|---|
| Formatting | `cargo fmt --all --check` | Consistent Rust style | [.github/workflows/ci.yml:1-42](https://github.com/chatbot-pf/shunt/blob/main/.github/workflows/ci.yml#L1-L42) |
| Lints | `cargo clippy --all-targets --all-features -- -D warnings` | No warnings in all targets/features | [.github/workflows/ci.yml:1-42](https://github.com/chatbot-pf/shunt/blob/main/.github/workflows/ci.yml#L1-L42) |
| Tests | `cargo test --all-features --workspace` | Unit and integration behavior | [.github/workflows/ci.yml:1-42](https://github.com/chatbot-pf/shunt/blob/main/.github/workflows/ci.yml#L1-L42) |
| PR checklist | Build, test, clippy, fmt, 500-line limit, spec sync | Review discipline | [.github/PULL_REQUEST_TEMPLATE.md:1-26](https://github.com/chatbot-pf/shunt/blob/main/.github/PULL_REQUEST_TEMPLATE.md#L1-L26) |
| Contribution guide | Same commands plus SHA-pinned GitHub Actions | Contributor expectations | [CONTRIBUTING.md:1-52](https://github.com/chatbot-pf/shunt/blob/main/CONTRIBUTING.md#L1-L52) |

## Related Pages

| Page | Relationship |
|---|---|
| [Operations](../01-getting-started/operations.md) | Shows the commands CI runs |
| [Adapters and Translation](./adapters-and-translation.md) | Explains the behavior translation tests protect |
| [Routing and Configuration](./routing-and-configuration.md) | Explains config/routing unit tests |
| [Contributor Guide](../onboarding/contributor-guide.md) | Contributor workflow and first task guidance |

## References

- [tests/passthrough.rs:72-247](https://github.com/chatbot-pf/shunt/blob/main/tests/passthrough.rs#L72-L247)
- [tests/responses_translate.rs:25-287](https://github.com/chatbot-pf/shunt/blob/main/tests/responses_translate.rs#L25-L287)
- [.github/workflows/ci.yml:1-42](https://github.com/chatbot-pf/shunt/blob/main/.github/workflows/ci.yml#L1-L42)
- [CONTRIBUTING.md:1-52](https://github.com/chatbot-pf/shunt/blob/main/CONTRIBUTING.md#L1-L52)
- [src/routing.rs:97-134](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L97-L134)
</doc>

<doc title="shunt Wiki" path="src/content/docs/index.md">
`shunt` is a Rust Claude Code LLM gateway that exposes the Anthropic Messages gateway surface, routes by request `model`, passes unmapped models through to Anthropic, and translates mapped OpenAI-family models to the OpenAI Responses API. The CLI starts an Axum server, the router chooses a provider from TOML/env configuration, adapters either stream pass-through bytes or translate Responses SSE, and auth helpers resolve OpenAI, ChatGPT/Codex, and Claude gateway credentials [src/main.rs:38-76](https://github.com/chatbot-pf/shunt/blob/main/src/main.rs#L38-L76) [src/server.rs:13-25](https://github.com/chatbot-pf/shunt/blob/main/src/server.rs#L13-L25) [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89) [src/adapters/responses.rs:34-213](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/responses.rs#L34-L213).

## Quick Start

| Step | Command | Expected result | Source |
|---|---|---|---|
| Build | `cargo build --release` | `target/release/shunt` is produced | [docs/running.md:26-37](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L26-L37) |
| Create config | `cp shunt.toml.example shunt.toml` | Local editable TOML config | [docs/running.md:40-55](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L40-L55) |
| Validate config | `./target/release/shunt check` | Prints `config ok` or a typed error | [src/main.rs:77-83](https://github.com/chatbot-pf/shunt/blob/main/src/main.rs#L77-L83) |
| Run gateway | `./target/release/shunt run` | Logs `shunt listening` | [src/main.rs:38-76](https://github.com/chatbot-pf/shunt/blob/main/src/main.rs#L38-L76) |
| Connect Claude Code | `export ANTHROPIC_BASE_URL=http://127.0.0.1:3001` | Claude Code sends gateway traffic locally | [docs/running.md:189-393](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L189-L393) |

```mermaid
flowchart LR
    CC[Claude Code] -->|ANTHROPIC_BASE_URL| S[shunt Axum gateway]
    S --> R{Route by model}
    R -->|unmapped| A[Anthropic Messages passthrough]
    R -->|mapped responses provider| O[OpenAI Responses translation]
    O --> C[OpenAI or ChatGPT Codex backend]
    A --> API[api.anthropic.com or compatible gateway]
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class CC,S,R,A,O,C,API dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: README.md:4, src/server.rs:13, src/routing.rs:37, src/adapters/anthropic.rs:31, src/adapters/responses.rs:34 -->

## Documentation Map

| Section | Purpose | Start here when... |
|---|---|---|
| [Onboarding](./onboarding/) | Audience-specific guides for contributors, staff engineers, executives, and PMs | You are new to the project |
| [Getting Started](./01-getting-started/overview.md) | Product overview, setup, config, and operations | You want to run or configure shunt |
| [Deep Dive](./02-deep-dive/architecture.md) | Architecture, routing, adapters, auth, and testing | You need to modify internals |

## Key Files

| File | Responsibility | Source |
|---|---|---|
| `src/main.rs` | CLI, tracing, `run`, `check`, and `token` commands | [src/main.rs:38-76](https://github.com/chatbot-pf/shunt/blob/main/src/main.rs#L38-L76) |
| `src/server.rs` | Axum router and shared `AppState` | [src/server.rs:13-25](https://github.com/chatbot-pf/shunt/blob/main/src/server.rs#L13-L25) |
| `src/proxy.rs` | Request buffering, routing, adapter dispatch, logging | [src/proxy.rs:19-126](https://github.com/chatbot-pf/shunt/blob/main/src/proxy.rs#L19-L126) |
| `src/routing.rs` | Exact route, prefix route, default-provider resolution | [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89) |
| `src/config.rs` | Typed config, defaults, figment TOML/env loading, validation | [src/config.rs:9-269](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L9-L269) |
| `src/adapters/anthropic.rs` | Pass-through Anthropic-compatible adapter | [src/adapters/anthropic.rs:31-104](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/anthropic.rs#L31-L104) |
| `src/adapters/responses.rs` | OpenAI Responses transport and streaming response conversion | [src/adapters/responses.rs:34-213](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/responses.rs#L34-L213) |
| `src/model/responses_request.rs` | Anthropic request to Responses request translation | [src/model/responses_request.rs:4-280](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses_request.rs#L4-L280) |
| `src/model/responses.rs` | Responses SSE to Anthropic SSE state machine | [src/model/responses.rs:45-378](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses.rs#L45-L378) |
| `src/auth/*` | Provider credential resolution and token refresh helpers | [src/auth/mod.rs:29-99](https://github.com/chatbot-pf/shunt/blob/main/src/auth/mod.rs#L29-L99) |

## Tech Stack Summary

| Layer | Technology | Why it exists | Source |
|---|---|---|---|
| CLI | `clap` | Provides `run`, `check`, and `token` command surface | [src/main.rs:7-35](https://github.com/chatbot-pf/shunt/blob/main/src/main.rs#L7-L35) |
| HTTP server | Axum | Serves Claude Code gateway endpoints and streams bodies | [src/server.rs:13-25](https://github.com/chatbot-pf/shunt/blob/main/src/server.rs#L13-L25) |
| Async runtime | Tokio | Drives server and HTTP client futures | [Cargo.toml:1-23](https://github.com/chatbot-pf/shunt/blob/main/Cargo.toml#L1-L23) |
| HTTP client | Reqwest with `rustls-tls` and `stream` | Streams upstream responses without OpenSSL | [Cargo.toml:1-23](https://github.com/chatbot-pf/shunt/blob/main/Cargo.toml#L1-L23) |
| Config | Figment + TOML + env | Merges defaults, file config, and `SHUNT_` overrides | [src/config.rs:185-194](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L185-L194) |
| Observability | Tracing | Logs per-request span fields and latency | [src/proxy.rs:26-60](https://github.com/chatbot-pf/shunt/blob/main/src/proxy.rs#L26-L60) |
| Tests | Wiremock + Tokio tests | Exercises gateway endpoints and translation behavior | [tests/passthrough.rs:72-247](https://github.com/chatbot-pf/shunt/blob/main/tests/passthrough.rs#L72-L247) |

## Related Pages

| Page | Relationship |
|---|---|
| [Overview](./01-getting-started/overview.md) | Explains the gateway problem shunt solves |
| [Configuration](./01-getting-started/configuration.md) | Shows provider and route setup |
| [Architecture](./02-deep-dive/architecture.md) | Maps the runtime components and invariants |
| [Adapters and Translation](./02-deep-dive/adapters-and-translation.md) | Details the pass-through and Responses adapters |
</doc>

<doc title="Contributor Guide" path="src/content/docs/onboarding/contributor-guide.md">
## Part I: Foundations

shunt is a Rust service. If you come from Python or JavaScript, think of the binary as an async web server plus a protocol translator. `tokio` is the event loop, Axum is the router, Reqwest is the outbound HTTP client, Serde owns JSON/TOML shape, and Figment merges configuration layers [Cargo.toml:1-23](https://github.com/chatbot-pf/shunt/blob/main/Cargo.toml#L1-L23) [src/server.rs:13-25](https://github.com/chatbot-pf/shunt/blob/main/src/server.rs#L13-L25) [src/config.rs:185-194](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L185-L194).

| Rust concept | Python/JS analogy | Where shunt uses it | Source |
|---|---|---|---|
| `async fn` on Tokio | `async def` / `async function` under an event loop | `main`, `run`, proxy and adapter paths | [src/main.rs:38-76](https://github.com/chatbot-pf/shunt/blob/main/src/main.rs#L38-L76) [src/proxy.rs:19-126](https://github.com/chatbot-pf/shunt/blob/main/src/proxy.rs#L19-L126) |
| `Result<T, E>` | Exception-returning operation made explicit | Config loading, token refresh, adapter forwarding | [src/main.rs:77-83](https://github.com/chatbot-pf/shunt/blob/main/src/main.rs#L77-L83) [src/auth/codex_auth.rs:34-63](https://github.com/chatbot-pf/shunt/blob/main/src/auth/codex_auth.rs#L34-L63) |
| `enum` variants | Tagged unions | `ProviderKind`, `AuthMode`, `AdapterKind`, `Credential` | [src/config.rs:9-269](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L9-L269) [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89) [src/auth/mod.rs:29-99](https://github.com/chatbot-pf/shunt/blob/main/src/auth/mod.rs#L29-L99) |
| Trait | Interface/protocol | `Adapter` abstracts provider protocol differences | [src/adapters/mod.rs:21-30](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/mod.rs#L21-L30) |
| Serde derive | Pydantic/dataclass/TypeScript interface with parser | Config and gateway JSON structs | [src/config.rs:9-269](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L9-L269) [src/discovery.rs:17-30](https://github.com/chatbot-pf/shunt/blob/main/src/discovery.rs#L17-L30) |

## Part II: This Codebase

shunt accepts Claude Code gateway requests and either forwards them to an Anthropic-compatible endpoint or translates them to OpenAI Responses. It does not run tools itself; it keeps Claude Code's tool loop intact by staying at the HTTP inference layer [README.md:1-60](https://github.com/chatbot-pf/shunt/blob/main/README.md#L1-L60) [docs/implementation-plan.md:46-71](https://github.com/chatbot-pf/shunt/blob/main/docs/implementation-plan.md#L46-L71).

```mermaid
graph TB
    CLI[main.rs CLI] --> Config[Config load and validation]
    Config --> Router[server.rs router]
    Router --> Proxy[proxy.rs post]
    Proxy --> Route[routing.rs resolve]
    Route --> Anthropic[AnthropicAdapter]
    Route --> Responses[ResponsesAdapter]
    Responses --> Req[responses_request.rs]
    Responses --> Sse[responses.rs state machine]
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class CLI,Config,Router,Proxy,Route,Anthropic,Responses,Req,Sse dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: src/main.rs:38, src/config.rs:185, src/server.rs:13, src/proxy.rs:19, src/routing.rs:37, src/adapters/responses.rs:34 -->

### Project Structure

| Path | Purpose | Why it matters | Source |
|---|---|---|---|
| `src/main.rs` | CLI and process lifecycle | Every local run starts here | [src/main.rs:38-76](https://github.com/chatbot-pf/shunt/blob/main/src/main.rs#L38-L76) |
| `src/server.rs` | Gateway HTTP surface | Defines supported endpoints | [src/server.rs:13-25](https://github.com/chatbot-pf/shunt/blob/main/src/server.rs#L13-L25) |
| `src/proxy.rs` | Request dispatch | Joins routing, adapters, and logging | [src/proxy.rs:19-126](https://github.com/chatbot-pf/shunt/blob/main/src/proxy.rs#L19-L126) |
| `src/config.rs` | Configuration schema and validation | Prevents invalid provider references | [src/config.rs:196-242](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L196-L242) |
| `src/routing.rs` | Model-to-provider route resolution | Central selectivity mechanism | [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89) |
| `src/adapters/` | Protocol-specific outbound paths | Keeps pass-through separate from translation | [src/adapters/anthropic.rs:31-104](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/anthropic.rs#L31-L104) [src/adapters/responses.rs:34-213](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/responses.rs#L34-L213) |
| `src/model/` | Request and SSE translation | Load-bearing protocol conversion | [src/model/responses_request.rs:4-280](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses_request.rs#L4-L280) [src/model/responses.rs:45-378](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses.rs#L45-L378) |
| `src/auth/` | Credential lookup and refresh | Keeps provider secrets inside shunt | [src/auth/mod.rs:29-99](https://github.com/chatbot-pf/shunt/blob/main/src/auth/mod.rs#L29-L99) |
| `tests/` | Integration/protocol behavior tests | Protects gateway compatibility | [tests/passthrough.rs:72-247](https://github.com/chatbot-pf/shunt/blob/main/tests/passthrough.rs#L72-L247) [tests/responses_translate.rs:25-287](https://github.com/chatbot-pf/shunt/blob/main/tests/responses_translate.rs#L25-L287) |

### Core Request Lifecycle

```mermaid
sequenceDiagram
    autonumber
    participant Dev as Contributor
    participant CLI as shunt run
    participant CC as Claude Code
    participant Proxy as proxy::post
    participant Routing as routing::resolve
    participant Adapter as Adapter
    Dev->>CLI: cargo run -- run
    CC->>Proxy: POST /v1/messages
    Proxy->>Routing: body bytes
    Routing-->>Proxy: Route
    Proxy->>Adapter: forward
    Adapter-->>CC: Anthropic-shaped response
```
<!-- Sources: src/main.rs:60, src/server.rs:23, src/proxy.rs:19, src/routing.rs:37, src/adapters/mod.rs:21 -->

### Data Model

```mermaid
erDiagram
    CONFIG ||--o{ PROVIDER_CONFIG : providers
    CONFIG ||--o{ ROUTE_CONFIG : routes
    CONFIG ||--o{ ROUTE_PREFIX_CONFIG : prefixes
    CONFIG ||--o{ MODEL_CONFIG : discovery
    ROUTE_CONFIG }o--|| PROVIDER_CONFIG : selects
    ROUTE_PREFIX_CONFIG }o--|| PROVIDER_CONFIG : selects
```
<!-- Sources: src/config.rs:9, src/config.rs:25, src/config.rs:89, src/config.rs:97, src/config.rs:103 -->

### First Task Walkthrough

| Goal | Files to read first | Change pattern | Tests to run | Source |
|---|---|---|---|---|
| Add provider documentation | `shunt.toml.example`, `docs/running.md` | Add a provider table and route example | `cargo test` if code unchanged is optional; docs review required | [shunt.toml.example:1-134](https://github.com/chatbot-pf/shunt/blob/main/shunt.toml.example#L1-L134) [docs/running.md:1-461](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L1-L461) |
| Add routing behavior | `src/routing.rs` | Extend resolver or route struct, then unit-test precedence | `cargo test routing` or full `cargo test` | [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89) |
| Add request conversion behavior | `src/model/responses_request.rs` | Add mapping helper and a focused test | `cargo test --test responses_translate` | [src/model/responses_request.rs:4-280](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses_request.rs#L4-L280) [tests/responses_translate.rs:25-287](https://github.com/chatbot-pf/shunt/blob/main/tests/responses_translate.rs#L25-L287) |
| Add SSE conversion behavior | `src/model/responses.rs` | Update `AnthropicSseMachine` and event-sequence tests | `cargo test --test responses_translate` | [src/model/responses.rs:45-378](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses.rs#L45-L378) [tests/responses_translate.rs:25-287](https://github.com/chatbot-pf/shunt/blob/main/tests/responses_translate.rs#L25-L287) |

## Part III: Getting Productive

| Tool | Version / source | Install / command | Expected output | Source |
|---|---|---|---|---|
| Rust stable | Cargo manifest edition 2021 | `rustup default stable` | stable toolchain active | [Cargo.toml:1-23](https://github.com/chatbot-pf/shunt/blob/main/Cargo.toml#L1-L23) |
| Cargo build | Rust toolchain | `cargo build` | debug binary compiles | [docs/running.md:1-461](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L1-L461) |
| Config check | shunt binary | `cargo run -- check` | `config ok` | [src/main.rs:77-83](https://github.com/chatbot-pf/shunt/blob/main/src/main.rs#L77-L83) |
| Tests | Cargo + Wiremock | `cargo test` | all unit and integration tests pass | [.github/workflows/ci.yml:1-42](https://github.com/chatbot-pf/shunt/blob/main/.github/workflows/ci.yml#L1-L42) |
| Format | rustfmt | `cargo fmt --all --check` | no diff | [.github/workflows/ci.yml:1-42](https://github.com/chatbot-pf/shunt/blob/main/.github/workflows/ci.yml#L1-L42) |
| Lints | Clippy | `cargo clippy --all-targets --all-features -- -D warnings` | no warnings | [.github/workflows/ci.yml:1-42](https://github.com/chatbot-pf/shunt/blob/main/.github/workflows/ci.yml#L1-L42) |

### Development Workflow

```mermaid
flowchart LR
    Issue[Pick focused task] --> Branch[Worktree branch]
    Branch --> Read[Read docs + source]
    Read --> Change[Small code/doc change]
    Change --> Test[cargo fmt + clippy + test]
    Test --> PR[Open PR with checklist]
    PR --> Review[Review and iterate]
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class Issue,Branch,Read,Change,Test,PR,Review dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: CONTRIBUTING.md:1, .github/PULL_REQUEST_TEMPLATE.md:1, .github/workflows/ci.yml:35 -->

### Common Pitfalls

| Pitfall | Symptom | Avoid it by | Source |
|---|---|---|---|
| Treating shunt as an agent runtime | Looking for tool execution inside shunt | Remember shunt only changes inference HTTP routing | [README.md:1-60](https://github.com/chatbot-pf/shunt/blob/main/README.md#L1-L60) |
| Breaking streaming | Claude Code appears stalled | Preserve `Body::from_stream` and SSE event emission | [tests/passthrough.rs:72-247](https://github.com/chatbot-pf/shunt/blob/main/tests/passthrough.rs#L72-L247) |
| Dropping tool IDs | Tool results no longer match tool calls | Preserve `call_id` in request translation | [src/model/responses_request.rs:4-280](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses_request.rs#L4-L280) [tests/responses_translate.rs:25-287](https://github.com/chatbot-pf/shunt/blob/main/tests/responses_translate.rs#L25-L287) |
| Assuming discovery shows `gpt-*` IDs | Model does not appear in `/model` | Use `ANTHROPIC_CUSTOM_MODEL_OPTION` or Claude-named aliases | [docs/running.md:189-393](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L189-L393) |
| Storing provider keys in Claude Code | Secrets leak into wrong process boundary | Put provider credentials in shunt env/auth files | [src/auth/mod.rs:29-99](https://github.com/chatbot-pf/shunt/blob/main/src/auth/mod.rs#L29-L99) |

### Glossary

| Term | Meaning | Source |
|---|---|---|
| Gateway | HTTP server Claude Code talks to instead of Anthropic directly | [docs/implementation-plan.md:46-71](https://github.com/chatbot-pf/shunt/blob/main/docs/implementation-plan.md#L46-L71) |
| Provider | Named upstream service in `[providers.<name>]` | [src/config.rs:9-269](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L9-L269) |
| Route | Exact model mapping to a provider | [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89) |
| Prefix route | Catch-all mapping by model prefix | [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89) |
| Default provider | Fallback provider for unmapped models | [src/config.rs:142-183](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L142-L183) |
| Anthropic adapter | Pass-through protocol adapter | [src/adapters/anthropic.rs:31-104](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/anthropic.rs#L31-L104) |
| Responses adapter | OpenAI Responses translation adapter | [src/adapters/responses.rs:34-213](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/responses.rs#L34-L213) |
| Reasoning effort | Claude Code effort mapped to Responses reasoning effort | [src/model/responses_request.rs:75-98](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses_request.rs#L75-L98) |
| Model discovery | `/v1/models` response consumed by Claude Code | [src/discovery.rs:17-30](https://github.com/chatbot-pf/shunt/blob/main/src/discovery.rs#L17-L30) |
| `apiKeyHelper` | Claude Code setting that can call `shunt token` | [src/auth/claude_auth.rs:27-92](https://github.com/chatbot-pf/shunt/blob/main/src/auth/claude_auth.rs#L27-L92) |
| ChatGPT OAuth | Codex login token source for ChatGPT-backed Responses | [src/auth/codex_auth.rs:34-63](https://github.com/chatbot-pf/shunt/blob/main/src/auth/codex_auth.rs#L34-L63) |
| SSE | Server-sent events streamed back to Claude Code | [src/model/responses.rs:45-378](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses.rs#L45-L378) |

## Related Pages

| Page | Relationship |
|---|---|
| [Overview](../01-getting-started/overview.md) | Project overview |
| [Operations](../01-getting-started/operations.md) | Copy-paste run commands |
| [Architecture](../02-deep-dive/architecture.md) | Full internal map |
| [Testing and Quality](../02-deep-dive/testing-and-quality.md) | Validation strategy |
</doc>

<doc title="Executive Guide" path="src/content/docs/onboarding/executive-guide.md">
## System Overview

shunt lets a Claude Code user keep the Claude Code workflow while selectively sending chosen model IDs to another model provider. This preserves developer productivity tooling while creating optionality across model vendors [README.md:1-60](https://github.com/chatbot-pf/shunt/blob/main/README.md#L1-L60) [docs/running.md:1-461](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L1-L461).

| Capability | Status | Maturity | Dependency | Source |
|---|---|---|---|---|
| Local gateway for Claude Code | Built | Working implementation | Axum/Rust binary | [src/server.rs:13-25](https://github.com/chatbot-pf/shunt/blob/main/src/server.rs#L13-L25) [src/main.rs:38-76](https://github.com/chatbot-pf/shunt/blob/main/src/main.rs#L38-L76) |
| Anthropic pass-through | Built | Tested | Anthropic-compatible upstream | [src/adapters/anthropic.rs:31-104](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/anthropic.rs#L31-L104) [tests/passthrough.rs:72-247](https://github.com/chatbot-pf/shunt/blob/main/tests/passthrough.rs#L72-L247) |
| OpenAI Responses translation | Built | Tested with fixtures | OpenAI Responses API shape | [src/adapters/responses.rs:34-213](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/responses.rs#L34-L213) [tests/responses_translate.rs:25-287](https://github.com/chatbot-pf/shunt/blob/main/tests/responses_translate.rs#L25-L287) |
| ChatGPT/Codex credential reuse | Built | Sensitive; needs operational care | `~/.codex/auth.json` | [src/auth/codex_auth.rs:34-63](https://github.com/chatbot-pf/shunt/blob/main/src/auth/codex_auth.rs#L34-L63) |
| Model discovery | Built | Limited by Claude Code ID rules | Claude Code gateway discovery | [src/discovery.rs:17-30](https://github.com/chatbot-pf/shunt/blob/main/src/discovery.rs#L17-L30) [docs/running.md:189-393](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L189-L393) |
| Production hardening | Partial | Roadmap item | Observability/timeouts/retries | [docs/implementation-plan.md:6-249](https://github.com/chatbot-pf/shunt/blob/main/docs/implementation-plan.md#L6-L249) |

```mermaid
graph LR
    Dev[Developer] --> Claude[Claude Code]
    Claude --> Shunt[Local shunt gateway]
    Shunt --> Anthropic[Anthropic]
    Shunt --> OpenAI[OpenAI]
    Shunt --> ChatGPT[ChatGPT Codex]
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class Dev,Claude,Shunt,Anthropic,OpenAI,ChatGPT dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: README.md:4, docs/running.md:6, src/server.rs:13, src/adapters/responses.rs:34 -->

## Technology Investment Thesis

| Technology | Purpose | Risk level | Investment view | Source |
|---|---|---|---|---|
| Rust + Tokio | Reliable local network service | Low | Good fit for streaming gateway | [Cargo.toml:1-23](https://github.com/chatbot-pf/shunt/blob/main/Cargo.toml#L1-L23) |
| Axum | HTTP routing and serving | Low | Simple implementation surface | [src/server.rs:13-25](https://github.com/chatbot-pf/shunt/blob/main/src/server.rs#L13-L25) |
| Reqwest streaming | Upstream calls and SSE relay | Medium | Critical for responsiveness | [src/adapters/anthropic.rs:31-104](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/anthropic.rs#L31-L104) |
| Figment TOML/env | Operator configuration | Low | Reduces code churn for providers | [src/config.rs:185-194](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L185-L194) |
| Token-file reuse | Fast setup for Codex/ChatGPT | Medium | Practical but security-sensitive | [src/auth/codex_auth.rs:34-63](https://github.com/chatbot-pf/shunt/blob/main/src/auth/codex_auth.rs#L34-L63) [SECURITY.md:1-38](https://github.com/chatbot-pf/shunt/blob/main/SECURITY.md#L1-L38) |

## Risk Assessment

| Risk | Likelihood | Impact | Mitigation | Owner |
|---|---|---|---|---|
| Provider API shape changes | Medium | High | Keep translation tests and fixtures current | Engineering |
| Credential refresh failure | Medium | Medium | Surface clear `codex login` errors and prefer setup tokens where possible | Engineering/Users |
| Streaming regression | Low | High | Wiremock and SSE state machine tests | Engineering |
| Discovery confusion | Medium | Low | Document custom model option and aliases | Developer Experience |
| Private early status | High | Medium | Keep docs explicit and avoid over-promising | Maintainers |

```mermaid
graph TB
    Shunt[shunt] --> Anthropic[Anthropic-compatible providers]
    Shunt --> OpenAI[OpenAI Platform]
    Shunt --> Codex[ChatGPT Codex backend]
    Shunt --> Files[Local credential files]
    Files --> Risk[Credential handling risk]
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class Shunt,Anthropic,OpenAI,Codex,Files,Risk dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: src/config.rs:142, src/auth/codex_auth.rs:34, src/auth/claude_auth.rs:27, SECURITY.md:1 -->

## Cost and Scaling Model

| Driver | Cost behavior | Current bottleneck | Source |
|---|---|---|---|
| Local CPU/memory | Minimal; proxy and translation only | JSON/SSE transformation in process | [src/model/responses_request.rs:4-280](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses_request.rs#L4-L280) [src/model/responses.rs:45-378](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses.rs#L45-L378) |
| Upstream inference | Scales with selected provider usage | Provider account limits and entitlements | [docs/running.md:189-393](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L189-L393) |
| Developer operations | One local process per user or environment | Credential setup and config correctness | [docs/running.md:1-461](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L1-L461) |

## Roadmap Alignment

```mermaid
flowchart LR
    M0[M0 pass-through] --> M1[M1 Responses translation]
    M1 --> M2[M2 ChatGPT OAuth]
    M2 --> M3[M3 discovery UX]
    M3 --> M4[M4 hardening]
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class M0,M1,M2,M3,M4 dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: docs/implementation-plan.md:238, docs/implementation-plan.md:240, docs/implementation-plan.md:243, docs/implementation-plan.md:244, docs/implementation-plan.md:245, docs/implementation-plan.md:246 -->

## Recommendations

| Priority | Recommendation | Expected impact | Source |
|---|---|---|---|
| 1 | Keep translation tests as release gate | Protects core compatibility | [tests/responses_translate.rs:25-287](https://github.com/chatbot-pf/shunt/blob/main/tests/responses_translate.rs#L25-L287) [.github/workflows/ci.yml:1-42](https://github.com/chatbot-pf/shunt/blob/main/.github/workflows/ci.yml#L1-L42) |
| 2 | Document supported provider/model combinations continuously | Reduces setup failures | [docs/running.md:1-461](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L1-L461) [shunt.toml.example:1-134](https://github.com/chatbot-pf/shunt/blob/main/shunt.toml.example#L1-L134) |
| 3 | Add production-hardening backlog around timeouts/retries/observability | Improves reliability if shared beyond local use | [docs/implementation-plan.md:6-249](https://github.com/chatbot-pf/shunt/blob/main/docs/implementation-plan.md#L6-L249) |
| 4 | Treat credential-file refresh as a security-sensitive area | Reduces incident risk | [SECURITY.md:1-38](https://github.com/chatbot-pf/shunt/blob/main/SECURITY.md#L1-L38) [src/auth/codex_auth.rs:34-63](https://github.com/chatbot-pf/shunt/blob/main/src/auth/codex_auth.rs#L34-L63) |

## Related Pages

| Page | Relationship |
|---|---|
| [Product Manager Guide](./product-manager-guide.md) | Product-facing explanation |
| [Staff Engineer Guide](./staff-engineer-guide.md) | Architectural details |
| [Operations](../01-getting-started/operations.md) | Current operating model |
</doc>

<doc title="Onboarding" path="src/content/docs/onboarding/index.md">
`shunt` is a local gateway for Claude Code: run it, point Claude Code at it with `ANTHROPIC_BASE_URL`, and it diverts only configured model IDs while preserving the Claude Code harness, tools, and skills [README.md:4-20](https://github.com/chatbot-pf/shunt/blob/main/README.md#L4-L20) [docs/running.md:6-9](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L6-L9).

| Guide | Audience | What You'll Learn | Time |
|---|---|---|---|
| [Contributor Guide](./contributor-guide.md) | New contributors with Python/JS experience | Rust setup, first PR, gateway code paths, testing | ~30 min |
| [Staff Engineer Guide](./staff-engineer-guide.md) | Staff/principal engineers | Architecture, invariants, tradeoffs, failure modes | ~45 min |
| [Executive Guide](./executive-guide.md) | VP/director-level engineering leaders | Capabilities, risks, ownership, investment thesis | ~20 min |
| [Product Manager Guide](./product-manager-guide.md) | Product managers and stakeholders | User journeys, product capabilities, constraints, FAQ | ~20 min |

```mermaid
flowchart TB
    New[New reader] --> Choice{What do you need?}
    Choice -->|Contribute code| CG[Contributor Guide]
    Choice -->|Evaluate architecture| SG[Staff Engineer Guide]
    Choice -->|Plan investment| EG[Executive Guide]
    Choice -->|Understand user value| PM[Product Manager Guide]
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class New,Choice,CG,SG,EG,PM dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: README.md:4, docs/running.md:1, docs/implementation-plan.md:6 -->
</doc>

<doc title="Product Manager Guide" path="src/content/docs/onboarding/product-manager-guide.md">
## What This System Does

shunt lets a Claude Code user choose that some conversations or agents use another model provider while everything else keeps working as normal in Claude Code. In plain terms, it is a switchboard: if the request names a configured model, shunt sends it to the configured provider; otherwise it lets it go to Anthropic [README.md:1-60](https://github.com/chatbot-pf/shunt/blob/main/README.md#L1-L60) [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89).

## User Journey

```mermaid
journey
    title Using shunt with Claude Code
    section Setup
      Build shunt: 3: User
      Copy config: 3: User
      Add provider credential: 2: User
    section Run
      Start shunt: 4: User
      Point Claude Code at gateway: 4: User
      Select mapped model: 4: User
    section Outcome
      Claude Code keeps tools: 5: User
      Selected model is diverted: 5: User
```
<!-- Sources: docs/running.md:438, docs/running.md:456, README.md:14 -->

## Feature Capability Map

| Feature | Status | User-facing behavior | Limitation | Source |
|---|---|---|---|---|
| Run local gateway | Live | User starts a local process | User must keep it running | [src/main.rs:38-76](https://github.com/chatbot-pf/shunt/blob/main/src/main.rs#L38-L76) [docs/running.md:1-461](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L1-L461) |
| Choose model by ID | Live | User picks or configures a model name | Config must match the model string | [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89) [shunt.toml.example:1-134](https://github.com/chatbot-pf/shunt/blob/main/shunt.toml.example#L1-L134) |
| Keep normal Claude models | Live | Unmapped requests continue to Anthropic | Requires valid Anthropic credential | [src/adapters/anthropic.rs:31-104](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/anthropic.rs#L31-L104) [docs/running.md:189-393](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L189-L393) |
| Use OpenAI API key | Live | Mapped model can go to OpenAI | Requires `OPENAI_API_KEY` | [src/auth/mod.rs:29-99](https://github.com/chatbot-pf/shunt/blob/main/src/auth/mod.rs#L29-L99) [shunt.toml.example:1-134](https://github.com/chatbot-pf/shunt/blob/main/shunt.toml.example#L1-L134) |
| Use ChatGPT/Codex login | Live | Mapped model can use Codex backend | Requires `codex login` and entitled model | [src/auth/codex_auth.rs:34-63](https://github.com/chatbot-pf/shunt/blob/main/src/auth/codex_auth.rs#L34-L63) [docs/running.md:189-393](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L189-L393) |
| Automatic model list | Partial | Claude-named aliases can appear in `/model` | `gpt-*` IDs are ignored by discovery | [src/discovery.rs:17-30](https://github.com/chatbot-pf/shunt/blob/main/src/discovery.rs#L17-L30) [docs/running.md:189-393](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L189-L393) |

## Product Data Model

```mermaid
erDiagram
    USER ||--o{ MODEL_CHOICE : selects
    MODEL_CHOICE ||--|| ROUTE_RULE : matches
    ROUTE_RULE ||--|| PROVIDER : sends_to
    PROVIDER ||--|| CREDENTIAL : uses
```
<!-- Sources: src/routing.rs:48, src/config.rs:89, src/config.rs:27, src/auth/mod.rs:17 -->

## Capability Flow

```mermaid
flowchart LR
    Request[Claude Code request] --> Model[Model name]
    Model --> Rule{Configured?}
    Rule -->|yes| Other[Other provider]
    Rule -->|no| Claude[Anthropic]
    Other --> Response[Claude Code response]
    Claude --> Response
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class Request,Model,Rule,Other,Claude,Response dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: README.md:4, src/routing.rs:48, src/adapters/anthropic.rs:31, src/adapters/responses.rs:34 -->

## Configuration and Controls

| Control | What it changes | Who can change it | Source |
|---|---|---|---|
| `server.bind` | Where shunt listens locally | Operator/developer | [shunt.toml.example:1-134](https://github.com/chatbot-pf/shunt/blob/main/shunt.toml.example#L1-L134) |
| `default_provider` | Where unmapped model requests go | Operator/developer | [src/config.rs:142-183](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L142-L183) |
| `[[routes]]` | Exact model-to-provider mapping | Operator/developer | [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89) |
| `[[route_prefixes]]` | Broad model-prefix mapping | Operator/developer | [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89) |
| `ANTHROPIC_CUSTOM_MODEL_OPTION` | Adds a model choice in Claude Code | User | [docs/running.md:189-393](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L189-L393) |
| Provider credentials | Enables access to provider | User/operator | [src/auth/mod.rs:29-99](https://github.com/chatbot-pf/shunt/blob/main/src/auth/mod.rs#L29-L99) |

## Known Limitations

| Limitation | User impact | Workaround | Source |
|---|---|---|---|
| Discovery does not show most `gpt-*` names | User may not see expected model in picker | Use custom model option or Claude-named alias | [docs/running.md:189-393](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L189-L393) |
| Unsupported Codex model slug fails | Request returns provider error | Use an entitled slug or route alias | [docs/running.md:189-393](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L189-L393) |
| Gateway must be running | Claude Code cannot reach mapped route | Start shunt before Claude Code | [docs/running.md:1-461](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L1-L461) |
| Private early project | API/UX may change | Follow docs and PR checklist | [README.md:1-60](https://github.com/chatbot-pf/shunt/blob/main/README.md#L1-L60) [.github/PULL_REQUEST_TEMPLATE.md:1-26](https://github.com/chatbot-pf/shunt/blob/main/.github/PULL_REQUEST_TEMPLATE.md#L1-L26) |

## Data and Privacy

| Data type | Where it goes | Retention | Source |
|---|---|---|---|
| Claude Code request body | Routed through local shunt to selected upstream | shunt is stateless by default | [src/proxy.rs:19-126](https://github.com/chatbot-pf/shunt/blob/main/src/proxy.rs#L19-L126) [docs/implementation-plan.md:6-249](https://github.com/chatbot-pf/shunt/blob/main/docs/implementation-plan.md#L6-L249) |
| Provider API key | shunt process environment or Codex auth file | Local machine | [src/auth/mod.rs:29-99](https://github.com/chatbot-pf/shunt/blob/main/src/auth/mod.rs#L29-L99) [src/auth/codex_auth.rs:34-63](https://github.com/chatbot-pf/shunt/blob/main/src/auth/codex_auth.rs#L34-L63) |
| ChatGPT OAuth token | `~/.codex/auth.json` | Local machine, refreshed by shunt when needed | [src/auth/codex_auth.rs:34-63](https://github.com/chatbot-pf/shunt/blob/main/src/auth/codex_auth.rs#L34-L63) |
| Claude OAuth token helper | `~/.claude/.credentials.json` or env override | Local machine | [src/auth/claude_auth.rs:27-92](https://github.com/chatbot-pf/shunt/blob/main/src/auth/claude_auth.rs#L27-L92) |

## FAQ

| Question | Answer | Source |
|---|---|---|
| Does shunt replace Claude Code? | No. Claude Code still runs the session; shunt only handles inference HTTP routing. | [README.md:1-60](https://github.com/chatbot-pf/shunt/blob/main/README.md#L1-L60) |
| Can only one model be diverted? | Yes. Exact routes can target one model ID. | [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89) |
| Can broad groups be diverted? | Yes. Prefix routes can catch names like `gpt-`. | [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89) |
| Do normal Claude models keep working? | Yes, if the default provider remains Anthropic and credentials are valid. | [src/adapters/anthropic.rs:31-104](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/anthropic.rs#L31-L104) |
| Does it store prompts? | The implementation is stateless by default according to the plan. | [docs/implementation-plan.md:6-249](https://github.com/chatbot-pf/shunt/blob/main/docs/implementation-plan.md#L6-L249) |
| Why does my `gpt-*` model not appear? | Claude Code discovery ignores IDs that do not start with `claude` or `anthropic`. | [docs/running.md:189-393](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L189-L393) |
| What credentials are needed? | Anthropic for pass-through plus provider credentials for mapped models. | [src/auth/mod.rs:29-99](https://github.com/chatbot-pf/shunt/blob/main/src/auth/mod.rs#L29-L99) |
| Is this production-ready? | It is early/private, with hardening listed as a roadmap milestone. | [README.md:1-60](https://github.com/chatbot-pf/shunt/blob/main/README.md#L1-L60) [docs/implementation-plan.md:6-249](https://github.com/chatbot-pf/shunt/blob/main/docs/implementation-plan.md#L6-L249) |
| How do we know translation works? | Tests cover request translation, SSE conversion, and errors. | [tests/responses_translate.rs:25-287](https://github.com/chatbot-pf/shunt/blob/main/tests/responses_translate.rs#L25-L287) |
| Who reports security issues? | Use the private security contact and process in `SECURITY.md`. | [SECURITY.md:1-38](https://github.com/chatbot-pf/shunt/blob/main/SECURITY.md#L1-L38) |

## Related Pages

| Page | Relationship |
|---|---|
| [Executive Guide](./executive-guide.md) | Strategic risk and investment view |
| [Overview](../01-getting-started/overview.md) | Technical overview with minimal jargon |
| [Configuration](../01-getting-started/configuration.md) | Details of switches and model mappings |
</doc>

<doc title="Staff Engineer Guide" path="src/content/docs/onboarding/staff-engineer-guide.md">
## Executive Summary

shunt owns one boundary: translating Claude Code's Anthropic-compatible gateway traffic into either pass-through Anthropic Messages traffic or OpenAI Responses traffic, selected by `model`. It delegates all agent behavior, tool execution, and UI/session orchestration back to Claude Code, which is why the gateway can be small, stateless, and testable [README.md:1-60](https://github.com/chatbot-pf/shunt/blob/main/README.md#L1-L60) [docs/implementation-plan.md:6-249](https://github.com/chatbot-pf/shunt/blob/main/docs/implementation-plan.md#L6-L249).

## Core Architectural Insight

The important decision is **route by model ID, not by agent identity**. In Python-like pseudocode:

```python
def handle_messages(request):
    route = resolve_model(config, request["model"])
    if route.adapter == "anthropic":
        return stream_passthrough(request, route)
    return stream_responses_translation(request, route)
```

That mirrors `routing::resolve_model` and `proxy::forward`: body bytes are parsed only for `model`, then a `Route` selects the adapter [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89) [src/proxy.rs:19-126](https://github.com/chatbot-pf/shunt/blob/main/src/proxy.rs#L19-L126).

```mermaid
graph TB
    Body[Request body bytes] --> Parse[Parse model only]
    Parse --> Resolve[Exact, prefix, default]
    Resolve --> Route[Route]
    Route --> A[Anthropic adapter]
    Route --> R[Responses adapter]
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class Body,Parse,Resolve,Route,A,R dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: src/proxy.rs:93, src/routing.rs:37, src/routing.rs:48, src/adapters/anthropic.rs:31, src/adapters/responses.rs:34 -->

## Architecture, Abstractions, and Invariants

| Abstraction | Why it is load-bearing | Failure if wrong | Source |
|---|---|---|---|
| `Config` | Makes providers and routes declarative | Adding providers would require code changes | [src/config.rs:9-269](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L9-L269) |
| `Route` | Carries adapter, provider, upstream model, effort | Adapter cannot build correct outbound request | [src/routing.rs:37-89](https://github.com/chatbot-pf/shunt/blob/main/src/routing.rs#L37-L89) |
| `Adapter` trait | Separates protocol behavior from dispatch | Pass-through and translation logic would tangle | [src/adapters/mod.rs:21-30](https://github.com/chatbot-pf/shunt/blob/main/src/adapters/mod.rs#L21-L30) |
| `Credential` | Keeps auth strategy explicit | Provider secrets could leak or be omitted | [src/auth/mod.rs:29-99](https://github.com/chatbot-pf/shunt/blob/main/src/auth/mod.rs#L29-L99) |
| `AnthropicSseMachine` | Preserves Claude Code's expected stream protocol | Tool loop or UI streaming can break | [src/model/responses.rs:62-113](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses.rs#L62-L113) |

```mermaid
classDiagram
    class Config
    class Route
    class Adapter
    class Credential
    class AnthropicSseMachine
    Config --> Route
    Route --> Adapter
    Adapter --> Credential
    Adapter --> AnthropicSseMachine
```
<!-- Sources: src/config.rs:9, src/routing.rs:23, src/adapters/mod.rs:21, src/auth/mod.rs:17, src/model/responses.rs:24 -->

## Domain Model

```mermaid
erDiagram
    CONFIG ||--o{ PROVIDER : declares
    PROVIDER ||--o{ ROUTE : selected_by
    PROVIDER ||--o{ PREFIX : selected_by
    ROUTE ||--|| ADAPTER : chooses
    ADAPTER ||--o{ CREDENTIAL : consumes
```
<!-- Sources: src/config.rs:9, src/config.rs:25, src/routing.rs:23, src/routing.rs:68, src/auth/mod.rs:17 -->

| Decision | Alternatives considered | Rationale | Source |
|---|---|---|---|
| Use model routing | Prompt fingerprinting, global swap | Claude Code already chooses models per context | [docs/implementation-plan.md:6-249](https://github.com/chatbot-pf/shunt/blob/main/docs/implementation-plan.md#L6-L249) |
| Keep pass-through default | Translate every request | Unmapped Claude models should remain native | [README.md:1-60](https://github.com/chatbot-pf/shunt/blob/main/README.md#L1-L60) |
| Use Figment TOML/env config | Hardcoded providers | Operators can add providers without code | [src/config.rs:185-194](https://github.com/chatbot-pf/shunt/blob/main/src/config.rs#L185-L194) |
| Use Responses API only for OpenAI-family path | Chat Completions adapter | Codex/ChatGPT target speaks Responses | [docs/implementation-plan.md:6-249](https://github.com/chatbot-pf/shunt/blob/main/docs/implementation-plan.md#L6-L249) |
| Reuse Codex auth file | New OAuth flow first | Faster setup, matches `codex login` workflow | [src/auth/codex_auth.rs:34-63](https://github.com/chatbot-pf/shunt/blob/main/src/auth/codex_auth.rs#L34-L63) |

## Request Lifecycle

```mermaid
sequenceDiagram
    autonumber
    participant CC as Claude Code
    participant Proxy as proxy
    participant Route as routing
    participant Adapter as adapter
    participant Up as upstream
    CC->>Proxy: POST /v1/messages
    Proxy->>Route: resolve body model
    Route-->>Proxy: Route
    Proxy->>Adapter: forward
    Adapter->>Up: upstream request
    Up-->>Adapter: stream/error
    Adapter-->>CC: Anthropic response
```
<!-- Sources: src/server.rs:23, src/proxy.rs:19, src/routing.rs:37, src/adapters/mod.rs:21, src/error.rs:77 -->

## Failure Modes

```mermaid
flowchart TB
    Req[Request] --> Parse{model JSON valid?}
    Parse -->|no| Bad[400 invalid_request_error]
    Parse -->|yes| Auth{credential available?}
    Auth -->|no| AuthErr[401 authentication_error]
    Auth -->|yes| Up[Upstream call]
    Up -->|transport error| Bg[502 api_error]
    Up -->|upstream 400/401/429| Map[Mapped Anthropic error]
    Up -->|ok| Stream[Stream response]
    classDef dark fill:#2d333b,stroke:#6d5dfc,color:#e6edf3;
    class Req,Parse,Bad,Auth,AuthErr,Up,Bg,Map,Stream dark;
    linkStyle default stroke:#8b949e;
```
<!-- Sources: src/routing.rs:37, src/auth/mod.rs:82, src/error.rs:40, src/adapters/responses.rs:128, src/proxy.rs:49 -->

## State Transitions

```mermaid
stateDiagram-v2
    [*] --> Idle
    Idle --> TextBlock: response output message
    Idle --> ToolBlock: response output function_call
    TextBlock --> Idle: text done
    ToolBlock --> Idle: arguments done
    Idle --> Stopped: response completed
    Idle --> Failed: response failed
```
<!-- Sources: src/model/responses.rs:62, src/model/responses.rs:146, src/model/responses.rs:237, src/model/responses.rs:273, src/model/responses.rs:79 -->

## Risk and Debt

| Issue | Risk level | Business/technical impact | Source |
|---|---|---|---|
| `GET /protocol` not implemented in current router | Medium | Full gateway contract discoverability is deferred | [src/server.rs:13-25](https://github.com/chatbot-pf/shunt/blob/main/src/server.rs#L13-L25) [docs/implementation-plan.md:6-249](https://github.com/chatbot-pf/shunt/blob/main/docs/implementation-plan.md#L6-L249) |
| Token refresh writes credential files | Medium | Requires careful file permission and concurrency awareness | [src/auth/codex_auth.rs:34-63](https://github.com/chatbot-pf/shunt/blob/main/src/auth/codex_auth.rs#L34-L63) [src/auth/claude_auth.rs:27-92](https://github.com/chatbot-pf/shunt/blob/main/src/auth/claude_auth.rs#L27-L92) |
| Responses translation is stateful | High | Incorrect state transitions break streaming/tool use | [src/model/responses.rs:45-378](https://github.com/chatbot-pf/shunt/blob/main/src/model/responses.rs#L45-L378) [tests/responses_translate.rs:25-287](https://github.com/chatbot-pf/shunt/blob/main/tests/responses_translate.rs#L25-L287) |
| Discovery only works for Claude-prefixed aliases | Low | UX caveat, documented workaround exists | [src/discovery.rs:17-30](https://github.com/chatbot-pf/shunt/blob/main/src/discovery.rs#L17-L30) [docs/running.md:189-393](https://github.com/chatbot-pf/shunt/blob/main/docs/running.md#L189-L393) |

## Related Pages

| Page | Relationship |
|---|---|
| [Architecture](../02-deep-dive/architecture.md) | Full system map |
| [Adapters and Translation](../02-deep-dive/adapters-and-translation.md) | Translation internals |
| [Authentication](../02-deep-dive/authentication.md) | Trust boundaries |
| [Testing and Quality](../02-deep-dive/testing-and-quality.md) | Verification strategy |
</doc>

Discussion

Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.

Posts are public.Sign in to post

No one has posted yet. Be the first.