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 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 |
| OpenRouter | openrouter() | Key | OPENROUTER_API_KEY | openrouter | Credits in 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 Vertex AI | googleVertex() | Key, Google Cloud credentials or Sign in with Google | GOOGLE_CLOUD_API_KEY or GOOGLE_CLOUD_PROJECT | google-vertex | See Google |
| 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 |
| Local or self-hosted | local() | Base URL, optional key | LOCAL_LLM_BASE_URL and more | local, openai-compat | See 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). OpenAI's image endpoint needs a platform API key, also with a ChatGPT sign-in.
The --provider names are for zidane run (CLI). bun run headless accepts fewer: anthropic, openai, openrouter, cerebras, baseten, neuralwatt, local and openai-compat (headless).
Anthropic#
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.
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#
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.
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#
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 APIsThese 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#
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. false sends no header.
Google Gemini and Vertex AI#
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 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):
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).
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 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. In the SDK, systemNotes and systemFreeze on agent.run() do the same (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).
Model fallback#
provider on createAgent accepts an array, and model on agent.run() an ordered list:
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-afteris over 15 s.
context_exceeded does not fall back. It goes through overflow recovery (errors). 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 CtrlP. 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.