# zidane/providers/openai-compat

> A factory for any OpenAI-compatible Chat Completions endpoint, with the message and stream helpers it uses.

Import: `import { … } from 'zidane/providers/openai-compat'`. Source: [src/providers/openai-compat.ts](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts).

Use `openaiCompat()` when your model sits behind an OpenAI-style `/chat/completions` route that has no dedicated provider here, such as a gateway or a self-hosted server. You pass the base URL and key. You can also set the auth header, extra headers and capability flags. `promptCacheKey` sends a cache-affinity key: `'session'` uses the agent's session id, and `true` uses it when there is one, else a hash of the static system prompt and the first user message. The OpenRouter, Cerebras, xAI, Baseten, Arcee, Neuralwatt, Z.ai, Mistral and local providers wrap it. The entry also exports the converters and stream readers, for custom providers.

```ts
import { createAgent } from 'zidane'
import { openaiCompat } from 'zidane/providers/openai-compat'

const provider = openaiCompat({
  name: 'my-gateway',
  apiKey: process.env.GATEWAY_API_KEY ?? '',
  baseURL: 'https://gateway.example.com/v1',
  defaultModel: 'my-model',
})

const agent = createAgent({ provider })
await agent.run({ prompt: 'Say hello.' })
```

## Functions

### `applyOAICacheBreakpoints`

Function.

Add `cache_control: { type: 'ephemeral' }` breakpoints to the system message's
last text part and the last message's final content part.

Mutates `messages` in place. Converts plain-string content into a single-element
content array so the cache marker can attach — this shape is accepted verbatim by
OpenRouter's Anthropic and Gemini routes and ignored by routes with automatic
caching (OpenAI, DeepSeek, Grok, Groq, Moonshot).

Skip conditions (safe no-ops):

- Empty messages array.
- Assistant messages with no text (tool-call-only) — attaching a cache marker to a
  `tool_calls` block has no defined semantics, so we skip and let the prior
  system/tools breakpoints carry caching.

