# Providers

> Every model provider zidane supports, how to sign in, prompt caching, model fallbacks and plan usage.

A provider wraps one model API behind a common streaming interface. In the SDK, you pass a provider to `createAgent`. The apps pick one from your sign-ins. In the apps, the Connect step of the [setup wizard](https://zidane.si/docs/ui-onboarding.md) lists these providers, signs you in and tests the model.

## Every provider

| Provider | SDK factory | Sign-in | Environment variable | `--provider` name | Notes |
| - | - | - | - | - | - |
| Anthropic | `anthropic()` | Key or Claude Pro/Max | `ANTHROPIC_API_KEY` | `anthropic` | Also Amazon Bedrock |
| OpenAI | `openai()` | Key or ChatGPT (Codex) | `OPENAI_CODEX_API_KEY` | `openai` | See [OpenAI](#openai) |
| OpenRouter | `openrouter()` | Key | `OPENROUTER_API_KEY` | `openrouter` | Credits in [plan usage](#plan-usage) |
| Cerebras | `cerebras()` | Key | `CEREBRAS_API_KEY` | `cerebras` | |
| xAI Grok | `xai()` | Key or SuperGrok / X Premium+ | `XAI_API_KEY` | `xai` | |
| Google Gemini | `google()` | Key | `GEMINI_API_KEY`, then `GOOGLE_API_KEY` | `google` | See [Google](#google-gemini-and-vertex-ai) |
| Google Vertex AI | `googleVertex()` | Key, Google Cloud credentials or Sign in with Google | `GOOGLE_CLOUD_API_KEY` or `GOOGLE_CLOUD_PROJECT` | `google-vertex` | See [Google](#google-gemini-and-vertex-ai) |
| Arcee | `arcee()` | Key | `ARCEE_API_KEY` | `arcee` | |
| Baseten | `baseten()` | Key | `BASETEN_API_KEY` | `baseten` | |
| Z.ai (GLM) | `zai()` | Key | `ZAI_API_KEY` | none | Apps and SDK only |
| Mistral | `mistral()` | Key | `MISTRAL_API_KEY` | none | Apps and SDK only |
| Neuralwatt | `neuralwatt()` | Key | `NEURALWATT_API_KEY` | `neuralwatt` | See [Neuralwatt](#neuralwatt) |
| Local or self-hosted | `local()` | Base URL, optional key | `LOCAL_LLM_BASE_URL` and more | `local`, `openai-compat` | See [local endpoints](#openai-compatible-and-local-endpoints) |
| Any OpenAI-compatible API | `openaiCompat()` | Key | Your choice | `openai-compat` | |
| Cursor | `cursor()` | Cursor subscription | none | none | Sign-in only. Running a model throws. Not in the apps. |

OpenRouter, OpenAI, Google Gemini and xAI also have an image endpoint, `provider.images`. The `generate_image` tool uses it ([Images](https://zidane.si/docs/images.md)). OpenAI's image endpoint needs a platform API key, also with a ChatGPT sign-in.

The `--provider` names are for `zidane run` ([CLI](https://zidane.si/docs/cli.md)). `bun run headless` accepts fewer: `anthropic`, `openai`, `openrouter`, `cerebras`, `baseten`, `neuralwatt`, `local` and `openai-compat` ([headless](https://zidane.si/docs/cli-headless.md)).

## Anthropic

```ts
import { anthropic } from 'zidane/providers'

anthropic({ apiKey: 'sk-ant-...' })
anthropic({ access: 'sk-ant-oat-...', refresh: '...', expires: Date.now() + 3600_000 }) // OAuth, refreshed for you
anthropic({ bedrock: { region: 'eu-west-1' }, defaultModel: 'global.anthropic.claude-haiku-4-5-20251001-v1:0' })
```

Credential order: `params.apiKey`, `params.access`, `ANTHROPIC_API_KEY`, then the OAuth entry in the [credentials file](#sign-in-and-credentials).

`bedrock` routes requests through Amazon Bedrock (optional `@anthropic-ai/bedrock-sdk` package). Region: `bedrock.region`, then `AWS_REGION`, then `us-west-2`. Model ids take the Bedrock form. Bedrock has no server-side web search.

### Betas and server-side context management

```ts
anthropic({
  apiKey: '...',
  extraBetas: [
    'context-management-2025-06-27',     // token-accurate compaction
    'token-efficient-tools-2026-03-28',  // ~4.5% output reduction
    'interleaved-thinking-2025-05-14',   // think between tool calls
  ],
  contextManagement: {
    edits: [{
      type: 'clear_tool_uses_20250919',
      trigger: { type: 'input_tokens', value: 180_000 },
      clear_at_least: { type: 'input_tokens', value: 140_000 },
      clear_tool_inputs: ['Read', 'Bash', 'Grep', 'Glob'],
    }],
  },
})
```

| Param | Effect |
| - | - |
| `extraBetas` | Merged with the OAuth defaults, de-duplicated. |
| `contextManagement` | Sent as `context_management`. Pair it with its beta. On other providers use `behavior.compactStrategy: 'tail'`. |
| `extraHeaders` | Request headers for proxies and gateways. Reserved Anthropic headers win a clash. |
| `extraBodyParams` | Untyped Messages API fields for betas without an option. `openaiCompat` takes it too. |

With `web_search` registered, Anthropic uses its server-side `web_search_20250305` tool and streams results through `stream:server_tool_use` and `stream:server_tool_result`.

## OpenAI

`openai()` uses the Responses API. A ChatGPT sign-in (Codex OAuth token) goes to the ChatGPT Codex backend and bills your plan. A platform key (`sk-...`) goes to the standard OpenAI API.

```ts
import { openai } from 'zidane/providers'

openai({ apiKey: process.env.OPENAI_API_KEY })   // platform key
openai()                                         // OPENAI_CODEX_API_KEY, then the stored Codex sign-in
openai({ access: 'eyJ...', refresh: '...', expires: Date.now() + 3600_000, accountId: 'acct_123' })
```

Without `apiKey`, `openai()` runs on `OPENAI_CODEX_API_KEY`, then on the stored Codex sign-in. It does not run on `OPENAI_API_KEY`, so pass a platform key yourself. Decisions and exact token counts need a platform key, from `apiKey` or `OPENAI_API_KEY`.

## Other hosted providers

```ts
import { arcee, baseten, cerebras, mistral, openrouter, xai, zai } from 'zidane/providers'

openrouter({ apiKey: 'sk-or-...', defaultModel: 'google/gemini-pro' })
cerebras({ apiKey: 'csk-...', defaultModel: 'gpt-oss-120b' })
xai({ apiKey: 'xai-...' })                                                // or OAuth (SuperGrok / X Premium+)
arcee({ apiKey: 'arcee-...', defaultModel: 'trinity-large-thinking' })
zai({ apiKey: '...', defaultModel: 'glm-5.3-flash' })                     // Z.ai (Zhipu) GLM Open Platform
mistral({ apiKey: '...', defaultModel: 'mistral-medium-latest' })         // thinking via reasoning_effort
baseten({ apiKey: '...', defaultModel: 'zai-org/GLM-5.3' })               // Baseten Model APIs
```

These providers wrap `openaiCompat` with vendor defaults. Credential order: `params.apiKey`, then the variable in the table above. `xai` also takes a stored SuperGrok or X Premium+ sign-in, and refreshes it on each turn. `mistral` and `zai` come only from `zidane/providers`. The other factories also have their own subpath, such as `zidane/providers/xai`.

### Neuralwatt

```ts
import { neuralwatt } from 'zidane/providers/neuralwatt'

neuralwatt()                                     // NEURALWATT_API_KEY, model kimi-k3
neuralwatt({ apiKey: '...', defaultModel: 'glm-5.3', capabilities: { vision: false, imageInToolResult: false } })
```

`neuralwatt()` wraps `openaiCompat` for Neuralwatt's hosted models. The default model is `kimi-k3`. The thinking level goes out as `reasoning_effort`: `off` sends `none`, `adaptive` sends nothing, and the other levels pass through. Set `vision: false` for a text-only model, because Neuralwatt drops images sent to those without an error.

Neuralwatt measures the energy of each request. It lands on `TurnUsage.energyJoules` in the `usage` hook. The cost it reports (`request_cost_usd`) becomes `TurnUsage.cost`. The session id goes out as the `X-NW-Conversation-ID` header, so Neuralwatt groups a session's requests and routes them to its cache. `conversationId` takes the same values as [`promptCacheKey`](#prompt-caching). `false` sends no header.

## Google Gemini and Vertex AI

```ts
import { google, googleVertex } from 'zidane/providers/google'

google({ apiKey: 'AIza...' })                     // Gemini API (Google AI Studio)
googleVertex({ project: 'my-project' })           // Vertex AI with gcloud Application Default Credentials
googleVertex({ project: 'my-project', location: 'us-central1', credentialsFile: './sa.json' })
```

`google()` reads `GEMINI_API_KEY`, then `GOOGLE_API_KEY`.

`googleVertex()` uses a Vertex API key (`GOOGLE_CLOUD_API_KEY`) when one is set. Otherwise it uses Google Cloud credentials: `GOOGLE_APPLICATION_CREDENTIALS`, then the login from `gcloud auth application-default login`. The project comes from `project`, then `GOOGLE_CLOUD_PROJECT`, then `GCLOUD_PROJECT`. The location comes from `location`, then `GOOGLE_CLOUD_LOCATION`, then `global`.

In the apps, Vertex AI also offers Sign in with Google. It needs `ZIDANE_GOOGLE_CLIENT_ID` and `ZIDANE_GOOGLE_CLIENT_SECRET`, which name a Desktop-app OAuth client from the Google Cloud console. The sign-in picks the project to bill and refreshes its token.

The thought signatures of Gemini stay in the session and go back to the route that issued them. Gemini 3 needs them for multi-step tool use.

PDFs and other documents go to Gemini as file parts, in prompts and in tool results. Extensions' [`http.request` and `http.response`](https://zidane.si/docs/extensions-hooks.md#httprequest-and-httpresponse) interceptors run for both providers. Vertex AI takes user, service-account and metadata-server credentials; `external_account` and `impersonated_service_account` credential files are refused, with an error that says so.

## OpenAI-compatible and local endpoints

`openaiCompat` targets any OpenAI Chat Completions endpoint (Fireworks, Groq, LM servers, corporate proxies):

```ts
import { local, openaiCompat } from 'zidane/providers'

local({ baseURL: 'http://localhost:8000/v1', defaultModel: 'qwen3-coder' })

openaiCompat({
  name: 'my-gateway',
  apiKey: process.env.GATEWAY_API_KEY!,
  baseURL: process.env.GATEWAY_URL!,
  authHeader: { name: 'Authorization', scheme: 'Api-Key' },  // vendor-specific
  capabilities: { vision: false, imageInToolResult: false },
  cacheBreakpoints: false,                                    // true only when the endpoint honors cache_control
})
```

`local()` reads these variables when you omit the params. Some are read only by the CLI (`zidane run`, `bun run headless`) or by the apps:

| Variable | Sets | Read by |
| - | - | - |
| `LOCAL_LLM_BASE_URL` | The endpoint | `local()`, the CLI and the apps |
| `LOCAL_LLM_API_KEY` | A bearer key, if the server wants one | `local()`, the CLI and the apps |
| `LOCAL_LLM_DEFAULT_MODEL` | The model id | `local()`, the CLI and the apps |
| `LOCAL_LLM_AUTHORIZATION` | The exact `Authorization` header value | The CLI |
| `LOCAL_LLM_HEADERS` | Extra headers, as a JSON object | The CLI |
| `LOCAL_LLM_CONTEXT_WINDOW` | The context window of the model, in tokens | The apps |

## Sign-in and credentials

In the apps, you sign in once from the setup wizard, from Settings › Authentication in the terminal app, or from Settings › Accounts in the desktop app. Both apps, `zidane run`, the daemon and ACP read the same `credentials.json` ([where](https://zidane.si/docs/files.md#config-and-data-files)).

A provider can hold one API key and several sign-ins. The active one sits under the provider's name (`anthropic`, `openai-codex`, `xai-oauth`). The others wait under `<name>#alt` (the key) and `<name>#account:N` (more sign-ins). Settings › Authentication (terminal) and Settings › Accounts (desktop) list every slot.

In the library, OAuth providers read `~/.credentials.json`, or the file in `ZIDANE_CREDENTIALS_PATH`. Point it at the apps' `credentials.json` to reuse their sign-ins. From a zidane repo checkout, `bun run auth` signs in and writes there:

| Flag | Signs in to |
| - | - |
| none | Anthropic and OpenAI Codex |
| `--anthropic` or `--claude` | Anthropic |
| `--openai` or `--codex` | OpenAI Codex |
| `--xai` or `--grok` | xAI |
| `--google` or `--vertex` | Google Vertex AI (needs `ZIDANE_GOOGLE_CLIENT_ID` and `ZIDANE_GOOGLE_CLIENT_SECRET`) |
| `--cursor` | Cursor |

## Prompt caching

`behavior.cache` defaults to on. Zidane sets `cache_control: { type: 'ephemeral' }` on three stable prefixes: the static half of the system prompt, the last tool definition, and the last cacheable block of the last message. When the request has fewer than four markers, a fourth goes on the message right before the latest assistant turn. Hits and writes appear as `TurnUsage.cacheRead` and `TurnUsage.cacheCreation` on the `usage` hook.

`behavior.cacheTtl: '1h'` uses the extended Anthropic cache. Writes cost 2x instead of 1.25x, and reads stay near 0.1x. Use it for interactive sessions, where a pause over five minutes loses the cached prefix. Keep the default `'5m'` for back-to-back headless runs. The built-in Build, Codemode and Plan profiles of the apps and [ACP](https://zidane.si/docs/acp.md) sessions use `'1h'`.

| Provider | Cache markers |
| - | - |
| `anthropic` | Honored. |
| `openrouter` | Forwarded. Anthropic and Gemini routes honor them. Other routes cache on their own. |
| `arcee`, `xai`, `zai`, `mistral`, `local`, `openaiCompat`, `cerebras` | Off. Set `openaiCompat({ cacheBreakpoints: true })` only for endpoints that honor `cache_control`. |
| `baseten` | Off. Baseten caches every request server-side. |
| `google`, `googleVertex` | Off. Gemini caches repeated prefixes on its own and reports them as `cacheRead`. |
| `neuralwatt` | Off. Neuralwatt caches repeated prefixes on its own. |
| `openai` | No markers. OpenAI caches repeated prefixes on its own. |

A cache key routes a session's requests to the same cache replica. The agent loop passes the session id to the provider (`StreamOptions.sessionId`) whenever a session is bound.

| Provider | Key |
| - | - |
| `openai` | Automatic. The session id goes out as `prompt_cache_key`, plus session headers on the platform API. |
| `neuralwatt` | On by default. The session id goes out as the `X-NW-Conversation-ID` header. |
| `anthropic` | `promptCacheKey`, sent as `metadata.user_id` |
| `openaiCompat`, `openrouter`, `baseten` | `promptCacheKey`, sent as the `prompt_cache_key` body field |

`promptCacheKey` is off by default. `'session'` sends the session id, and nothing when the run has none. `true` sends the session id, or without one a 32-character hash of the static half of the system prompt and the first user message. You can also pass a string or a function. `promptCacheKeyHeader` copies the key into a header that you name. On `openaiCompat`, `promptCacheKeyField` renames the body field (`'user'`, for example), and `false` sends the key in the header only.

In the apps, each session's system prompt is frozen on its first run, and later changes reach the model as notes, so the cache keeps hitting. `/refresh` re-reads the prompt and `/cache` shows what the cache did for the last requests. How the apps keep the prefix stable: [system prompt](https://github.com/Tahul/zidane/blob/main/docs/CHAT.md#system-prompt). In the SDK, `systemNotes` and `systemFreeze` on `agent.run()` do the same ([keeping the prefix stable](https://github.com/Tahul/zidane/blob/main/docs/SKILL.md#keeping-the-prefix-stable)).

On Anthropic the thinking budget and level are part of the cache key. With caching on, use the step form of `behavior.thinkingDecay` ([tool dispatch](https://zidane.si/docs/hooks.md#tool-dispatch)).

## Model fallback

`provider` on `createAgent` accepts an array, and `model` on `agent.run()` an ordered list:

```ts
const agent = createAgent({
  provider: [anthropic({ /* ... */ }), openrouter({ /* ... */ })], // names must be unique
})

await agent.run({
  prompt: '…',
  model: [
    { provider: 'anthropic', model: 'claude-sonnet-4-5' },   // primary
    { provider: 'openrouter', model: 'anthropic/claude-sonnet-4-5' },
    'claude-haiku-4-5',   // bare string: tried on every provider, in order
  ],
})
```

Each provider needs a unique, non-empty `name`. Model entries refer to it. The run moves to the next target when a stream fails before anything streamed, for one of these reasons:

- The retries ran out.
- The error cannot be retried (auth, billing).
- The `retry-after` is over 15 s.

`context_exceeded` does not fall back. It goes through overflow recovery ([errors](https://zidane.si/docs/errors.md)). Only tool definitions get reformatted for the new provider.

| Behavior | Details |
| - | - |
| Run-sticky | The run stays on the new target. The next `run()` starts from the primary. |
| `model:fallback` hook | Fires on each advance with `from`, `to`, `targetIndex` and `err`. `err` is the classified `AgentProviderError`, with the raw provider error on `err.cause`. Only the failure of the last target reaches `stream:error`. |
| Budgets carry over | `maxTokens` and the thinking budget keep the primary's values, so size them for the smallest model. |

## Plan usage

Both apps show how much of your plan is left: subscription windows for Anthropic, OpenAI Codex and xAI sign-ins, and OpenRouter credits. The terminal app opens the panel with `Ctrl`+`P`. The desktop app shows it in a modal and on the welcome screen.

The Check usage setting (`subscriptionUsage`, on by default) runs the automatic checks. `ZIDANE_NO_USAGE_CHECK=1` or `CI` turns off every check. Only the OpenRouter endpoint is official, so the others can break without notice. A failed check shows as unavailable.

## Reference

- [`zidane/providers`](https://zidane.si/docs/reference/providers.md)
- [`zidane/providers/anthropic`](https://zidane.si/docs/reference/providers-anthropic.md)
- [`zidane/providers/openai`](https://zidane.si/docs/reference/providers-openai.md)
- [`zidane/providers/openai-compat`](https://zidane.si/docs/reference/providers-openai-compat.md)
- [`zidane/providers/openrouter`](https://zidane.si/docs/reference/providers-openrouter.md)
- [`zidane/providers/xai`](https://zidane.si/docs/reference/providers-xai.md)
- [`zidane/providers/google`](https://zidane.si/docs/reference/providers-google.md)
- [`zidane/providers/cerebras`](https://zidane.si/docs/reference/providers-cerebras.md)
- [`zidane/providers/baseten`](https://zidane.si/docs/reference/providers-baseten.md)
- [`zidane/providers/arcee`](https://zidane.si/docs/reference/providers-arcee.md)
- [`zidane/providers/local`](https://zidane.si/docs/reference/providers-local.md)
