Skip to content

zidane/providers

The Provider contract, the shared provider helpers, OAuth credential handling and the Mistral, Z.ai and Cursor providers.

import { … } from 'zidane/providers'src/providers/index.ts

Use zidane/providers to write your own provider or manage OAuth credentials. It holds the Provider interface and its types, the model catalog, prompt-cache key helpers and tool-schema sanitizing, plus mistral(), zai() and cursor(). The other providers, Neuralwatt included, are also exported here, and each has its own entry too. The agent passes its session id to every stream call (StreamOptions.sessionId), so a provider can route a conversation to one cache replica. See Providers.

TypeScript
import { createAgent } from 'zidane'
import { mistral } from 'zidane/providers'

const provider = mistral()
const agent = createAgent({ provider })

console.log(provider.name, provider.meta.defaultModel)
await agent.run({ prompt: 'Say hello.' })

Functions#

credentialsFilePathFunction#

Resolve the creds-file path at call time rather than module-load time, so env changes between import and first OAuth use (test suites, multi-project CLIs) are honored.

Order:

  1. ZIDANE_CREDENTIALS_PATH — explicit override. The chat host sets this at launch to point at the credentials file in its storage directory, so the harness providers find the same file the host manages.
  2. ~/.credentials.json — home-anchored default.

The working directory is never consulted: launching zidane inside an untrusted checkout would let that repo plant a .credentials.json and become the credential source (and receive rotated refresh tokens on the next refresh persist). Same trust model as the TUI's legacy-file migration in chat/credentials.ts, which only honors the home-anchored location.

credentialsFromParamsFunction#

Builds OAuth credentials from the runtime access, refresh and expires params, plus any listed extra keys that are set. Returns undefined when one of the three is missing.

cursorFunction#

Cursor provider (OAuth).

Auth is fully wired: bun run auth --cursor (or the TUI wizard) logs in via Cursor's poll-based PKCE flow and persists credentials; this provider resolves

Inference is not wired yet. Cursor does not expose an OpenAI-compatible HTTP endpoint — it speaks a protobuf/Connect agent protocol over HTTP/2 to api2.cursor.sh. Implementing stream() requires porting that transport (generated protobuf schemas + an HTTP/2 streaming client) in-process, which is intentionally left as a follow-up. Until then stream() resolves the token (proving auth works) and then throws a clear, actionable error.

The openaiCompat base supplies the message-format helpers (formatTools, userMessage, toolResultsMessage, …) so the provider satisfies the full Provider contract and slots into the registry; only stream is overridden.

derivePromptCacheKeyFunction#

A key for what a session shares when no session id is available: the static half of the system prompt plus the first user message.

Deliberately NOT the whole transcript — that grows every turn, which would hand out a new key per request and defeat the point. Nor the dynamic half of the system prompt (after SYSTEM_PROMPT_BOUNDARY, see splitSystemPrompt): it carries the date and working directory, so hashing it would move the key when the day rolls over mid-session. What remains stays byte-identical for a session's life and moves only with a different agent profile, a new opener, or a compaction that rewrites the head.

The message contribution is capped at its first 4 KiB of JSON: this runs on every stream call, and a first message carrying inline media (multi-MB base64) would pay a full stringify + hash per turn for no routing benefit — the first few hundred bytes already distinguish sessions, and the servers themselves only hash ~the first 256 tokens for routing. Two sessions that agree on system + 4 KiB of opener share a routing bucket, which is exactly when sharing a replica helps rather than hurts.

32 hex chars of sha256, matching the digest width used in src/attachment-handle.ts.

ensureBundledOAuthFlowsFunction#

OAuth registry surface.

refreshAnthropicToken / refreshOpenAICodexToken are zidane's local reimplementations of functions pi-ai exported until 0.80.6 and dropped in 0.80.8. The runtime of its @earendil-works/pi-ai/oauth entrypoint is export {} (it ships types only). They are exported HERE because consumers that imported them from pi-ai otherwise have no supported replacement.

extractRuntimeCredentialsFunction#

Pull a complete { access, refresh, expires } triple out of provider params when (and only when) all three OAuth fields are present and well-typed. Returns undefined for the static-api-key path. Shared by the OAuth-capable providers so the runtime-credential guard can't drift between them.

