Skip to content

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#

ProviderSDK factorySign-inEnvironment variable--provider nameNotes
Anthropicanthropic()Key or Claude Pro/MaxANTHROPIC_API_KEYanthropicAlso Amazon Bedrock
OpenAIopenai()Key or ChatGPT (Codex)OPENAI_CODEX_API_KEYopenaiSee OpenAI
OpenRouteropenrouter()KeyOPENROUTER_API_KEYopenrouterCredits in plan usage
Cerebrascerebras()KeyCEREBRAS_API_KEYcerebras
xAI Grokxai()Key or SuperGrok / X Premium+XAI_API_KEYxai
Google Geminigoogle()KeyGEMINI_API_KEY, then GOOGLE_API_KEYgoogleSee Google
Google Vertex AIgoogleVertex()Key, Google Cloud credentials or Sign in with GoogleGOOGLE_CLOUD_API_KEY or GOOGLE_CLOUD_PROJECTgoogle-vertexSee Google
Arceearcee()KeyARCEE_API_KEYarcee
Basetenbaseten()KeyBASETEN_API_KEYbaseten
Z.ai (GLM)zai()KeyZAI_API_KEYnoneApps and SDK only
Mistralmistral()KeyMISTRAL_API_KEYnoneApps and SDK only
Neuralwattneuralwatt()KeyNEURALWATT_API_KEYneuralwattSee Neuralwatt
Local or self-hostedlocal()Base URL, optional keyLOCAL_LLM_BASE_URL and morelocal, openai-compatSee local endpoints
Any OpenAI-compatible APIopenaiCompat()KeyYour choiceopenai-compat
Cursorcursor()Cursor subscriptionnonenoneSign-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#

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

TypeScript
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'],
    }],
  },
})
ParamEffect
extraBetasMerged with the OAuth defaults, de-duplicated.
contextManagementSent as context_management. Pair it with its beta. On other providers use behavior.compactStrategy: 'tail'.
extraHeadersRequest headers for proxies and gateways. Reserved Anthropic headers win a clash.
extraBodyParamsUntyped 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.

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

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

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

TypeScript
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):

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

VariableSetsRead by
LOCAL_LLM_BASE_URLThe endpointlocal(), the CLI and the apps
LOCAL_LLM_API_KEYA bearer key, if the server wants onelocal(), the CLI and the apps
LOCAL_LLM_DEFAULT_MODELThe model idlocal(), the CLI and the apps
LOCAL_LLM_AUTHORIZATIONThe exact Authorization header valueThe CLI
LOCAL_LLM_HEADERSExtra headers, as a JSON objectThe CLI
LOCAL_LLM_CONTEXT_WINDOWThe context window of the model, in tokensThe 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:

FlagSigns in to
noneAnthropic and OpenAI Codex
--anthropic or --claudeAnthropic
--openai or --codexOpenAI Codex
--xai or --grokxAI
--google or --vertexGoogle Vertex AI (needs ZIDANE_GOOGLE_CLIENT_ID and ZIDANE_GOOGLE_CLIENT_SECRET)
--cursorCursor

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

ProviderCache markers
anthropicHonored.
openrouterForwarded. Anthropic and Gemini routes honor them. Other routes cache on their own.
arcee, xai, zai, mistral, local, openaiCompat, cerebrasOff. Set openaiCompat({ cacheBreakpoints: true }) only for endpoints that honor cache_control.
basetenOff. Baseten caches every request server-side.
google, googleVertexOff. Gemini caches repeated prefixes on its own and reports them as cacheRead.
neuralwattOff. Neuralwatt caches repeated prefixes on its own.
openaiNo 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.

ProviderKey
openaiAutomatic. The session id goes out as prompt_cache_key, plus session headers on the platform API.
neuralwattOn by default. The session id goes out as the X-NW-Conversation-ID header.
anthropicpromptCacheKey, sent as metadata.user_id
openaiCompat, openrouter, basetenpromptCacheKey, 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:

TypeScript
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). Only tool definitions get reformatted for the new provider.

BehaviorDetails
Run-stickyThe run stays on the new target. The next run() starts from the primary.
model:fallback hookFires 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 overmaxTokens 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.

Reference#