import { c as classifyErrorPrelude, d as matchesContextExceeded, f as matchesToolPairingError, l as errorMessage, u as isRetryableHttpStatus } from "./errors-DkR6GPJw.js";
import { A as renderSystemForWire, C as sanitizeToolSpecs, M as splitSystemPrompt, b as toolResultsMessage, f as toAnthropic, g as assistantMessage, l as fromAnthropic, n as SYNTHETIC_TOOL_RESULT_PLACEHOLDER, w as fillEstimatedCost, x as userMessage, y as openaiCompat } from "./messages-C_1AmSpk.js";
import { n as documentBlockMarker } from "./types-BiobHM1D.js";
import { dirname, resolve } from "node:path";
import { getModel } from "@earendil-works/pi-ai";
import { Buffer } from "node:buffer";
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { getOAuthApiKey } from "@earendil-works/pi-ai/oauth";
import { streamOpenAICodexResponses } from "@earendil-works/pi-ai/openai-codex-responses";
//#region src/chat/anthropic-models.ts
/**
* Anthropic "fast mode" — `speed: "fast"` on the Messages API. ~2.5× output
* tokens/sec at a per-model price multiplier. Supported on Opus 4.6 / 4.7 / 4.8
* only; the API errors if sent on any other model, so it's attached just to
* those entries (see {@link FAST_MODE_OPTIONS}).
*
* The multiplier scales ALL rate components (input, output, and both cache
* rates) uniformly: per Anthropic, "prompt caching multipliers apply on top of
* fast mode pricing" across the full context window. Opus 4.8 is 2× ($5/$25 →
* $10/$50); Opus 4.6 / 4.7 are a steeper 6× ($5/$25 → $30/$150).
* See https://platform.claude.com/docs/en/build-with-claude/fast-mode.
*/
const FAST_MODE_OPTION = {
	id: "fast",
	label: "Fast mode",
	description: "~2.5× faster output at premium pricing",
	costMultiplier: {
		input: 2,
		output: 2,
		cacheRead: 2,
		cacheWrite: 2
	}
};
const FAST_MODE_OPTION_LEGACY = {
	...FAST_MODE_OPTION,
	description: "~2.5× faster output at 6× pricing",
	costMultiplier: {
		input: 6,
		output: 6,
		cacheRead: 6,
		cacheWrite: 6
	}
};
/** Model ids that accept `speed: "fast"`, mapped to their fast-mode option. */
const FAST_MODE_OPTIONS = {
	"claude-opus-4-8": FAST_MODE_OPTION,
	"claude-opus-4-7": FAST_MODE_OPTION_LEGACY,
	"claude-opus-4-6": FAST_MODE_OPTION_LEGACY
};
/**
* The price multiplier to apply to a model's base rates when fast mode is
* active, or `undefined` when the model has no fast-mode pricing (i.e. doesn't
* support it). Mirrors {@link FAST_MODE_OPTIONS} — kept as a separate accessor
* so the cost layer can stay decoupled from the picker-facing option objects.
*/
function fastModeCostMultiplier(modelId) {
	return FAST_MODE_OPTIONS[modelId]?.costMultiplier;
}
/**
* Claude Fable 5 — GA 2026-06-09. Anthropic's most capable widely released
* model: $10/$50 per MTok, 1M context, 128k output, adaptive thinking
* (always on), vision. No fast-mode support (Opus 4.6/4.7/4.8 only).
*
* Claude Opus 4.8 — shipped 2026-05-28. Same price/context/output as Opus 4.7
* ($5/$25 per MTok, 1M context, 128k output), adaptive thinking, vision.
* See https://www.anthropic.com/news/claude-opus-4-8.
*
* Claude Mythos 5 is deliberately absent: it's invitation-only (Project
* Glasswing), so it would be a dead picker entry for virtually all users.
*
* See https://platform.claude.com/docs/en/about-claude/models/overview.
*/
const ANTHROPIC_EXTRA_MODELS = [{
	id: "claude-fable-5",
	name: "Claude Fable 5",
	reasoning: true,
	input: ["text", "image"],
	cost: {
		input: 10,
		output: 50,
		cacheRead: 1,
		cacheWrite: 12.5
	},
	contextWindow: 1e6,
	maxTokens: 128e3,
	provider: "anthropic"
}, {
	id: "claude-opus-4-8",
	name: "Claude Opus 4.8",
	reasoning: true,
	input: ["text", "image"],
	cost: {
		input: 5,
		output: 25,
		cacheRead: .5,
		cacheWrite: 6.25
	},
	contextWindow: 1e6,
	maxTokens: 128e3,
	provider: "anthropic",
	options: [FAST_MODE_OPTION]
}];
//#endregion
//#region src/atomic-write.ts
/**
* Synchronous atomic file write helper.
*
* Used by the chat-layer state / safelist / credentials / keybindings writers
* and the OAuth credentials store. The pattern is always the same: write to a
* sibling temp file, then `renameSync` over the target. `rename(2)` is atomic
* on the same filesystem, so concurrent readers either see the previous file
* or the new one — never a half-written intermediate. The temp suffix folds
* `process.pid` + a per-process monotonic counter so two writers — same
* process or same-millisecond siblings — can't pick the same tmp path.
*
* `ensureDir` runs `mkdirSync(dirname(path), { recursive: true })` before
* the write so callers don't have to repeat the boilerplate. `mode` is
* forwarded to `writeFileSync` (e.g. `0o600` for credentials files).
*/
let tmpCounter = 0;
function writeFileAtomic(path, contents, options = {}) {
	if (options.ensureDir) mkdirSync(dirname(path), { recursive: true });
	tmpCounter = (tmpCounter + 1) % Number.MAX_SAFE_INTEGER;
	const tmp = `${path}.${process.pid}.${tmpCounter.toString(36)}.tmp`;
	try {
		writeFileSync(tmp, contents, options.mode !== void 0 ? { mode: options.mode } : void 0);
		renameSync(tmp, path);
	} catch (err) {
		try {
			unlinkSync(tmp);
		} catch {}
		throw err;
	}
}
//#endregion
//#region src/providers/oauth.ts
/**
* Resolve the creds-file path at call time rather than module-load time, so
* env changes between import and first OAuth use (test suites, multi-project
* CLIs) are honored.
*
* Order:
*   1. `ZIDANE_CREDENTIALS_PATH` — explicit override. The TUI sets this at
*      launch to point at `<storageDir>/credentials.json` so the harness
*      providers find the same file the TUI manages.
*   2. `~/.credentials.json` — home-anchored default.
*
* The historic `cwd/.credentials.json` fallback was deliberately dropped:
* launching zidane inside an untrusted checkout would let that repo plant a
* `.credentials.json` and become the credential source (and receive rotated
* refresh tokens on the next refresh persist). Same trust model as the TUI's
* legacy-file migration in `chat/credentials.ts`, which only honors the
* home-anchored location. The only "trusted" cwd under that model is `$HOME`
* itself — where the cwd-relative path resolves to `~/.credentials.json`
* anyway, so anchoring at home preserves the legitimate legacy case.
*/
function credentialsFilePath() {
	return process.env.ZIDANE_CREDENTIALS_PATH ?? resolve(homedir(), ".credentials.json");
}
/**
* POSIX mode for credentials on disk. Read+write for the owner only — refresh
* tokens and API keys leak if the file is world-readable on a shared box.
* Ignored on Windows.
*/
const CREDENTIALS_FILE_MODE = 384;
/**
* In-process mutex table keyed by provider id. Concurrent `resolveOAuthApiKey`
* calls for the same provider (typical when a host fans out multiple streams
* at once) coalesce onto a single refresh. Without this, N concurrent calls
* each hit `getOAuthApiKey`, producing N writes to `.credentials.json` with
* last-write-wins semantics — and, in worst-case interleaving, a corrupted
* file.
*/
const refreshLocks = /* @__PURE__ */ new Map();
/**
* Pull a complete `{ access, refresh, expires }` triple out of provider params
* when (and only when) all three OAuth fields are present and well-typed.
* Returns `undefined` for the static-api-key path. Shared by the OAuth-capable
* providers so the runtime-credential guard can't drift between them.
*/
/**
* Whether a credential string is an OAuth *access token* rather than a real,
* static API key. Anthropic OAuth tokens are prefixed `sk-ant-oat`; OpenAI
* Codex OAuth access tokens are JWTs (`eyJ...`). Used to decide whether an
* env-provided value can be refreshed (OAuth) or must be sent verbatim (API key).
*/
function isOAuthAccessToken(value) {
	return value.startsWith("sk-ant-oat") || isJwtLikeAccessToken(value);
}
function isJwtLikeAccessToken(value) {
	const parts = value.split(".");
	if (parts.length !== 3 || !parts[0]?.startsWith("eyJ")) return false;
	try {
		const header = JSON.parse(Buffer.from(parts[0], "base64url").toString("utf8"));
		return Boolean(header && typeof header === "object" && "alg" in header);
	} catch {
		return false;
	}
}
function extractRuntimeCredentials(params) {
	if (typeof params?.access === "string" && typeof params.refresh === "string" && typeof params.expires === "number") return {
		access: params.access,
		refresh: params.refresh,
		expires: params.expires
	};
}
/**
* Read the shared credentials file.
*
* Returns `{}` when the file is missing OR when the file exists but is corrupted
* / not valid JSON. Corrupt-file tolerance is important: the refresh-then-persist
* path below writes atomically, but older builds wrote in place and could have
* truncated the file on a crash. Treating corruption as "no stored creds" lets
* users re-auth without manually deleting the file.
*
* Supports two on-disk shapes:
*   1. **Legacy** (untagged): `{ "anthropic": { access, refresh, expires, ... } }`
*      — written by older `bun run auth`.
*   2. **New** (kind-tagged): `{ "anthropic": { kind: 'oauth', access, refresh, expires, ... } }`
*      — written by the TUI. Entries with `kind: 'apikey'` are skipped — those
*      reach providers via env vars set at TUI launch (see `applyApiKeyEnv`).
*/
function readOAuthCredentials() {
	const path = credentialsFilePath();
	if (!existsSync(path)) return {};
	try {
		const raw = readFileSync(path, "utf-8");
		const parsed = JSON.parse(raw);
		if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
		const result = {};
		for (const [key, value] of Object.entries(parsed)) {
			if (!value || typeof value !== "object" || Array.isArray(value)) continue;
			const v = value;
			if (v.kind === "apikey") continue;
			if (typeof v.access === "string" && typeof v.refresh === "string" && typeof v.expires === "number") result[key] = {
				...v,
				access: v.access,
				refresh: v.refresh,
				expires: v.expires
			};
		}
		return result;
	} catch {
		return {};
	}
}
/**
* Cross-process lockfile parameters for the credentials read-merge-write.
* A holder normally completes in well under a millisecond (read + JSON merge
* + atomic write of a tiny file), so the stale threshold is generous and the
* total wait short — on timeout we proceed without the lock rather than fail
* the refresh (degrades to the historic last-write-wins behavior).
*/
const CREDENTIALS_LOCK_STALE_MS = 5e3;
const CREDENTIALS_LOCK_RETRY_MS = 20;
const CREDENTIALS_LOCK_MAX_WAIT_MS = 2e3;
/** Synchronous sleep without spinning — works on Bun and Node main threads. */
function sleepSync(ms) {
	Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
}
/**
* Best-effort exclusive lock via `O_EXCL` create of `<file>.lock`. Returns
* `true` when the lock was acquired (caller must release), `false` on
* timeout (caller proceeds unlocked — see threshold rationale above). Locks
* older than {@link CREDENTIALS_LOCK_STALE_MS} are treated as leftovers from
* a crashed process and broken.
*/
function acquireCredentialsLock(lockPath) {
	const deadline = Date.now() + CREDENTIALS_LOCK_MAX_WAIT_MS;
	while (true) try {
		writeFileSync(lockPath, String(process.pid), {
			flag: "wx",
			mode: CREDENTIALS_FILE_MODE
		});
		return true;
	} catch (err) {
		if (err.code !== "EEXIST") return false;
		try {
			if (Date.now() - statSync(lockPath).mtimeMs > CREDENTIALS_LOCK_STALE_MS) {
				unlinkSync(lockPath);
				continue;
			}
		} catch {}
		if (Date.now() >= deadline) return false;
		sleepSync(CREDENTIALS_LOCK_RETRY_MS);
	}
}
function releaseCredentialsLock(lockPath) {
	try {
		unlinkSync(lockPath);
	} catch {}
}
/**
* Atomically write `.credentials.json` with mode 0o600.
*
* Writes to a sibling temp file first, then renames. `rename(2)` is atomic on
* the same filesystem, so readers either see the old file or the new one —
* never a half-written one. This matters when a host has multiple processes
* pointed at the same creds file.
*
* Merges the incoming OAuth entries on top of the raw file contents so that
* entries this layer doesn't manage (e.g. `kind: 'apikey'` from the TUI
* wizard) survive an OAuth refresh cycle. Without this merge,
* `readOAuthCredentials` filters those entries out, and a naive overwrite
* would delete them.
*
* The read-merge-write runs under a best-effort cross-process lockfile
* (`<file>.lock`) so two processes refreshing different providers at the
* same moment can't drop each other's rotated refresh tokens. On lock
* timeout the write proceeds unlocked (last-write-wins, the pre-lock
* behavior) rather than failing the refresh.
*/
function writeOAuthCredentials(credentials) {
	const path = credentialsFilePath();
	const lockPath = `${path}.lock`;
	const locked = acquireCredentialsLock(lockPath);
	try {
		let existing = {};
		try {
			if (existsSync(path)) {
				const raw = readFileSync(path, "utf-8");
				const parsed = JSON.parse(raw);
				if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) existing = parsed;
			}
		} catch {}
		const merged = {
			...existing,
			...credentials
		};
		for (const [key, value] of Object.entries(merged)) if (value === void 0) delete merged[key];
		writeFileAtomic(path, JSON.stringify(merged, null, 2), { mode: CREDENTIALS_FILE_MODE });
	} finally {
		if (locked) releaseCredentialsLock(lockPath);
	}
}
function credentialsFromParams(params, extraKeys = []) {
	if (typeof params?.access !== "string" || typeof params.refresh !== "string" || typeof params.expires !== "number") return void 0;
	const extras = Object.fromEntries(extraKeys.map((key) => [key, params[key]]).filter(([, value]) => value !== void 0));
	return {
		access: params.access,
		refresh: params.refresh,
		expires: params.expires,
		...extras
	};
}
async function resolveOAuthApiKey(options, callbacks) {
	if (typeof options.params?.apiKey === "string") return options.params.apiKey;
	const paramsCredentials = credentialsFromParams(options.params, options.extraCredentialKeys);
	if (paramsCredentials) return await withRefreshLock(`params:${options.providerId}`, () => resolveCredentialSource("params", paramsCredentials));
	if (typeof options.params?.access === "string") return options.params.access;
	const readCredentials = options.readCredentials ?? readOAuthCredentials;
	const writeCredentials = options.writeCredentials ?? writeOAuthCredentials;
	const envValue = options.envKey ? process.env[options.envKey] : void 0;
	if (envValue) {
		if (!isOAuthAccessToken(envValue)) return envValue;
		if (!readCredentials()[options.providerId]) return envValue;
		return await withRefreshLock(`file:${options.providerId}`, async () => {
			const allCredentials = readCredentials();
			const stored = allCredentials[options.providerId];
			if (!stored) return envValue;
			return await resolveCredentialSource("file", stored, allCredentials, writeCredentials);
		});
	}
	return await withRefreshLock(`file:${options.providerId}`, async () => {
		const allCredentials = readCredentials();
		const storedCredentials = allCredentials[options.providerId];
		if (!storedCredentials) throw new Error(options.missingError);
		return await resolveCredentialSource("file", storedCredentials, allCredentials, writeCredentials);
	});
	async function resolveCredentialSource(source, current, allCredentials, persistCredentials) {
		let result;
		try {
			result = await (options.getOAuthApiKey ?? getOAuthApiKey)(options.providerId, { [options.providerId]: current });
		} catch (err) {
			throw new Error(options.refreshError(errorMessage(err)));
		}
		if (!result) throw new Error(options.refreshError(options.missingError));
		if (result.newCredentials !== current) {
			if (source === "file" && allCredentials && persistCredentials) {
				allCredentials[options.providerId] = result.newCredentials;
				persistCredentials(allCredentials);
			}
			await callbacks?.onOAuthRefresh?.({
				provider: options.provider,
				providerId: options.providerId,
				source,
				previousCredentials: { ...current },
				credentials: { ...result.newCredentials }
			});
		}
		return result.apiKey;
	}
}
/**
* Coalesce concurrent refresh calls onto a single in-flight promise per key.
* The key combines the source (`params` vs `file`) and provider id, so a
* per-agent param-only refresh does not starve a separate file-backed one.
*/
async function withRefreshLock(key, fn) {
	const existing = refreshLocks.get(key);
	if (existing) return existing;
	const task = (async () => {
		try {
			return await fn();
		} finally {
			refreshLocks.delete(key);
		}
	})();
	refreshLocks.set(key, task);
	return task;
}
//#endregion
//#region src/providers/anthropic.ts
let _sdkCtor = null;
async function loadAnthropicSdk() {
	if (_sdkCtor) return _sdkCtor;
	try {
		_sdkCtor = (await import("@anthropic-ai/sdk")).default;
		return _sdkCtor;
	} catch (err) {
		throw new Error("The `anthropic` provider requires the `@anthropic-ai/sdk` package, which is an optional peer dependency. Install it with your package manager (e.g. `bun add @anthropic-ai/sdk`).", err instanceof Error ? { cause: err } : void 0);
	}
}
/** Beta flags sent unconditionally on the OAuth path (Claude Code parity). */
const OAUTH_DEFAULT_BETAS = ["claude-code-20250219", "oauth-2025-04-20"];
/**
* Beta flag that enables the `speed: "fast"` request field (fast mode — up to
* 2.5× output tokens/sec on Opus 4.6/4.7/4.8 at premium pricing). Sent as an
* `anthropic-beta` header only on requests that opt into fast mode; absent
* otherwise so standard requests keep a byte-stable header for prompt caching.
*
* See: https://platform.claude.com/docs/en/build-with-claude/fast-mode
*/
const FAST_MODE_BETA = "fast-mode-2026-02-01";
const API_KEY_RESERVED_EXTRA_HEADER_NAMES = new Set(["anthropic-beta"]);
const OAUTH_RESERVED_EXTRA_HEADER_NAMES = new Set([
	"anthropic-beta",
	"anthropic-dangerous-direct-browser-access",
	"user-agent",
	"x-app"
]);
function filterReservedExtraHeaders(extraHeaders, reservedNames) {
	const out = {};
	for (const [name, value] of Object.entries(extraHeaders ?? {})) if (!reservedNames.has(name.toLowerCase())) out[name] = value;
	return out;
}
/**
* Build the `anthropic-beta` header value — OAuth defaults plus caller-supplied
* extras, de-duped while preserving order. Returns `undefined` when no betas
* apply (non-OAuth, no extras).
*/
function resolveAnthropicBetas(isOAuth, extraBetas) {
	const seen = /* @__PURE__ */ new Set();
	const out = [];
	if (isOAuth) {
		for (const b of OAUTH_DEFAULT_BETAS) if (!seen.has(b)) {
			seen.add(b);
			out.push(b);
		}
	}
	if (extraBetas) {
		for (const b of extraBetas) if (typeof b === "string" && b.length > 0 && !seen.has(b)) {
			seen.add(b);
			out.push(b);
		}
	}
	return out.length > 0 ? out.join(",") : void 0;
}
function getConfiguredApiKey(anthropicParams) {
	if (anthropicParams?.apiKey) return anthropicParams.apiKey;
	if (anthropicParams?.access) return anthropicParams.access;
	if (process.env.ANTHROPIC_API_KEY) return process.env.ANTHROPIC_API_KEY;
	const access = readOAuthCredentials().anthropic?.access;
	if (typeof access === "string" && access.length > 0) return access;
	throw new Error("No API key found. Run `bun run auth` first.");
}
function createClient(SDK, apiKey, isOAuth, baseURL, extraBetas, extraHeaders) {
	const base = baseURL ? { baseURL } : {};
	const betaHeader = resolveAnthropicBetas(isOAuth, extraBetas);
	if (isOAuth) {
		const defaultHeaders = {
			...filterReservedExtraHeaders(extraHeaders, OAUTH_RESERVED_EXTRA_HEADER_NAMES),
			"anthropic-dangerous-direct-browser-access": "true",
			"user-agent": "zidane/2.0.0",
			"x-app": "cli"
		};
		if (betaHeader) defaultHeaders["anthropic-beta"] = betaHeader;
		return new SDK({
			apiKey: null,
			authToken: apiKey,
			dangerouslyAllowBrowser: true,
			defaultHeaders,
			...base
		});
	}
	const defaultHeaders = filterReservedExtraHeaders(extraHeaders, API_KEY_RESERVED_EXTRA_HEADER_NAMES);
	if (betaHeader) defaultHeaders["anthropic-beta"] = betaHeader;
	return new SDK({
		apiKey,
		...Object.keys(defaultHeaders).length > 0 ? { defaultHeaders } : {},
		...base
	});
}
/**
* Map `ThinkingLevel` budgeted tiers to Anthropic's `output_config.effort`
* enum. Anthropic exposes `low | medium | high | xhigh | max`; we surface the
* three middle levels plus a `minimal` alias that collapses to `low` (the
* closest equivalent — Anthropic does not have a sub-`low` tier).
*/
const EFFORT_FOR_LEVEL = {
	minimal: "low",
	low: "low",
	medium: "medium",
	high: "high"
};
/**
* Decide how to translate a `ThinkingLevel` into Anthropic's request shape.
* Pure / synchronous — exported so tests can assert routing without standing
* up the SDK.
*
* Routing rules:
* - `'off'` → `null` (no thinking field, no effort hint).
* - `'adaptive'` → adaptive thinking with no effort hint (model decides
*   everything). When `customBudget` is set, it is carried as `maxTokensCap`
*   so the request builder caps `max_tokens` accordingly — adaptive has no
*   native budget knob, but capping the response envelope soft-bounds the
*   thinking that lives inside it.
* - `'minimal' | 'low' | 'medium' | 'high'` → adaptive thinking with an
*   `effort` hint, unless `customBudget` is provided.
* - Any level + `customBudget` → explicit-budget `enabled` path. The caller
*   has opted into precise budget control and accepts the Anthropic
*   deprecation warning that comes with it on opus 4.6+. `'adaptive'` is the
*   sole exception: it never falls back to enabled, since adaptive is the
*   current Anthropic API surface for self-budgeted thinking.
*/
function planAnthropicThinking(level, customBudget) {
	if (level === "off") return null;
	if (level === "adaptive") {
		if (typeof customBudget === "number" && customBudget > 0) return {
			kind: "adaptive",
			maxTokensCap: customBudget
		};
		return { kind: "adaptive" };
	}
	if (customBudget !== void 0) return {
		kind: "enabled",
		budgetTokens: customBudget,
		maxTokensBump: customBudget
	};
	return {
		kind: "adaptive",
		effort: EFFORT_FOR_LEVEL[level]
	};
}
/**
* Map Anthropic's native `stop_reason` to the zidane `TurnFinishReason` union.
*
* `pause_turn` and `model_context_window_exceeded` are 4.6+ stop reasons that
* pre-Z21 collapsed to `'other'` and silently terminated the run. They now
* map to `'pause'` and `'length'` respectively, and the surrounding caller
* adjusts the `done` flag so the loop can recover.
*/
function mapStopReason(stopReason) {
	if (!stopReason) return void 0;
	switch (stopReason) {
		case "end_turn":
		case "stop_sequence": return "stop";
		case "tool_use": return "tool-calls";
		case "max_tokens":
		case "model_context_window_exceeded": return "length";
		case "refusal": return "content-filter";
		case "pause_turn": return "pause";
		default: return "other";
	}
}
const EPHEMERAL = { type: "ephemeral" };
/**
* Mutate an Anthropic request in place to add cache breakpoints on the three
* stable prefixes:
*  1. System prompt — `cache_control` on the static prefix only (see below).
*  2. Tool definitions — last tool.
*  3. Conversation — last content block of the last message.
*
* Each breakpoint tells Anthropic to cache the prefix ending at that block;
* subsequent turns reuse the cached prefix and pay only for the delta. Safe
* no-op when any prefix is empty (no tools, empty system, etc.).
*
* System-prompt boundary handling (`SYSTEM_PROMPT_BOUNDARY`):
*
* - When `originalSystem` is provided AND carries the marker, the system
*   block is rewritten as a 2-block array: static (cached) then dynamic
*   (uncached). The caller is expected to have already stripped the marker
*   from `params.system` via {@link renderSystemForWire} — this helper
*   only re-splits to apply `cache_control` cleanly.
* - When `originalSystem` is omitted or marker-free, the existing single-
*   block treatment applies: `cache_control` on the whole system string.
* - Array system prompts: existing semantics — `cache_control` lands on the
*   last block. Callers building their own multi-block layout own the
*   breakpoint placement.
*/
function applyAnthropicCacheBreakpoints(params, originalSystem) {
	if (typeof params.system === "string") {
		if (params.system.length > 0) {
			const parts = splitSystemPrompt(originalSystem && originalSystem.length > 0 ? originalSystem : params.system);
			if (parts.dynamic.length > 0) {
				const blocks = [];
				if (parts.static.length > 0) blocks.push({
					type: "text",
					text: parts.static,
					cache_control: EPHEMERAL
				});
				blocks.push({
					type: "text",
					text: parts.dynamic
				});
				params.system = blocks;
			} else params.system = [{
				type: "text",
				text: parts.static,
				cache_control: EPHEMERAL
			}];
		}
	} else if (Array.isArray(params.system) && params.system.length > 0) {
		const lastIdx = params.system.length - 1;
		params.system = params.system.map((block, i) => i === lastIdx ? {
			...block,
			cache_control: EPHEMERAL
		} : block);
	}
	if (params.tools && params.tools.length > 0) {
		const lastIdx = params.tools.length - 1;
		params.tools = params.tools.map((tool, i) => i === lastIdx ? {
			...tool,
			cache_control: EPHEMERAL
		} : tool);
	}
	if (params.messages.length === 0) return;
	const lastMsgIdx = params.messages.length - 1;
	const lastMsg = params.messages[lastMsgIdx];
	if (typeof lastMsg.content === "string") {
		if (lastMsg.content.length === 0) return;
		params.messages[lastMsgIdx] = {
			...lastMsg,
			content: [{
				type: "text",
				text: lastMsg.content,
				cache_control: EPHEMERAL
			}]
		};
		return;
	}
	if (!Array.isArray(lastMsg.content) || lastMsg.content.length === 0) return;
	const blocks = lastMsg.content;
	let targetIdx = blocks.length - 1;
	while (targetIdx >= 0 && isThinkingBlock(blocks[targetIdx])) targetIdx -= 1;
	if (targetIdx < 0) return;
	const nextBlocks = blocks.slice();
	nextBlocks[targetIdx] = {
		...nextBlocks[targetIdx],
		cache_control: EPHEMERAL
	};
	params.messages[lastMsgIdx] = {
		...lastMsg,
		content: nextBlocks
	};
}
function isThinkingBlock(block) {
	return block.type === "thinking" || block.type === "redacted_thinking";
}
/**
* Duck-type check for an Anthropic SDK `APIError` — avoids a runtime dependency
* on `@anthropic-ai/sdk` so `classifyError` stays usable even when the optional
* peer dep isn't loaded (e.g. host code calling it on an unrelated provider's
* error).
*
* Anthropic's APIError shape: `.status: number` + `.error` (parsed body, object
* or null). Plain `Error`s don't have `.status` + `.error`.
*/
function looksLikeAnthropicApiError(err) {
	if (!err || typeof err !== "object") return false;
	const e = err;
	return typeof e.status === "number" && "error" in e;
}
/**
* Anthropic SSE `error` event types that warrant a retry. `overloaded_error`
* is the headline case (capacity event mid-stream); `api_error` and
* `timeout_error` are both server-side transient failures. `invalid_request_error`,
* `authentication_error`, `permission_error`, `not_found_error`, `billing_error`
* are terminal — retrying them would just hammer a bad request.
*
* `rate_limit_error` here would be redundant: the SDK's pre-stream retry
* handles 429, and a mid-stream `rate_limit_error` is exceedingly rare. We
* include it anyway for symmetry with the HTTP-level retry policy.
*/
const SSE_RETRYABLE_ERROR_TYPES = new Set([
	"overloaded_error",
	"api_error",
	"timeout_error",
	"rate_limit_error"
]);
/**
* Try to parse an Anthropic SSE `error` event payload out of a thrown error's
* message. The SDK's `MessageStream` surfaces mid-stream error events by
* throwing an `AnthropicError` whose `.message` contains the raw JSON shape:
*
*     {"type":"error","error":{"type":"overloaded_error","message":"Overloaded"},"request_id":"req_..."}
*
* Returns the inner `{ type, message }` or `null` when the message isn't
* the canonical SSE error shape. Tolerant: silently returns `null` for
* non-string input, non-JSON content, or any shape mismatch.
*/
function matchSseErrorEvent(message) {
	if (typeof message !== "string" || message.length === 0) return null;
	if (!message.includes("\"type\":\"error\"") && !message.includes("\"type\": \"error\"")) return null;
	const start = message.indexOf("{");
	if (start < 0) return null;
	try {
		const parsed = JSON.parse(message.slice(start));
		if (!parsed || typeof parsed !== "object") return null;
		const outer = parsed;
		if (outer.type !== "error" || !outer.error || typeof outer.error !== "object") return null;
		const inner = outer.error;
		if (typeof inner.type !== "string") return null;
		return {
			type: inner.type,
			message: typeof inner.message === "string" ? inner.message : inner.type
		};
	} catch {
		return null;
	}
}
/**
* Classify an Anthropic SDK / HTTP error for typed-error wrapping.
*
* - `prompt is too long` (400 invalid_request_error) → `context_exceeded`.
* - `'tool_use' ids were found without 'tool_result' blocks` /
*   `'tool_result must be preceded by a tool_call'` (400) →
*   `tool_pairing_corruption`. The local repair pass should have caught
*   this — the typed error tells consumers their pre-send pipeline is
*   shipping orphaned blocks somehow (custom `context:transform` hook,
*   bypassed loop, mid-stream session edit) so they can patch the root
*   cause instead of chasing 400s.
* - Any other Anthropic `APIError`-shaped value → `provider_error` with the
*   native status/type code.
* - Unknown errors → `null` (loop wraps in `AgentProviderError` generically).
*
* Anthropic's wire error shape is `{ type: 'error', error: { type, message } }` — the
* SDK stores the parsed body on `err.error`. We walk both levels so callers see the
* most specific `providerCode` we can find.
*/
function classifyAnthropicError(err) {
	const prelude = classifyErrorPrelude(err);
	if (prelude === "not-object") return null;
	if (prelude === "aborted") return { kind: "aborted" };
	const anyErr = err;
	if (!looksLikeAnthropicApiError(err)) {
		const sseError = matchSseErrorEvent(anyErr.message);
		if (sseError) return {
			kind: "provider_error",
			providerCode: sseError.type,
			message: sseError.message,
			retryable: SSE_RETRYABLE_ERROR_TYPES.has(sseError.type)
		};
		return null;
	}
	const innerType = anyErr.error?.error?.type;
	const outerType = anyErr.error?.type;
	const nativeType = innerType && innerType !== "error" ? innerType : outerType;
	const message = anyErr.error?.error?.message ?? anyErr.error?.message ?? anyErr.message ?? "";
	if (matchesContextExceeded(message)) return {
		kind: "context_exceeded",
		providerCode: nativeType ?? "invalid_request_error",
		message
	};
	if (matchesToolPairingError(message)) return {
		kind: "tool_pairing_corruption",
		providerCode: nativeType ?? "invalid_request_error",
		message
	};
	const status = anyErr.status;
	const retryable = typeof status === "number" ? isRetryableHttpStatus(status) : void 0;
	return {
		kind: "provider_error",
		providerCode: nativeType ?? (status ? String(status) : void 0),
		message,
		...retryable !== void 0 ? { retryable } : {}
	};
}
/**
* Build a user `SessionMessage` from multimodal prompt parts.
*
* - Text parts → text blocks.
* - Image parts → base64 image blocks.
* - Document parts → canonical document blocks. `toAnthropic` emits native
*   document blocks only for base64 PDFs; text/non-PDF documents fall back to
*   attachment-tagged text to avoid invalid Anthropic source media types.
*
* The format mirrors `defaultPromptMessage`'s output on shape — Anthropic-specific
* handling differs only in that `promptMessage` being present tells the agent loop
* to route PromptPart[] through this function rather than throwing on base64 docs.
*/
function anthropicPromptMessage(parts) {
	const content = [];
	for (const part of parts) {
		if (part.type === "text") {
			if (part.text.length > 0) content.push({
				type: "text",
				text: part.text
			});
			continue;
		}
		if (part.type === "image") {
			content.push({
				type: "image",
				mediaType: part.mediaType,
				data: part.data
			});
			continue;
		}
		content.push({
			type: "document",
			mediaType: part.mediaType,
			data: part.data,
			encoding: part.encoding,
			...part.name ? { name: part.name } : {}
		});
	}
	return {
		role: "user",
		content
	};
}
function anthropic(anthropicParams) {
	const isOAuth = getConfiguredApiKey(anthropicParams).includes("sk-ant-oat");
	const defaultModel = anthropicParams?.defaultModel || "claude-opus-4-8";
	let runtimeCredentials = extractRuntimeCredentials(anthropicParams);
	return {
		name: "anthropic",
		meta: {
			defaultModel,
			isOAuth,
			capabilities: {
				vision: true,
				imageInToolResult: true,
				documents: true,
				nativeWebSearch: { maxUses: 5 }
			}
		},
		formatTools(tools) {
			const nativeWebSearch = this.meta.capabilities?.nativeWebSearch;
			const hasWebSearch = !!nativeWebSearch && tools.some((t) => t.name === "web_search");
			const out = sanitizeToolSpecs(hasWebSearch ? tools.filter((t) => t.name !== "web_search") : tools, { profile: "anthropic" }).map((t) => ({
				name: t.name,
				description: t.description,
				input_schema: t.inputSchema
			}));
			if (hasWebSearch && nativeWebSearch) {
				const block = {
					type: "web_search_20250305",
					name: "web_search"
				};
				if (typeof nativeWebSearch.maxUses === "number") block.max_uses = nativeWebSearch.maxUses;
				out.push(block);
			}
			return out;
		},
		userMessage(content) {
			return {
				role: "user",
				content: [{
					type: "text",
					text: content
				}]
			};
		},
		assistantMessage(content) {
			return {
				role: "assistant",
				content: [{
					type: "text",
					text: content
				}]
			};
		},
		toolResultsMessage(results) {
			return {
				role: "user",
				content: results.map((r) => ({
					type: "tool_result",
					callId: r.id,
					output: r.content,
					...r.isError ? { isError: true } : {}
				}))
			};
		},
		promptMessage: anthropicPromptMessage,
		classifyError: classifyAnthropicError,
		async countTokens(payload, signal) {
			try {
				const SDK = await loadAnthropicSdk();
				const apiKey = await resolveOAuthApiKey({
					provider: "anthropic",
					providerId: "anthropic",
					params: runtimeCredentials ? {
						...anthropicParams,
						...runtimeCredentials
					} : anthropicParams,
					envKey: "ANTHROPIC_API_KEY",
					missingError: "No API key found. Run `bun run auth` first.",
					refreshError: (reason) => `Anthropic OAuth token refresh failed. ${reason}`
				});
				const callIsOAuth = apiKey.includes("sk-ant-oat");
				const client = createClient(SDK, apiKey, callIsOAuth, anthropicParams?.baseURL, anthropicParams?.extraBetas, anthropicParams?.extraHeaders);
				const wireSystem = renderSystemForWire(payload.system);
				const system = callIsOAuth ? `You are Claude Code, Anthropic's official CLI for Claude.` : wireSystem;
				const messages = callIsOAuth && payload.system ? [
					{
						role: "user",
						content: [{
							type: "text",
							text: wireSystem
						}]
					},
					{
						role: "assistant",
						content: [{
							type: "text",
							text: "Understood. I will proceed with these instructions above the rest of my system prompt."
						}]
					},
					...payload.messages
				] : [...payload.messages];
				return (await client.messages.countTokens({
					model: payload.model,
					system,
					tools: payload.tools,
					messages: messages.map((m) => toAnthropic(m))
				}, signal ? { signal } : void 0)).input_tokens;
			} catch (err) {
				if (process.env.ZIDANE_DEBUG_COUNT) console.error("[anthropic.countTokens] failed:", err);
				return null;
			}
		},
		async stream(options, callbacks) {
			const SDK = await loadAnthropicSdk();
			const apiKey = await resolveOAuthApiKey({
				provider: "anthropic",
				providerId: "anthropic",
				params: runtimeCredentials ? {
					...anthropicParams,
					...runtimeCredentials
				} : anthropicParams,
				envKey: "ANTHROPIC_API_KEY",
				missingError: "No API key found. Run `bun run auth` first.",
				refreshError: (reason) => `Anthropic OAuth token refresh failed. Run \`bun run auth --anthropic\` again. ${reason}`
			}, {
				...callbacks,
				async onOAuthRefresh(ctx) {
					if (ctx.source === "params") runtimeCredentials = {
						access: ctx.credentials.access,
						refresh: ctx.credentials.refresh,
						expires: ctx.credentials.expires
					};
					await callbacks.onOAuthRefresh?.(ctx);
				}
			});
			const requestBetas = options.modelOptions?.fast ? [...anthropicParams?.extraBetas ?? [], FAST_MODE_BETA] : anthropicParams?.extraBetas;
			const callIsOAuth = apiKey.includes("sk-ant-oat");
			const client = createClient(SDK, apiKey, callIsOAuth, anthropicParams?.baseURL, requestBetas, anthropicParams?.extraHeaders);
			const wireSystem = renderSystemForWire(options.system);
			const system = callIsOAuth ? `You are Claude Code, Anthropic's official CLI for Claude.` : wireSystem;
			const messages = callIsOAuth && options.system ? [
				{
					role: "user",
					content: [{
						type: "text",
						text: wireSystem
					}]
				},
				{
					role: "assistant",
					content: [{
						type: "text",
						text: "Understood. I will proceed with these instructions above the rest of my system prompt."
					}]
				},
				...options.messages
			] : [...options.messages];
			const thinking = options.thinking ?? "off";
			const modelId = options.model;
			const params = {
				...anthropicParams?.extraBodyParams ?? {},
				model: modelId,
				max_tokens: options.maxTokens,
				system,
				tools: options.tools,
				messages: messages.map((m) => toAnthropic(m)),
				stream: true
			};
			if (anthropicParams?.contextManagement) params.context_management = anthropicParams.contextManagement;
			if (options.cache !== false) applyAnthropicCacheBreakpoints(params, callIsOAuth ? void 0 : options.system);
			const plan = planAnthropicThinking(thinking, options.thinkingBudget);
			if (plan) {
				if (plan.kind === "enabled") {
					params.thinking = {
						type: "enabled",
						budget_tokens: plan.budgetTokens,
						display: "summarized"
					};
					params.max_tokens = plan.maxTokensBump + params.max_tokens;
				} else {
					params.thinking = {
						type: "adaptive",
						display: "summarized"
					};
					if (plan.effort) params.output_config = { effort: plan.effort };
					if (typeof plan.maxTokensCap === "number" && plan.maxTokensCap > 0) params.max_tokens = Math.min(params.max_tokens, plan.maxTokensCap);
				}
				params.temperature = 1;
			}
			if (options.modelOptions?.fast) params.speed = "fast";
			if (options.toolChoice) if (options.toolChoice.type === "tool" && options.toolChoice.name) params.tool_choice = {
				type: "tool",
				name: options.toolChoice.name
			};
			else if (options.toolChoice.type === "required") params.tool_choice = { type: "any" };
			else params.tool_choice = { type: "auto" };
			const s = client.messages.stream(params, { signal: options.signal });
			let text = "";
			s.on("text", (delta) => {
				text += delta;
				callbacks.onText(delta);
			});
			if (callbacks.onThinking) s.on("thinking", (delta) => {
				callbacks.onThinking(delta);
			});
			if (callbacks.onServerToolUse || callbacks.onServerToolResult) s.on("contentBlock", (block) => {
				if (block.type === "server_tool_use") callbacks.onServerToolUse?.({
					id: block.id,
					name: block.name,
					input: block.input ?? {}
				});
				else if (block.type === "web_search_tool_result") callbacks.onServerToolResult?.({
					toolUseId: block.tool_use_id,
					toolName: "web_search",
					content: block.content
				});
			});
			const response = await s.finalMessage();
			const toolCalls = response.content.filter((b) => b.type === "tool_use").map((b) => ({
				id: b.id,
				name: b.name,
				input: b.input
			}));
			const finishReason = mapStopReason(response.stop_reason);
			const isPause = response.stop_reason === "pause_turn";
			return {
				assistantMessage: fromAnthropic({
					role: "assistant",
					content: response.content
				}),
				text,
				toolCalls,
				done: !isPause && (response.stop_reason === "end_turn" || toolCalls.length === 0),
				usage: fillEstimatedCost({
					input: response.usage.input_tokens,
					output: response.usage.output_tokens,
					cacheCreation: response.usage.cache_creation_input_tokens ?? void 0,
					cacheRead: response.usage.cache_read_input_tokens ?? void 0,
					...finishReason ? { finishReason } : {},
					modelId: response.model ?? options.model
				}, "anthropic", options.modelOptions?.fast ? fastModeCostMultiplier(response.model ?? options.model) : void 0)
			};
		}
	};
}
//#endregion
//#region src/providers/arcee.ts
const BASE_URL$3 = "https://api.arcee.ai/api/v1";
function getApiKey$4(params) {
	if (typeof params?.apiKey === "string" && params.apiKey.length > 0) return params.apiKey;
	if (process.env.ARCEE_API_KEY) return process.env.ARCEE_API_KEY;
	throw new Error("No Arcee API key found. Pass `apiKey` or set ARCEE_API_KEY in your environment.");
}
/**
* Arcee provider (Trinity-Large-Thinking and friends).
*
* Thin wrapper around {@link openaiCompat} pinned to the `reasoning_content`
* round-trip mode: Trinity emits chain-of-thought in `<think>…</think>` blocks
* that vLLM parses into a plain `reasoning_content` string. We capture it and
* echo it back on every assistant message so the model resumes its prior
* reasoning across tool calls — without this, tool calls leak into the
* reasoning field as raw XML (Arcee's `xml_in_reasoning` pitfall). `content` is
* also normalized from `null` → `''` on tool-call turns per the same guidance.
*
* See https://docs.arcee.ai/capabilities/reasoning-traces.
*/
function arcee(params) {
	return openaiCompat({
		name: "arcee",
		apiKey: getApiKey$4(params),
		baseURL: params?.baseURL || BASE_URL$3,
		defaultModel: params?.defaultModel || "trinity-large-thinking",
		capabilities: params?.capabilities ?? {
			vision: false,
			imageInToolResult: false
		},
		supportsReasoning: true,
		reasoningMode: "reasoning_content",
		reasoningContentField: params?.reasoningContentField ?? "reasoning",
		normalizeNullContent: true,
		extraHeaders: params?.extraHeaders
	});
}
//#endregion
//#region src/providers/baseten.ts
const BASE_URL$2 = "https://inference.baseten.co/v1";
/**
* Models that take OpenAI-style `reasoning_effort` (low/medium/high) and
* reason by default. Everything else on Model APIs (Kimi, GLM, Nemotron) is
* opt-in via `chat_template_args: { enable_thinking: true }`.
* See https://docs.baseten.co/inference/model-apis/reasoning.
*/
const EFFORT_MODELS = new Set(["deepseek-ai/DeepSeek-V4-Pro", "openai/gpt-oss-120b"]);
function getApiKey$3(params) {
	if (typeof params?.apiKey === "string" && params.apiKey.length > 0) return params.apiKey;
	if (process.env.BASETEN_API_KEY) return process.env.BASETEN_API_KEY;
	throw new Error("No Baseten API key found. Pass `apiKey` or set BASETEN_API_KEY in your environment.");
}
/**
* Map zidane's thinking level to Baseten's per-model reasoning request shape:
*
* - `reasoning_effort` models (DeepSeek V4 Pro, GPT-OSS 120B) reason by
*   default; the effort string tunes depth. `'adaptive'` sends nothing
*   (server default = medium); `'off'` sends nothing (cannot be disabled).
* - All other models opt in via `chat_template_args.enable_thinking` — any
*   non-off level enables it (the template arg is boolean, no depth knob).
*
* Exported for tests.
*/
function planBasetenReasoning(ctx) {
	const { model, thinking } = ctx;
	if (!thinking || thinking === "off") return void 0;
	if (EFFORT_MODELS.has(model)) {
		if (thinking === "adaptive") return void 0;
		return { reasoning_effort: thinking === "minimal" ? "low" : thinking };
	}
	return { chat_template_args: { enable_thinking: true } };
}
/**
* Baseten Model APIs provider.
*
* Thin wrapper around {@link openaiCompat} pinned to the `reasoning_content`
* round-trip mode: thinking arrives in the `reasoning_content` delta field and
* is echoed back on assistant messages under the same name so reasoning
* models resume their chain-of-thought across tool calls. The thinking level
* maps to Baseten's request shape via {@link planBasetenReasoning}.
*/
function baseten(params) {
	return openaiCompat({
		name: "baseten",
		apiKey: getApiKey$3(params),
		baseURL: params?.baseURL || BASE_URL$2,
		defaultModel: params?.defaultModel || "zai-org/GLM-5.1",
		capabilities: params?.capabilities ?? {
			vision: false,
			imageInToolResult: false
		},
		supportsReasoning: true,
		reasoningMode: "reasoning_content",
		reasoningContentField: "reasoning_content",
		planReasoningRequest: planBasetenReasoning,
		extraHeaders: params?.extraHeaders
	});
}
//#endregion
//#region src/providers/cerebras.ts
const BASE_URL$1 = "https://api.cerebras.ai/v1";
function getApiKey$2(params) {
	if (typeof params?.apiKey === "string" && params.apiKey.length > 0) return params.apiKey;
	if (process.env.CEREBRAS_API_KEY) return process.env.CEREBRAS_API_KEY;
	throw new Error("No Cerebras API key found. Pass `apiKey` or set CEREBRAS_API_KEY in your environment.");
}
/**
* Cerebras provider.
*
* Thin wrapper around {@link openaiCompat} with Cerebras-specific defaults
* (base URL, default model).
*/
function cerebras(params) {
	return openaiCompat({
		name: "cerebras",
		apiKey: getApiKey$2(params),
		baseURL: BASE_URL$1,
		defaultModel: params?.defaultModel || "zai-glm-4.7",
		capabilities: params?.capabilities ?? {
			vision: false,
			imageInToolResult: false
		},
		extraHeaders: params?.extraHeaders
	});
}
//#endregion
//#region src/chat/oauth-page/pkce.ts
/**
* PKCE helper. Inlined here (rather than imported from pi-ai) because
* `@earendil-works/pi-ai/oauth` does not re-export it, and we don't want
* to reach into `node_modules/.../utils/oauth/pkce.js` — that's an
* implementation-detail path that breaks on minor upstream rearrangements.
*
* Web Crypto API only. Works under Bun + Node 22+ without any node:crypto
* import (matches pi-ai's own behavior). Output is base64url per RFC 7636.
*/
function base64UrlEncode(bytes) {
	let binary = "";
	for (const byte of bytes) binary += String.fromCharCode(byte);
	return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}