getModelCatalogFunction#

The process-wide catalog, normalized to source form.

getOAuthApiKeyFunction#

Get an API key for a provider from OAuth credentials, refreshing expired tokens through the registered flow. Same contract as the pi-ai original (null when no credentials are stored for the provider, throws on unknown provider or refresh failure), with two deliberate additions:

  • refresh failures keep the underlying reason — the original discarded it, leaving invalid_grant (needs re-login) indistinguishable from a transient network error.
  • options.force rotates regardless of the local expiry, for 401 recovery when the server revoked the grant before our stored timestamp said so. Expressed as a flag rather than by handing this function a credential with a doctored expires: callers persist whatever comes back, so a fabricated expiry that a provider echoes through would land in the credential store. See resolveOAuthApiKey in ./oauth.ts.

getOAuthProviderFunction#

Get a registered OAuth provider by id.

isOAuthAccessTokenFunction#

Whether a credential string is an OAuth access token rather than a real, static API key. Anthropic OAuth tokens are prefixed sk-ant-oat; OpenAI Codex OAuth access tokens are JWTs (eyJ...). Used to decide whether an env-provided value can be refreshed (OAuth) or must be sent verbatim (API key).

mistralFunction#

Mistral provider (La Plateforme).

Thin wrapper around openaiCompat with Mistral-specific defaults (base URL, default model, reasoning_effort planner). supportsReasoning stays off: Mistral carries thinking inside content chunks rather than a reasoning_content field, so there is nothing to echo back upstream — thinking streams to the UI and is dropped from subsequent requests.

openrouterImageModelsFunction#

OpenRouter's image models, from pi-ai's bundled catalog.

pinnedAccountMissingErrorFunction#

Why a call pinned to account cannot run: the account is no longer stored.

planMistralReasoningFunction#

Map zidane's thinking level to Mistral's reasoning_effort. Only the two documented values are emitted — 'none' (thinking chunk omitted, content stays a plain string) and 'high' (full thinking chunk). adaptive sends nothing so the model's own default applies.

Exported for tests.

planZaiReasoningFunction#

Map zidane's thinking level to Z.ai's extra_body.thinking request shape. GLM has no depth knob — thinking is a binary switch — so any non-off level enables it and off explicitly disables it (GLM-5.x/4.7 think by default, so a silent omission would not turn thinking off).

Exported for tests.

readAccountOAuthCredentialFunction#

The stored OAuth credential of baseKey whose stable id is account, as the provider-side reader sees it; undefined when no slot holds it.

readOAuthCredentialsFunction#

Read the shared credentials file.

Returns {} when the file is missing OR when the file exists but is corrupted / not valid JSON. Corrupt-file tolerance is important: the refresh-then-persist path below writes atomically, but older builds wrote in place and could have truncated the file on a crash. Treating corruption as "no stored creds" lets users re-auth without manually deleting the file.

Supports two on-disk shapes:

  1. Legacy (untagged): { "anthropic": { access, refresh, expires, ... } } — written by older bun run auth.
  2. New (kind-tagged): { "anthropic": { kind: 'oauth', access, refresh, expires, ... } } — written by the TUI. Entries with kind: 'apikey' are skipped — those reach providers via env vars set at TUI launch (see applyApiKeyEnv).

refreshAnthropicTokenFunction#

Refresh an Anthropic (Claude Pro/Max) OAuth token. Drop-in replacement for the refreshAnthropicToken pi-ai exported until 0.80.6; delegates to the canonical flow on anthropicProvider().auth.oauth.

refreshOpenAICodexTokenFunction#

Refresh an OpenAI Codex (ChatGPT) OAuth token. Drop-in replacement for the removed refreshOpenAICodexToken; the canonical flow still derives accountId from the refreshed access token's JWT claims, so the returned credentials keep the extra field older callers persisted.

registerCustomOAuthProviderFunction#

Register a custom OAuth flow with the local registry (./oauth-registry) so lazy refresh through resolveOAuthApiKey can resolve providerId.