System-prompt boundary handling: when `originalSystem` (the un-rendered
system text passed to [`toOAIMessages`](https://zidane.si/docs/reference/providers-openai-compat.md#toOAIMessages)) contains
`SYSTEM_PROMPT_BOUNDARY`, the leading system message is rewritten as
a two-part array — `cache_control` lands on the static prefix only, so
per-turn churn in the dynamic suffix doesn't invalidate the cached
doctrine above. `originalSystem` is optional: omit it (or pass a
marker-free string) for the single-block behavior.

```ts
function applyOAICacheBreakpoints(
  messages: OAIMessage[],
  originalSystem?: string,
  ttl?: CacheTtl,
): void
```

**Parameters**

- `messages` (`OAIMessage[]`): Wire messages to mutate. The first system message and the last cacheable message get a marker.
- `originalSystem?` (`string`): System prompt before boundary rendering, used to split the static and dynamic parts.
- `ttl?` (`CacheTtl`): Cache lifetime for the markers. Uses the host default when omitted.

Source: [src/providers/openai-compat.ts:559](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts#L559)

### `applyOAIToolCacheBreakpoint`

Function.

Return a copy of `tools` with `cache_control` on the last entry.

OpenRouter accepts the marker alongside the standard `type` + `function` fields
and forwards it when routing to Anthropic/Gemini. Leaves the non-cached tools
unchanged so the caller's reference is not mutated.

```ts
function applyOAIToolCacheBreakpoint(tools: OAITool[], ttl?: CacheTtl): OAITool[]
```

**Parameters**

- `tools` (`OAITool[]`): Wire tools. Not mutated.
- `ttl?` (`CacheTtl`): Cache lifetime for the marker. Uses the host default when omitted.

**Returns** `OAITool[]`

Source: [src/providers/openai-compat.ts:659](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts#L659)

### `assistantMessage`

Function.

Creates a text-only assistant message, for priming.

```ts
function assistantMessage(content: string): SessionMessage
```

**Parameters**

- `content` (`string`): Message text.

**Returns** `SessionMessage`

Source: [src/providers/openai-compat.ts:744](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts#L744)

### `buildAssistantContent`

Function.

Builds an assistant message from streamed text, tool calls and reasoning. Falls back to a `[no content]` text block when everything is empty.

```ts
function buildAssistantContent(
  text: string,
  toolCalls: { id: string; input: Record<string, unknown>; name: string }[],
  thinking?: string,
  reasoning?: { details: unknown[]; model?: string; producer: 'openrouter' } | { model?: string; producer: 'vllm'; reasoningText: string },
): SessionMessage
```

**Parameters**

- `text` (`string`): Streamed text.
- `toolCalls` (`{ id: string; input: Record<string, unknown>; name: string }[]`): Tool calls requested by the model.
- `thinking?` (`string`): Streamed thinking text.
- `reasoning?` (`{ details: unknown[]; model?: string; producer: 'openrouter' } | { model?: string; producer: 'vllm'; reasoningText: string }`): Reasoning state to persist, from OpenRouter details or vLLM-style text.

**Returns** `SessionMessage`

Source: [src/providers/openai-compat.ts:795](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts#L795)

### `classifyOpenAICompatError`

Function.

Classify an OpenAI-compatible error into `ClassifiedError`.

Recognizes:

- `AbortError` (from fetch) → `aborted`.
- `OpenAICompatHttpError` with a context-exceeded code or message → `context_exceeded`.
- A rate limit, spent credit or overload (429, 402, 529) → `provider_error`
  worded by `describeProviderLimit`.
- Any other `OpenAICompatHttpError` → `provider_error`.

Returns `null` for unrecognized error shapes (the loop falls back to `AgentProviderError`).

```ts
function classifyOpenAICompatError(err: unknown, label?: string): ClassifiedError | null
```

**Parameters**

- `err` (`unknown`): Error thrown by `fetch` or the SSE consumer.
- `label?` (`string`, default `'openai-compat'`): Product a limit's message names (`OpenRouter`). Defaults to `'openai-compat'`, the factory's default `name`.

**Returns** `ClassifiedError | null`

Source: [src/providers/openai-compat.ts:894](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts#L894)

### `consumeSSE`

Function.

Reads an SSE chat-completions response to the end, forwarding text and thinking deltas to `callbacks`. Returns the accumulated text, tool calls, usage, finish reason and, when the server sent them, energy in joules (see `readEnergyJoules`) and the request's own USD price as `costUsd` (see `readRequestCostUsd`). Throws [`OpenAICompatStreamError`](https://zidane.si/docs/reference/providers-openai-compat.md#OpenAICompatStreamError) on an empty or truncated stream and an `AbortError` when `signal` aborts.

```ts
function consumeSSE(
  response: Response,
  callbacks: StreamCallbacks,
  signal?: AbortSignal,
): Promise<{ costUsd: number | undefined; energyJoules: number | undefined; finishReason: string; reasoningDetails: Record<string, unknown>[]; text: string; thinking: string; toolCalls: { id: string; input: Record<string, unknown>; malformedArgs?: MalformedToolArguments; name: string }[]; usage: OpenAICompatUsage }>
```

**Parameters**

- `response` (`Response`): Streaming HTTP response with a body.
- `callbacks` (`StreamCallbacks`): Receives text, thinking and tool-call activity as chunks arrive.
- `signal?` (`AbortSignal`): Aborts reading when signaled.

**Returns** `Promise<{ costUsd: number | undefined; energyJoules: number | undefined; finishReason: string; reasoningDetails: Record<string, unknown>[]; text: string; thinking: string; toolCalls: { id: string; input: Record<string, unknown>; malformedArgs?: MalformedToolArguments; name: string }[]; usage: OpenAICompatUsage }>`

Source: [src/providers/openai-compat-stream.ts:225](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat-stream.ts#L225)

### `formatTools`

Function.

Converts tool specs to OpenAI function tools, after schema sanitization for the `openai` profile.

```ts
function formatTools(tools: ToolSpec[]): OAITool[]
```

**Parameters**

- `tools` (`ToolSpec[]`): Tool specs to convert.

**Returns** `OAITool[]`

Source: [src/providers/openai-compat.ts:678](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts#L678)

### `mapOAIFinishReason`

Function.

Map an OpenAI-compatible `finish_reason` string to the zidane `TurnFinishReason` union.

```ts
function mapOAIFinishReason(
  reason: string | null | undefined,
): TurnFinishReason | undefined
```

**Parameters**

- `reason` (`string | null | undefined`): Raw `finish_reason` from the response.

**Returns** `TurnFinishReason | undefined`

Source: [src/providers/openai-compat.ts:957](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts#L957)

### `openaiCompat`

Function.

Factory for any OpenAI-compatible HTTP endpoint.

Speaks the standard `POST /chat/completions` + `stream: true` + SSE dialect.
Thin wrappers such as `openrouter`, `cerebras` and `baseten` call this with pinned defaults.

```ts
function openaiCompat(params: OpenAICompatParams): Provider
```

**Parameters**

- `params` (`OpenAICompatParams`): Endpoint, API key, model and capability settings for the OpenAI-compatible host.

**Returns** `Provider`

**Examples**

```ts
openaiCompat({
  name: 'baseten',
  apiKey: process.env.BASETEN_API_KEY!,
  baseURL: process.env.BASETEN_PROXY_URL!,
  authHeader: { name: 'Authorization', scheme: 'Api-Key' },
})
```

```ts
openaiCompat({
  name: 'openai',
  apiKey: process.env.OPENAI_API_KEY!,
  baseURL: 'https://api.openai.com/v1',
  capabilities: { vision: true, documents: true },
})
```

Source: [src/providers/openai-compat.ts:1247](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts#L1247)

### `openAICompatPromptMessage`

Function.

Build a user `SessionMessage` from multimodal prompt parts.

Passes every kind (text/image/audio/video/document) through as a canonical
block; the actual wire emission, capability gating, and unsupported-media
throwing all happen later in [`toOAIMessages`](https://zidane.si/docs/reference/providers-openai-compat.md#toOAIMessages). Implementing this (rather
than letting the agent fall back to `defaultPromptMessage`) is what lets
openai-compat routes accept base64 documents/PDFs at all — the default
builder throws on base64 documents.

```ts
function openAICompatPromptMessage(parts: PromptPart[]): SessionMessage
```

**Parameters**

- `parts` (`PromptPart[]`): Prompt parts to pack into one user message.

**Returns** `SessionMessage`

Source: [src/providers/openai-compat.ts:715](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts#L715)

### `toOAIMessages`

Function.

Converts a system prompt and session messages to OpenAI Chat Completions messages. Starts with a system message and applies the capability and reasoning settings in `options`.

```ts
function toOAIMessages(
  system: string,
  messages: SessionMessage[],
  options?: ToOAIOptions,
): OAIMessage[]
```

**Parameters**

- `system` (`string`): System prompt. The prompt-boundary marker is removed before sending.
- `messages` (`SessionMessage[]`): Session messages to convert.
- `options?` (`ToOAIOptions`, default `{}`): Capability, reasoning and model settings that shape the wire messages.

**Returns** `OAIMessage[]`

Source: [src/providers/openai-compat.ts:271](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts#L271)

### `toolResultsMessage`

Function.

Creates a user message carrying tool results back to the model.

```ts
function toolResultsMessage(results: ToolResult[]): SessionMessage
```

**Parameters**

- `results` (`ToolResult[]`): Results of the tool calls of the previous turn.

**Returns** `SessionMessage`

Source: [src/providers/openai-compat.ts:753](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts#L753)

### `userMessage`

Function.

Creates a text-only user message.

```ts
function userMessage(content: string): SessionMessage
```

**Parameters**

- `content` (`string`): Message text.

**Returns** `SessionMessage`

Source: [src/providers/openai-compat.ts:699](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts#L699)

## Classes

### `OpenAICompatHttpError`

Class.

HTTP error thrown when an OpenAI-compatible endpoint returns a non-OK response.

The body is best-effort JSON-parsed; `error.message` / `error.code` / `error.type`
are extracted for clean downstream classification.

```ts
class OpenAICompatHttpError extends Error
```

**Constructor**

```ts
new OpenAICompatHttpError(status: number, bodyText: string)
```

Parses `bodyText` as JSON to build the error message when possible.

**Parameters**

- `status` (`number`): HTTP status code.
- `bodyText` (`string`): Raw response body.

**Returns** `OpenAICompatHttpError`

**Properties**

- `bodyText` (`string`, readonly): Raw response body.
- `providerCode?` (`string`, readonly): Error `code` or `type` from the response body, when present.
- `status` (`number`, readonly): HTTP status code of the response.

Source: [src/providers/openai-compat.ts:844](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts#L844)

### `OpenAICompatStreamError`

Class.

Error thrown when an OpenAI-compatible stream is empty, truncated or malformed. Classified as retryable.

```ts
class OpenAICompatStreamError extends Error
```

**Constructor**

```ts
new OpenAICompatStreamError(message: string)
```

Creates the error.

**Parameters**

- `message` (`string`): Description of the stream failure.

**Returns** `OpenAICompatStreamError`

Source: [src/providers/openai-compat-stream.ts:27](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat-stream.ts#L27)

## Constants

### `ASSISTANT_TOOL_CALLS_TAG`

Constant.

Sentinel tag that marks a message carrying round-tripped assistant tool calls.

```ts
const ASSISTANT_TOOL_CALLS_TAG = '__zidane_assistant_tc__'
```

Source: [src/providers/openai-compat.ts:75](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts#L75)

### `TOOL_RESULTS_TAG`

Constant.

Sentinel tag that marks a message carrying round-tripped tool results.

```ts
const TOOL_RESULTS_TAG = '__zidane_tool_results__'
```

Source: [src/providers/openai-compat.ts:73](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts#L73)

## Interfaces

### `OAIMessage`

Interface.

A chat message in the OpenAI Chat Completions wire format.

```ts
interface OAIMessage
```

**Properties**

- `content?` (`unknown`): Message content: a string or an array of content parts. `null` or absent on tool-call turns.
- `reasoning?` (`string`): Plain reasoning string, used when `OpenAICompatParams.reasoningContentField` is `reasoning`.
- `reasoning_content?` (`string`): Plain reasoning string echoed back on assistant messages for vLLM-style
  reasoning models (Arcee Trinity-Thinking, DeepSeek-R1). Arcee's API accepts
  `reasoning_content`; some self-hosted vLLM versions read `reasoning`.
- `reasoning_details?` (`unknown[]`): OpenRouter normalized reasoning envelope. Echoed back unmodified on assistant
  messages so the gateway can resume an extended-reasoning chain on the
  upstream route. Ignored by hosts that don't speak the envelope.
- `role` (`'system' | 'tool' | 'user' | 'assistant'`): Author of the message.
- `tool_call_id?` (`string`): Id of the tool call a `tool` message answers.
- `tool_calls?` (`{ function: { arguments: string; name: string }; id: string; type: 'function' }[]`): Tool calls requested by an assistant message.

Source: [src/providers/openai-compat.ts:35](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts#L35)

### `OAITool`

Interface.

A function tool in the OpenAI Chat Completions wire format.

```ts
interface OAITool
```

**Properties**

- `function` (`{ description: string; name: string; parameters: Record<string, unknown> }`): Function name, description and JSON Schema parameters.
- `type` (`'function'`): Tool kind. Always `function`.

Source: [src/providers/openai-compat.ts:61](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts#L61)

### `OpenAICompatAuthHeader`

Interface.

Auth header config. `scheme` is prepended to the api key with a space, e.g.
`{ name: 'Authorization', scheme: 'Bearer' }` → `Authorization: Bearer <key>`.
Omit `scheme` for raw header values (e.g. `{ name: 'X-Api-Key' }` → `X-Api-Key: <key>`).

Real-world examples:

- Default OpenAI / OpenRouter / Cerebras: `{ name: 'Authorization', scheme: 'Bearer' }`.
- Baseten dedicated deployments: `{ name: 'Authorization', scheme: 'Api-Key' }`.
- Some gateways: `{ name: 'X-Api-Key' }`.

```ts
interface OpenAICompatAuthHeader
```

**Properties**

- `name` (`string`): Header name.
- `scheme?` (`string`): Scheme prepended to the key with a space, such as `Bearer`. Omit to send the key raw.

Source: [src/providers/openai-compat.ts:989](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts#L989)

### `OpenAICompatParams`

Interface.

Options for [`openaiCompat`](https://zidane.si/docs/reference/providers-openai-compat.md#openaiCompat).

```ts
interface OpenAICompatParams
```

**Properties**

- `apiKey` (`string`): Bearer-style API key.
- `authHeader?` (`OpenAICompatAuthHeader`): Auth header shape. Defaults to `{ name: 'Authorization', scheme: 'Bearer' }`.
- `baseURL` (`string`): Base URL — `/chat/completions` is appended.
- `cacheBreakpoints?` (`boolean`): Whether this endpoint honors `cache_control: { type: 'ephemeral' }` markers
  on message content parts and tool definitions.

  - `true`  — inject markers when the caller asks for caching. OpenRouter routes
    to Anthropic/Gemini forward the markers; routes to OpenAI/DeepSeek/
    Grok/Groq/Moonshot ignore them (those backends cache automatically).
  - `false` — never inject markers. Safe default for endpoints that strictly
    validate the request schema (OpenAI direct, most OSS inference
    servers) and would reject unknown fields.

  Default: `false`. The `openrouter` wrapper sets this to `true`.
- `capabilities?` (`ProviderCapabilities`): Provider-level capability flags. Routed into the message shaper and the
  agent loop so images in tool results + user messages are handled correctly
  for the underlying model.

  Defaults when omitted: `vision: false`, `imageInToolResult: false`,
  `audio: false`, `video: false` — a conservative assumption matching most
  OSS text-only OpenAI-compat endpoints. Override when routing to a known
  capable endpoint: set `audio: true` for audio-input models (`input_audio`),
  `video: true` for video-input models (`video_url`, e.g. Gemini via
  OpenRouter), `documents: true` for PDF-capable models (OpenAI `file`
  content part with base64 `file_data`), or `vision: true` for vision models.
- `defaultModel?` (`string`): Default model id when `run({ model })` is omitted.
- `extraBodyParams?` (`Record<string, unknown>`): Generic pass-through for fields on the Chat Completions request body that
  zidane does not yet type. Spread into the request before the typed core
  (model / messages / tools / max_tokens / stream / tool_choice), so
  explicit options always win on collision.

  Forward-compat escape hatch for endpoints that ship one-off fields ahead
  of zidane (e.g. OpenAI `reasoning_effort`, OpenRouter `provider` routing,
  vendor-specific `safety_level` knobs).
- `extraHeaders?` (`Record<string, string>`): Extra headers sent with every request (e.g. referer, user-agent).
- `label?` (`string`): Product a rate-limit, quota or overload message names (`OpenRouter`). Defaults to `name`.
- `name?` (`string`): Provider name exposed as `Provider.name`. Defaults to `'openai-compat'`.
- `normalizeNullContent?` (`boolean`): Normalize `content: null` → `''` on assistant tool-call turns. Some
  reasoning models (Arcee/vLLM) degrade or emit malformed follow-ups when a
  tool-call assistant message carries a null content. Off by default to keep
  the wire byte-identical for existing providers (OpenRouter, Cerebras, …).
- `pdfEngine?` (`'native' | 'pdf-text' | 'mistral-ocr'`): PDF-parsing engine for OpenRouter's `file-parser` plugin. Only takes effect
  on endpoints that honor the `plugins` request field (OpenRouter) AND only
  when a request actually carries a forwarded `file` content part — otherwise
  it's omitted, so it's a safe no-op for plain-text turns and non-OpenRouter
  endpoints (which ignore the unknown field).

  Without an explicit engine, OpenRouter's per-model native PDF handling is
  inconsistent (some models silently fail to receive the parsed document).
  Pinning the engine makes it deterministic:

  - `'native'`     — use the model's own file support (free; for gpt-4o /
    Gemini / Claude routes). Opt-in: the `openrouter` wrapper
    defaults to `'pdf-text'`.
  - `'pdf-text'`   — free text-only extraction (no OCR / images).
  - `'mistral-ocr'`— OCR for scans / image PDFs (paid).

  When unset, no `plugins` field is added.
- `planReasoningRequest?` (`(ctx: { model: string; thinking?: 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'adaptive'; thinkingBudget?: number }) => Record<string, unknown> | undefined`): Per-request reasoning body planner for hosts whose thinking knob is a
  vendor-specific request field rather than OpenRouter's `reasoning` object
  (e.g. Baseten's `reasoning_effort` / `chat_template_args.enable_thinking`).
  Called on every `stream()` with the resolved model + thinking options;
  the returned fields are merged into the request body (after
  `extraBodyParams`, before the typed core, so model/messages/etc. still
  win on collision). Return `undefined` to send nothing.
- `promptCacheKey?` (`PromptCacheKeyOption`): Prompt-cache affinity key — an opt-in routing hint that keeps a session's
  requests landing on a replica whose KV cache is already warm. See
  [`PromptCacheKeyOption`](https://zidane.si/docs/reference/providers.md#PromptCacheKeyOption) for the shapes, and `./prompt-cache-key.ts`
  for which routes actually honor it (real on OpenAI/Azure and OpenRouter,
  effectively a no-op on Baseten, real on any proxy/gateway in front).

  Default: unset — the field is absent from requests.
- `promptCacheKeyField?` (`false | 'user' | (string & {}) | 'prompt_cache_key'`): Request-body field carrying `OpenAICompatParams.promptCacheKey`.

  - `'prompt_cache_key'` (default) — OpenAI's own hint, combined server-side
    with the prefix hash; also what OpenRouter reads as its sticky-routing
    key.
  - `'user'` — the legacy spelling of the same idea, for older routes that
    bucket on it but reject `prompt_cache_key`.
  - `false` — no body field; the key travels only in
    `OpenAICompatParams.promptCacheKeyHeader`.

  Any other field name is accepted for vendor-specific spellings.
- `promptCacheKeyHeader?` (`string`): Additionally send the affinity key as this HTTP header, for gateways that
  route on a header rather than a body field. Defers to any same-named entry
  in `OpenAICompatParams.extraHeaders`.

  Default: unset — no header is added.
- `reasoningContentField?` (`'reasoning_content' | 'reasoning'`): Field name to use when echoing plain reasoning back upstream in
  `'reasoning_content'` mode. The OpenAI SDK returns `reasoning_content`, but
  some self-hosted vLLM versions only read `reasoning` on input (vllm#38488).
  Default: `'reasoning'`.
- `reasoningMode?` (`'openrouter' | 'reasoning_content'`): Shape used to round-trip reasoning state across turns when
  `supportsReasoning` is `true`. Ignored when `supportsReasoning` is `false`.

  - `'openrouter'` (default) — capture/echo OpenRouter's structured
    `reasoning_details[]` envelope.
  - `'reasoning_content'` — capture the plain `reasoning_content` string and
    echo it back on assistant messages (DeepSeek-R1 reasoning parser / Arcee
    Trinity-Thinking served via vLLM). See
    [https://docs.arcee.ai/capabilities/reasoning-traces](https://docs.arcee.ai/capabilities/reasoning-traces).

  Default: `'openrouter'`.
- `supportsReasoning?` (`boolean`): Whether this endpoint speaks a reasoning round-trip shape selected by
  `reasoningMode` — either OpenRouter's normalized `reasoning` request field
  plus structured `reasoning_details[]`, or vLLM/Arcee's plain
  `reasoning_content` assistant-message field.

  - `true`  — capture compatible reasoning fields from streaming responses
    into `provider_reasoning` blocks and echo them back on
    subsequent assistant messages. In `'openrouter'` mode, also map
    zidane's `behavior.thinking` / `behavior.thinkingBudget` to the
    request's `reasoning` field.
  - `false` — never set the field; drop any stored `provider_reasoning`
    blocks before sending. Safe default for hosts that strict-
    validate the request schema.

  Default: `false`. The `openrouter` wrapper sets this to `true`.

Source: [src/providers/openai-compat.ts:997](https://github.com/Tahul/zidane/blob/main/src/providers/openai-compat.ts#L997)