async function generatePkce() {
	const verifierBytes = new Uint8Array(32);
	crypto.getRandomValues(verifierBytes);
	const verifier = base64UrlEncode(verifierBytes);
	const data = new TextEncoder().encode(verifier);
	const hashBuffer = await crypto.subtle.digest("SHA-256", data);
	return {
		verifier,
		challenge: base64UrlEncode(new Uint8Array(hashBuffer))
	};
}
//#endregion
//#region src/chat/oauth-page/cursor.ts
/** pi-ai / zidane OAuth provider id. Also the credentials-file key. */
const CURSOR_OAUTH_PROVIDER_ID = "cursor";
const CURSOR_LOGIN_URL = "https://cursor.com/loginDeepControl";
const CURSOR_POLL_URL = "https://api2.cursor.sh/auth/poll";
const CURSOR_REFRESH_URL = "https://api2.cursor.sh/auth/exchange_user_api_key";
const POLL_MAX_ATTEMPTS = 150;
const POLL_BASE_DELAY_MS = 1e3;
const POLL_MAX_DELAY_MS = 1e4;
const POLL_BACKOFF_MULTIPLIER = 1.2;
const POLL_MAX_CONSECUTIVE_ERRORS = 3;
/** Fallback lifetime when the JWT carries no `exp`. */
const DEFAULT_TOKEN_TTL_MS = 3600 * 1e3;
/** Refresh slightly early so requests don't race expiry. */
const TOKEN_EXPIRY_SKEW_MS = 300 * 1e3;
/**
* Derive expiry (epoch ms) from a JWT's `exp` claim, minus a safety skew.
* Returns `now + 1h` for malformed tokens so a missing claim never wedges
* refresh into an immediate-expire loop.
*/
function cursorTokenExpiry(token) {
	try {
		const parts = token.split(".");
		const payload = parts[1];
		if (parts.length !== 3 || !payload) return Date.now() + DEFAULT_TOKEN_TTL_MS;
		const json = JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/")));
		if (typeof json.exp === "number") return json.exp * 1e3 - TOKEN_EXPIRY_SKEW_MS;
	} catch {}
	return Date.now() + DEFAULT_TOKEN_TTL_MS;
}
function delay(ms) {
	return new Promise((r) => setTimeout(r, ms));
}
/**
* Poll Cursor's auth endpoint until login completes. `404` means the user
* hasn't finished the browser flow yet; anything else non-2xx is a hard error.
*/
async function pollForTokens(uuid, verifier, callbacks) {
	let wait = POLL_BASE_DELAY_MS;
	let consecutiveErrors = 0;
	for (let attempt = 0; attempt < POLL_MAX_ATTEMPTS; attempt++) {
		if (callbacks.signal?.aborted) throw new Error("Cursor login aborted");
		await delay(wait);
		try {
			const url = `${CURSOR_POLL_URL}?uuid=${encodeURIComponent(uuid)}&verifier=${encodeURIComponent(verifier)}`;
			const response = await fetch(url, { signal: callbacks.signal });
			if (response.status === 404) {
				consecutiveErrors = 0;
				wait = Math.min(wait * POLL_BACKOFF_MULTIPLIER, POLL_MAX_DELAY_MS);
				continue;
			}
			if (response.ok) {
				const data = await response.json();
				if (!data.accessToken || !data.refreshToken) throw new Error(`Cursor poll response missing tokens: ${JSON.stringify(data)}`);
				return {
					accessToken: data.accessToken,
					refreshToken: data.refreshToken
				};
			}
			throw new Error(`Cursor poll failed (${response.status})`);
		} catch (err) {
			if (callbacks.signal?.aborted) throw new Error("Cursor login aborted");
			consecutiveErrors++;
			if (consecutiveErrors >= POLL_MAX_CONSECUTIVE_ERRORS) throw err instanceof Error ? err : new Error(String(err));
			callbacks.onProgress?.("Waiting for Cursor login to complete…");
		}
	}
	throw new Error("Cursor authentication timed out");
}
async function loginCursor(callbacks) {
	const { verifier, challenge } = await generatePkce();
	const uuid = crypto.randomUUID();
	const authUrl = new URL(CURSOR_LOGIN_URL);
	authUrl.searchParams.set("challenge", challenge);
	authUrl.searchParams.set("uuid", uuid);
	authUrl.searchParams.set("mode", "login");
	authUrl.searchParams.set("redirectTarget", "cli");
	callbacks.onAuth({
		url: authUrl.toString(),
		instructions: "A browser window should open. Complete login in Cursor to finish."
	});
	const tokens = await pollForTokens(uuid, verifier, callbacks);
	return {
		access: tokens.accessToken,
		refresh: tokens.refreshToken,
		expires: cursorTokenExpiry(tokens.accessToken)
	};
}
async function refreshCursorToken(credentials) {
	const response = await fetch(CURSOR_REFRESH_URL, {
		method: "POST",
		headers: {
			"Authorization": `Bearer ${credentials.refresh}`,
			"Content-Type": "application/json"
		},
		body: "{}"
	});
	if (!response.ok) {
		const text = await response.text().catch(() => "");
		throw new Error(`Cursor token refresh failed (${response.status}): ${text || response.statusText}`);
	}
	const data = await response.json();
	if (!data.accessToken) throw new Error(`Cursor token refresh response missing access token: ${JSON.stringify(data)}`);
	return {
		access: data.accessToken,
		refresh: data.refreshToken || credentials.refresh,
		expires: cursorTokenExpiry(data.accessToken)
	};
}
/**
* Build a Cursor `OAuthProviderInterface`. Shape matches Anthropic / Codex so
* it drops into `BUILTIN_PROVIDERS` and `src/auth.ts` unchanged.
*
* No `renderPage` parameter: Cursor's poll flow has no local callback server,
* so there is no post-redirect page to theme.
*/
function createCursorOAuthProvider() {
	return {
		id: CURSOR_OAUTH_PROVIDER_ID,
		name: "Cursor",
		usesCallbackServer: false,
		login: loginCursor,
		refreshToken: refreshCursorToken,
		getApiKey: (credentials) => credentials.access
	};
}
//#endregion
//#region src/providers/cursor.ts
const DEFAULT_MODEL$1 = "claude-4.6-sonnet";
const NOT_IMPLEMENTED_MESSAGE = "Cursor OAuth login works, but inference over Cursor is not implemented yet. Cursor uses a protobuf/HTTP-2 agent protocol (not an OpenAI-compatible API), so the streaming transport still needs to be ported. Use another provider for now.";
function cursor(params) {
	const defaultModel = params?.defaultModel || DEFAULT_MODEL$1;
	const base = openaiCompat({
		name: "cursor",
		apiKey: params?.apiKey ?? "oauth",
		baseURL: "https://api2.cursor.sh",
		defaultModel,
		capabilities: {
			vision: true,
			imageInToolResult: false
		},
		extraHeaders: params?.extraHeaders
	});
	return {
		...base,
		meta: {
			...base.meta,
			defaultModel
		},
		async stream(options, callbacks) {
			await resolveOAuthApiKey({
				provider: "cursor",
				providerId: CURSOR_OAUTH_PROVIDER_ID,
				params,
				missingError: "No Cursor credentials found. Run `bun run auth --cursor` first.",
				refreshError: (reason) => `Cursor OAuth token refresh failed. Run \`bun run auth --cursor\` again. ${reason}`
			}, callbacks);
			throw new Error(NOT_IMPLEMENTED_MESSAGE);
		}
	};
}
//#endregion
//#region src/providers/local.ts
function getBaseURL(params) {
	if (typeof params?.baseURL === "string" && params.baseURL.length > 0) return params.baseURL;
	if (process.env.LOCAL_LLM_BASE_URL) return process.env.LOCAL_LLM_BASE_URL;
	throw new Error("No local LLM base URL found. Pass `baseURL` or set LOCAL_LLM_BASE_URL (e.g. http://localhost:11434/v1 for Ollama, http://localhost:8000/v1 for vLLM).");
}
function getApiKey$1(params) {
	if (typeof params?.apiKey === "string" && params.apiKey.length > 0) return params.apiKey;
	if (process.env.LOCAL_LLM_API_KEY) return process.env.LOCAL_LLM_API_KEY;
	return "no-key";
}
function getDefaultModel(params) {
	if (typeof params?.defaultModel === "string" && params.defaultModel.length > 0) return params.defaultModel;
	if (process.env.LOCAL_LLM_DEFAULT_MODEL) return process.env.LOCAL_LLM_DEFAULT_MODEL;
}
/**
* Local OpenAI-compatible LLM provider.
*
* Thin wrapper around {@link openaiCompat} for self-hosted runtimes that
* speak the standard `POST /chat/completions` + SSE dialect: Ollama, vLLM,
* LM Studio, Lemonade, llama.cpp's server, text-generation-webui, etc.
*
* Caching and reasoning are left off — local runtimes typically strict-
* validate the request schema and would 400 on the extra fields. Override
* `capabilities` when pointing at a vision-capable deployment.
*/
function local(params) {
	return openaiCompat({
		name: "local",
		apiKey: getApiKey$1(params),
		baseURL: getBaseURL(params),
		defaultModel: getDefaultModel(params),
		capabilities: params?.capabilities ?? {
			vision: false,
			imageInToolResult: false
		},
		extraHeaders: params?.extraHeaders
	});
}
//#endregion
//#region src/providers/openai.ts
const PROVIDER_ID = "openai-codex";
const DEFAULT_MODEL = "gpt-5.5";
const lookupModel = getModel;
function resolveModel(modelId) {
	const model = lookupModel(PROVIDER_ID, modelId);
	if (model) return model;
	const fallback = lookupModel(PROVIDER_ID, DEFAULT_MODEL);
	if (!fallback) throw new Error(`OpenAI Codex model registry is missing the default model: ${DEFAULT_MODEL}`);
	return {
		...fallback,
		id: modelId,
		name: modelId
	};
}
function emptyUsage() {
	return {
		input: 0,
		output: 0,
		cacheRead: 0,
		cacheWrite: 0,
		totalTokens: 0,
		cost: {
			input: 0,
			output: 0,
			cacheRead: 0,
			cacheWrite: 0,
			total: 0
		}
	};
}
function formatTools(tools) {
	return sanitizeToolSpecs(tools, { profile: "openai" }).map((t) => ({
		name: t.name,
		description: t.description,
		parameters: t.inputSchema
	}));
}
function documentMarker(doc) {
	return documentBlockMarker(doc, "document omitted");
}
function piToolResultMessage(result, toolName = "") {
	const content = typeof result.output === "string" ? [{
		type: "text",
		text: result.output
	}] : result.output.map((block) => block.type === "image" ? {
		type: "image",
		data: block.data,
		mimeType: block.mediaType
	} : {
		type: "text",
		text: block.type === "document" ? documentMarker(block) : block.text
	});
	return {
		role: "toolResult",
		toolCallId: result.callId,
		toolName,
		content,
		isError: result.isError ?? false,
		timestamp: Date.now()
	};
}
function piUserMessage(content) {
	const textBlocks = content.filter((b) => b.type === "text");
	const imageBlocks = content.filter((b) => b.type === "image");
	const documentBlocks = content.filter((b) => b.type === "document");
	if (imageBlocks.length === 0 && textBlocks.length === 0 && documentBlocks.length === 0) return null;
	if (imageBlocks.length === 0 && documentBlocks.length === 0 && textBlocks.length === 1) return {
		role: "user",
		content: textBlocks[0].text,
		timestamp: Date.now()
	};
	return {
		role: "user",
		content: [
			...imageBlocks.map((img) => ({
				type: "image",
				data: img.data,
				mimeType: img.mediaType
			})),
			...documentBlocks.map((block) => ({
				type: "text",
				text: documentMarker(block)
			})),
			...textBlocks.map((block) => ({
				type: "text",
				text: block.text
			}))
		],
		timestamp: Date.now()
	};
}
function piAssistantMessage(content, modelId) {
	const piContent = [];
	for (const block of content) if (block.type === "text") piContent.push({
		type: "text",
		text: block.text
	});
	else if (block.type === "thinking") {
		if (block.signatureProducer === "anthropic") continue;
		piContent.push({
			type: "thinking",
			thinking: block.text,
			thinkingSignature: block.signature
		});
	} else if (block.type === "tool_call") piContent.push({
		type: "toolCall",
		id: block.id,
		name: block.name,
		arguments: block.input
	});
	return {
		role: "assistant",
		content: piContent,
		api: "openai-codex-responses",
		provider: PROVIDER_ID,
		model: modelId,
		usage: emptyUsage(),
		stopReason: "stop",
		timestamp: Date.now()
	};
}
function toPiMessages(messages, modelId) {
	const out = [];
	for (let i = 0; i < messages.length; i++) {
		const msg = messages[i];
		const toolCalls = msg.content.filter((b) => b.type === "tool_call");
		if (msg.role === "assistant" && toolCalls.length > 0) {
			const next = messages[i + 1];
			const nextToolResults = next?.role === "user" ? next.content.filter((b) => b.type === "tool_result") : [];
			const resultById = new Map(nextToolResults.map((result) => [result.callId, result]));
			let assistantContent = [];
			for (const block of msg.content) {
				assistantContent.push(block);
				if (block.type !== "tool_call") continue;
				out.push(piAssistantMessage(assistantContent, modelId));
				assistantContent = [];
				const result = resultById.get(block.id);
				if (result) out.push(piToolResultMessage(result, block.name));
				else out.push(piToolResultMessage({
					type: "tool_result",
					callId: block.id,
					output: SYNTHETIC_TOOL_RESULT_PLACEHOLDER,
					isError: true
				}, block.name));
			}
			if (assistantContent.length > 0) out.push(piAssistantMessage(assistantContent, modelId));
			if (next?.role === "user") {
				const userMessage = piUserMessage(next.content.filter((block) => {
					if (block.type !== "tool_result") return true;
					return false;
				}));
				if (userMessage) out.push(userMessage);
				i++;
			}
			continue;
		}
		if (msg.content.filter((b) => b.type === "tool_result").length > 0) {
			const userMsg = msg.role === "user" ? piUserMessage(msg.content.filter((b) => b.type !== "tool_result")) : null;
			if (userMsg) out.push(userMsg);
			continue;
		}
		if (msg.role === "user") {
			const userMsg = piUserMessage(msg.content);
			if (userMsg) out.push(userMsg);
			continue;
		}
		out.push(piAssistantMessage(msg.content, modelId));
	}
	return out;
}
/**
* Map pi-ai's `StopReason` to the zidane `TurnFinishReason` union — the same
* native-stop-reason fidelity anthropic's `mapStopReason` / openai-compat's
* `mapOAIFinishReason` provide. Notably `length` (max-token truncation) is no
* longer misreported as a clean `stop`. `hasToolCalls` is the fallback for
* messages whose stop reason is missing or doesn't carry the tool-call signal.
*/
function mapCodexStopReason(stopReason, hasToolCalls) {
	switch (stopReason) {
		case "toolUse": return "tool-calls";
		case "length": return "length";
		case "error": return "error";
		case "stop": return hasToolCalls ? "tool-calls" : "stop";
		case "aborted": return "other";
		default: return hasToolCalls ? "tool-calls" : "stop";
	}
}
function fromPiAssistantMessage(message) {
	const content = [];
	for (const block of message.content) if (block.type === "text") content.push({
		type: "text",
		text: block.text
	});
	else if (block.type === "thinking") {
		const out = {
			type: "thinking",
			text: block.thinking
		};
		if (typeof block.thinkingSignature === "string") {
			out.signature = block.thinkingSignature;
			out.signatureProducer = "openai";
		}
		content.push(out);
	} else if (block.type === "toolCall") content.push({
		type: "tool_call",
		id: block.id,
		name: block.name,
		input: block.arguments
	});
	return {
		role: "assistant",
		content
	};
}
function extractToolCalls(message) {
	return message.content.filter((block) => block.type === "toolCall").map((block) => ({
		id: block.id,
		name: block.name,
		input: block.arguments
	}));
}
function extractText(message) {
	return message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
}
function toTurnUsage(usage, finishReason, modelId) {
	return fillEstimatedCost({
		input: usage.input,
		output: usage.output,
		cacheRead: usage.cacheRead || void 0,
		cacheCreation: usage.cacheWrite || void 0,
		cost: usage.cost.total || void 0,
		...finishReason ? { finishReason } : {},
		modelId
	}, "openai");
}
/**
* Transient pi-ai error patterns worth a loop-level retry. pi-ai already retries
* 429 / 5xx pre-stream 3× internally (see `openai-codex-responses.js`); by the
* time the error reaches us, that budget is exhausted. The loop-level retry
* with its longer backoff is a second line of defense for capacity events
* that persist past pi-ai's short retry window, plus mid-stream emissions
* (pi-ai surfaces those as plain `Error`s with no status).
*
* Mirrors pi-ai's own `isRetryableError` regex so the two retry layers
* recognize the same failure modes.
*/
const TRANSIENT_OPENAI_MESSAGE_RE = /rate.?limit|overloaded|service.?unavailable|upstream.?connect|connection.?refused|gateway.?time.?out|temporarily.?unavailable/i;
/** Numeric HTTP status codes pi-ai sometimes attaches when bubbling structured errors. */
function isRetryableStatusCode(err) {
	const status = err.status;
	if (typeof status !== "number") return false;
	return isRetryableHttpStatus(status);
}
/**
* Classify an OpenAI Codex error. pi-ai surfaces errors either as thrown `Error`s
* (wrapping `event.error.errorMessage`) or via stream event types.
*
* Retryable hint is set when pi-ai's own retry budget is exhausted on a
* transient failure — pattern-matched against `message` since pi-ai strips
* structured fields on plain-Error throws. Set `behavior.retry.maxAttempts: 1`
* to disable the loop-level retry.
*/
function classifyOpenAIError(err) {
	const prelude = classifyErrorPrelude(err);
	if (prelude === "not-object") return null;
	if (prelude === "aborted") return { kind: "aborted" };
	const anyErr = err;
	const message = anyErr.message ?? "";
	const code = anyErr.code ?? anyErr.type;
	if (code === "context_length_exceeded" || matchesContextExceeded(message)) return {
		kind: "context_exceeded",
		providerCode: code ?? "context_length_exceeded",
		message
	};
	if (matchesToolPairingError(message)) return {
		kind: "tool_pairing_corruption",
		providerCode: code ?? "invalid_request_error",
		message
	};
	if (message.length > 0) return {
		kind: "provider_error",
		providerCode: code,
		message,
		...isRetryableStatusCode(anyErr) || TRANSIENT_OPENAI_MESSAGE_RE.test(message) ? { retryable: true } : {}
	};
	return null;
}
/**
* A standard OpenAI API key usable against `api.openai.com` (for the exact
* token-count endpoint), or `null` when only a Codex-OAuth credential is
* available. Codex OAuth access tokens are JWTs (`eyJ…`) and are NOT valid
* bearers for the standard API; a real key looks like `sk-…` (but not the
* Anthropic `sk-ant-…` form). Checks `params.apiKey` then `OPENAI_API_KEY`.
*/
function pickStandardOpenAIKey(params) {
	const candidates = [params?.apiKey, process.env.OPENAI_API_KEY];
	for (const key of candidates) if (typeof key === "string" && key.startsWith("sk-") && !key.startsWith("sk-ant-")) return key;
	return null;
}
/**
* Convert canonical {@link SessionMessage}s to OpenAI Responses-API `input`
* items for the token-count endpoint. Only the text-bearing shape is needed
* here (the count path sends a tiny dummy message); images/tool-results are
* down-converted to text so the count never throws on an exotic block.
*/
function toResponsesInput(messages) {
	return messages.map((msg) => {
		const text = msg.content.map((b) => b.type === "text" ? b.text : "").filter(Boolean).join("\n");
		const part = msg.role === "assistant" ? "output_text" : "input_text";
		return {
			role: msg.role === "assistant" ? "assistant" : "user",
			content: [{
				type: part,
				text
			}]
		};
	});
}
/** Convert pi-ai `PiTool` specs to Responses-API function tools. */
function toResponsesTools(tools) {
	return tools.map((t) => {
		const tool = t;
		return {
			type: "function",
			name: tool.name,
			description: tool.description,
			parameters: tool.parameters
		};
	});
}
function applyPayloadOverrides(payload, options) {
	const body = payload;
	if (options.toolChoice) if (options.toolChoice.type === "tool" && options.toolChoice.name) body.tool_choice = {
		type: "function",
		name: options.toolChoice.name
	};
	else if (options.toolChoice.type === "required") body.tool_choice = "required";
	else body.tool_choice = "auto";
	return body;
}
function openai(params) {
	const defaultModel = params?.defaultModel || DEFAULT_MODEL;
	const baseCredentials = extractRuntimeCredentials(params);
	let runtimeCredentials = baseCredentials ? {
		...baseCredentials,
		...params?.accountId ? { accountId: params.accountId } : {}
	} : void 0;
	return {
		name: "openai",
		meta: {
			defaultModel,
			isOAuth: true,
			capabilities: {
				vision: true,
				imageInToolResult: true
			}
		},
		formatTools,
		userMessage,
		assistantMessage,
		toolResultsMessage,
		classifyError: classifyOpenAIError,
		async countTokens(payload, signal) {
			const standardKey = pickStandardOpenAIKey(params);
			if (!standardKey) return null;
			try {
				const res = await fetch("https://api.openai.com/v1/responses/input_tokens", {
					method: "POST",
					headers: {
						...params?.extraHeaders,
						"authorization": `Bearer ${standardKey}`,
						"content-type": "application/json"
					},
					body: JSON.stringify({
						model: payload.model || defaultModel,
						instructions: renderSystemForWire(payload.system),
						input: toResponsesInput(payload.messages),
						tools: toResponsesTools(payload.tools)
					}),
					...signal ? { signal } : {}
				});
				if (!res.ok) return null;
				const json = await res.json();
				return typeof json.input_tokens === "number" ? json.input_tokens : null;
			} catch {
				return null;
			}
		},
		async stream(options, callbacks) {
			const modelId = options.model || defaultModel;
			const model = resolveModel(modelId);
			const apiKey = await resolveOAuthApiKey({
				provider: "openai",
				providerId: PROVIDER_ID,
				params: runtimeCredentials ? {
					...params,
					...runtimeCredentials
				} : params,
				envKey: "OPENAI_CODEX_API_KEY",
				extraCredentialKeys: ["accountId"],
				missingError: "No OpenAI Codex OAuth token found. Run `bun run auth --openai` first.",
				refreshError: (reason) => `OpenAI Codex OAuth token refresh failed. Run \`bun run auth --openai\` again. ${reason}`
			}, {
				...callbacks,
				async onOAuthRefresh(ctx) {
					if (ctx.source === "params") runtimeCredentials = {
						access: ctx.credentials.access,
						refresh: ctx.credentials.refresh,
						expires: ctx.credentials.expires,
						...typeof ctx.credentials.accountId === "string" ? { accountId: ctx.credentials.accountId } : {}
					};
					await callbacks.onOAuthRefresh?.(ctx);
				}
			});
			const context = {
				systemPrompt: renderSystemForWire(options.system),
				messages: toPiMessages(options.messages, modelId),
				tools: options.tools
			};
			const reasoningLevel = options.thinking && options.thinking !== "off" && options.thinking !== "adaptive" ? options.thinking : void 0;
			const stream = streamOpenAICodexResponses(model, context, {
				apiKey,
				maxTokens: options.maxTokens,
				signal: options.signal,
				transport: params?.transport,
				reasoningEffort: reasoningLevel,
				reasoningSummary: reasoningLevel ? "auto" : void 0,
				...params?.extraHeaders ? { headers: params.extraHeaders } : {},
				onPayload: (payload) => applyPayloadOverrides(payload, options)
			});
			let finalMessage;
			let text = "";
			let thinking = "";
			for await (const event of stream) if (event.type === "text_delta") {
				text += event.delta;
				callbacks.onText(event.delta);
			} else if (event.type === "thinking_delta") {
				thinking += event.delta;
				callbacks.onThinking?.(event.delta);
			} else if (event.type === "thinking_end") {
				const delta = event.content.startsWith(thinking) ? event.content.slice(thinking.length) : thinking ? "" : event.content;
				if (delta) {
					thinking += delta;
					callbacks.onThinking?.(delta);
				}
			} else if (event.type === "done") finalMessage = event.message;
			else if (event.type === "error") throw new Error(event.error.errorMessage || "OpenAI Codex API error");
			finalMessage ??= await stream.result();
			text ||= extractText(finalMessage);
			const toolCalls = extractToolCalls(finalMessage);
			const assistantTurn = fromPiAssistantMessage(finalMessage);
			const finishReason = mapCodexStopReason(finalMessage.stopReason, toolCalls.length > 0);
			return {
				assistantMessage: assistantTurn,
				text,
				toolCalls,
				done: toolCalls.length === 0,
				usage: toTurnUsage(finalMessage.usage, finishReason, modelId)
			};
		}
	};
}
//#endregion
//#region src/providers/openrouter.ts
const BASE_URL = "https://openrouter.ai/api/v1";
function getApiKey(params) {
	if (typeof params?.apiKey === "string" && params.apiKey.length > 0) return params.apiKey;
	if (process.env.OPENROUTER_API_KEY) return process.env.OPENROUTER_API_KEY;
	throw new Error("No OpenRouter API key found. Pass `apiKey` or set OPENROUTER_API_KEY in your environment.");
}
/**
* OpenRouter provider.
*
* Thin wrapper around {@link openaiCompat} with OpenRouter-specific defaults
* (base URL, default model) and required attribution headers.
*/
function openrouter(params) {
	return openaiCompat({
		name: "openrouter",
		apiKey: getApiKey(params),
		baseURL: BASE_URL,
		defaultModel: params?.defaultModel || "anthropic/claude-sonnet-4-6",
		extraHeaders: {
			"HTTP-Referer": "https://github.com/Tahul/zidane",
			"X-Title": "zidane"
		},
		capabilities: params?.capabilities ?? {
			vision: true,
			imageInToolResult: false
		},
		cacheBreakpoints: true,
		supportsReasoning: true
	});
}
//#endregion
export { createCursorOAuthProvider as a, baseten as c, anthropic as d, applyAnthropicCacheBreakpoints as f, FAST_MODE_OPTIONS as h, cursor as i, planBasetenReasoning as l, ANTHROPIC_EXTRA_MODELS as m, openai as n, generatePkce as o, writeFileAtomic as p, local as r, cerebras as s, openrouter as t, arcee as u };

//# sourceMappingURL=providers-BGBB18zz.js.map