Public because provider extensions need it: without registration, getOAuthApiKey('<id>', …) throws Unknown OAuth provider the first time a stored token expires. Idempotent per id (the registry is a keyed map). Returns a promise for API stability.

registerOAuthProviderFunction#

Register (or replace) an OAuth provider by id.

resolveOAuthApiKeyFunction#

Resolves the API key for an OAuth provider. Returns a static apiKey, runtime access token or env key as is, otherwise reads the stored credentials and refreshes them when expired. Refreshed credentials are persisted and concurrent refreshes of the same credential are shared. Throws missingError when no credential exists and refreshError when the refresh fails.

resolvePromptCacheKeyFunction#

Resolve a configured option into the key to send, or undefined to send nothing. Empty strings (including an empty session id) are treated as "send nothing" so a host threading through a possibly-unset id can pass it straight in.

sanitizeToolSchemaFunction#

Sanitize a single tool's inputSchema for safe forwarding to the provider. Returns the rewritten schema + a list of warnings describing everything that changed.

Never mutates the input. Returns the same reference when no rewrite was needed (clean-schema fast path) — sanitizeToolSpecs relies on this to keep the formatTools hot loop allocation-free across turns when the registered tool set is already wire-valid.

sanitizeToolSpecsFunction#

Convenience: sanitize a batch of tools and emit a single de-duped console.warn per unique warning line. Returns the rewritten tools preserving original ordering and reference identity for clean schemas (no reallocation when nothing needed to change).

The sanitiser runs every request. The de-dupe set is local to one call, so within a batch each warning line is logged once and the first occurrence is kept.

Final name guard: a tool whose name violates the provider constraint is DROPPED (not throwingly forwarded) with a one-time warning. An invalid name 400s the entire messages.create — taking every other tool and the turn down with it — so silently degrading one capability is the strictly safer failure mode. Upstream aliasing already coerces MCP names to valid wire names, so in practice this only fires for names that cannot be represented at all; it's defense-in-depth that keeps a malformed name in the list from reaching the wire.

setModelCatalogFunction#

Install (or clear, with null) the process-wide catalog. Latest call wins.

Prefer behavior.modelCatalog when you can: it needs no global state and scopes to one agent or one run. Use this for a deployment-wide correction, or when the host's model picker must see the override too.

writeOAuthCredentialsFunction#

Incoming entries are a provider-local patch, not a read-time snapshot.

zaiFunction#

Z.ai (Zhipu) GLM provider.

Thin wrapper around openaiCompat pinned to the reasoning_content round-trip mode: thinking arrives in the reasoning_content delta field and is echoed back on assistant messages under the same name, keeping GLM's interleaved/preserved thinking coherent across tool calls.

Constants#

GOOGLE_IMAGE_MODELSConstant#

Gemini image models on the Gemini API.

IMAGE_ASPECT_RATIOSConstant#

Aspect ratios every adapter accepts. Each adapter maps them to its API (OpenAI to the nearest of its three sizes).

MAX_IMAGES_PER_REQUESTConstant#

Most images one request returns.

OPENAI_IMAGE_MODELSConstant#

OpenAI's GPT Image models.

XAI_IMAGE_MODELSConstant#

xAI's Grok Imagine image models.

Interfaces#

CursorParamsInterface#

Options for cursor.

HistoryRepairInterface#

Request from a provider to repair canonical session history before it retries a rejected request.

ImageDataInterface#

A base64 image, as sent to or returned by an image API.

ImageModelInfoInterface#

One image model of a provider.

InputTransformationInterface#

One replayed thinking / redacted_thinking block the API discarded before sampling, reported in the response's input_transformations array (requires the thinking-binding-controls-2026-08-01 beta; Anthropic-only today).

MistralParamsInterface#

Options for mistral.

ModelCatalogEntryInterface#

Metadata for one model. Every field is optional and merges FIELD-LEVEL over the bundled entry, so { maxTokens: 128000 } corrects the ceiling and leaves pricing and context window to the registry.

ModelCatalogSourceInterface#

Programmatic catalog. Both members are optional and both fall through on undefined, which is what keeps overrides scoped to the providers you actually own.

OAuthParamsInterface#

Credential params shared by OAuth-capable providers: a static key or runtime OAuth credentials.

