zidane 6.2.12 → 6.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-C-auiVTk.js → acp-DxYjSCQW.js} +5 -5
- package/dist/{acp-C-auiVTk.js.map → acp-DxYjSCQW.js.map} +1 -1
- package/dist/acp-cli.js +6 -6
- package/dist/acp.js +1 -1
- package/dist/{agent-DhfdXgL2.js → agent-DoEx_WD3.js} +6 -5
- package/dist/{agent-DhfdXgL2.js.map → agent-DoEx_WD3.js.map} +1 -1
- package/dist/agent.js +2 -2
- package/dist/aliasing-lVFbm81n.js +217 -0
- package/dist/aliasing-lVFbm81n.js.map +1 -0
- package/dist/{anthropic-uXfsormU.js → anthropic-BYsc6qWj.js} +3 -3
- package/dist/{anthropic-uXfsormU.js.map → anthropic-BYsc6qWj.js.map} +1 -1
- package/dist/{auth-TZg3MVi2.js → auth-Bb479DFB.js} +4 -4
- package/dist/{auth-TZg3MVi2.js.map → auth-Bb479DFB.js.map} +1 -1
- package/dist/chat.js +3 -3
- package/dist/eval.js +1 -1
- package/dist/extensions.js +1 -1
- package/dist/headless.js +3 -3
- package/dist/index-l0wehkWU.d.ts.map +1 -1
- package/dist/index.js +7 -7
- package/dist/{messages-BDpXLFoe.js → messages-CvHfYB7f.js} +3 -3
- package/dist/{messages-BDpXLFoe.js.map → messages-CvHfYB7f.js.map} +1 -1
- package/dist/{openai-compat-J_JZ7qsw.js → openai-compat-pHI0kBWH.js} +37 -8
- package/dist/openai-compat-pHI0kBWH.js.map +1 -0
- package/dist/presets.js +1 -1
- package/dist/providers/anthropic.js +1 -1
- package/dist/providers/arcee.js +1 -1
- package/dist/providers/baseten.js +1 -1
- package/dist/providers/cerebras.js +1 -1
- package/dist/providers/local.js +1 -1
- package/dist/providers/openai-compat.js +1 -1
- package/dist/providers/openai.js +27 -10
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.js +1 -1
- package/dist/providers/xai.js +1 -1
- package/dist/{providers-CclOzGxc.js → providers-D9Y2RndM.js} +3 -3
- package/dist/{providers-CclOzGxc.js.map → providers-D9Y2RndM.js.map} +1 -1
- package/dist/providers.js +4 -4
- package/dist/{read-state-Dq_TXUX1.js → read-state-rDmfeLmz.js} +3 -196
- package/dist/read-state-rDmfeLmz.js.map +1 -0
- package/dist/restate.js +2 -1
- package/dist/restate.js.map +1 -1
- package/dist/{session-CIXE-Bzp.js → session-DzrXKLw-.js} +2 -2
- package/dist/{session-CIXE-Bzp.js.map → session-DzrXKLw-.js.map} +1 -1
- package/dist/session.js +2 -2
- package/dist/tools.js +2 -2
- package/dist/{transcript-anchors-BzcuKnq8.js → transcript-anchors-yogJHuBW.js} +3 -3
- package/dist/{transcript-anchors-BzcuKnq8.js.map → transcript-anchors-yogJHuBW.js.map} +1 -1
- package/dist/tui.js +6 -5
- package/dist/tui.js.map +1 -1
- package/dist/{xai-D-wALLuZ.js → xai-BwhzoehX.js} +2 -2
- package/dist/{xai-D-wALLuZ.js.map → xai-BwhzoehX.js.map} +1 -1
- package/package.json +1 -1
- package/dist/hash-DJRzmcPI.js +0 -24
- package/dist/hash-DJRzmcPI.js.map +0 -1
- package/dist/openai-compat-J_JZ7qsw.js.map +0 -1
- package/dist/read-state-Dq_TXUX1.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { m as openaiCompat } from "./openai-compat-
|
|
1
|
+
import { m as openaiCompat } from "./openai-compat-pHI0kBWH.js";
|
|
2
2
|
import { i as resolveOAuthApiKey, n as isOAuthAccessToken, t as extractRuntimeCredentials } from "./oauth-8LqbeI_Q.js";
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
4
|
//#region src/chat/oauth-page/pkce.ts
|
|
@@ -376,4 +376,4 @@ function xai(params) {
|
|
|
376
376
|
//#endregion
|
|
377
377
|
export { createXaiOAuthProvider as n, generatePkce as r, xai as t };
|
|
378
378
|
|
|
379
|
-
//# sourceMappingURL=xai-
|
|
379
|
+
//# sourceMappingURL=xai-BwhzoehX.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xai-D-wALLuZ.js","names":[],"sources":["../src/chat/oauth-page/pkce.ts","../src/chat/oauth-page/xai.ts","../src/providers/xai.ts"],"sourcesContent":["/**\n * PKCE helper. Inlined here (rather than imported from pi-ai) because\n * `@earendil-works/pi-ai/oauth` does not re-export it, and we don't want\n * to reach into `node_modules/.../utils/oauth/pkce.js` — that's an\n * implementation-detail path that breaks on minor upstream rearrangements.\n *\n * Web Crypto API only. Works under Bun + Node 22+ without any node:crypto\n * import (matches pi-ai's own behavior). Output is base64url per RFC 7636.\n */\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n let binary = ''\n for (const byte of bytes)\n binary += String.fromCharCode(byte)\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '')\n}\n\nexport interface PkcePair {\n /** Random verifier used in the token-exchange step. */\n verifier: string\n /** SHA-256(verifier) sent on the authorize URL. */\n challenge: string\n}\n\nexport async function generatePkce(): Promise<PkcePair> {\n const verifierBytes = new Uint8Array(32)\n crypto.getRandomValues(verifierBytes)\n const verifier = base64UrlEncode(verifierBytes)\n\n const data = new TextEncoder().encode(verifier)\n const hashBuffer = await crypto.subtle.digest('SHA-256', data)\n const challenge = base64UrlEncode(new Uint8Array(hashBuffer))\n\n return { verifier, challenge }\n}\n","/**\n * xAI Grok OAuth flow (SuperGrok / X Premium+).\n *\n * xAI is **not** built into pi-ai, so — like Cursor — we implement the flow\n * here and register it with pi-ai's OAuth registry (`registerXaiOAuthProvider`)\n * so `getOAuthApiKey('xai-oauth', …)` resolves to it during lazy token refresh.\n *\n * Auth shape: OAuth 2.0 + PKCE with a loopback callback server, against xAI's\n * OIDC issuer (`https://auth.x.ai`). Differs from the Anthropic / Codex flows\n * (which use the shared `startCallbackServer`) on three points, so it gets its\n * own login impl rather than reusing that primitive:\n *\n * 1. The `redirect_uri` must be `http://127.0.0.1:56121/callback` verbatim\n * (xAI matches the registered loopback IP, not `localhost`).\n * 2. Endpoints come from OIDC discovery, not hard-coded constants.\n * 3. Token exchange + refresh are `application/x-www-form-urlencoded`\n * (the Anthropic flow posts JSON).\n *\n * The callback page still routes through {@link OAuthCallbackPageRenderer} so\n * the post-redirect HTML matches the rest of zidane's themed flows.\n *\n * Flow + constants adapted from the public reference implementations\n * (`stnly/pi-grok`, `BlockedPath/pi-xai-oauth`) and the Hermes Agent docs.\n * xAI's OAuth surface is unofficial/undocumented — re-verify the client id,\n * scopes, and callback port if login starts failing.\n */\n\nimport type {\n OAuthCredentials,\n OAuthLoginCallbacks,\n OAuthProviderInterface,\n} from '@earendil-works/pi-ai/oauth'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { OAuthCallbackPageRenderer } from './render'\nimport { createServer } from 'node:http'\nimport { generatePkce } from './pkce'\n\n/** pi-ai / zidane OAuth provider id. Also the credentials-file key. */\nexport const XAI_OAUTH_PROVIDER_ID = 'xai-oauth'\n\nconst DEFAULT_BASE_URL = 'https://api.x.ai/v1'\nconst ISSUER = 'https://auth.x.ai'\nconst DISCOVERY_URL = `${ISSUER}/.well-known/openid-configuration`\n/** Public client id used by the Grok CLI OAuth surface. */\nconst DEFAULT_CLIENT_ID = 'b1a00492-073a-47ea-816f-4c329264a828'\nconst DEFAULT_SCOPE = 'openid profile email offline_access grok-cli:access api:access'\n/** xAI matches the registered loopback IP exactly — `localhost` is rejected. */\nconst DEFAULT_CALLBACK_HOST = '127.0.0.1'\nconst DEFAULT_CALLBACK_PORT = 56121\nconst CALLBACK_PATH = '/callback'\nconst PROVIDER_NAME = 'xAI Grok'\n/** Refresh slightly early so requests don't race expiry. */\nconst REFRESH_SKEW_MS = 120_000\nconst CALLBACK_TIMEOUT_MS = 180_000\n\nfunction xaiOAuthClientId(): string {\n return process.env.PI_XAI_OAUTH_CLIENT_ID || DEFAULT_CLIENT_ID\n}\n\nfunction xaiOAuthScope(): string {\n return process.env.PI_XAI_OAUTH_SCOPE || DEFAULT_SCOPE\n}\n\nfunction xaiOAuthCallbackHost(): string {\n return process.env.PI_XAI_OAUTH_CALLBACK_HOST || DEFAULT_CALLBACK_HOST\n}\n\nfunction xaiOAuthCallbackPort(): number {\n const parsed = Number.parseInt(process.env.PI_XAI_OAUTH_CALLBACK_PORT || String(DEFAULT_CALLBACK_PORT), 10)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_CALLBACK_PORT\n}\n\n/** Resolve the xAI API base URL (env override → default). */\nexport function xaiBaseUrl(): string {\n return (process.env.PI_XAI_BASE_URL || process.env.XAI_BASE_URL || DEFAULT_BASE_URL)\n .replace(/\\/+$/, '')\n}\n\ninterface XaiDiscovery {\n authorization_endpoint: string\n token_endpoint: string\n}\n\n/**\n * xAI OAuth credentials. Extends the base `{ access, refresh, expires }` with\n * the resolved `token_endpoint` so refresh doesn't need a second discovery\n * round-trip, plus the cached discovery doc for validation.\n */\nexport interface XaiOAuthCredentials extends OAuthCredentials {\n tokenEndpoint?: string\n discovery?: XaiDiscovery\n}\n\nfunction base64Url(bytes: Uint8Array): string {\n let binary = ''\n for (const b of bytes)\n binary += String.fromCharCode(b)\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\nfunction randomToken(byteLength = 16): string {\n return base64Url(crypto.getRandomValues(new Uint8Array(byteLength)))\n}\n\n/**\n * Refuse any OIDC endpoint that isn't HTTPS on an xAI origin.\n *\n * The discovery doc is cached long-term in `credentials.json`. A single MITM\n * during initial login could otherwise pin a malicious `token_endpoint` that\n * receives every subsequent refresh token. Validating scheme + host keeps the\n * endpoint on `x.ai` / `*.x.ai`.\n */\nfunction validateEndpoint(value: string, field: string): string {\n let url: URL\n try {\n url = new URL(value)\n }\n catch {\n throw new Error(`xAI OAuth discovery returned an invalid ${field}: ${value}`)\n }\n if (url.protocol !== 'https:')\n throw new Error(`xAI OAuth ${field} must use HTTPS: ${value}`)\n const host = url.hostname.toLowerCase()\n if (host !== 'x.ai' && !host.endsWith('.x.ai'))\n throw new Error(`Refusing non-xAI OAuth ${field}: ${value}`)\n return url.toString()\n}\n\nasync function discover(): Promise<XaiDiscovery> {\n let response: Response\n try {\n response = await fetch(DISCOVERY_URL, {\n headers: { Accept: 'application/json' },\n signal: AbortSignal.timeout(15_000),\n })\n }\n catch (cause) {\n throw new Error(`xAI OIDC discovery failed: ${cause instanceof Error ? cause.message : String(cause)}`)\n }\n if (!response.ok)\n throw new Error(`xAI OIDC discovery returned ${response.status}`)\n\n const payload = await response.json() as Record<string, unknown>\n return {\n authorization_endpoint: validateEndpoint(String(payload.authorization_endpoint ?? ''), 'authorization_endpoint'),\n token_endpoint: validateEndpoint(String(payload.token_endpoint ?? ''), 'token_endpoint'),\n }\n}\n\ninterface CallbackResult {\n code?: string\n state?: string\n error?: string\n errorDescription?: string\n}\n\ninterface XaiCallbackServer {\n redirectUri: string\n waitForCallback: (timeoutMs: number) => Promise<CallbackResult>\n close: () => void\n}\n\n/**\n * Start the loopback callback server on `127.0.0.1:56121`. Falls back to an\n * OS-assigned port if 56121 is taken — xAI accepts any loopback port as long\n * as the `redirect_uri` we send on the authorize URL matches the listener.\n */\nasync function startXaiCallbackServer(renderPage: OAuthCallbackPageRenderer): Promise<XaiCallbackServer> {\n const callbackHost = xaiOAuthCallbackHost()\n const callbackPort = xaiOAuthCallbackPort()\n let settle: ((value: CallbackResult) => void) | undefined\n let settled = false\n const callbackPromise = new Promise<CallbackResult>((resolve) => {\n settle = (value) => {\n if (settled)\n return\n settled = true\n resolve(value)\n }\n })\n\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n try {\n // accounts.x.ai may issue a CORS preflight against the loopback listener.\n const origin = req.headers.origin\n if (origin === 'https://accounts.x.ai' || origin === 'https://auth.x.ai') {\n res.setHeader('Access-Control-Allow-Origin', origin)\n res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS')\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type')\n res.setHeader('Access-Control-Allow-Private-Network', 'true')\n res.setHeader('Vary', 'Origin')\n }\n if (req.method === 'OPTIONS') {\n res.statusCode = 204\n res.end()\n return\n }\n\n const url = new URL(req.url ?? '/', `http://${callbackHost}`)\n if (url.pathname !== CALLBACK_PATH) {\n res.statusCode = 404\n res.end('Not found')\n return\n }\n\n const result: CallbackResult = {\n code: url.searchParams.get('code') ?? undefined,\n state: url.searchParams.get('state') ?? undefined,\n error: url.searchParams.get('error') ?? undefined,\n errorDescription: url.searchParams.get('error_description') ?? undefined,\n }\n\n res.statusCode = result.error ? 400 : 200\n res.setHeader('Content-Type', 'text/html; charset=utf-8')\n res.end(renderPage(result.error\n ? {\n kind: 'error',\n provider: PROVIDER_NAME,\n message: `${PROVIDER_NAME} authentication did not complete.`,\n details: result.errorDescription ?? result.error,\n }\n : {\n kind: 'success',\n provider: PROVIDER_NAME,\n message: 'xAI authentication completed. You can close this window.',\n }))\n settle?.(result)\n }\n catch {\n res.statusCode = 500\n res.end('Internal error')\n }\n })\n\n const listen = (port: number) => new Promise<number>((resolve, reject) => {\n server.once('error', reject)\n server.listen(port, callbackHost, () => {\n server.removeListener('error', reject)\n const addr = server.address()\n resolve(typeof addr === 'object' && addr ? addr.port : port)\n })\n })\n\n let actualPort: number\n try {\n actualPort = await listen(callbackPort)\n }\n catch {\n actualPort = await listen(0)\n }\n\n return {\n redirectUri: `http://${callbackHost}:${actualPort}${CALLBACK_PATH}`,\n waitForCallback: timeoutMs => Promise.race([\n callbackPromise,\n new Promise<CallbackResult>(resolve =>\n setTimeout(\n resolve,\n timeoutMs,\n { error: 'timeout', errorDescription: 'Timed out waiting for the xAI OAuth callback.' },\n )),\n ]),\n close: () => {\n try { server.close() }\n catch { /* already closed */ }\n },\n }\n}\n\nfunction expiryFromPayload(payload: Record<string, unknown>): number {\n const expiresIn = typeof payload.expires_in === 'number'\n ? payload.expires_in\n : Number(payload.expires_in ?? 3600)\n return Date.now() + expiresIn * 1000 - REFRESH_SKEW_MS\n}\n\nasync function exchangeCode(\n tokenEndpoint: string,\n code: string,\n redirectUri: string,\n verifier: string,\n discovery: XaiDiscovery,\n): Promise<XaiOAuthCredentials> {\n const clientId = xaiOAuthClientId()\n const response = await fetch(tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: clientId,\n code,\n redirect_uri: redirectUri,\n code_verifier: verifier,\n }),\n signal: AbortSignal.timeout(30_000),\n })\n if (!response.ok)\n throw new Error(`xAI token exchange failed: ${response.status} ${await response.text().catch(() => '')}`)\n\n const payload = await response.json() as Record<string, unknown>\n const access = String(payload.access_token ?? '')\n const refresh = String(payload.refresh_token ?? '')\n if (!access)\n throw new Error('xAI token exchange did not return an access_token.')\n if (!refresh)\n throw new Error('xAI token exchange did not return a refresh_token.')\n\n return { access, refresh, expires: expiryFromPayload(payload), tokenEndpoint, discovery }\n}\n\nexport async function loginXai(\n renderPage: OAuthCallbackPageRenderer,\n callbacks: OAuthLoginCallbacks,\n): Promise<XaiOAuthCredentials> {\n const discovery = await discover()\n const { verifier, challenge } = await generatePkce()\n const state = randomToken()\n const nonce = randomToken()\n const server = await startXaiCallbackServer(renderPage)\n\n try {\n const authUrl = new URL(discovery.authorization_endpoint)\n authUrl.searchParams.set('response_type', 'code')\n authUrl.searchParams.set('client_id', xaiOAuthClientId())\n authUrl.searchParams.set('redirect_uri', server.redirectUri)\n authUrl.searchParams.set('scope', xaiOAuthScope())\n authUrl.searchParams.set('code_challenge', challenge)\n authUrl.searchParams.set('code_challenge_method', 'S256')\n authUrl.searchParams.set('state', state)\n authUrl.searchParams.set('nonce', nonce)\n // Opt into xAI's generic OAuth plan tier (SuperGrok / X Premium+).\n authUrl.searchParams.set('plan', 'generic')\n authUrl.searchParams.set('referrer', 'zidane')\n\n callbacks.onAuth({\n url: authUrl.toString(),\n instructions: `Sign in to xAI and approve access. If the browser is on another machine, the callback listener is ${server.redirectUri}.`,\n })\n\n const result = await server.waitForCallback(CALLBACK_TIMEOUT_MS)\n if (result.error)\n throw new Error(result.errorDescription ?? result.error)\n if (result.state !== state)\n throw new Error('xAI OAuth state mismatch — possible CSRF. Please retry.')\n if (!result.code)\n throw new Error('xAI OAuth callback did not include an authorization code.')\n\n callbacks.onProgress?.('Exchanging authorization code for tokens...')\n return await exchangeCode(discovery.token_endpoint, result.code, server.redirectUri, verifier, discovery)\n }\n finally {\n server.close()\n }\n}\n\nexport async function refreshXaiToken(credentials: OAuthCredentials): Promise<XaiOAuthCredentials> {\n const xai = credentials as XaiOAuthCredentials\n const clientId = xaiOAuthClientId()\n if (!credentials.refresh)\n throw new Error('xAI credentials are missing a refresh token — re-login required.')\n\n const tokenEndpoint = xai.tokenEndpoint\n ?? xai.discovery?.token_endpoint\n ?? (await discover()).token_endpoint\n validateEndpoint(tokenEndpoint, 'token_endpoint')\n\n const response = await fetch(tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: clientId,\n refresh_token: credentials.refresh,\n }),\n signal: AbortSignal.timeout(30_000),\n })\n if (!response.ok)\n throw new Error(`xAI token refresh failed: ${response.status} ${await response.text().catch(() => '')}`)\n\n const payload = await response.json() as Record<string, unknown>\n const access = String(payload.access_token ?? '')\n if (!access)\n throw new Error('xAI token refresh did not return an access_token.')\n\n return {\n ...xai,\n access,\n // xAI may omit a rotated refresh token — keep the existing one.\n refresh: String(payload.refresh_token ?? credentials.refresh),\n expires: expiryFromPayload(payload),\n tokenEndpoint,\n }\n}\n\n/**\n * Build an xAI `OAuthProviderInterface`. Shape matches Anthropic / Codex /\n * Cursor so it drops into `BUILTIN_PROVIDERS` and `src/auth.ts` unchanged.\n *\n * `usesCallbackServer: true` — the loopback flow has a real callback server,\n * so the TUI doesn't need to prompt for a manual code paste.\n */\nexport function createXaiOAuthProvider(renderPage: OAuthCallbackPageRenderer): OAuthProviderInterface {\n return {\n id: XAI_OAUTH_PROVIDER_ID,\n name: 'xAI Grok (SuperGrok / X Premium+)',\n usesCallbackServer: true,\n login: callbacks => loginXai(renderPage, callbacks),\n refreshToken: refreshXaiToken,\n getApiKey: credentials => credentials.access,\n }\n}\n","import type { Provider, ProviderCapabilities, StreamCallbacks, StreamOptions, TurnResult } from '.'\nimport type { OAuthParams } from './oauth'\nimport { XAI_OAUTH_PROVIDER_ID, xaiBaseUrl } from '../chat/oauth-page/xai'\nimport { extractRuntimeCredentials, isOAuthAccessToken, resolveOAuthApiKey } from './oauth'\nimport { openaiCompat } from './openai-compat'\n\nconst DEFAULT_MODEL = 'grok-4.3'\n\n/**\n * xAI Grok provider — supports both a static API key (`XAI_API_KEY`) and the\n * SuperGrok / X Premium+ OAuth subscription.\n *\n * Both auth modes hit the same OpenAI-compatible endpoint (`https://api.x.ai/v1`),\n * so the wire handling is entirely {@link openaiCompat}. The only difference is\n * the bearer:\n *\n * - **API key**: `XAI_API_KEY` (or `params.apiKey`) → used verbatim.\n * - **OAuth**: no static key → {@link resolveOAuthApiKey} resolves + lazily\n * refreshes the stored `xai-oauth` token on every turn.\n *\n * `resolveOAuthApiKey` unifies the two: a real API key in `XAI_API_KEY` is\n * returned as-is, while an OAuth access token (or no env value at all) falls\n * through to the refreshable stored credentials. So this single factory serves\n * both paths without branching at construction time.\n */\nexport interface XaiParams extends OAuthParams {\n defaultModel?: string\n /**\n * Provider capability flags. Grok models are vision-capable; default\n * `{ vision: true, imageInToolResult: false }`. Override for text-only\n * deployments.\n */\n capabilities?: ProviderCapabilities\n /** Extra HTTP headers sent on every request. */\n extraHeaders?: Record<string, string>\n}\n\nexport function xai(params?: XaiParams): Provider {\n const defaultModel = params?.defaultModel || DEFAULT_MODEL\n const baseURL = xaiBaseUrl()\n const capabilities: ProviderCapabilities = params?.capabilities ?? {\n vision: true,\n imageInToolResult: false,\n }\n const baseCredentials = extractRuntimeCredentials(params)\n let runtimeCredentials = baseCredentials\n\n // Factory-time snapshot for the static `meta.isOAuth` display flag only (the\n // terminal header's `oauth`/`key` badge). The real bearer resolves per turn\n // in `stream`, so this is best-effort, mirroring anthropic's pattern. It's\n // OAuth unless a genuine static API key is present: a `xai-…` key → key mode;\n // runtime OAuth creds, a JWT access token, or no static key (stored creds\n // used) → oauth mode.\n const staticKey = params?.apiKey ?? process.env.XAI_API_KEY\n const isOAuth = baseCredentials !== undefined || !staticKey || isOAuthAccessToken(staticKey)\n\n /**\n * Build the openai-compat delegate for a resolved bearer. Cheap — the factory\n * only wires closures — so we re-create it per turn with the freshly resolved\n * (possibly refreshed) OAuth token rather than baking a stale key in once.\n */\n function delegateFor(apiKey: string): Provider {\n return openaiCompat({\n name: 'xai',\n apiKey,\n baseURL,\n defaultModel,\n capabilities,\n extraHeaders: params?.extraHeaders,\n })\n }\n\n // A delegate built with a placeholder key supplies the non-stream methods\n // (formatTools, userMessage, message builders, classifyError). Only `stream`\n // resolves the real bearer, so the placeholder never reaches the wire.\n const skeleton = delegateFor('placeholder-resolved-at-stream')\n\n return {\n ...skeleton,\n name: 'xai',\n // Reuse the delegate's normalized capability flags (openaiCompat fills the\n // audio/video/documents defaults) so the advertised shape matches the wire.\n meta: { ...skeleton.meta, defaultModel, isOAuth },\n\n async stream(options: StreamOptions, callbacks: StreamCallbacks): Promise<TurnResult> {\n const apiKey = await resolveOAuthApiKey(\n {\n provider: 'xai',\n providerId: XAI_OAUTH_PROVIDER_ID,\n params: runtimeCredentials ? { ...params, ...runtimeCredentials } : params,\n envKey: 'XAI_API_KEY',\n missingError: 'No xAI credentials found. Set XAI_API_KEY or run `bun run auth --xai` first.',\n refreshError: reason => `xAI OAuth token refresh failed. Run \\`bun run auth --xai\\` again. ${reason}`,\n },\n {\n ...callbacks,\n async onOAuthRefresh(ctx) {\n // Keep param-sourced credentials current so a long-lived provider\n // instance reuses the rotated token instead of re-refreshing.\n if (ctx.source === 'params') {\n runtimeCredentials = {\n access: ctx.credentials.access,\n refresh: ctx.credentials.refresh,\n expires: ctx.credentials.expires,\n }\n }\n await callbacks.onOAuthRefresh?.(ctx)\n },\n },\n )\n\n return delegateFor(apiKey).stream(options, callbacks)\n },\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"xai-BwhzoehX.js","names":[],"sources":["../src/chat/oauth-page/pkce.ts","../src/chat/oauth-page/xai.ts","../src/providers/xai.ts"],"sourcesContent":["/**\n * PKCE helper. Inlined here (rather than imported from pi-ai) because\n * `@earendil-works/pi-ai/oauth` does not re-export it, and we don't want\n * to reach into `node_modules/.../utils/oauth/pkce.js` — that's an\n * implementation-detail path that breaks on minor upstream rearrangements.\n *\n * Web Crypto API only. Works under Bun + Node 22+ without any node:crypto\n * import (matches pi-ai's own behavior). Output is base64url per RFC 7636.\n */\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n let binary = ''\n for (const byte of bytes)\n binary += String.fromCharCode(byte)\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '')\n}\n\nexport interface PkcePair {\n /** Random verifier used in the token-exchange step. */\n verifier: string\n /** SHA-256(verifier) sent on the authorize URL. */\n challenge: string\n}\n\nexport async function generatePkce(): Promise<PkcePair> {\n const verifierBytes = new Uint8Array(32)\n crypto.getRandomValues(verifierBytes)\n const verifier = base64UrlEncode(verifierBytes)\n\n const data = new TextEncoder().encode(verifier)\n const hashBuffer = await crypto.subtle.digest('SHA-256', data)\n const challenge = base64UrlEncode(new Uint8Array(hashBuffer))\n\n return { verifier, challenge }\n}\n","/**\n * xAI Grok OAuth flow (SuperGrok / X Premium+).\n *\n * xAI is **not** built into pi-ai, so — like Cursor — we implement the flow\n * here and register it with pi-ai's OAuth registry (`registerXaiOAuthProvider`)\n * so `getOAuthApiKey('xai-oauth', …)` resolves to it during lazy token refresh.\n *\n * Auth shape: OAuth 2.0 + PKCE with a loopback callback server, against xAI's\n * OIDC issuer (`https://auth.x.ai`). Differs from the Anthropic / Codex flows\n * (which use the shared `startCallbackServer`) on three points, so it gets its\n * own login impl rather than reusing that primitive:\n *\n * 1. The `redirect_uri` must be `http://127.0.0.1:56121/callback` verbatim\n * (xAI matches the registered loopback IP, not `localhost`).\n * 2. Endpoints come from OIDC discovery, not hard-coded constants.\n * 3. Token exchange + refresh are `application/x-www-form-urlencoded`\n * (the Anthropic flow posts JSON).\n *\n * The callback page still routes through {@link OAuthCallbackPageRenderer} so\n * the post-redirect HTML matches the rest of zidane's themed flows.\n *\n * Flow + constants adapted from the public reference implementations\n * (`stnly/pi-grok`, `BlockedPath/pi-xai-oauth`) and the Hermes Agent docs.\n * xAI's OAuth surface is unofficial/undocumented — re-verify the client id,\n * scopes, and callback port if login starts failing.\n */\n\nimport type {\n OAuthCredentials,\n OAuthLoginCallbacks,\n OAuthProviderInterface,\n} from '@earendil-works/pi-ai/oauth'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { OAuthCallbackPageRenderer } from './render'\nimport { createServer } from 'node:http'\nimport { generatePkce } from './pkce'\n\n/** pi-ai / zidane OAuth provider id. Also the credentials-file key. */\nexport const XAI_OAUTH_PROVIDER_ID = 'xai-oauth'\n\nconst DEFAULT_BASE_URL = 'https://api.x.ai/v1'\nconst ISSUER = 'https://auth.x.ai'\nconst DISCOVERY_URL = `${ISSUER}/.well-known/openid-configuration`\n/** Public client id used by the Grok CLI OAuth surface. */\nconst DEFAULT_CLIENT_ID = 'b1a00492-073a-47ea-816f-4c329264a828'\nconst DEFAULT_SCOPE = 'openid profile email offline_access grok-cli:access api:access'\n/** xAI matches the registered loopback IP exactly — `localhost` is rejected. */\nconst DEFAULT_CALLBACK_HOST = '127.0.0.1'\nconst DEFAULT_CALLBACK_PORT = 56121\nconst CALLBACK_PATH = '/callback'\nconst PROVIDER_NAME = 'xAI Grok'\n/** Refresh slightly early so requests don't race expiry. */\nconst REFRESH_SKEW_MS = 120_000\nconst CALLBACK_TIMEOUT_MS = 180_000\n\nfunction xaiOAuthClientId(): string {\n return process.env.PI_XAI_OAUTH_CLIENT_ID || DEFAULT_CLIENT_ID\n}\n\nfunction xaiOAuthScope(): string {\n return process.env.PI_XAI_OAUTH_SCOPE || DEFAULT_SCOPE\n}\n\nfunction xaiOAuthCallbackHost(): string {\n return process.env.PI_XAI_OAUTH_CALLBACK_HOST || DEFAULT_CALLBACK_HOST\n}\n\nfunction xaiOAuthCallbackPort(): number {\n const parsed = Number.parseInt(process.env.PI_XAI_OAUTH_CALLBACK_PORT || String(DEFAULT_CALLBACK_PORT), 10)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_CALLBACK_PORT\n}\n\n/** Resolve the xAI API base URL (env override → default). */\nexport function xaiBaseUrl(): string {\n return (process.env.PI_XAI_BASE_URL || process.env.XAI_BASE_URL || DEFAULT_BASE_URL)\n .replace(/\\/+$/, '')\n}\n\ninterface XaiDiscovery {\n authorization_endpoint: string\n token_endpoint: string\n}\n\n/**\n * xAI OAuth credentials. Extends the base `{ access, refresh, expires }` with\n * the resolved `token_endpoint` so refresh doesn't need a second discovery\n * round-trip, plus the cached discovery doc for validation.\n */\nexport interface XaiOAuthCredentials extends OAuthCredentials {\n tokenEndpoint?: string\n discovery?: XaiDiscovery\n}\n\nfunction base64Url(bytes: Uint8Array): string {\n let binary = ''\n for (const b of bytes)\n binary += String.fromCharCode(b)\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\nfunction randomToken(byteLength = 16): string {\n return base64Url(crypto.getRandomValues(new Uint8Array(byteLength)))\n}\n\n/**\n * Refuse any OIDC endpoint that isn't HTTPS on an xAI origin.\n *\n * The discovery doc is cached long-term in `credentials.json`. A single MITM\n * during initial login could otherwise pin a malicious `token_endpoint` that\n * receives every subsequent refresh token. Validating scheme + host keeps the\n * endpoint on `x.ai` / `*.x.ai`.\n */\nfunction validateEndpoint(value: string, field: string): string {\n let url: URL\n try {\n url = new URL(value)\n }\n catch {\n throw new Error(`xAI OAuth discovery returned an invalid ${field}: ${value}`)\n }\n if (url.protocol !== 'https:')\n throw new Error(`xAI OAuth ${field} must use HTTPS: ${value}`)\n const host = url.hostname.toLowerCase()\n if (host !== 'x.ai' && !host.endsWith('.x.ai'))\n throw new Error(`Refusing non-xAI OAuth ${field}: ${value}`)\n return url.toString()\n}\n\nasync function discover(): Promise<XaiDiscovery> {\n let response: Response\n try {\n response = await fetch(DISCOVERY_URL, {\n headers: { Accept: 'application/json' },\n signal: AbortSignal.timeout(15_000),\n })\n }\n catch (cause) {\n throw new Error(`xAI OIDC discovery failed: ${cause instanceof Error ? cause.message : String(cause)}`)\n }\n if (!response.ok)\n throw new Error(`xAI OIDC discovery returned ${response.status}`)\n\n const payload = await response.json() as Record<string, unknown>\n return {\n authorization_endpoint: validateEndpoint(String(payload.authorization_endpoint ?? ''), 'authorization_endpoint'),\n token_endpoint: validateEndpoint(String(payload.token_endpoint ?? ''), 'token_endpoint'),\n }\n}\n\ninterface CallbackResult {\n code?: string\n state?: string\n error?: string\n errorDescription?: string\n}\n\ninterface XaiCallbackServer {\n redirectUri: string\n waitForCallback: (timeoutMs: number) => Promise<CallbackResult>\n close: () => void\n}\n\n/**\n * Start the loopback callback server on `127.0.0.1:56121`. Falls back to an\n * OS-assigned port if 56121 is taken — xAI accepts any loopback port as long\n * as the `redirect_uri` we send on the authorize URL matches the listener.\n */\nasync function startXaiCallbackServer(renderPage: OAuthCallbackPageRenderer): Promise<XaiCallbackServer> {\n const callbackHost = xaiOAuthCallbackHost()\n const callbackPort = xaiOAuthCallbackPort()\n let settle: ((value: CallbackResult) => void) | undefined\n let settled = false\n const callbackPromise = new Promise<CallbackResult>((resolve) => {\n settle = (value) => {\n if (settled)\n return\n settled = true\n resolve(value)\n }\n })\n\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n try {\n // accounts.x.ai may issue a CORS preflight against the loopback listener.\n const origin = req.headers.origin\n if (origin === 'https://accounts.x.ai' || origin === 'https://auth.x.ai') {\n res.setHeader('Access-Control-Allow-Origin', origin)\n res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS')\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type')\n res.setHeader('Access-Control-Allow-Private-Network', 'true')\n res.setHeader('Vary', 'Origin')\n }\n if (req.method === 'OPTIONS') {\n res.statusCode = 204\n res.end()\n return\n }\n\n const url = new URL(req.url ?? '/', `http://${callbackHost}`)\n if (url.pathname !== CALLBACK_PATH) {\n res.statusCode = 404\n res.end('Not found')\n return\n }\n\n const result: CallbackResult = {\n code: url.searchParams.get('code') ?? undefined,\n state: url.searchParams.get('state') ?? undefined,\n error: url.searchParams.get('error') ?? undefined,\n errorDescription: url.searchParams.get('error_description') ?? undefined,\n }\n\n res.statusCode = result.error ? 400 : 200\n res.setHeader('Content-Type', 'text/html; charset=utf-8')\n res.end(renderPage(result.error\n ? {\n kind: 'error',\n provider: PROVIDER_NAME,\n message: `${PROVIDER_NAME} authentication did not complete.`,\n details: result.errorDescription ?? result.error,\n }\n : {\n kind: 'success',\n provider: PROVIDER_NAME,\n message: 'xAI authentication completed. You can close this window.',\n }))\n settle?.(result)\n }\n catch {\n res.statusCode = 500\n res.end('Internal error')\n }\n })\n\n const listen = (port: number) => new Promise<number>((resolve, reject) => {\n server.once('error', reject)\n server.listen(port, callbackHost, () => {\n server.removeListener('error', reject)\n const addr = server.address()\n resolve(typeof addr === 'object' && addr ? addr.port : port)\n })\n })\n\n let actualPort: number\n try {\n actualPort = await listen(callbackPort)\n }\n catch {\n actualPort = await listen(0)\n }\n\n return {\n redirectUri: `http://${callbackHost}:${actualPort}${CALLBACK_PATH}`,\n waitForCallback: timeoutMs => Promise.race([\n callbackPromise,\n new Promise<CallbackResult>(resolve =>\n setTimeout(\n resolve,\n timeoutMs,\n { error: 'timeout', errorDescription: 'Timed out waiting for the xAI OAuth callback.' },\n )),\n ]),\n close: () => {\n try { server.close() }\n catch { /* already closed */ }\n },\n }\n}\n\nfunction expiryFromPayload(payload: Record<string, unknown>): number {\n const expiresIn = typeof payload.expires_in === 'number'\n ? payload.expires_in\n : Number(payload.expires_in ?? 3600)\n return Date.now() + expiresIn * 1000 - REFRESH_SKEW_MS\n}\n\nasync function exchangeCode(\n tokenEndpoint: string,\n code: string,\n redirectUri: string,\n verifier: string,\n discovery: XaiDiscovery,\n): Promise<XaiOAuthCredentials> {\n const clientId = xaiOAuthClientId()\n const response = await fetch(tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: clientId,\n code,\n redirect_uri: redirectUri,\n code_verifier: verifier,\n }),\n signal: AbortSignal.timeout(30_000),\n })\n if (!response.ok)\n throw new Error(`xAI token exchange failed: ${response.status} ${await response.text().catch(() => '')}`)\n\n const payload = await response.json() as Record<string, unknown>\n const access = String(payload.access_token ?? '')\n const refresh = String(payload.refresh_token ?? '')\n if (!access)\n throw new Error('xAI token exchange did not return an access_token.')\n if (!refresh)\n throw new Error('xAI token exchange did not return a refresh_token.')\n\n return { access, refresh, expires: expiryFromPayload(payload), tokenEndpoint, discovery }\n}\n\nexport async function loginXai(\n renderPage: OAuthCallbackPageRenderer,\n callbacks: OAuthLoginCallbacks,\n): Promise<XaiOAuthCredentials> {\n const discovery = await discover()\n const { verifier, challenge } = await generatePkce()\n const state = randomToken()\n const nonce = randomToken()\n const server = await startXaiCallbackServer(renderPage)\n\n try {\n const authUrl = new URL(discovery.authorization_endpoint)\n authUrl.searchParams.set('response_type', 'code')\n authUrl.searchParams.set('client_id', xaiOAuthClientId())\n authUrl.searchParams.set('redirect_uri', server.redirectUri)\n authUrl.searchParams.set('scope', xaiOAuthScope())\n authUrl.searchParams.set('code_challenge', challenge)\n authUrl.searchParams.set('code_challenge_method', 'S256')\n authUrl.searchParams.set('state', state)\n authUrl.searchParams.set('nonce', nonce)\n // Opt into xAI's generic OAuth plan tier (SuperGrok / X Premium+).\n authUrl.searchParams.set('plan', 'generic')\n authUrl.searchParams.set('referrer', 'zidane')\n\n callbacks.onAuth({\n url: authUrl.toString(),\n instructions: `Sign in to xAI and approve access. If the browser is on another machine, the callback listener is ${server.redirectUri}.`,\n })\n\n const result = await server.waitForCallback(CALLBACK_TIMEOUT_MS)\n if (result.error)\n throw new Error(result.errorDescription ?? result.error)\n if (result.state !== state)\n throw new Error('xAI OAuth state mismatch — possible CSRF. Please retry.')\n if (!result.code)\n throw new Error('xAI OAuth callback did not include an authorization code.')\n\n callbacks.onProgress?.('Exchanging authorization code for tokens...')\n return await exchangeCode(discovery.token_endpoint, result.code, server.redirectUri, verifier, discovery)\n }\n finally {\n server.close()\n }\n}\n\nexport async function refreshXaiToken(credentials: OAuthCredentials): Promise<XaiOAuthCredentials> {\n const xai = credentials as XaiOAuthCredentials\n const clientId = xaiOAuthClientId()\n if (!credentials.refresh)\n throw new Error('xAI credentials are missing a refresh token — re-login required.')\n\n const tokenEndpoint = xai.tokenEndpoint\n ?? xai.discovery?.token_endpoint\n ?? (await discover()).token_endpoint\n validateEndpoint(tokenEndpoint, 'token_endpoint')\n\n const response = await fetch(tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: clientId,\n refresh_token: credentials.refresh,\n }),\n signal: AbortSignal.timeout(30_000),\n })\n if (!response.ok)\n throw new Error(`xAI token refresh failed: ${response.status} ${await response.text().catch(() => '')}`)\n\n const payload = await response.json() as Record<string, unknown>\n const access = String(payload.access_token ?? '')\n if (!access)\n throw new Error('xAI token refresh did not return an access_token.')\n\n return {\n ...xai,\n access,\n // xAI may omit a rotated refresh token — keep the existing one.\n refresh: String(payload.refresh_token ?? credentials.refresh),\n expires: expiryFromPayload(payload),\n tokenEndpoint,\n }\n}\n\n/**\n * Build an xAI `OAuthProviderInterface`. Shape matches Anthropic / Codex /\n * Cursor so it drops into `BUILTIN_PROVIDERS` and `src/auth.ts` unchanged.\n *\n * `usesCallbackServer: true` — the loopback flow has a real callback server,\n * so the TUI doesn't need to prompt for a manual code paste.\n */\nexport function createXaiOAuthProvider(renderPage: OAuthCallbackPageRenderer): OAuthProviderInterface {\n return {\n id: XAI_OAUTH_PROVIDER_ID,\n name: 'xAI Grok (SuperGrok / X Premium+)',\n usesCallbackServer: true,\n login: callbacks => loginXai(renderPage, callbacks),\n refreshToken: refreshXaiToken,\n getApiKey: credentials => credentials.access,\n }\n}\n","import type { Provider, ProviderCapabilities, StreamCallbacks, StreamOptions, TurnResult } from '.'\nimport type { OAuthParams } from './oauth'\nimport { XAI_OAUTH_PROVIDER_ID, xaiBaseUrl } from '../chat/oauth-page/xai'\nimport { extractRuntimeCredentials, isOAuthAccessToken, resolveOAuthApiKey } from './oauth'\nimport { openaiCompat } from './openai-compat'\n\nconst DEFAULT_MODEL = 'grok-4.3'\n\n/**\n * xAI Grok provider — supports both a static API key (`XAI_API_KEY`) and the\n * SuperGrok / X Premium+ OAuth subscription.\n *\n * Both auth modes hit the same OpenAI-compatible endpoint (`https://api.x.ai/v1`),\n * so the wire handling is entirely {@link openaiCompat}. The only difference is\n * the bearer:\n *\n * - **API key**: `XAI_API_KEY` (or `params.apiKey`) → used verbatim.\n * - **OAuth**: no static key → {@link resolveOAuthApiKey} resolves + lazily\n * refreshes the stored `xai-oauth` token on every turn.\n *\n * `resolveOAuthApiKey` unifies the two: a real API key in `XAI_API_KEY` is\n * returned as-is, while an OAuth access token (or no env value at all) falls\n * through to the refreshable stored credentials. So this single factory serves\n * both paths without branching at construction time.\n */\nexport interface XaiParams extends OAuthParams {\n defaultModel?: string\n /**\n * Provider capability flags. Grok models are vision-capable; default\n * `{ vision: true, imageInToolResult: false }`. Override for text-only\n * deployments.\n */\n capabilities?: ProviderCapabilities\n /** Extra HTTP headers sent on every request. */\n extraHeaders?: Record<string, string>\n}\n\nexport function xai(params?: XaiParams): Provider {\n const defaultModel = params?.defaultModel || DEFAULT_MODEL\n const baseURL = xaiBaseUrl()\n const capabilities: ProviderCapabilities = params?.capabilities ?? {\n vision: true,\n imageInToolResult: false,\n }\n const baseCredentials = extractRuntimeCredentials(params)\n let runtimeCredentials = baseCredentials\n\n // Factory-time snapshot for the static `meta.isOAuth` display flag only (the\n // terminal header's `oauth`/`key` badge). The real bearer resolves per turn\n // in `stream`, so this is best-effort, mirroring anthropic's pattern. It's\n // OAuth unless a genuine static API key is present: a `xai-…` key → key mode;\n // runtime OAuth creds, a JWT access token, or no static key (stored creds\n // used) → oauth mode.\n const staticKey = params?.apiKey ?? process.env.XAI_API_KEY\n const isOAuth = baseCredentials !== undefined || !staticKey || isOAuthAccessToken(staticKey)\n\n /**\n * Build the openai-compat delegate for a resolved bearer. Cheap — the factory\n * only wires closures — so we re-create it per turn with the freshly resolved\n * (possibly refreshed) OAuth token rather than baking a stale key in once.\n */\n function delegateFor(apiKey: string): Provider {\n return openaiCompat({\n name: 'xai',\n apiKey,\n baseURL,\n defaultModel,\n capabilities,\n extraHeaders: params?.extraHeaders,\n })\n }\n\n // A delegate built with a placeholder key supplies the non-stream methods\n // (formatTools, userMessage, message builders, classifyError). Only `stream`\n // resolves the real bearer, so the placeholder never reaches the wire.\n const skeleton = delegateFor('placeholder-resolved-at-stream')\n\n return {\n ...skeleton,\n name: 'xai',\n // Reuse the delegate's normalized capability flags (openaiCompat fills the\n // audio/video/documents defaults) so the advertised shape matches the wire.\n meta: { ...skeleton.meta, defaultModel, isOAuth },\n\n async stream(options: StreamOptions, callbacks: StreamCallbacks): Promise<TurnResult> {\n const apiKey = await resolveOAuthApiKey(\n {\n provider: 'xai',\n providerId: XAI_OAUTH_PROVIDER_ID,\n params: runtimeCredentials ? { ...params, ...runtimeCredentials } : params,\n envKey: 'XAI_API_KEY',\n missingError: 'No xAI credentials found. Set XAI_API_KEY or run `bun run auth --xai` first.',\n refreshError: reason => `xAI OAuth token refresh failed. Run \\`bun run auth --xai\\` again. ${reason}`,\n },\n {\n ...callbacks,\n async onOAuthRefresh(ctx) {\n // Keep param-sourced credentials current so a long-lived provider\n // instance reuses the rotated token instead of re-refreshing.\n if (ctx.source === 'params') {\n runtimeCredentials = {\n access: ctx.credentials.access,\n refresh: ctx.credentials.refresh,\n expires: ctx.credentials.expires,\n }\n }\n await callbacks.onOAuthRefresh?.(ctx)\n },\n },\n )\n\n return delegateFor(apiKey).stream(options, callbacks)\n },\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
package/dist/hash-DJRzmcPI.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
//#region src/hash.ts
|
|
2
|
-
/**
|
|
3
|
-
* FNV-1a (32-bit) rendered in base36 — a tiny, dependency-free, deterministic
|
|
4
|
-
* hash.
|
|
5
|
-
*
|
|
6
|
-
* Used to disambiguate sanitized identifiers: distinct inputs that would
|
|
7
|
-
* otherwise collapse to the same sanitized string (e.g. `a.b` and `a/b` both
|
|
8
|
-
* → `a_b`, or two over-long names truncated to the same prefix) keep a stable,
|
|
9
|
-
* unique suffix. Determinism matters because the suffix must be identical
|
|
10
|
-
* across runs and across a `tool_use` / `tool_result` pair derived from the
|
|
11
|
-
* same original value.
|
|
12
|
-
*/
|
|
13
|
-
function fnv1aBase36(value) {
|
|
14
|
-
let hash = 2166136261;
|
|
15
|
-
for (let i = 0; i < value.length; i++) {
|
|
16
|
-
hash ^= value.charCodeAt(i);
|
|
17
|
-
hash = Math.imul(hash, 16777619);
|
|
18
|
-
}
|
|
19
|
-
return (hash >>> 0).toString(36);
|
|
20
|
-
}
|
|
21
|
-
//#endregion
|
|
22
|
-
export { fnv1aBase36 as t };
|
|
23
|
-
|
|
24
|
-
//# sourceMappingURL=hash-DJRzmcPI.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hash-DJRzmcPI.js","names":[],"sources":["../src/hash.ts"],"sourcesContent":["/**\n * FNV-1a (32-bit) rendered in base36 — a tiny, dependency-free, deterministic\n * hash.\n *\n * Used to disambiguate sanitized identifiers: distinct inputs that would\n * otherwise collapse to the same sanitized string (e.g. `a.b` and `a/b` both\n * → `a_b`, or two over-long names truncated to the same prefix) keep a stable,\n * unique suffix. Determinism matters because the suffix must be identical\n * across runs and across a `tool_use` / `tool_result` pair derived from the\n * same original value.\n */\nexport function fnv1aBase36(value: string): string {\n let hash = 0x811C9DC5\n for (let i = 0; i < value.length; i++) {\n hash ^= value.charCodeAt(i)\n hash = Math.imul(hash, 0x01000193)\n }\n return (hash >>> 0).toString(36)\n}\n"],"mappings":";;;;;;;;;;;;AAWA,SAAgB,YAAY,OAAuB;CACjD,IAAI,OAAO;CACX,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,QAAQ,MAAM,WAAW,CAAC;EAC1B,OAAO,KAAK,KAAK,MAAM,QAAU;CACnC;CACA,QAAQ,SAAS,EAAA,CAAG,SAAS,EAAE;AACjC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"openai-compat-J_JZ7qsw.js","names":[],"sources":["../src/prompt.ts","../src/providers/cost.ts","../src/providers/schema-sanitize.ts","../src/providers/openai-compat.ts"],"sourcesContent":["/**\n * Prompt canonicalization + default multimodal message builder.\n *\n * `agent.run({ prompt })` accepts either `string` or `PromptPart[]`. The\n * agent normalizes both to `PromptPart[]` before handing to the provider.\n *\n * Providers may implement `Provider.promptMessage` for native-format support\n * (e.g. Anthropic's document blocks). Providers that don't fall back to\n * `defaultPromptMessage`, which inlines text-encoded documents and throws on\n * base64-encoded documents.\n */\n\nimport type { Provider } from './providers'\nimport type { PromptPart, SessionContentBlock, SessionMessage } from './types'\n\n/**\n * Coerce the run-level prompt into a `PromptPart[]`.\n *\n * - `string` prompt → a single `text` part. Empty string returns `undefined`\n * so callers skip pushing an empty user turn.\n * - `PromptPart[]` prompt → validated and returned as-is. An empty array, or\n * an array whose text parts are all empty with no image/document parts,\n * returns `undefined`.\n * - `undefined` → `undefined` (promptless resume path).\n */\nexport function canonicalizePrompt(\n prompt: string | PromptPart[] | undefined,\n): PromptPart[] | undefined {\n if (prompt === undefined)\n return undefined\n\n if (typeof prompt === 'string') {\n if (prompt.length === 0)\n return undefined\n return [{ type: 'text', text: prompt }]\n }\n\n // Array path — drop if empty, or if every text part is empty and there are no\n // image/document parts (nothing meaningful to send).\n if (prompt.length === 0)\n return undefined\n\n // Reject malformed parts up front rather than letting them slip through to\n // `defaultPromptMessage` / `provider.promptMessage`, where the failure\n // mode is a confusing \"Cannot read property 'text' of undefined\" or a\n // base64-document throw far from the call-site. The check is cheap and\n // catches the common typo (wrong `type` field) loudly.\n for (const part of prompt) {\n if (!part || typeof part !== 'object' || typeof (part as { type?: unknown }).type !== 'string') {\n throw new Error('Invalid PromptPart: each part must be an object with a `type` field.')\n }\n const type = (part as { type: string }).type\n if (type !== 'text' && type !== 'image' && type !== 'audio' && type !== 'video' && type !== 'document') {\n throw new Error(`Invalid PromptPart type \"${type}\". Expected \"text\" | \"image\" | \"audio\" | \"video\" | \"document\".`)\n }\n }\n\n const hasMeaningfulPart = prompt.some(part => (\n (part.type === 'text' && part.text.length > 0)\n || part.type === 'image'\n || part.type === 'audio'\n || part.type === 'video'\n || part.type === 'document'\n ))\n if (!hasMeaningfulPart)\n return undefined\n\n return prompt\n}\n\n/**\n * Standard error for a media kind the active provider/model can't accept.\n *\n * Per the audio/video design, unsupported media is a hard error (not a silent\n * marker) so the caller switches to a capable model rather than paying for a\n * request the provider would 400 — or worse, one that succeeds with the bytes\n * stripped and the model confabulating over what it can't see.\n */\nexport function unsupportedMediaError(kind: 'audio' | 'video', provider: string): Error {\n return new Error(\n `Provider \"${provider}\" does not support ${kind} input. `\n + `Route to a ${kind}-capable model (e.g. an OpenAI-compat / OpenRouter model `\n + `with capabilities.${kind} = true) to send ${kind} attachments.`,\n )\n}\n\n/**\n * Build a user `SessionMessage` from prompt parts without provider-specific handling.\n *\n * - `text` parts map to `{ type: 'text', text }` blocks.\n * - `image` parts map to `{ type: 'image', mediaType, data }` blocks.\n * - `audio` / `video` parts map to their canonical blocks verbatim. Providers\n * that cannot accept them throw later (in `promptMessage` / loop gating); the\n * default builder stays lossless so capable providers receive the bytes.\n * - `document` parts with `encoding: 'text'` are inlined as an attachment-tagged\n * text block so every provider can read them.\n * - `document` parts with `encoding: 'base64'` throw — the caller should switch\n * to a provider that implements `promptMessage` (e.g. Anthropic for PDFs).\n */\nexport function defaultPromptMessage(parts: PromptPart[]): SessionMessage {\n const content: SessionContentBlock[] = []\n\n for (const part of parts) {\n if (part.type === 'text') {\n if (part.text.length > 0)\n content.push({ type: 'text', text: part.text })\n continue\n }\n\n if (part.type === 'image') {\n content.push({ type: 'image', mediaType: part.mediaType, data: part.data, ...(part.name ? { name: part.name } : {}) })\n continue\n }\n\n if (part.type === 'audio') {\n content.push({ type: 'audio', mediaType: part.mediaType, data: part.data, ...(part.name ? { name: part.name } : {}) })\n continue\n }\n\n if (part.type === 'video') {\n content.push({ type: 'video', mediaType: part.mediaType, data: part.data, ...(part.name ? { name: part.name } : {}) })\n continue\n }\n\n // document\n if (part.encoding === 'text') {\n const header = part.name\n ? `<attachment name=\"${part.name}\" media_type=\"${part.mediaType}\">`\n : `<attachment media_type=\"${part.mediaType}\">`\n content.push({ type: 'text', text: `${header}\\n${part.data}\\n</attachment>` })\n continue\n }\n\n throw new Error(\n `Provider does not support base64 document parts (mediaType: ${part.mediaType}). `\n + `Use a text-encoded document or a provider that implements promptMessage (e.g. Anthropic).`,\n )\n }\n\n return { role: 'user', content }\n}\n\n/**\n * Build the prompt `SessionMessage` for a given provider.\n *\n * Prefers `provider.promptMessage` when defined, falling back to `defaultPromptMessage`.\n */\nexport function buildPromptMessage(provider: Provider, parts: PromptPart[]): SessionMessage {\n for (const part of parts) {\n if (part.type === 'audio' && provider.meta.capabilities?.audio !== true)\n throw unsupportedMediaError('audio', provider.name)\n if (part.type === 'video' && provider.meta.capabilities?.video !== true)\n throw unsupportedMediaError('video', provider.name)\n }\n\n if (provider.promptMessage)\n return provider.promptMessage(parts)\n return defaultPromptMessage(parts)\n}\n","import type { TurnUsage } from '../types'\n\ninterface ModelCostRates {\n input?: number\n output?: number\n cacheRead?: number\n cacheWrite?: number\n}\n\ntype GetModel = (provider: string, model: string) => { cost?: ModelCostRates } | undefined\n\n// pi-ai ships ESM-only (no `require` export condition), so it can only be pulled\n// in via an async dynamic import — there is no synchronous load path. Loading it\n// lazily (instead of a static import) also keeps its multi-MB price registry out\n// of the static bundle, so edge consumers can externalize `@earendil-works/pi-ai`;\n// the `.catch` then degrades to `EXTRA_COST_RATES`. The promise is cached, so the\n// import runs at most once per process.\nlet getModelPromise: Promise<GetModel | null> | undefined\nfunction loadGetModel(): Promise<GetModel | null> {\n getModelPromise ??= import('@earendil-works/pi-ai/providers/all')\n .then(mod => mod.getBuiltinModel as unknown as GetModel)\n .catch(() => null)\n return getModelPromise\n}\n\n/**\n * Per-MTok rates for models not yet in pi-ai's bundled registry. Keyed by\n * `provider:modelId`. Consulted only when pi-ai's `getModel` misses, so a\n * later dep bump that bundles the model transparently takes over.\n */\nconst EXTRA_COST_RATES: Readonly<Record<string, ModelCostRates>> = {\n // Claude Fable 5 — $10/$50 per MTok (cache rates at the standard 0.1×/1.25×).\n 'anthropic:claude-fable-5': { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },\n // Claude Opus 4.8 — same pricing as 4.7 ($5/$25 per MTok).\n 'anthropic:claude-opus-4-8': { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },\n // `gpt-5.6` is OpenAI's shorthand alias for the Sol flagship. pi-ai's\n // registry bundles the concrete ids (sol/terra/luna) but not the bare\n // alias, so usage recorded against the shorthand needs this local entry\n // (Sol's short-context standard-tier rates).\n 'openai:gpt-5.6': { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 },\n // Arcee Trinity — platform pricing per MTok.\n 'arcee:trinity-large-thinking': { input: 0.25, output: 0.8 },\n 'arcee:trinity-large-preview': { input: 0.45, output: 0.15 },\n 'arcee:trinity-mini': { input: 0.045, output: 0.15 },\n // Baseten Model APIs — published per-MTok rates (cached-input discount as\n // cacheRead). GLM 5.1 / Nemotron Ultra omitted: no published rate yet.\n 'baseten:zai-org/GLM-5': { input: 0.95, output: 3.15, cacheRead: 0.2 },\n 'baseten:zai-org/GLM-4.7': { input: 0.6, output: 2.2, cacheRead: 0.12 },\n 'baseten:moonshotai/Kimi-K2.6': { input: 1, output: 3.9, cacheRead: 0.2 },\n 'baseten:moonshotai/Kimi-K2.5': { input: 0.6, output: 3, cacheRead: 0.12 },\n 'baseten:deepseek-ai/DeepSeek-V4-Pro': { input: 1.74, output: 3.48, cacheRead: 0.145 },\n 'baseten:nvidia/Nemotron-120B-A12B': { input: 0.3, output: 0.75, cacheRead: 0.06 },\n 'baseten:openai/gpt-oss-120b': { input: 0.1, output: 0.5 },\n}\n\n/**\n * Per-rate multipliers applied to the looked-up base rates before computing the\n * total. Used for premium-priced modes (e.g. Anthropic fast mode at 2×/6×). A\n * missing key defaults to `1` (no change).\n */\nexport interface CostMultiplier {\n input?: number\n output?: number\n cacheRead?: number\n cacheWrite?: number\n}\n\n/**\n * Fill in `usage.cost` from pi-ai's bundled price registry when the\n * provider didn't report one. Provider-reported cost (e.g. OpenRouter's\n * `total_cost`, OpenAI's pi-ai-computed total) always wins — we only\n * estimate when `usage.cost` is undefined.\n *\n * Lookup is `(provider, usage.modelId)`. On a miss (unknown / unbundled\n * model), `cost` stays undefined and the footer indicator hides — better\n * than fabricating a $0.\n *\n * The number is an estimate: token counts come from the API, rates from\n * the locally-bundled registry that refreshes with `@earendil-works/pi-ai`\n * version bumps. If a provider changes prices between bumps the figure\n * will drift until the dep updates. An optional `multiplier` scales the\n * base rates for premium modes (e.g. fast mode) before totaling.\n */\nexport async function fillEstimatedCost(\n usage: TurnUsage,\n provider: string,\n multiplier?: CostMultiplier,\n): Promise<TurnUsage> {\n if (usage.cost !== undefined)\n return usage\n if (!usage.modelId)\n return usage\n\n // Await the cached pi-ai load (resolves once; instant thereafter). `getModel`\n // accepts arbitrary strings here (the openai-compat adapter routes both known\n // and user-defined endpoint names through this helper) and throws on a miss,\n // so the lookup is guarded.\n const getModel = await loadGetModel()\n let model: { cost?: ModelCostRates } | undefined\n if (getModel) {\n try {\n model = getModel(provider, usage.modelId)\n }\n catch {\n model = undefined\n }\n }\n\n // pi-ai miss → fall back to the local rate table.\n const rates = model?.cost ?? EXTRA_COST_RATES[`${provider}:${usage.modelId}`]\n if (!rates)\n return usage\n\n const mul = multiplier ?? {}\n const total\n = ((rates.input ?? 0) * (mul.input ?? 1) * (usage.input ?? 0)\n + (rates.output ?? 0) * (mul.output ?? 1) * (usage.output ?? 0)\n + (rates.cacheRead ?? 0) * (mul.cacheRead ?? 1) * (usage.cacheRead ?? 0)\n + (rates.cacheWrite ?? 0) * (mul.cacheWrite ?? 1) * (usage.cacheCreation ?? 0)) / 1_000_000\n\n if (total <= 0)\n return usage\n\n return { ...usage, cost: total }\n}\n","/**\n * JSON Schema sanitizer for tool `inputSchema` forwarded to LLM providers.\n *\n * Why this exists: MCP servers ship arbitrary JSON Schema in `tools/list`.\n * Anthropic and (to a lesser extent) OpenAI reject specific keyword\n * combinations with a 400 on `messages.create`, and the error message\n * lands deep inside an SDK exception that consumers blame on their own\n * code. The most common failure modes in the wild:\n *\n * - Root `$ref` (server-published schema lives under `#/$defs/X`).\n * - Root `oneOf` / `anyOf` / `allOf` (Anthropic allows neither at the\n * top level; expects `type: 'object'`).\n * - `type: ['object', 'null']` at the root (nullable variants of an\n * otherwise valid object schema).\n * - Missing `properties` on an object root.\n * - `nullable: true` (Swagger / OpenAPI 3.0 idiom — not standard JSON\n * Schema, ignored by every modern provider but Anthropic warns).\n * - Nested `$ref` pointing at `#/definitions/X` without the schema\n * actually inlining the referenced definition.\n *\n * Pure rewrite. Returns a new schema (or the same reference when nothing\n * needed to change — the hot path on stable tool registries) and a list\n * of `warnings` describing each rewrite. Providers call\n * `sanitizeToolSpecs` from `formatTools` so every tool — eager MCP, lazy\n * MCP, native, skill — gets the same treatment, and warnings surface\n * through `console.warn` once per batch (the seen-set in\n * `sanitizeToolSpecs` is local to one call, so a stable bad schema logs\n * once per request rather than every turn).\n *\n * Scope:\n * - Pure function. No I/O, no globals.\n * - Bounded recursion (`MAX_DEPTH = 32`) so a malicious server can't\n * wedge the agent with a deeply self-referential schema.\n * - Bounded `$ref` resolution (`MAX_REF_HOPS = 16`) for the same\n * reason. Cycles fall back to `{}` (permissive) rather than\n * throwing — the request still succeeds, the schema is just looser.\n *\n * Non-goals:\n * - Validating that the schema is internally consistent.\n * - Cleaning up tool *descriptions* (handled elsewhere).\n * - Provider-side feature detection (handled by the provider's own\n * `formatTools`; this module is a syntactic guard, not a semantic one).\n */\n\n/** Strictness profile — picks which transformations to apply. */\nexport type SchemaSanitizeProfile = 'anthropic' | 'openai' | 'permissive'\n\nexport interface SchemaSanitizeOptions {\n /** Strictness profile. Defaults to `'permissive'`. */\n profile?: SchemaSanitizeProfile\n /**\n * Tool name for context in warnings. Optional — when set, every warning\n * is prefixed with `[tool:<name>] ` so log lines correlate to the tool.\n */\n toolName?: string\n}\n\nexport interface SchemaSanitizeResult {\n /** Sanitized schema, safe to forward to the provider. */\n schema: Record<string, unknown>\n /**\n * Human-readable strings describing each rewrite the sanitizer performed.\n * Empty on a clean schema. Consumers may log these (recommended) or\n * thread them through a hook for observability.\n */\n warnings: string[]\n}\n\n/**\n * Provider tool-name constraint. The charset (`[A-Za-z0-9_-]`) is universal\n * across Anthropic / OpenAI / OpenAI-compat; only the length cap differs —\n * Anthropic allows 128, OpenAI/-compat cap at 64. A name violating this 400s\n * the WHOLE request (`tools.N.custom.name: String should match pattern ...`),\n * not just the one tool — so it's a request-killer, not a per-tool nuisance.\n */\nconst TOOL_NAME_CHARSET = /^[\\w-]+$/\n\nfunction maxToolNameLen(profile: SchemaSanitizeProfile): number {\n return profile === 'anthropic' ? 128 : 64\n}\n\nfunction isValidToolName(name: string, profile: SchemaSanitizeProfile): boolean {\n return name.length >= 1 && name.length <= maxToolNameLen(profile) && TOOL_NAME_CHARSET.test(name)\n}\n\n/** Max nesting depth before sub-schemas are replaced with `{}`. */\nconst MAX_DEPTH = 32\n\n/** Max `$ref` hop count before resolution gives up. */\nconst MAX_REF_HOPS = 16\n\n/** Keys that hold a single sub-schema. */\nconst SUBSCHEMA_KEYS = ['items', 'additionalProperties', 'contains', 'not', 'if', 'then', 'else', 'propertyNames'] as const\n\n/**\n * Keys that hold a record of sub-schemas.\n *\n * `$defs` / `definitions` are intentionally NOT included — they're\n * metaschema storage, only reached via `$ref` resolution (which has its\n * own walker in `resolveRef`), and `enforceRoot` strips them at the\n * wire layer once inlining is done. Recursing into them would cost\n * cycles and produce warnings about sub-trees the provider never sees.\n */\nconst SUBSCHEMA_RECORD_KEYS = ['properties', 'patternProperties', 'dependentSchemas'] as const\n\n/** Keys that hold an array of sub-schemas. */\nconst SUBSCHEMA_ARRAY_KEYS = ['oneOf', 'anyOf', 'allOf', 'prefixItems'] as const\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Resolve a JSON pointer like `#/$defs/Foo` against `root`. Returns\n * `undefined` when the pointer doesn't resolve or hops past `MAX_REF_HOPS`.\n * The caller treats `undefined` as \"give up, leave as a permissive schema\".\n */\nfunction resolveRef(root: Record<string, unknown>, ref: string, hops = 0): Record<string, unknown> | undefined {\n if (hops > MAX_REF_HOPS)\n return undefined\n if (!ref.startsWith('#/'))\n return undefined\n\n const parts = ref.slice(2).split('/').map(decodeRefSegment)\n let cursor: unknown = root\n for (const part of parts) {\n if (!isPlainObject(cursor))\n return undefined\n cursor = cursor[part]\n }\n if (!isPlainObject(cursor))\n return undefined\n\n if (typeof cursor.$ref === 'string')\n return resolveRef(root, cursor.$ref, hops + 1)\n return cursor\n}\n\nfunction decodeRefSegment(seg: string): string {\n return seg.replace(/~1/g, '/').replace(/~0/g, '~')\n}\n\ninterface SanitizeCtx {\n root: Record<string, unknown>\n warnings: string[]\n profile: SchemaSanitizeProfile\n prefix: string\n}\n\n/**\n * Recursively sanitize a sub-schema. Returns the **original** reference\n * when nothing needed to change — every turn's `formatTools` re-runs the\n * sanitizer, and the no-alloc fast path keeps the hot loop cheap when\n * the registered tool set is already clean. Returns a fresh object when\n * any rewrite happened; never mutates the input.\n *\n * The dirty-tracking is local to each frame so a clean sub-tree under a\n * dirty parent doesn't get cloned uselessly.\n */\nfunction sanitizeNode(node: unknown, ctx: SanitizeCtx, depth: number, path: string): Record<string, unknown> {\n if (depth > MAX_DEPTH) {\n ctx.warnings.push(`${ctx.prefix}schema nested deeper than ${MAX_DEPTH} levels at ${path || '$'} — replaced with permissive {}`)\n return {}\n }\n if (!isPlainObject(node))\n return {}\n\n let dirty = false\n let working: Record<string, unknown> = node\n\n // Inline `$ref` first so subsequent passes see the resolved shape.\n // Sibling keys (per JSON Schema 2019-09+ semantics) win over the\n // referenced definition. Unresolvable refs collapse to whatever\n // siblings remained so the surrounding schema still validates.\n if (typeof working.$ref === 'string') {\n const ref = working.$ref\n const resolved = resolveRef(ctx.root, ref)\n const { $ref: _drop, ...rest } = working\n if (resolved) {\n ctx.warnings.push(`${ctx.prefix}inlined $ref \"${ref}\" at ${path || '$'}`)\n working = { ...resolved, ...rest }\n }\n else {\n ctx.warnings.push(`${ctx.prefix}dropped unresolvable $ref \"${ref}\" at ${path || '$'}`)\n working = rest\n }\n dirty = true\n }\n\n // OpenAPI 3.0 `nullable` → JSON-Schema `type: [..., 'null']`. Modern\n // providers don't recognise `nullable`; Anthropic in particular ignores\n // it silently, which lets the model emit `null` and then the validator\n // complains the value isn't the declared type. Translation keeps the\n // intent explicit on the wire.\n if (working.nullable === true) {\n if (!dirty) {\n working = { ...working }\n dirty = true\n }\n const t = working.type\n if (typeof t === 'string') {\n working.type = [t, 'null']\n ctx.warnings.push(`${ctx.prefix}converted nullable:true → type:[${t},null] at ${path || '$'}`)\n }\n else if (Array.isArray(t) && !t.includes('null')) {\n working.type = [...t, 'null']\n ctx.warnings.push(`${ctx.prefix}converted nullable:true → type:[…,null] at ${path || '$'}`)\n }\n else if (Array.isArray(t)) {\n // Redundant — type already includes 'null'. Silent strip keeps the\n // wire shape clean without surfacing a warning the caller can't act on.\n }\n else {\n ctx.warnings.push(`${ctx.prefix}stripped nullable:true at ${path || '$'} (no base type)`)\n }\n delete working.nullable\n }\n\n // Sub-schema recursion. Sweep every key that JSON Schema 2020-12\n // reserves for nested schemas; leave annotation keywords (`title`,\n // `description`, `examples`, `default`, ...) and validation keywords\n // (`minimum`, `pattern`, ...) alone.\n for (const key of SUBSCHEMA_KEYS) {\n const child = working[key]\n if (isPlainObject(child)) {\n const sanitized = sanitizeNode(child, ctx, depth + 1, `${path}/${key}`)\n if (sanitized !== child) {\n if (!dirty) {\n working = { ...working }\n dirty = true\n }\n working[key] = sanitized\n }\n }\n }\n\n for (const key of SUBSCHEMA_RECORD_KEYS) {\n const rec = working[key]\n if (isPlainObject(rec)) {\n let recDirty = false\n let out: Record<string, unknown> = rec\n for (const [k, v] of Object.entries(rec)) {\n const sanitized = sanitizeNode(v, ctx, depth + 1, `${path}/${key}/${k}`)\n if (sanitized !== v) {\n if (!recDirty) {\n out = { ...rec }\n recDirty = true\n }\n out[k] = sanitized\n }\n }\n if (recDirty) {\n if (!dirty) {\n working = { ...working }\n dirty = true\n }\n working[key] = out\n }\n }\n }\n\n for (const key of SUBSCHEMA_ARRAY_KEYS) {\n const arr = working[key]\n if (Array.isArray(arr)) {\n let arrDirty = false\n let out: unknown[] = arr\n for (let i = 0; i < arr.length; i++) {\n const sanitized = sanitizeNode(arr[i], ctx, depth + 1, `${path}/${key}/${i}`)\n if (sanitized !== arr[i]) {\n if (!arrDirty) {\n out = [...arr]\n arrDirty = true\n }\n out[i] = sanitized\n }\n }\n if (arrDirty) {\n if (!dirty) {\n working = { ...working }\n dirty = true\n }\n working[key] = out\n }\n }\n }\n\n return working\n}\n\n/**\n * Apply root-level coercions every supported provider expects:\n *\n * - `type === 'object'` (Anthropic requires it; OpenAI tolerates more\n * but rejects unions at root in strict mode).\n * - `properties` is a plain object (Anthropic 400s when missing).\n * - No root-level `$ref` / `oneOf` / `anyOf` / `allOf` (Anthropic rejects;\n * OpenAI behaviour varies by endpoint).\n * - `$defs` / `definitions` stripped (recursive pass already inlined\n * refs; the metaschema storage is dead weight at the wire layer).\n *\n * Applied for every profile because portability matters more than the\n * marginal permissiveness of any one host. Profile-specific extras\n * (currently `$schema` stripping on `anthropic`) are gated below.\n */\nfunction enforceRoot(schema: Record<string, unknown>, ctx: SanitizeCtx): Record<string, unknown> {\n // Decide what the final shape needs first, then materialise a clone\n // only when at least one change is required. The recursive pass calls\n // this last, so a fully-clean schema reaches the wire as the exact\n // reference the consumer passed in.\n let out: Record<string, unknown> = schema\n let dirty = false\n const dirtyOnce = (): void => {\n if (!dirty) {\n out = { ...schema }\n dirty = true\n }\n }\n\n const t = out.type\n if (Array.isArray(t)) {\n if (t.includes('object')) {\n dirtyOnce()\n out.type = 'object'\n if (t.length > 1)\n ctx.warnings.push(`${ctx.prefix}collapsed root type:[${t.join(',')}] to 'object'`)\n }\n else {\n dirtyOnce()\n ctx.warnings.push(`${ctx.prefix}root type:[${t.join(',')}] does not include 'object' — coerced to 'object'`)\n out.type = 'object'\n }\n }\n else if (typeof t === 'string' && t !== 'object') {\n dirtyOnce()\n ctx.warnings.push(`${ctx.prefix}coerced root type:'${t}' → 'object'`)\n out.type = 'object'\n }\n else if (t === undefined) {\n dirtyOnce()\n out.type = 'object'\n }\n\n // Anthropic + OpenAI both require `properties` on object schemas — even\n // when empty. Synthesise the object so the wire shape is well-formed.\n if (!isPlainObject(out.properties)) {\n if ('properties' in out)\n ctx.warnings.push(`${ctx.prefix}replaced non-object 'properties' at root with {}`)\n dirtyOnce()\n out.properties = {}\n }\n\n for (const key of ['oneOf', 'anyOf', 'allOf'] as const) {\n if (key in out) {\n dirtyOnce()\n ctx.warnings.push(`${ctx.prefix}stripped root '${key}' (providers require a single object schema)`)\n delete out[key]\n }\n }\n\n if ('$ref' in out) {\n dirtyOnce()\n ctx.warnings.push(`${ctx.prefix}stripped root $ref`)\n delete out.$ref\n }\n\n if ('$defs' in out) {\n dirtyOnce()\n delete out.$defs\n }\n if ('definitions' in out) {\n dirtyOnce()\n delete out.definitions\n }\n\n if (ctx.profile === 'anthropic' && '$schema' in out) {\n dirtyOnce()\n delete out.$schema\n }\n\n return out\n}\n\n/**\n * Sanitize a single tool's `inputSchema` for safe forwarding to the\n * provider. Returns the rewritten schema + a list of warnings describing\n * everything that changed.\n *\n * Never mutates the input. Returns the **same reference** when no rewrite\n * was needed (clean-schema fast path) — `sanitizeToolSpecs` relies on\n * this to keep the formatTools hot loop allocation-free across turns\n * when the registered tool set is already wire-valid.\n */\nexport function sanitizeToolSchema(\n input: unknown,\n options: SchemaSanitizeOptions = {},\n): SchemaSanitizeResult {\n const profile: SchemaSanitizeProfile = options.profile ?? 'permissive'\n const prefix = options.toolName ? `[tool:${options.toolName}] ` : ''\n\n // Non-object inputs always allocate a fresh `{}` root — there's no\n // possible identity to preserve, and the wire still needs a valid object\n // schema either way. `sanitizeToolSpecs` won't hit its no-alloc path for\n // these, which is fine; the model has nothing useful to call on a\n // malformed inputSchema anyway.\n if (!isPlainObject(input)) {\n return {\n schema: { type: 'object', properties: {} },\n warnings: [],\n }\n }\n\n const ctx: SanitizeCtx = {\n root: input,\n warnings: [],\n profile,\n prefix,\n }\n\n const recursed = sanitizeNode(input, ctx, 0, '')\n const enforced = enforceRoot(recursed, ctx)\n\n return { schema: enforced, warnings: ctx.warnings }\n}\n\n/**\n * Convenience: sanitize a batch of tools and emit a single de-duped\n * `console.warn` per unique warning line. Returns the rewritten tools\n * preserving original ordering and reference identity for clean schemas\n * (no reallocation when nothing needed to change).\n *\n * The sanitiser runs every request, so log noise from a stable bad\n * schema would multiply across turns; the de-dupe keeps the signal\n * useful in production logs without dropping the first occurrence.\n *\n * Final name guard: a tool whose `name` violates the provider constraint\n * is DROPPED (not throwingly forwarded) with a one-time warning. An invalid\n * name 400s the entire `messages.create` — taking every other tool and the\n * turn down with it — so silently degrading one capability is the strictly\n * safer failure mode. Upstream aliasing already coerces MCP names to valid\n * wire names, so in practice this only fires for names that cannot be\n * represented at all; it's defense-in-depth that guarantees a malformed\n * name can never reach the wire, regardless of how it got into the list.\n */\nexport function sanitizeToolSpecs<T extends { name: string, inputSchema?: unknown }>(\n tools: readonly T[],\n options: { profile?: SchemaSanitizeProfile, onWarning?: (line: string) => void } = {},\n): T[] {\n const profile: SchemaSanitizeProfile = options.profile ?? 'permissive'\n const warn = options.onWarning ?? defaultWarn\n const seen = new Set<string>()\n const emitOnce = (line: string): void => {\n if (seen.has(line))\n return\n seen.add(line)\n warn(line)\n }\n\n const out: T[] = []\n for (const tool of tools) {\n if (!isValidToolName(tool.name, profile)) {\n emitOnce(\n `dropped tool with invalid name \"${clipToolName(tool.name)}\" — `\n + `must match ^[A-Za-z0-9_-]{1,${maxToolNameLen(profile)}}$ for the ${profile} provider`,\n )\n continue\n }\n const result = sanitizeToolSchema(tool.inputSchema, { profile, toolName: tool.name })\n if (result.warnings.length === 0 && result.schema === tool.inputSchema) {\n out.push(tool)\n continue\n }\n for (const line of result.warnings)\n emitOnce(line)\n out.push({ ...tool, inputSchema: result.schema })\n }\n return out\n}\n\n/** Clip an over-long / weird name so a warning line stays readable in logs. */\nfunction clipToolName(name: string): string {\n return name.length > 80 ? `${name.slice(0, 80)}…` : name\n}\n\nfunction defaultWarn(line: string): void {\n console.warn(`[zidane:schema] ${line}`)\n}\n","/**\n * OpenAI-compatible provider factory + shared utilities.\n *\n * `openaiCompat(params)` returns a `Provider` that talks to any OpenAI-compatible\n * HTTP endpoint (OpenRouter, Cerebras, Baseten, Fireworks, Groq, local LM servers, ...).\n * Helpers (`consumeSSE`, `toOAIMessages`, ...) are shared by the bespoke `openrouter`\n * and `cerebras` wrappers, which pin defaults on top of this factory.\n */\n\nimport type { Provider, ProviderCapabilities, StreamCallbacks, StreamOptions, ToolResult, ToolSpec, TurnResult } from '.'\nimport type { ClassifiedError } from '../errors'\nimport type { PromptPart, SessionContentBlock, SessionMessage, ThinkingLevel, ToolResultContent, ToolResultDocumentContent, TurnFinishReason } from '../types'\nimport { estimateTokens } from '../compact/utils'\nimport { classifyErrorPrelude, errorMessage, isRetryableHttpStatus, matchesContextExceeded, matchesToolPairingError } from '../errors'\nimport { unsupportedMediaError } from '../prompt'\nimport { renderSystemForWire, splitSystemPrompt } from '../system-prompt'\nimport { audioFormatFromMediaType } from '../tools/media-sniff'\nimport { assertResolvedMediaBlock, documentBlockMarker } from '../types'\nimport { fillEstimatedCost } from './cost'\nimport { sanitizeToolSpecs } from './schema-sanitize'\n\n// ---------------------------------------------------------------------------\n// OpenAI-compatible types\n// ---------------------------------------------------------------------------\n\nexport interface OAIMessage {\n role: 'system' | 'user' | 'assistant' | 'tool'\n content?: unknown\n tool_calls?: { id: string, type: 'function', function: { name: string, arguments: string } }[]\n tool_call_id?: string\n /**\n * OpenRouter normalized reasoning envelope. Echoed back unmodified on assistant\n * messages so the gateway can resume an extended-reasoning chain on the\n * upstream route. Ignored by hosts that don't speak the envelope.\n */\n reasoning_details?: unknown[]\n /**\n * Plain reasoning string echoed back on assistant messages for vLLM-style\n * reasoning models (Arcee Trinity-Thinking, DeepSeek-R1). Arcee's API accepts\n * `reasoning_content`; some self-hosted vLLM versions read `reasoning`.\n */\n reasoning_content?: string\n reasoning?: string\n}\n\nexport interface OAITool {\n type: 'function'\n function: { name: string, description: string, parameters: Record<string, unknown> }\n}\n\n// Sentinel tags zidane uses to round-trip tool_calls / tool_results through\n// the OpenAI-compat session shape. `toOpenAI` writes them; `fromOpenAI` reads\n// them back. The `__zidane_` prefix avoids collisions with any literal `_tag`\n// value a host might emit on real wire messages.\nexport const TOOL_RESULTS_TAG = '__zidane_tool_results__'\nexport const ASSISTANT_TOOL_CALLS_TAG = '__zidane_assistant_tc__'\n\n// ---------------------------------------------------------------------------\n// SSE stream parser\n// ---------------------------------------------------------------------------\n\n/**\n * Ceiling on how many UTF-16 code units may accumulate between two newline\n * boundaries when parsing an SSE stream. A broken or malicious server that\n * emits an unbounded stream of non-newline bytes would otherwise grow `buffer`\n * without end and eventually OOM the host. Measured in JS string length (UTF-16\n * units, 8M units ≈ 8–24 MB of UTF-8 input) rather than bytes — close enough\n * for a pathological-stream tripwire without paying `Buffer.byteLength` on\n * every chunk. Safely above the largest tool-call arg JSON we reasonably\n * expect.\n */\nconst SSE_MAX_BUFFER_LENGTH = 8 * 1024 * 1024\n\nexport class OpenAICompatStreamError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'OpenAICompatStreamError'\n }\n}\n\ninterface OpenAICompatUsage {\n input: number\n output: number\n cost?: number\n cacheRead?: number\n cacheCreation?: number\n}\n\nfunction usageFromChunk(chunk: Record<string, unknown>): OpenAICompatUsage | null {\n const chunkUsage = chunk.usage as\n | {\n prompt_tokens?: number\n completion_tokens?: number\n total_cost?: number\n cache_discount?: number\n prompt_tokens_details?: {\n cached_tokens?: number\n cache_creation_input_tokens?: number\n cache_write_tokens?: number\n }\n cache_creation_input_tokens?: number\n }\n | undefined\n\n if (chunkUsage) {\n const cachedRead = chunkUsage.prompt_tokens_details?.cached_tokens\n // OpenRouter surfaces cache writes via `prompt_tokens_details.cache_creation_input_tokens`\n // (Anthropic passthrough) or `cache_write_tokens` (normalized form). Either is cumulative\n // for the request, so pick whichever the route populated.\n const detailsWrite = chunkUsage.prompt_tokens_details?.cache_creation_input_tokens\n ?? chunkUsage.prompt_tokens_details?.cache_write_tokens\n const cachedWrite = detailsWrite ?? chunkUsage.cache_creation_input_tokens\n // `TurnUsage.input` is Anthropic-shaped: FRESH tokens only, cache\n // traffic broken out (the context gauge and `fillEstimatedCost` both\n // sum the buckets). OpenAI's `prompt_tokens` is the opposite — it\n // INCLUDES everything reported under `prompt_tokens_details` — so the\n // detail buckets must be subtracted here. Without this, cached tokens\n // were double-counted in the context gauge AND double-priced by the\n // estimator (full input rate + cacheRead rate), inflating displayed\n // costs by nearly the whole cache-hit volume on long sessions.\n // The TOP-LEVEL `cache_creation_input_tokens` fallback keeps\n // Anthropic's additive semantics (not part of `prompt_tokens`), so\n // only detail-carried writes are subtracted.\n const includedInPrompt = (cachedRead ?? 0) + (detailsWrite ?? 0)\n return {\n input: Math.max(0, (chunkUsage.prompt_tokens ?? 0) - includedInPrompt),\n output: chunkUsage.completion_tokens ?? 0,\n cost: chunkUsage.total_cost ?? undefined,\n ...(typeof cachedRead === 'number' && cachedRead > 0 ? { cacheRead: cachedRead } : {}),\n ...(typeof cachedWrite === 'number' && cachedWrite > 0 ? { cacheCreation: cachedWrite } : {}),\n }\n }\n\n // Ollama's native streaming final chunk reports usage with these field names.\n // Some gateways expose that shape even when clients use an OpenAI-compatible\n // route, so accept it here before falling back to local estimates.\n const promptEvalCount = chunk.prompt_eval_count\n const evalCount = chunk.eval_count\n if (typeof promptEvalCount === 'number' || typeof evalCount === 'number') {\n return {\n input: typeof promptEvalCount === 'number' ? promptEvalCount : 0,\n output: typeof evalCount === 'number' ? evalCount : 0,\n }\n }\n\n return null\n}\n\nexport async function consumeSSE(\n response: Response,\n callbacks: StreamCallbacks,\n signal?: AbortSignal,\n) {\n // A null body (e.g. a 204, or a proxy that returns headers but no stream)\n // would otherwise NPE on `.getReader()` with an opaque TypeError.\n if (!response.body) {\n throw new OpenAICompatStreamError(\n 'Streaming response had no body — upstream returned an empty stream (status '\n + `${response.status} ${response.statusText}).`,\n )\n }\n const reader = response.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n let text = ''\n let thinking = ''\n let finishReason = 'stop'\n let usage: OpenAICompatUsage = { input: 0, output: 0 }\n const tcMap = new Map<number, { id: string, name: string, args: string }>()\n // Per-stream nonce mixed into fabricated tool-call ids. A bare\n // `call_${index}` is stable ACROSS turns — when a host omits ids on every\n // turn, turn N's `call_0` collides with turn 1's, the pairing pass strips\n // the \"duplicate\" tool_use plus its result, and the model loops on\n // `[Tool use interrupted]` forever (the Kimi/Fireworks pairing-storm\n // signature). The nonce keeps fabricated ids unique per request.\n const idNonce = Math.random().toString(36).slice(2, 10)\n // OpenRouter `reasoning_details` deltas, keyed by item index. Items are merged\n // field-by-field: text/summary concatenate, signature/data/format/id/type are\n // last-write-wins (they only appear once per item in practice).\n const reasoningMap = new Map<number, Record<string, unknown>>()\n let sawReasoningDetails = false\n\n try {\n while (true) {\n // Abort can land either between reads (caught here) or mid-`read()`\n // (fetch rejects with AbortError). Throw the same AbortError shape on\n // this path so both converge on `classifyOpenAICompatError` →\n // `{ kind: 'aborted' }` instead of returning partial content as a\n // normal result depending on timing.\n if (signal?.aborted)\n throw new DOMException('The operation was aborted.', 'AbortError')\n const { done, value } = await reader.read()\n if (done)\n break\n\n buffer += decoder.decode(value, { stream: true })\n // Cap the line-less buffer. Any single event must fit under the ceiling.\n if (buffer.length > SSE_MAX_BUFFER_LENGTH) {\n throw new OpenAICompatStreamError(\n `SSE buffer exceeded ${SSE_MAX_BUFFER_LENGTH} characters without a line boundary — upstream may be streaming non-SSE data.`,\n )\n }\n const lines = buffer.split('\\n')\n buffer = lines.pop() || ''\n\n for (const line of lines) {\n // Spec allows both `data:` and `data: ` — the `.trim()` below\n // absorbs the optional single space after the colon.\n if (!line.startsWith('data:'))\n continue\n const data = line.slice(5).trim()\n if (data === '[DONE]')\n continue\n\n let chunk: Record<string, unknown>\n try {\n chunk = JSON.parse(data) as Record<string, unknown>\n }\n catch {\n // Malformed JSON frame — skip (SSE keepalives, injected comments, etc.)\n continue\n }\n\n const parsedUsage = usageFromChunk(chunk)\n if (parsedUsage)\n usage = parsedUsage\n\n const choices = chunk.choices as Array<Record<string, unknown>> | undefined\n const choice = choices?.[0]\n if (!choice)\n continue\n const fr = choice.finish_reason as string | undefined\n if (fr)\n finishReason = fr\n\n const delta = choice.delta as Record<string, unknown> | undefined\n\n // OpenRouter normalized reasoning envelope. Items arrive as deltas keyed\n // by `index`; merge into a stable list and stream the visible text portion\n // through `onThinking` so the UI sees reasoning live.\n const reasoningDeltaArr = delta?.reasoning_details as Array<Record<string, unknown>> | undefined\n if (reasoningDeltaArr && reasoningDeltaArr.length > 0) {\n sawReasoningDetails = true\n for (const item of reasoningDeltaArr) {\n const idx = (typeof item.index === 'number' ? item.index : 0)\n const existing = reasoningMap.get(idx) ?? {}\n // Concatenate text/summary across deltas.\n if (typeof item.text === 'string') {\n existing.text = ((existing.text as string | undefined) ?? '') + item.text\n thinking += item.text\n callbacks.onThinking?.(item.text)\n }\n if (typeof item.summary === 'string') {\n existing.summary = ((existing.summary as string | undefined) ?? '') + item.summary\n thinking += item.summary\n callbacks.onThinking?.(item.summary)\n }\n // Last-write-wins for opaque fields.\n for (const key of ['type', 'signature', 'data', 'format', 'id'] as const) {\n const v = item[key]\n if (typeof v === 'string')\n existing[key] = v\n }\n reasoningMap.set(idx, existing)\n }\n }\n\n // Legacy text-only reasoning fields (DeepSeek `reasoning_content`,\n // pre-`reasoning_details` OpenRouter, Qwen-thinking, …). Suppressed\n // when the structured envelope is in flight to avoid double-counting\n // the same reasoning text into the `thinking` buffer.\n if (!sawReasoningDetails) {\n const thinkingDelta = (delta?.reasoning_content ?? delta?.reasoning) as string | undefined\n if (thinkingDelta) {\n thinking += thinkingDelta\n callbacks.onThinking?.(thinkingDelta)\n }\n }\n\n const contentDelta = delta?.content as string | undefined\n if (contentDelta) {\n text += contentDelta\n callbacks.onText(contentDelta)\n }\n\n const toolCallsDelta = delta?.tool_calls as Array<{\n index: number\n id?: string\n function?: { name?: string, arguments?: string }\n }> | undefined\n if (toolCallsDelta) {\n // Liveness signal for tool-only turns (no text/thinking deltas) so\n // the loop's first-response watchdog doesn't abort a stream that's\n // actively emitting tool-call argument deltas. (B2)\n if (toolCallsDelta.length > 0)\n callbacks.onToolCallDelta?.()\n for (const tc of toolCallsDelta) {\n const existing = tcMap.get(tc.index)\n if (existing) {\n // Some hosts deliver `id` / `function.name` on a later delta\n // than the first one for an index — backfill them so the call\n // doesn't keep the fabricated nonce id / empty name.\n if (tc.id)\n existing.id = tc.id\n existing.name ||= tc.function?.name ?? ''\n if (tc.function?.arguments)\n existing.args += tc.function.arguments\n }\n else {\n tcMap.set(tc.index, {\n id: tc.id || `call_${tc.index}_${idNonce}`,\n name: tc.function?.name || '',\n args: tc.function?.arguments || '',\n })\n }\n }\n }\n }\n }\n }\n catch (err) {\n // On a non-abort error (SSE buffer overflow, transport crash), cancel\n // the reader so the underlying HTTP body is actually terminated rather\n // than left half-consumed (a bare releaseLock() leaks the socket until\n // GC). Aborts already tear the body down via the fetch signal, so skip\n // the redundant cancel there.\n const isAbort = err instanceof Error && err.name === 'AbortError'\n if (!isAbort)\n await reader.cancel().catch(() => {})\n throw err\n }\n finally {\n reader.releaseLock()\n }\n\n // Tool call JSON is accumulated across many deltas. If the stream truncates\n // mid-object (network hiccup, server crash) `tc.args` won't parse — fall\n // back to an empty input and surface a stream error rather than letting the\n // whole turn reject with `SyntaxError: Unexpected end of JSON input`, which\n // gives callers no actionable signal.\n const toolCalls: Array<{ id: string, name: string, input: Record<string, unknown> }> = []\n for (const tc of tcMap.values()) {\n if (!tc.args) {\n toolCalls.push({ id: tc.id, name: tc.name, input: {} })\n continue\n }\n try {\n toolCalls.push({ id: tc.id, name: tc.name, input: JSON.parse(tc.args) as Record<string, unknown> })\n }\n catch (err) {\n throw new OpenAICompatStreamError(\n `Tool call \"${tc.name}\" (${tc.id}) arguments were truncated or malformed: ${errorMessage(err)}`,\n )\n }\n }\n\n // Sort by index so `reasoning_details` round-trip preserves item order.\n const reasoningDetails = Array.from(reasoningMap.entries())\n .sort(([a], [b]) => a - b)\n .map(([, item]) => item)\n\n return { text, thinking, toolCalls, finishReason, usage, reasoningDetails }\n}\n\n// ---------------------------------------------------------------------------\n// Message conversion: SessionMessage[] → OAIMessage[]\n// ---------------------------------------------------------------------------\n\n/**\n * Encode a single image block as an OpenAI `image_url` multi-part entry.\n */\nfunction toImageUrlPart(img: { mediaType: string, data: string }) {\n return {\n type: 'image_url' as const,\n image_url: { url: `data:${img.mediaType};base64,${img.data}` },\n }\n}\n\n/**\n * Encode an audio block as an OpenAI-compat `input_audio` part. The `data` is\n * RAW base64 with NO data-URI prefix (unlike images), per the spec.\n */\nfunction toInputAudioPart(audio: { mediaType: string, data: string }) {\n return {\n type: 'input_audio' as const,\n input_audio: { data: audio.data, format: audioFormatFromMediaType(audio.mediaType) },\n }\n}\n\n/**\n * Encode a video block as an OpenAI-compat `video_url` part. Uses the same\n * base64 data-URL shape as images. Only emitted on video-capable routes\n * (currently Gemini via OpenRouter).\n */\nfunction toVideoUrlPart(video: { mediaType: string, data: string }) {\n return {\n type: 'video_url' as const,\n video_url: { url: `data:${video.mediaType};base64,${video.data}` },\n }\n}\n\nfunction documentMarker(doc: ToolResultDocumentContent): string {\n return documentBlockMarker(doc, 'document omitted')\n}\n\n/**\n * Whether a document block can be forwarded natively to OpenAI's `file` content\n * part. Only base64-encoded files qualify (the `file_data` data-URL shape);\n * text-encoded documents are already inlined as attachment text upstream and\n * have no portable binary representation.\n */\nfunction isForwardableDocument(\n doc: { encoding: 'base64' | 'text', mediaType: string },\n): boolean {\n return doc.encoding === 'base64'\n}\n\n/**\n * Encode a base64 document as an OpenAI-compat `file` content part:\n * `{ type: 'file', file: { filename, file_data: 'data:<mime>;base64,<data>' } }`.\n *\n * Supported by OpenAI (Chat Completions + Responses) and routes that proxy it\n * (e.g. OpenRouter → OpenAI/Gemini/Anthropic). Gated behind `capabilities.documents`.\n */\nfunction toFilePart(doc: { mediaType: string, data: string, name?: string }) {\n return {\n type: 'file' as const,\n file: {\n filename: doc.name ?? `file.${doc.mediaType.split('/')[1] ?? 'bin'}`,\n file_data: `data:${doc.mediaType};base64,${doc.data}`,\n },\n }\n}\n\n/** True when any built OAI message carries a `{ type: 'file' }` content part. */\nfunction messagesContainFilePart(messages: OAIMessage[]): boolean {\n return messages.some(m =>\n Array.isArray(m.content)\n && m.content.some(p => typeof p === 'object' && p !== null && (p as { type?: unknown }).type === 'file'),\n )\n}\n\n/**\n * Summarize a `tool_result` output for the companion-user-message path — text blocks\n * are joined (separated by `\\n`) so the tool message carries all textual context; image\n * blocks are collected in a flat list for the companion user message.\n *\n * Used only on the fallback path; the native path walks `output` in-order to preserve\n * text↔image interleaving.\n */\nfunction summarizeToolResultOutput(output: string | ToolResultContent[], documentsEnabled = false): {\n text: string\n images: Array<{ mediaType: string, data: string }>\n audios: Array<{ mediaType: string, data: string }>\n videos: Array<{ mediaType: string, data: string }>\n documents: Array<{ mediaType: string, data: string, name?: string }>\n} {\n if (typeof output === 'string')\n return { text: output, images: [], audios: [], videos: [], documents: [] }\n\n const texts: string[] = []\n const images: Array<{ mediaType: string, data: string }> = []\n const audios: Array<{ mediaType: string, data: string }> = []\n const videos: Array<{ mediaType: string, data: string }> = []\n const documents: Array<{ mediaType: string, data: string, name?: string }> = []\n for (const block of output) {\n if (block.type === 'text') {\n texts.push(block.text)\n }\n else if (block.type === 'image') {\n assertResolvedMediaBlock(block, 'OpenAI-compatible wire messages')\n images.push({ mediaType: block.mediaType, data: block.data })\n }\n else if (block.type === 'audio') {\n audios.push({ mediaType: block.mediaType, data: block.data })\n }\n else if (block.type === 'video') {\n videos.push({ mediaType: block.mediaType, data: block.data })\n }\n else if (block.type === 'document' && documentsEnabled && isForwardableDocument(block)) {\n assertResolvedMediaBlock(block, 'OpenAI-compatible wire messages')\n documents.push({ mediaType: block.mediaType, data: block.data, ...(block.name ? { name: block.name } : {}) })\n }\n else if (block.type === 'document') {\n assertResolvedMediaBlock(block, 'OpenAI-compatible wire messages')\n texts.push(documentMarker(block))\n }\n }\n return { text: texts.join('\\n'), images, audios, videos, documents }\n}\n\n/**\n * Options that influence OpenAI-compat message shaping.\n *\n * `imageInToolResult` defaults to `false`: the loop routes images via a companion\n * user message emitted immediately after the `tool` message. Providers with\n * genuine multi-part tool support can set it to `true` to emit a single\n * multi-part `tool` message instead.\n */\ninterface ToOAIOptions {\n imageInToolResult?: boolean\n /**\n * Route accepts audio input (`input_audio` content parts). When false,\n * audio blocks throw rather than silently dropping bytes a non-audio model\n * would 400 on.\n */\n audio?: boolean\n /**\n * Route accepts video input (`video_url` content parts, base64 data-URL).\n * Currently Gemini-on-OpenRouter. When false, video blocks throw.\n */\n video?: boolean\n /**\n * Route accepts native document/PDF input (OpenAI `file` content part with\n * base64 `file_data`). When false (default), base64 documents degrade to the\n * `[document omitted: …]` text marker. Text-encoded documents are always\n * inlined upstream regardless of this flag.\n */\n documents?: boolean\n /**\n * Host accepts provider-bound reasoning state echoed back on assistant\n * messages. The exact wire shape is selected by `reasoningMode`: OpenRouter's\n * structured `reasoning_details[]` envelope, or vLLM/Arcee's plain\n * `reasoning_content` / `reasoning` string. When `false` (default), stored reasoning blocks\n * are silently dropped to avoid 400s on hosts that strict-validate the schema.\n */\n supportsReasoning?: boolean\n /**\n * Reasoning round-trip shape. `'openrouter'` echoes structured\n * `reasoning_details[]`; `'reasoning_content'` echoes the plain reasoning\n * string (vLLM/Arcee). Only consulted when `supportsReasoning` is `true`.\n * Default: `'openrouter'`.\n */\n reasoningMode?: 'openrouter' | 'reasoning_content'\n /**\n * Wire field used to echo plain reasoning in `'reasoning_content'` mode.\n * Default: `'reasoning'` (broadest vLLM compatibility).\n */\n reasoningContentField?: 'reasoning_content' | 'reasoning'\n /**\n * Normalize `content: null` → `''` on assistant tool-call turns. Default off.\n */\n normalizeNullContent?: boolean\n /**\n * Active model id. Used to gate `provider_reasoning` blocks — reasoning state\n * is bound to the route that produced it, so a model switch invalidates the\n * embedded signatures.\n */\n model?: string\n}\n\nexport function toOAIMessages(system: string, messages: SessionMessage[], options: ToOAIOptions = {}): OAIMessage[] {\n // Collapse the system-prompt boundary marker before it leaves the harness —\n // it's purely structural metadata for `applyOAICacheBreakpoints`, not\n // content the model should see. Cache-aware routes call the breakpoint\n // helper with the ORIGINAL `system` so the split is preserved on the wire;\n // cache-disabled routes get a clean, marker-free system message here.\n const renderedSystem = renderSystemForWire(system)\n const out: OAIMessage[] = [{ role: 'system', content: renderedSystem }]\n const nativeImageInTool = options.imageInToolResult === true\n const audioEnabled = options.audio === true\n const videoEnabled = options.video === true\n const documentsEnabled = options.documents === true\n const reasoningEnabled = options.supportsReasoning === true\n const reasoningMode = options.reasoningMode ?? 'openrouter'\n const reasoningContentField = options.reasoningContentField ?? 'reasoning'\n const normalizeNullContent = options.normalizeNullContent === true\n const activeModel = options.model\n // Producer whose stored `provider_reasoning` blocks are valid for the active\n // route shape. `reasoning_content` mode round-trips `'vllm'` blocks; the\n // default `'openrouter'` mode round-trips `'openrouter'` blocks.\n const reasoningProducer: 'openrouter' | 'vllm' = reasoningMode === 'reasoning_content' ? 'vllm' : 'openrouter'\n\n for (const msg of messages) {\n // Single-pass partition into per-kind buckets — avoids re-walking\n // `msg.content` once per block type (P5). Reasoning blocks are gated here\n // by producer + active route, matching the previous double-filter.\n const toolResults: Extract<SessionContentBlock, { type: 'tool_result' }>[] = []\n const toolCalls: Extract<SessionContentBlock, { type: 'tool_call' }>[] = []\n const textBlocks: Extract<SessionContentBlock, { type: 'text' }>[] = []\n const imageBlocks: Extract<SessionContentBlock, { type: 'image' }>[] = []\n const audioBlocks: Extract<SessionContentBlock, { type: 'audio' }>[] = []\n const videoBlocks: Extract<SessionContentBlock, { type: 'video' }>[] = []\n const documentBlocks: Extract<SessionContentBlock, { type: 'document' }>[] = []\n const reasoningBlocks: Extract<SessionContentBlock, { type: 'provider_reasoning' }>[] = []\n for (const b of msg.content) {\n switch (b.type) {\n case 'tool_result':\n toolResults.push(b)\n break\n case 'tool_call':\n toolCalls.push(b)\n break\n case 'text':\n textBlocks.push(b)\n break\n case 'image':\n imageBlocks.push(b)\n break\n case 'audio':\n audioBlocks.push(b)\n break\n case 'video':\n videoBlocks.push(b)\n break\n case 'document':\n documentBlocks.push(b)\n break\n case 'provider_reasoning':\n // Provider-bound reasoning state — kept only when the host supports\n // it, the producer matches the active mode's shape, AND the active\n // route matches the producing model (signatures/CoT are route-bound).\n if (reasoningEnabled\n && b.producer === reasoningProducer\n && !(b.model && activeModel && b.model !== activeModel)) {\n reasoningBlocks.push(b)\n }\n break\n }\n }\n const reasoningDetails = reasoningBlocks.flatMap(b => b.producer === 'openrouter' ? b.details : [])\n // Plain reasoning text (vLLM/Arcee) — concatenated across blocks in order.\n const reasoningText = reasoningMode === 'reasoning_content'\n ? reasoningBlocks.map(b => b.producer === 'vllm' ? b.reasoningText : '').filter(Boolean).join('')\n : ''\n\n // Direct message-content audio/video are hard-gated by route capability:\n // throw rather than drop bytes a non-capable model would 400 on (or\n // strip-and-confabulate). Checked up here so every downstream branch\n // (multimodal user message, or a turn that also carries tool_calls) is\n // covered uniformly. Tool-result audio/video are gated per-block in the\n // tool_results branch below, where the companion-vs-native split lives.\n if (audioBlocks.length > 0 && !audioEnabled)\n throw unsupportedMediaError('audio', 'openai-compat')\n if (videoBlocks.length > 0 && !videoEnabled)\n throw unsupportedMediaError('video', 'openai-compat')\n\n // Tool results → individual tool messages (plus optional companion user message for images)\n if (toolResults.length > 0) {\n for (const tr of toolResults) {\n // Pure-string output → text-only fast path, wire-identical to pre-multimodal behavior.\n if (typeof tr.output === 'string') {\n out.push({ role: 'tool', tool_call_id: tr.callId, content: tr.output })\n continue\n }\n\n const imageToolParts = tr.output as Array<\n Extract<ToolResultContent, { type: 'text' | 'image' }>\n >\n const hasNonImageMedia = tr.output.some(block =>\n block.type === 'audio' || block.type === 'video' || block.type === 'document')\n\n // Native multi-part path — walk blocks in order so text↔image\n // interleaving is preserved. `imageInToolResult` only says the tool\n // role accepts images; audio/video/file parts are less portable there,\n // so route those through the companion-user-message path below.\n if (nativeImageInTool && !hasNonImageMedia) {\n const parts = imageToolParts.map((block) => {\n if (block.type === 'image') {\n assertResolvedMediaBlock(block, 'OpenAI-compatible wire messages')\n return toImageUrlPart({ mediaType: block.mediaType, data: block.data })\n }\n return { type: 'text' as const, text: block.text }\n })\n out.push({ role: 'tool', tool_call_id: tr.callId, content: parts })\n continue\n }\n\n // Companion-user-message fallback (Claude Desktop / Cline pattern).\n // Works on any Chat Completions endpoint that accepts images in user messages.\n const { text, images, audios, videos, documents } = summarizeToolResultOutput(tr.output, documentsEnabled)\n if (audios.length > 0 && !audioEnabled)\n throw unsupportedMediaError('audio', 'openai-compat')\n if (videos.length > 0 && !videoEnabled)\n throw unsupportedMediaError('video', 'openai-compat')\n const mediaCount = images.length + audios.length + videos.length + documents.length\n if (mediaCount === 0) {\n // Structured array happened to be text-only — collapse to pure-text path.\n out.push({ role: 'tool', tool_call_id: tr.callId, content: text })\n continue\n }\n\n // Use the precise \"image(s)\" wording for the image-only case (the common\n // path); fall back to the generic \"attachment(s)\" noun only when audio,\n // video, or a document is mixed in.\n const imageOnly = audios.length === 0 && videos.length === 0 && documents.length === 0\n const noun = imageOnly\n ? (mediaCount === 1 ? 'image' : 'images')\n : (mediaCount === 1 ? 'attachment' : 'attachments')\n const attachedMarker = `[${mediaCount} ${noun} attached — see next user message]`\n const toolMarker = text.length > 0 ? `${text}\\n\\n${attachedMarker}` : attachedMarker\n out.push({ role: 'tool', tool_call_id: tr.callId, content: toolMarker })\n\n out.push({\n role: 'user',\n content: [\n ...images.map(toImageUrlPart),\n ...audios.map(toInputAudioPart),\n ...videos.map(toVideoUrlPart),\n ...documents.map(toFilePart),\n { type: 'text' as const, text: `(${noun} returned by tool call ${tr.callId})` },\n ],\n })\n }\n // A message can legitimately carry text/image/audio/video/document blocks\n // alongside its tool_results (e.g. a user follow-up typed while tools ran).\n // Don't drop them — fall through to the non-tool-result shaping below.\n if (textBlocks.length === 0 && imageBlocks.length === 0 && audioBlocks.length === 0\n && videoBlocks.length === 0 && documentBlocks.length === 0) {\n continue\n }\n }\n\n // Tool calls → assistant message with tool_calls array\n if (toolCalls.length > 0) {\n const textContent = textBlocks.length > 0\n ? textBlocks.map(b => b.text).join('\\n')\n : (normalizeNullContent ? '' : null)\n const m: OAIMessage = {\n role: 'assistant',\n content: textContent,\n tool_calls: toolCalls.map(tc => ({\n id: tc.id,\n type: 'function' as const,\n function: { name: tc.name, arguments: JSON.stringify(tc.input) },\n })),\n }\n if (reasoningDetails.length > 0)\n m.reasoning_details = reasoningDetails\n if (reasoningText.length > 0)\n m[reasoningContentField] = reasoningText\n out.push(m)\n continue\n }\n\n // Images/audio/video/documents → multimodal array. Documents are\n // represented as text markers because Chat Completions has no portable\n // PDF/document block.\n if (imageBlocks.length > 0 || audioBlocks.length > 0 || videoBlocks.length > 0 || documentBlocks.length > 0) {\n const parts: unknown[] = []\n for (const img of imageBlocks) {\n assertResolvedMediaBlock(img, 'OpenAI-compatible wire messages')\n parts.push({\n type: 'image_url',\n image_url: { url: `data:${img.mediaType};base64,${img.data}` },\n })\n }\n for (const b of audioBlocks)\n parts.push(toInputAudioPart({ mediaType: b.mediaType, data: b.data }))\n for (const b of videoBlocks)\n parts.push(toVideoUrlPart({ mediaType: b.mediaType, data: b.data }))\n for (const b of documentBlocks) {\n assertResolvedMediaBlock(b, 'OpenAI-compatible wire messages')\n if (documentsEnabled && isForwardableDocument(b))\n parts.push(toFilePart(b))\n else\n parts.push({ type: 'text', text: documentMarker(b) })\n }\n for (const b of textBlocks) {\n parts.push({ type: 'text', text: b.text })\n }\n const m: OAIMessage = { role: msg.role, content: parts }\n if (msg.role === 'assistant' && reasoningDetails.length > 0)\n m.reasoning_details = reasoningDetails\n if (msg.role === 'assistant' && reasoningText.length > 0)\n m[reasoningContentField] = reasoningText\n out.push(m)\n continue\n }\n\n // Pure text\n let pushed: OAIMessage\n if (textBlocks.length === 1) {\n pushed = { role: msg.role, content: textBlocks[0].text }\n }\n else if (textBlocks.length > 1) {\n pushed = { role: msg.role, content: textBlocks.map(b => ({ type: 'text', text: b.text })) }\n }\n else {\n pushed = { role: msg.role, content: null }\n }\n if (msg.role === 'assistant' && reasoningDetails.length > 0)\n pushed.reasoning_details = reasoningDetails\n if (msg.role === 'assistant' && reasoningText.length > 0)\n pushed[reasoningContentField] = reasoningText\n out.push(pushed)\n }\n\n return out\n}\n\n// ---------------------------------------------------------------------------\n// Prompt caching\n// ---------------------------------------------------------------------------\n\nconst EPHEMERAL = { type: 'ephemeral' as const }\n\n/**\n * Add `cache_control: { type: 'ephemeral' }` breakpoints to the system message's\n * last text part and the last message's final content part.\n *\n * Mutates `messages` in place. Converts plain-string content into a single-element\n * content array so the cache marker can attach — this shape is accepted verbatim by\n * OpenRouter's Anthropic and Gemini routes and ignored by routes with automatic\n * caching (OpenAI, DeepSeek, Grok, Groq, Moonshot).\n *\n * Skip conditions (safe no-ops):\n * - Empty messages array.\n * - Assistant messages with no text (tool-call-only) — attaching a cache marker to a\n * `tool_calls` block has no defined semantics, so we skip and let the prior\n * system/tools breakpoints carry caching.\n *\n * System-prompt boundary handling: when `originalSystem` (the un-rendered\n * system text passed to {@link toOAIMessages}) contains\n * {@link SYSTEM_PROMPT_BOUNDARY}, the leading system message is rewritten as\n * a two-part array — `cache_control` lands on the static prefix only, so\n * per-turn churn in the dynamic suffix doesn't invalidate the cached\n * doctrine above. `originalSystem` defaults to `undefined` for back-compat;\n * omit it (or pass a marker-free string) for the single-block historic\n * behavior.\n */\nexport function applyOAICacheBreakpoints(messages: OAIMessage[], originalSystem?: string): void {\n if (messages.length === 0)\n return\n\n const first = messages[0]\n if (first.role === 'system')\n markSystemMessage(first, originalSystem)\n\n const lastIdx = messages.length - 1\n if (lastIdx > 0)\n markLastContentPart(messages[lastIdx])\n}\n\n/**\n * Place the cache marker on the static prefix of the system message. When\n * `originalSystem` carried {@link SYSTEM_PROMPT_BOUNDARY}, the message becomes\n * a two-part array — static (cached) then dynamic (uncached). Otherwise falls\n * through to {@link markLastContentPart} for the historic single-block\n * treatment.\n *\n * String-only branch — system messages produced by {@link toOAIMessages} are\n * always plain strings (`{ role: 'system', content: <rendered> }`). Hosts\n * that supply pre-built multi-part system messages bypass this branch and\n * get the generic last-block treatment.\n */\nfunction markSystemMessage(msg: OAIMessage, originalSystem?: string): void {\n if (typeof msg.content !== 'string' || msg.content.length === 0) {\n markLastContentPart(msg)\n return\n }\n // `originalSystem` is the authoritative source for the boundary split —\n // `msg.content` has typically been pre-rendered by `toOAIMessages` and no\n // longer carries the marker. Fall back to `msg.content` itself when no\n // original was supplied (or an empty original) so an external caller that\n // embedded a marker directly doesn't leak it into the cached block.\n const splitSource = originalSystem && originalSystem.length > 0\n ? originalSystem\n : msg.content\n const parts = splitSystemPrompt(splitSource)\n if (parts.dynamic.length === 0) {\n // No marker — wrap the (already marker-free) string in a single cached\n // block. `parts.static` equals `splitSource` in this branch.\n msg.content = [{ type: 'text', text: parts.static, cache_control: EPHEMERAL }]\n return\n }\n const next: Array<Record<string, unknown>> = []\n if (parts.static.length > 0)\n next.push({ type: 'text', text: parts.static, cache_control: EPHEMERAL })\n next.push({ type: 'text', text: parts.dynamic })\n msg.content = next\n}\n\n/**\n * Mark the last content part of an OAI message with `cache_control`. Normalizes\n * string content into a `[{ type: 'text', text, cache_control }]` array so the\n * marker has a block to attach to.\n *\n * No-op for messages without string or array content (tool-call-only assistant\n * messages fall through; the system/tools breakpoints carry the cache prefix).\n */\nfunction markLastContentPart(msg: OAIMessage): void {\n if (typeof msg.content === 'string') {\n if (msg.content.length === 0)\n return\n msg.content = [{ type: 'text', text: msg.content, cache_control: EPHEMERAL }]\n return\n }\n if (!Array.isArray(msg.content) || msg.content.length === 0)\n return\n\n const parts = msg.content as Array<Record<string, unknown>>\n const lastBlockIdx = parts.length - 1\n parts[lastBlockIdx] = { ...parts[lastBlockIdx], cache_control: EPHEMERAL }\n}\n\n/**\n * Return a copy of `tools` with `cache_control` on the last entry.\n *\n * OpenRouter accepts the marker alongside the standard `type` + `function` fields\n * and forwards it when routing to Anthropic/Gemini. Leaves the non-cached tools\n * unchanged so the caller's reference is not mutated.\n */\nexport function applyOAIToolCacheBreakpoint(tools: OAITool[]): OAITool[] {\n if (tools.length === 0)\n return tools\n const lastIdx = tools.length - 1\n return tools.map((tool, i) =>\n i === lastIdx ? ({ ...tool, cache_control: EPHEMERAL } as OAITool & { cache_control: typeof EPHEMERAL }) : tool,\n )\n}\n\n// ---------------------------------------------------------------------------\n// Shared message builders\n// ---------------------------------------------------------------------------\n\nexport function formatTools(tools: ToolSpec[]): OAITool[] {\n // Same sanitization Anthropic gets — OpenAI-compat hosts (OpenRouter,\n // Cerebras, Groq, ...) reject the same root-level shapes (missing\n // `properties` on object schemas, root `$ref` / `oneOf` / `anyOf`). The\n // `openai` profile is a touch more permissive about annotation\n // keywords, but the core rewrites are identical.\n const sanitized = sanitizeToolSpecs(tools, { profile: 'openai' })\n return sanitized.map(t => ({\n type: 'function' as const,\n function: { name: t.name, description: t.description, parameters: t.inputSchema as Record<string, unknown> },\n }))\n}\n\nexport function userMessage(content: string): SessionMessage {\n return { role: 'user', content: [{ type: 'text', text: content }] }\n}\n\n/**\n * Build a user `SessionMessage` from multimodal prompt parts.\n *\n * Passes every kind (text/image/audio/video/document) through as a canonical\n * block; the actual wire emission, capability gating, and unsupported-media\n * throwing all happen later in {@link toOAIMessages}. Implementing this (rather\n * than letting the agent fall back to `defaultPromptMessage`) is what lets\n * openai-compat routes accept base64 documents/PDFs at all — the default\n * builder throws on base64 documents.\n */\nexport function openAICompatPromptMessage(parts: PromptPart[]): SessionMessage {\n const content: SessionContentBlock[] = []\n for (const part of parts) {\n if (part.type === 'text') {\n if (part.text.length > 0)\n content.push({ type: 'text', text: part.text })\n continue\n }\n if (part.type === 'image' || part.type === 'audio' || part.type === 'video') {\n content.push({ type: part.type, mediaType: part.mediaType, data: part.data, ...(part.name ? { name: part.name } : {}) })\n continue\n }\n // document\n content.push({\n type: 'document',\n mediaType: part.mediaType,\n data: part.data,\n encoding: part.encoding,\n ...(part.name ? { name: part.name } : {}),\n })\n }\n return { role: 'user', content }\n}\n\nexport function assistantMessage(content: string): SessionMessage {\n return { role: 'assistant', content: [{ type: 'text', text: content }] }\n}\n\nexport function toolResultsMessage(results: ToolResult[]): SessionMessage {\n return {\n role: 'user',\n content: results.map(r => ({\n type: 'tool_result' as const,\n callId: r.id,\n output: r.content,\n ...(r.isError ? { isError: true as const } : {}),\n ...(r.metadata ? { metadata: r.metadata } : {}),\n })),\n }\n}\n\n/**\n * Recover human-readable reasoning text from an OpenRouter-style\n * `reasoning_details` array. Used as a fallback when a `reasoning_content`-mode\n * host emits the structured envelope instead of the plain text field — the\n * `text` / `summary` fields carry the visible chain-of-thought; opaque fields\n * (signature/data/format) are ignored.\n */\nfunction extractReasoningDetailsText(details: unknown[]): string {\n let out = ''\n for (const item of details) {\n if (!item || typeof item !== 'object')\n continue\n const rec = item as Record<string, unknown>\n if (typeof rec.text === 'string')\n out += rec.text\n else if (typeof rec.summary === 'string')\n out += rec.summary\n }\n return out\n}\n\nexport function buildAssistantContent(\n text: string,\n toolCalls: { id: string, name: string, input: Record<string, unknown> }[],\n thinking?: string,\n reasoning?:\n | { details: unknown[], producer: 'openrouter', model?: string }\n | { reasoningText: string, producer: 'vllm', model?: string },\n): SessionMessage {\n const content: SessionContentBlock[] = []\n // `provider_reasoning` precedes `thinking` so re-attached envelopes arrive\n // before the legacy text mirror in the assistant content list.\n const hasReasoning = reasoning\n && (reasoning.producer === 'openrouter'\n ? reasoning.details.length > 0\n : reasoning.reasoningText.length > 0)\n if (reasoning && hasReasoning) {\n const block: Extract<SessionContentBlock, { type: 'provider_reasoning' }> = reasoning.producer === 'openrouter'\n ? { type: 'provider_reasoning', producer: 'openrouter', details: reasoning.details }\n : { type: 'provider_reasoning', producer: 'vllm', reasoningText: reasoning.reasoningText }\n if (reasoning.model)\n block.model = reasoning.model\n content.push(block)\n }\n if (thinking)\n content.push({ type: 'thinking', text: thinking })\n if (text)\n content.push({ type: 'text', text })\n for (const tc of toolCalls) {\n content.push({ type: 'tool_call', id: tc.id, name: tc.name, input: tc.input })\n }\n // Never persist an empty assistant content array — replaying\n // `{ role: 'assistant', content: [] }` to providers that strict-validate\n // (Anthropic) 400s the whole request. Marker text mirrors the pairing\n // repair convention in `session/messages.ts`.\n if (content.length === 0)\n content.push({ type: 'text', text: '[no content]' })\n return { role: 'assistant', content }\n}\n\n// ---------------------------------------------------------------------------\n// Error classification\n// ---------------------------------------------------------------------------\n\n/**\n * HTTP error thrown when an OpenAI-compatible endpoint returns a non-OK response.\n *\n * The body is best-effort JSON-parsed; `error.message` / `error.code` / `error.type`\n * are extracted for clean downstream classification.\n */\nexport class OpenAICompatHttpError extends Error {\n readonly status: number\n readonly providerCode?: string\n readonly bodyText: string\n\n constructor(status: number, bodyText: string) {\n let message = bodyText\n let code: string | undefined\n try {\n const parsed = JSON.parse(bodyText)\n message = parsed?.error?.message ?? bodyText\n code = parsed?.error?.code ?? parsed?.error?.type\n }\n catch {\n // Leave message as the raw body.\n }\n super(`HTTP ${status}: ${message}`)\n this.name = 'OpenAICompatHttpError'\n this.status = status\n this.providerCode = code\n this.bodyText = bodyText\n }\n}\n\nconst TRAILING_SLASH_RE = /\\/$/\n\n/**\n * Classify an OpenAI-compatible error into `ClassifiedError`.\n *\n * Recognizes:\n * - `AbortError` (from fetch) → `aborted`.\n * - `OpenAICompatHttpError` with a context-exceeded code or message → `context_exceeded`.\n * - Any other `OpenAICompatHttpError` → `provider_error`.\n *\n * Returns `null` for unrecognized error shapes (the loop falls back to `AgentProviderError`).\n */\nexport function classifyOpenAICompatError(err: unknown): ClassifiedError | null {\n const prelude = classifyErrorPrelude(err)\n if (prelude === 'not-object')\n return null\n if (prelude === 'aborted')\n return { kind: 'aborted' }\n\n // A truncated / malformed SSE stream is a transient network-level failure —\n // typically worth a retry from the caller's perspective.\n if (err instanceof OpenAICompatStreamError) {\n return {\n kind: 'provider_error',\n providerCode: 'stream_error',\n message: err.message,\n retryable: true,\n }\n }\n\n if (!(err instanceof OpenAICompatHttpError))\n return null\n\n const code = err.providerCode\n const msg = err.message\n\n if (code === 'context_length_exceeded' || matchesContextExceeded(msg)) {\n return {\n kind: 'context_exceeded',\n providerCode: code ?? 'context_length_exceeded',\n message: msg,\n }\n }\n\n if (matchesToolPairingError(msg)) {\n return {\n kind: 'tool_pairing_corruption',\n providerCode: code ?? 'invalid_request_error',\n message: msg,\n }\n }\n\n return {\n kind: 'provider_error',\n providerCode: code ?? String(err.status),\n message: msg,\n retryable: isRetryableHttpStatus(err.status),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Finish-reason mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Map an OpenAI-compatible `finish_reason` string to the zidane `TurnFinishReason` union.\n */\nexport function mapOAIFinishReason(reason: string | null | undefined): TurnFinishReason | undefined {\n if (!reason)\n return undefined\n switch (reason) {\n case 'stop':\n return 'stop'\n case 'tool_calls':\n case 'function_call':\n return 'tool-calls'\n case 'length':\n return 'length'\n case 'content_filter':\n return 'content-filter'\n default:\n return 'other'\n }\n}\n\n// ---------------------------------------------------------------------------\n// openaiCompat factory\n// ---------------------------------------------------------------------------\n\n/**\n * Auth header config. `scheme` is prepended to the api key with a space, e.g.\n * `{ name: 'Authorization', scheme: 'Bearer' }` → `Authorization: Bearer <key>`.\n * Omit `scheme` for raw header values (e.g. `{ name: 'X-Api-Key' }` → `X-Api-Key: <key>`).\n *\n * Real-world examples:\n * - Default OpenAI / OpenRouter / Cerebras: `{ name: 'Authorization', scheme: 'Bearer' }`.\n * - Baseten: `{ name: 'Authorization', scheme: 'Api-Key' }`.\n * - Some gateways: `{ name: 'X-Api-Key' }`.\n */\nexport interface OpenAICompatAuthHeader {\n name: string\n scheme?: string\n}\n\nexport interface OpenAICompatParams {\n /** Bearer-style API key. */\n apiKey: string\n /** Base URL — `/chat/completions` is appended. */\n baseURL: string\n /** Default model id when `run({ model })` is omitted. */\n defaultModel?: string\n /** Provider name exposed as `Provider.name`. Defaults to `'openai-compat'`. */\n name?: string\n /** Auth header shape. Defaults to `{ name: 'Authorization', scheme: 'Bearer' }`. */\n authHeader?: OpenAICompatAuthHeader\n /** Extra headers sent with every request (e.g. referer, user-agent). */\n extraHeaders?: Record<string, string>\n /**\n * Provider-level capability flags. Routed into the message shaper and the\n * agent loop so images in tool results + user messages are handled correctly\n * for the underlying model.\n *\n * Defaults when omitted: `vision: false`, `imageInToolResult: false`,\n * `audio: false`, `video: false` — a conservative assumption matching most\n * OSS text-only OpenAI-compat endpoints. Override when routing to a known\n * capable endpoint: set `audio: true` for audio-input models (`input_audio`),\n * `video: true` for video-input models (`video_url`, e.g. Gemini via\n * OpenRouter), `documents: true` for PDF-capable models (OpenAI `file`\n * content part with base64 `file_data`), or `vision: true` for vision models.\n */\n capabilities?: ProviderCapabilities\n /**\n * Whether this endpoint honors `cache_control: { type: 'ephemeral' }` markers\n * on message content parts and tool definitions.\n *\n * - `true` — inject markers when the caller asks for caching. OpenRouter routes\n * to Anthropic/Gemini forward the markers; routes to OpenAI/DeepSeek/\n * Grok/Groq/Moonshot ignore them (those backends cache automatically).\n * - `false` — never inject markers. Safe default for endpoints that strictly\n * validate the request schema (OpenAI direct, most OSS inference\n * servers) and would reject unknown fields.\n *\n * Default: `false`. The `openrouter` wrapper sets this to `true`.\n */\n cacheBreakpoints?: boolean\n /**\n * Whether this endpoint speaks a reasoning round-trip shape selected by\n * `reasoningMode` — either OpenRouter's normalized `reasoning` request field\n * plus structured `reasoning_details[]`, or vLLM/Arcee's plain\n * `reasoning_content` assistant-message field.\n *\n * - `true` — capture compatible reasoning fields from streaming responses\n * into `provider_reasoning` blocks and echo them back on\n * subsequent assistant messages. In `'openrouter'` mode, also map\n * zidane's `behavior.thinking` / `behavior.thinkingBudget` to the\n * request's `reasoning` field.\n * - `false` — never set the field; drop any stored `provider_reasoning`\n * blocks before sending. Safe default for hosts that strict-\n * validate the request schema.\n *\n * Default: `false`. The `openrouter` wrapper sets this to `true`.\n */\n supportsReasoning?: boolean\n /**\n * Shape used to round-trip reasoning state across turns when\n * `supportsReasoning` is `true`. Ignored when `supportsReasoning` is `false`.\n *\n * - `'openrouter'` (default) — capture/echo OpenRouter's structured\n * `reasoning_details[]` envelope.\n * - `'reasoning_content'` — capture the plain `reasoning_content` string and\n * echo it back on assistant messages (DeepSeek-R1 reasoning parser / Arcee\n * Trinity-Thinking served via vLLM). See\n * https://docs.arcee.ai/capabilities/reasoning-traces.\n *\n * Default: `'openrouter'`.\n */\n reasoningMode?: 'openrouter' | 'reasoning_content'\n /**\n * Field name to use when echoing plain reasoning back upstream in\n * `'reasoning_content'` mode. The OpenAI SDK returns `reasoning_content`, but\n * some self-hosted vLLM versions only read `reasoning` on input (vllm#38488).\n * Default: `'reasoning'`.\n */\n reasoningContentField?: 'reasoning_content' | 'reasoning'\n /**\n * Normalize `content: null` → `''` on assistant tool-call turns. Some\n * reasoning models (Arcee/vLLM) degrade or emit malformed follow-ups when a\n * tool-call assistant message carries a null content. Off by default to keep\n * the wire byte-identical for existing providers (OpenRouter, Cerebras, …).\n */\n normalizeNullContent?: boolean\n /**\n * Generic pass-through for fields on the Chat Completions request body that\n * zidane does not yet type. Spread into the request before the typed core\n * (model / messages / tools / max_tokens / stream / tool_choice), so\n * explicit options always win on collision.\n *\n * Forward-compat escape hatch for endpoints that ship one-off fields ahead\n * of zidane (e.g. OpenAI `reasoning_effort`, OpenRouter `provider` routing,\n * vendor-specific `safety_level` knobs).\n */\n extraBodyParams?: Record<string, unknown>\n /**\n * PDF-parsing engine for OpenRouter's `file-parser` plugin. Only takes effect\n * on endpoints that honor the `plugins` request field (OpenRouter) AND only\n * when a request actually carries a forwarded `file` content part — otherwise\n * it's omitted, so it's a safe no-op for plain-text turns and non-OpenRouter\n * endpoints (which ignore the unknown field).\n *\n * Without an explicit engine, OpenRouter's per-model native PDF handling is\n * inconsistent (some models silently fail to receive the parsed document).\n * Pinning the engine makes it deterministic:\n * - `'native'` — use the model's own file support (free; for gpt-4o /\n * Gemini / Claude routes). Default for the `openrouter` wrapper.\n * - `'pdf-text'` — free text-only extraction (no OCR / images).\n * - `'mistral-ocr'`— OCR for scans / image PDFs (paid).\n *\n * When unset, no `plugins` field is added (current behavior preserved).\n */\n pdfEngine?: 'native' | 'pdf-text' | 'mistral-ocr'\n /**\n * Per-request reasoning body planner for hosts whose thinking knob is a\n * vendor-specific request field rather than OpenRouter's `reasoning` object\n * (e.g. Baseten's `reasoning_effort` / `chat_template_args.enable_thinking`).\n * Called on every `stream()` with the resolved model + thinking options;\n * the returned fields are merged into the request body (after\n * `extraBodyParams`, before the typed core, so model/messages/etc. still\n * win on collision). Return `undefined` to send nothing.\n */\n planReasoningRequest?: (ctx: {\n model: string\n thinking?: ThinkingLevel\n thinkingBudget?: number\n }) => Record<string, unknown> | undefined\n}\n\n/**\n * Map zidane's `ThinkingLevel` + optional explicit budget to the OpenRouter\n * `reasoning` request field. Returns `undefined` when reasoning should not be\n * sent (off, or no level + no budget).\n *\n * - `'low' | 'medium' | 'high' | 'xhigh' | 'max'` → `{ effort }`.\n * - `'minimal'` → `{ effort: 'low' }` (closest match in OpenRouter's vocabulary).\n * - `'adaptive'` → `{}` (let upstream decide; OpenRouter passes through).\n * - explicit `thinkingBudget` → `{ max_tokens }` overlaid on the level.\n */\nfunction planOpenRouterReasoning(\n thinking: ThinkingLevel | undefined,\n thinkingBudget: number | undefined,\n): Record<string, unknown> | undefined {\n if ((!thinking || thinking === 'off') && typeof thinkingBudget !== 'number')\n return undefined\n const out: Record<string, unknown> = {}\n if (thinking && thinking !== 'off' && thinking !== 'adaptive') {\n out.effort = thinking === 'minimal' ? 'low' : thinking\n }\n if (typeof thinkingBudget === 'number' && thinkingBudget > 0) {\n out.max_tokens = thinkingBudget\n }\n return out\n}\n\nfunction usageWasReported(usage: OpenAICompatUsage): boolean {\n return usage.input > 0\n || usage.output > 0\n || (usage.cacheRead ?? 0) > 0\n || (usage.cacheCreation ?? 0) > 0\n || usage.cost !== undefined\n}\n\nfunction estimateMissingUsage(body: Record<string, unknown>, result: Awaited<ReturnType<typeof consumeSSE>>): OpenAICompatUsage {\n return {\n input: estimateTokens(JSON.stringify({\n messages: body.messages,\n tools: body.tools,\n })),\n output: estimateTokens([\n result.thinking,\n result.text,\n result.toolCalls.length > 0 ? JSON.stringify(result.toolCalls) : '',\n ].filter(Boolean).join('\\n')),\n }\n}\n\n/**\n * Factory for any OpenAI-compatible HTTP endpoint.\n *\n * Speaks the standard `POST /chat/completions` + `stream: true` + SSE dialect.\n * Thin wrappers (`openrouter`, `cerebras`) call this with pinned defaults.\n *\n * @example Baseten (non-standard auth scheme)\n * ```ts\n * openaiCompat({\n * name: 'baseten',\n * apiKey: process.env.BASETEN_API_KEY!,\n * baseURL: process.env.BASETEN_PROXY_URL!,\n * authHeader: { name: 'Authorization', scheme: 'Api-Key' },\n * })\n * ```\n *\n * @example OpenAI directly — including native PDF input\n * The bundled `openai` provider routes through pi-ai (Codex Responses), whose\n * content union is text+image only, so it degrades documents to a marker. To\n * send PDFs to OpenAI, point this factory at OpenAI's Chat Completions endpoint\n * and enable `documents` — base64 PDFs are forwarded as the `{type:'file'}`\n * content part. NOTE: OpenAI's Chat Completions `file` part accepts\n * `application/pdf` ONLY; spreadsheets/`.docx` are Responses-API-only and will\n * 400 here.\n * ```ts\n * openaiCompat({\n * name: 'openai',\n * apiKey: process.env.OPENAI_API_KEY!,\n * baseURL: 'https://api.openai.com/v1',\n * capabilities: { vision: true, documents: true },\n * })\n * ```\n */\nexport function openaiCompat(params: OpenAICompatParams): Provider {\n const name = params.name ?? 'openai-compat'\n const defaultModel = params.defaultModel ?? 'gpt-4o-mini'\n const authHeaderName = params.authHeader?.name ?? 'Authorization'\n const authHeaderValue = params.authHeader?.scheme\n ? `${params.authHeader.scheme} ${params.apiKey}`\n : (params.authHeader ? params.apiKey : `Bearer ${params.apiKey}`)\n const extraHeaders = params.extraHeaders ?? {}\n const endpoint = `${params.baseURL.replace(TRAILING_SLASH_RE, '')}/chat/completions`\n const capabilities: ProviderCapabilities = {\n vision: params.capabilities?.vision ?? false,\n imageInToolResult: params.capabilities?.imageInToolResult ?? false,\n audio: params.capabilities?.audio ?? false,\n video: params.capabilities?.video ?? false,\n documents: params.capabilities?.documents ?? false,\n }\n\n const cacheBreakpointsEnabled = params.cacheBreakpoints === true\n const reasoningEnabled = params.supportsReasoning === true\n const reasoningMode = params.reasoningMode ?? 'openrouter'\n const reasoningContentField = params.reasoningContentField ?? 'reasoning'\n const normalizeNullContent = params.normalizeNullContent === true\n\n return {\n name,\n meta: { defaultModel, capabilities },\n formatTools,\n userMessage,\n assistantMessage,\n toolResultsMessage,\n promptMessage: openAICompatPromptMessage,\n classifyError: classifyOpenAICompatError,\n\n async stream(options: StreamOptions, callbacks: StreamCallbacks): Promise<TurnResult> {\n const modelId = options.model || defaultModel\n const messages = toOAIMessages(options.system, options.messages, {\n imageInToolResult: capabilities.imageInToolResult === true,\n audio: capabilities.audio === true,\n video: capabilities.video === true,\n documents: capabilities.documents === true,\n supportsReasoning: reasoningEnabled,\n reasoningMode,\n reasoningContentField,\n normalizeNullContent,\n model: modelId,\n })\n // Prompt caching — insert `cache_control: { type: 'ephemeral' }` breakpoints on\n // the system message's last text part, the last tool definition, and the last\n // message's final content part. Only emitted when the factory was constructed\n // with `cacheBreakpoints: true` (set by the `openrouter` wrapper); endpoints\n // that strict-validate the request schema otherwise reject unknown fields.\n const shouldCache = cacheBreakpointsEnabled && options.cache !== false\n if (shouldCache) {\n // Forward the un-rendered `options.system` so the boundary split\n // lands on the wire — `toOAIMessages` already collapsed the marker\n // into the rendered system content, but the cache helper needs the\n // original byte stream to split correctly.\n applyOAICacheBreakpoints(messages, options.system)\n }\n\n // Output cap. Unset → omit `max_tokens` below so the model runs to a\n // natural stop (the OpenAI-style default), instead of an arbitrary cap\n // truncating mid-output. When an explicit cap IS set and a thinking\n // budget is configured, pad the budget on top so reasoning + reply fit.\n const maxTokens = options.maxTokens === undefined\n ? undefined\n : options.thinkingBudget\n ? options.thinkingBudget + options.maxTokens\n : options.maxTokens\n\n // Vendor-specific thinking fields (Baseten `reasoning_effort` /\n // `chat_template_args`, …) planned per request. Sits between the static\n // extraBodyParams and the typed core in spread order.\n const reasoningBody = params.planReasoningRequest?.({\n model: modelId,\n thinking: options.thinking,\n thinkingBudget: options.thinkingBudget,\n })\n\n const body: Record<string, unknown> = {\n // Spread first so the typed core below wins on collision — explicit\n // always overrides generic.\n ...(params.extraBodyParams ?? {}),\n ...(reasoningBody ?? {}),\n model: modelId,\n messages,\n ...(maxTokens !== undefined ? { max_tokens: maxTokens } : {}),\n stream: true,\n }\n\n // OpenRouter `file-parser` plugin — only when an engine is configured AND\n // the request actually carries a forwarded `file` part. Selecting the\n // engine explicitly makes PDF parsing deterministic (OpenRouter's\n // per-model native fallback is otherwise inconsistent). Omitted entirely\n // for text-only turns and engines-unset configs, so non-OpenRouter\n // endpoints never see the field.\n //\n // Merge (don't clobber) any host-provided `plugins` from extraBodyParams,\n // and defer to a host-supplied `file-parser` if one is already present —\n // explicit caller config wins, matching the rest of the body contract.\n if (params.pdfEngine && messagesContainFilePart(messages)) {\n const existing = Array.isArray(body.plugins) ? (body.plugins as unknown[]) : []\n const hasFileParser = existing.some(p =>\n typeof p === 'object' && p !== null && (p as { id?: unknown }).id === 'file-parser',\n )\n if (!hasFileParser)\n body.plugins = [...existing, { id: 'file-parser', pdf: { engine: params.pdfEngine } }]\n }\n\n // OpenRouter normalized reasoning request field. Only set in\n // `'openrouter'` mode — vLLM/Arcee hosts don't speak the `reasoning`\n // object on requests (thinking there is controlled by serving flags) and\n // would 400 on the unknown field.\n if (reasoningEnabled && reasoningMode === 'openrouter') {\n const reasoning = planOpenRouterReasoning(options.thinking, options.thinkingBudget)\n if (reasoning)\n body.reasoning = reasoning\n }\n\n if (options.tools && (options.tools as unknown[]).length > 0) {\n body.tools = shouldCache\n ? applyOAIToolCacheBreakpoint(options.tools as OAITool[])\n : options.tools\n }\n\n if (options.toolChoice) {\n if (options.toolChoice.type === 'tool' && options.toolChoice.name)\n body.tool_choice = { type: 'function', function: { name: options.toolChoice.name } }\n else if (options.toolChoice.type === 'required')\n body.tool_choice = 'required'\n else\n body.tool_choice = 'auto'\n }\n\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: {\n [authHeaderName]: authHeaderValue,\n 'Content-Type': 'application/json',\n ...extraHeaders,\n },\n body: JSON.stringify(body),\n signal: options.signal,\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new OpenAICompatHttpError(response.status, errorText)\n }\n\n const result = await consumeSSE(response, callbacks, options.signal)\n const finishReason = mapOAIFinishReason(result.finishReason)\n const usage = usageWasReported(result.usage)\n ? result.usage\n : estimateMissingUsage(body, result)\n\n // Build the provider-bound reasoning block to persist for the next turn.\n // `'openrouter'` → structured details; `'reasoning_content'` → the plain\n // reasoning string captured into `result.thinking` during the stream.\n let reasoningArg:\n | { details: unknown[], producer: 'openrouter', model?: string }\n | { reasoningText: string, producer: 'vllm', model?: string }\n | undefined\n // In `reasoning_content` mode the plain reasoning is round-tripped via the\n // `provider_reasoning` (vllm) block, so suppress the redundant `thinking`\n // mirror to avoid persisting the same text twice. (OpenRouter keeps the\n // mirror — its structured `details` aren't human-readable.)\n let thinkingForBlock: string | undefined = result.thinking\n if (reasoningEnabled) {\n if (reasoningMode === 'reasoning_content') {\n // Plain reasoning normally lands in `result.thinking`. Guard against a\n // host that emits the structured `reasoning_details` envelope instead\n // (which suppresses the legacy text capture in `consumeSSE`) by\n // recovering any visible text from those items.\n const reasoningText = result.thinking.length > 0\n ? result.thinking\n : extractReasoningDetailsText(result.reasoningDetails)\n if (reasoningText.length > 0) {\n reasoningArg = { reasoningText, producer: 'vllm', model: modelId }\n thinkingForBlock = undefined\n }\n }\n else if (result.reasoningDetails.length > 0) {\n reasoningArg = { details: result.reasoningDetails, producer: 'openrouter', model: modelId }\n }\n }\n\n const filledUsage = await fillEstimatedCost({\n input: usage.input,\n output: usage.output,\n ...(usage.cacheRead !== undefined ? { cacheRead: usage.cacheRead } : {}),\n ...(usage.cacheCreation !== undefined ? { cacheCreation: usage.cacheCreation } : {}),\n ...(usage.cost !== undefined ? { cost: usage.cost } : {}),\n ...(finishReason ? { finishReason } : {}),\n modelId,\n }, name)\n return {\n assistantMessage: buildAssistantContent(\n result.text,\n result.toolCalls,\n thinkingForBlock,\n reasoningArg,\n ),\n text: result.text,\n toolCalls: result.toolCalls,\n done: result.toolCalls.length === 0,\n usage: filledUsage,\n }\n },\n }\n}\n"],"mappings":"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"}
|