OAuthProviderInterfaceInterface#

Provider-owned OAuth flow. Mirrors the interface pi-ai exported from @earendil-works/pi-ai/oauth up to 0.80.6 — kept as a local type so zidane's descriptor seam (ProviderDescriptor.oauthProvider) and provider extensions (e.g. extensions/bolt) stay source-compatible across the upstream removal.

PromptCacheKeyContextInterface#

What a request needs to expose for a key to be derived from it.

ProviderInterface#

A model backend. Converts messages and tools to the wire format, streams turns and reports capabilities.

ProviderCapabilitiesInterface#

Provider-level capability flags used by the agent loop to route tool results and user messages appropriately.

Known providers should set these explicitly. For legacy compatibility, omitted vision is still treated permissively in some tool-result paths; newer media kinds (audio, video) and documents are opt-in and should be set to true before bytes are forwarded natively.

ProviderDecisionRequestInterface#

Request passed to ProviderDecisions.decide.

ProviderDecisionResponseInterface#

Response of ProviderDecisions.decide.

ProviderDecisionsInterface#

Native Decisions endpoint of a provider.

ProviderImageRequestInterface#

Request passed to ProviderImages.generate.

ProviderImageResponseInterface#

Response of ProviderImages.generate.

ProviderImagesInterface#

Image generation endpoint of a provider.

ProviderMetaInterface#

Provider-level metadata the agent core reads.

Every field the core consumes is declared here. The open Record<string, unknown> tail exists for provider-PRIVATE bookkeeping (a provider stashing something only it reads back) — core code must never reach into it through a structural cast, because a cast is an undeclared contract between two modules that no compiler checks.

ResolveOAuthApiKeyOptionsInterface#

Options for resolveOAuthApiKey.

SchemaSanitizeOptionsInterface#

Options for sanitizeToolSchema.

SchemaSanitizeResultInterface#

Result of sanitizeToolSchema.

StreamCallbacksInterface#

Callbacks a provider invokes while streaming a turn.

StreamOptionsInterface#

Options for one Provider.stream call.

TokenCountPayloadInterface#

Payload for Provider.countTokens — the input half of a request whose token footprint we want measured WITHOUT running inference. Mirrors the subset of StreamOptions that contributes to input tokens.

ToolCallInterface#

A tool call requested by the model.

ToolResultInterface#

The outcome of running a ToolCall, sent back to the model.

ToolSpecInterface#

Provider-neutral description of a tool the model can call.

TurnResultInterface#

Outcome of one streamed assistant turn.

ZaiParamsInterface#

Options for zai.

Types#

ImageAspectRatioType#

One of IMAGE_ASPECT_RATIOS.

ModelCatalogType#

A catalog: either a ModelCatalogSource or the table shorthand — a plain record keyed "<provider>:<modelId>".

The table form is a pure OVERLAY: entries patch or add models, and each provider keeps every bundled model the table doesn't name. Hiding bundled models requires the list function, so the easy path can't accidentally blank a provider's picker.

Text
setModelCatalog({ 'openai:gpt-5.6': { maxTokens: 128000 } })

ModelTierType#

Size class of a model within its provider's lineup. Drives the automatic text model (titles, suggestions): the smallest current model of the session's provider. Bundled models get it from a family heuristic (src/chat/text-model.ts); a catalog entry's value wins over it.

PromptCacheKeyOptionType#

How to obtain the affinity key for a request.

  • undefined / false — send nothing (default).
  • 'session' — the request's session id; send nothing when it has none.
  • true — the request's session id when it has one, otherwise a hash of the cacheable prefix via derivePromptCacheKey.
  • any other string — use verbatim, for every request.
  • function — compute per request; returning undefined omits the field for that request, so a host can key some traffic and not the rest.

ProviderDecisionAvailabilityType#

Whether a provider can serve decisions with the credentials it has: available, or unavailable with a reason.

ProviderImageAvailabilityType#

Whether a provider can generate images with the credentials it has: available, or unavailable with a reason.

SchemaSanitizeProfileType#

Strictness profile — picks which transformations to apply.

Re-exports#

These names are documented on the entry point that owns them.