zidane 6.1.13 → 6.2.6
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-BqfKqQ9x.js → acp-CYJvnqPa.js} +2 -2
- package/dist/{acp-BqfKqQ9x.js.map → acp-CYJvnqPa.js.map} +1 -1
- package/dist/acp-cli.js +4 -4
- package/dist/acp.d.ts +1 -1
- package/dist/acp.js +1 -1
- package/dist/{agent-CZ3WSWqR.js → agent-D11SxCvA.js} +155 -75
- package/dist/agent-D11SxCvA.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +2 -2
- package/dist/{anthropic-CV_OSYVD.js → anthropic-DdlSgT6K.js} +47 -25
- package/dist/anthropic-DdlSgT6K.js.map +1 -0
- package/dist/{auth-D5FfJtLf.js → auth-Ys_7TmRj.js} +2 -2
- package/dist/{auth-D5FfJtLf.js.map → auth-Ys_7TmRj.js.map} +1 -1
- package/dist/{cache-telemetry-BKAwsNxm.js → cache-telemetry-KJ7B-_Gh.js} +8 -3
- package/dist/cache-telemetry-KJ7B-_Gh.js.map +1 -0
- package/dist/chat/pure.d.ts +3 -3
- package/dist/chat/pure.js +2 -2
- package/dist/chat.d.ts +12 -5
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +7 -7
- package/dist/{completion-core-CXYSVhZo.js → completion-core-BRo86R_L.js} +23 -10
- package/dist/completion-core-BRo86R_L.js.map +1 -0
- package/dist/contexts/daytona.d.ts +1 -1
- package/dist/contexts/e2b.d.ts +1 -1
- package/dist/eval.d.ts +1 -1
- package/dist/extensions.d.ts +2 -2
- package/dist/extensions.js +2 -2
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +1 -1
- package/dist/{index-X02SI0nt.d.ts → index-CNyYEOy0.d.ts} +351 -42
- package/dist/index-CNyYEOy0.d.ts.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -6
- package/dist/{generate-data-WL_yGVL6.js → inline-autocomplete-BAwBcZtD.js} +191 -6
- package/dist/inline-autocomplete-BAwBcZtD.js.map +1 -0
- package/dist/mcp.d.ts +1 -1
- package/dist/output/stream-json.d.ts +1 -1
- package/dist/output/terminal.d.ts +1 -1
- package/dist/presets.d.ts +1 -1
- package/dist/presets.js +1 -1
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/anthropic.js +1 -1
- package/dist/providers/arcee.d.ts +1 -1
- package/dist/providers/baseten.d.ts +1 -1
- package/dist/providers/cerebras.d.ts +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/openai-compat.d.ts +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +1 -1
- package/dist/restate.d.ts +1 -1
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/{tool-formatters-kg8O6wHZ.d.ts → tool-formatters-B7oWiy5m.d.ts} +2 -2
- package/dist/tool-formatters-B7oWiy5m.d.ts.map +1 -0
- package/dist/tools/fetch-url.d.ts +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +1 -1
- package/dist/{transcript-anchors-DAF4RbyU.d.ts → transcript-anchors-BUZ915pM.d.ts} +2 -2
- package/dist/{transcript-anchors-DAF4RbyU.d.ts.map → transcript-anchors-BUZ915pM.d.ts.map} +1 -1
- package/dist/{transcript-anchors-DAf0Mr_9.js → transcript-anchors-cc4gsU-j.js} +23 -8
- package/dist/transcript-anchors-cc4gsU-j.js.map +1 -0
- package/dist/tui.d.ts +25 -15
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +539 -57
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-zkZ1L0eb.d.ts → turn-operations-2Ou_DkbC.d.ts} +3 -3
- package/dist/{turn-operations-zkZ1L0eb.d.ts.map → turn-operations-2Ou_DkbC.d.ts.map} +1 -1
- package/dist/{turn-operations-D9p-y9EC.js → turn-operations-CXjRaltC.js} +72 -2
- package/dist/{turn-operations-D9p-y9EC.js.map → turn-operations-CXjRaltC.js.map} +1 -1
- package/dist/types-BIarq1qE.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/EXTENSIONS.md +43 -6
- package/package.json +2 -1
- package/scripts/eval.ts +21 -3
- package/dist/agent-CZ3WSWqR.js.map +0 -1
- package/dist/anthropic-CV_OSYVD.js.map +0 -1
- package/dist/cache-telemetry-BKAwsNxm.js.map +0 -1
- package/dist/completion-core-CXYSVhZo.js.map +0 -1
- package/dist/generate-data-WL_yGVL6.js.map +0 -1
- package/dist/index-X02SI0nt.d.ts.map +0 -1
- package/dist/tool-formatters-kg8O6wHZ.d.ts.map +0 -1
- package/dist/transcript-anchors-DAf0Mr_9.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"anthropic-CV_OSYVD.js","names":[],"sources":["../src/chat/anthropic-models.ts","../src/providers/anthropic.ts"],"sourcesContent":["/**\n * Anthropic models not (yet) bundled in `@earendil-works/pi-ai`'s registry.\n *\n * pi-ai ships a static price/metadata registry that lags new Anthropic\n * releases by a dep bump. Until the bump lands, list freshly-shipped models\n * here so they show in the picker, expose the reasoning knob, and get an\n * accurate cost estimate. Entries are merged ahead of pi-ai's list in\n * {@link modelsForDescriptor} and de-duped by id, so once pi-ai catches up\n * the bundled entry simply wins.\n */\n\nimport type { ModelInfo, ModelOption } from './providers'\n\n/**\n * Anthropic \"fast mode\" — `speed: \"fast\"` on the Messages API. ~2.5× output\n * tokens/sec at a per-model price multiplier. Supported on Opus 4.6 / 4.7 / 4.8\n * only; the API errors if sent on any other model, so it's attached just to\n * those entries (see {@link FAST_MODE_OPTIONS}).\n *\n * The multiplier scales ALL rate components (input, output, and both cache\n * rates) uniformly: per Anthropic, \"prompt caching multipliers apply on top of\n * fast mode pricing\" across the full context window. Opus 4.8 is 2× ($5/$25 →\n * $10/$50); Opus 4.6 / 4.7 are a steeper 6× ($5/$25 → $30/$150).\n * See https://platform.claude.com/docs/en/build-with-claude/fast-mode.\n */\nexport const FAST_MODE_OPTION: ModelOption = {\n id: 'fast',\n label: 'Fast mode',\n description: '~2.5× faster output at premium pricing',\n costMultiplier: { input: 2, output: 2, cacheRead: 2, cacheWrite: 2 },\n}\n\nconst FAST_MODE_OPTION_LEGACY: ModelOption = {\n ...FAST_MODE_OPTION,\n description: '~2.5× faster output at 6× pricing',\n costMultiplier: { input: 6, output: 6, cacheRead: 6, cacheWrite: 6 },\n}\n\n/** Model ids that accept `speed: \"fast\"`, mapped to their fast-mode option. */\nexport const FAST_MODE_OPTIONS: Readonly<Record<string, ModelOption>> = {\n 'claude-opus-4-8': FAST_MODE_OPTION,\n 'claude-opus-4-7': FAST_MODE_OPTION_LEGACY,\n 'claude-opus-4-6': FAST_MODE_OPTION_LEGACY,\n}\n\n/**\n * The price multiplier to apply to a model's base rates when fast mode is\n * active, or `undefined` when the model has no fast-mode pricing (i.e. doesn't\n * support it). Mirrors {@link FAST_MODE_OPTIONS} — kept as a separate accessor\n * so the cost layer can stay decoupled from the picker-facing option objects.\n */\nexport function fastModeCostMultiplier(modelId: string): ModelOption['costMultiplier'] {\n return FAST_MODE_OPTIONS[modelId]?.costMultiplier\n}\n\n/**\n * Claude Fable 5 — GA 2026-06-09. Anthropic's most capable widely released\n * model: $10/$50 per MTok, 1M context, 128k output, adaptive thinking\n * (always on), vision. No fast-mode support (Opus 4.6/4.7/4.8 only).\n *\n * Claude Opus 4.8 — shipped 2026-05-28. Same price/context/output as Opus 4.7\n * ($5/$25 per MTok, 1M context, 128k output), adaptive thinking, vision.\n * See https://www.anthropic.com/news/claude-opus-4-8.\n *\n * Claude Mythos 5 is deliberately absent: it's invitation-only (Project\n * Glasswing), so it would be a dead picker entry for virtually all users.\n *\n * See https://platform.claude.com/docs/en/about-claude/models/overview.\n */\nexport const ANTHROPIC_EXTRA_MODELS: readonly ModelInfo[] = [\n {\n id: 'claude-fable-5',\n name: 'Claude Fable 5',\n reasoning: true,\n input: ['text', 'image'],\n cost: { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },\n contextWindow: 1_000_000,\n maxTokens: 128_000,\n provider: 'anthropic',\n },\n {\n id: 'claude-opus-4-8',\n name: 'Claude Opus 4.8',\n reasoning: true,\n input: ['text', 'image'],\n cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },\n contextWindow: 1_000_000,\n maxTokens: 128_000,\n provider: 'anthropic',\n options: [FAST_MODE_OPTION],\n },\n]\n","// Type-only imports — erased at build time. Safe when the peer dep is not installed\n// at consume time; the types only need to resolve during Zidane's own build\n// (where `@anthropic-ai/sdk` is present as a devDependency).\nimport type Anthropic from '@anthropic-ai/sdk'\nimport type { Model } from '@anthropic-ai/sdk/resources'\nimport type { Provider, StreamCallbacks, ToolResult, ToolSpec, TurnResult } from '.'\nimport type { ClassifiedError } from '../errors'\nimport type { PromptPart, SessionContentBlock, SessionMessage, ThinkingLevel, TurnFinishReason, TurnUsage } from '../types'\nimport type { OAuthParams } from './oauth'\nimport { fastModeCostMultiplier } from '../chat/anthropic-models'\nimport { classifyErrorPrelude, isRetryableHttpStatus, matchesContextExceeded, matchesToolPairingError } from '../errors'\nimport { lazyAsync } from '../lazy'\nimport { unsupportedMediaError } from '../prompt'\nimport { fromAnthropic, toAnthropic } from '../session/messages'\nimport { renderSystemForWire, splitSystemPrompt } from '../system-prompt'\nimport { fillEstimatedCost } from './cost'\nimport { extractRuntimeCredentials, readOAuthCredentials, resolveOAuthApiKey } from './oauth'\nimport { sanitizeToolSpecs } from './schema-sanitize'\n\ntype AnthropicCtor = typeof Anthropic\ntype AnthropicInstance = InstanceType<AnthropicCtor>\n\n// Lazy-loaded SDK class. `@anthropic-ai/sdk` is an OPTIONAL peer dependency: the\n// module is imported only when the anthropic provider opens a connection.\nconst loadAnthropicSdk = lazyAsync(async (): Promise<AnthropicCtor> => {\n try {\n const mod = await import('@anthropic-ai/sdk')\n return mod.default as unknown as AnthropicCtor\n }\n catch (err) {\n throw new Error(\n 'The `anthropic` provider requires the `@anthropic-ai/sdk` package, which is an optional peer dependency. '\n + 'Install it with your package manager (e.g. `bun add @anthropic-ai/sdk`).',\n err instanceof Error ? { cause: err } : undefined,\n )\n }\n})\n\n/**\n * Server-side context-management config — the body of `context_management` on\n * the Messages API. Typed loosely (Record-of-unknown) so we don't pin a specific\n * SDK schema version: the v0.90 SDK does not yet type this field, but the wire\n * format is stable behind the `context-management-2025-06-27` beta.\n *\n * See: https://docs.anthropic.com/en/docs/build-with-claude/context-management\n */\nexport interface AnthropicContextManagement {\n edits?: Array<Record<string, unknown>>\n [key: string]: unknown\n}\n\nexport interface AnthropicParams extends OAuthParams {\n defaultModel?: string\n /**\n * Optional override for the Anthropic SDK base URL. Honored end-to-end — headers and\n * routing pass through to the forwarded host. Useful for proxies (e.g. corporate\n * gateways, internal router).\n */\n baseURL?: string\n /**\n * Additional `anthropic-beta` flags to opt into. Merged with the OAuth-path\n * defaults (`claude-code-20250219`, `oauth-2025-04-20`); duplicates are\n * de-duped. Examples:\n *\n * - `'context-management-2025-06-27'` — server-side context compaction\n * (token-accurate; pair with {@link AnthropicParams.contextManagement}).\n * - `'token-efficient-tools-2026-03-28'` — terser tool_use wire format.\n * - `'interleaved-thinking-2025-05-14'` — think between tool calls within\n * one turn.\n * - `'redact-thinking-2026-02-12'` — replace large thinking blocks with\n * stubs server-side.\n * - `'prompt-caching-scope-2026-01-05'` — extended prompt-cache scope.\n *\n * Honored on both the OAuth and API-key paths.\n */\n extraBetas?: readonly string[]\n /**\n * Server-side context-management directive. Sent on the request body as\n * `context_management`. Requires the `context-management-2025-06-27` beta —\n * add it to {@link AnthropicParams.extraBetas}.\n *\n * Typed loosely so future Anthropic schema additions land without an SDK\n * bump. A typical compaction edit:\n *\n * ```ts\n * contextManagement: {\n * edits: [{\n * type: 'clear_tool_uses_20250919',\n * trigger: { type: 'input_tokens', value: 180_000 },\n * clear_at_least: { type: 'input_tokens', value: 140_000 },\n * clear_tool_inputs: ['Read', 'Bash', 'Grep'],\n * }],\n * }\n * ```\n */\n contextManagement?: AnthropicContextManagement\n /**\n * Generic pass-through for fields on the Messages API request body that the\n * SDK does not yet type. Spread into the request before the typed fields,\n * so explicit options ({@link AnthropicParams.contextManagement} and the\n * built-in fields like `model` / `tools` / `messages`) win on collision.\n *\n * Forward-compat escape hatch for new Anthropic betas — when a future flag\n * ships before zidane has a dedicated typed knob, set it here without\n * waiting on a release. Most fields will still need the matching beta in\n * {@link AnthropicParams.extraBetas}.\n */\n extraBodyParams?: Record<string, unknown>\n /**\n * Extra HTTP headers sent on every request, merged into the SDK's\n * `defaultHeaders`. Honored on both the OAuth and API-key paths.\n *\n * Use for routing/attribution headers a proxy or gateway in front of the\n * `baseURL` consumes (e.g. a target-provider selector). Reserved zidane\n * headers (`anthropic-beta`, `user-agent`, `x-app`, and the OAuth browser\n * flag) take precedence on collision.\n *\n * May be a **function** resolved per request (awaited) instead of a static\n * map — the seam for gateways minting short-lived/signed auth tokens that a\n * long, suspended, or replayed durable run outlives. The resolver runs on\n * every `stream()` / `countTokens()` call, so it can return a freshly-minted\n * token each turn (e.g. `{ authorization: \\`Bearer ${await mintWorkToken()}\\` }`)\n * without rebuilding the provider.\n */\n extraHeaders?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>)\n}\n\n/** Beta flags sent unconditionally on the OAuth path (Claude Code parity). */\nconst OAUTH_DEFAULT_BETAS = ['claude-code-20250219', 'oauth-2025-04-20'] as const\n\n/**\n * Beta flag that enables the `speed: \"fast\"` request field (fast mode — up to\n * 2.5× output tokens/sec on Opus 4.6/4.7/4.8 at premium pricing). Sent as an\n * `anthropic-beta` header only on requests that opt into fast mode; absent\n * otherwise so standard requests keep a byte-stable header for prompt caching.\n *\n * See: https://platform.claude.com/docs/en/build-with-claude/fast-mode\n */\nconst FAST_MODE_BETA = 'fast-mode-2026-02-01'\nconst API_KEY_RESERVED_EXTRA_HEADER_NAMES = new Set(['anthropic-beta'])\nconst OAUTH_RESERVED_EXTRA_HEADER_NAMES = new Set([\n 'anthropic-beta',\n 'anthropic-dangerous-direct-browser-access',\n 'user-agent',\n 'x-app',\n])\n\nfunction filterReservedExtraHeaders(\n extraHeaders: Record<string, string> | undefined,\n reservedNames: ReadonlySet<string>,\n): Record<string, string> {\n const out: Record<string, string> = {}\n for (const [name, value] of Object.entries(extraHeaders ?? {})) {\n if (!reservedNames.has(name.toLowerCase()))\n out[name] = value\n }\n return out\n}\n\n/**\n * Build the `anthropic-beta` header value — OAuth defaults plus caller-supplied\n * extras, de-duped while preserving order. Returns `undefined` when no betas\n * apply (non-OAuth, no extras).\n */\nexport function resolveAnthropicBetas(\n isOAuth: boolean,\n extraBetas?: readonly string[],\n): string | undefined {\n const seen = new Set<string>()\n const out: string[] = []\n if (isOAuth) {\n for (const b of OAUTH_DEFAULT_BETAS) {\n if (!seen.has(b)) { seen.add(b); out.push(b) }\n }\n }\n if (extraBetas) {\n for (const b of extraBetas) {\n if (typeof b === 'string' && b.length > 0 && !seen.has(b)) {\n seen.add(b)\n out.push(b)\n }\n }\n }\n return out.length > 0 ? out.join(',') : undefined\n}\n\nfunction getConfiguredApiKey(anthropicParams?: AnthropicParams): string {\n if (anthropicParams?.apiKey)\n return anthropicParams.apiKey\n\n if (anthropicParams?.access)\n return anthropicParams.access\n\n if (process.env.ANTHROPIC_API_KEY)\n return process.env.ANTHROPIC_API_KEY\n\n // `readOAuthCredentials` tolerates a missing or corrupted file and returns `{}`\n // in both cases, so stale/half-written creds never crash agent construction.\n const access = readOAuthCredentials().anthropic?.access\n if (typeof access === 'string' && access.length > 0)\n return access\n\n throw new Error('No API key found. Run `bun run auth` first.')\n}\n\n/**\n * Resolve {@link AnthropicParams.extraHeaders} to a concrete map. A function\n * form is invoked (and awaited) per call so gateway auth tokens can refresh\n * each turn; a static map passes through.\n */\nasync function resolveExtraHeaders(\n value: AnthropicParams['extraHeaders'],\n): Promise<Record<string, string> | undefined> {\n return typeof value === 'function' ? value() : value\n}\n\nfunction createClient(\n SDK: AnthropicCtor,\n apiKey: string,\n isOAuth: boolean,\n baseURL?: string,\n extraBetas?: readonly string[],\n extraHeaders?: Record<string, string>,\n): AnthropicInstance {\n const base = baseURL ? { baseURL } : {}\n const betaHeader = resolveAnthropicBetas(isOAuth, extraBetas)\n if (isOAuth) {\n const callerHeaders = filterReservedExtraHeaders(extraHeaders, OAUTH_RESERVED_EXTRA_HEADER_NAMES)\n const defaultHeaders: Record<string, string> = {\n ...callerHeaders,\n 'anthropic-dangerous-direct-browser-access': 'true',\n 'user-agent': 'zidane/2.0.0',\n 'x-app': 'cli',\n }\n if (betaHeader)\n defaultHeaders['anthropic-beta'] = betaHeader\n return new SDK({\n apiKey: null,\n authToken: apiKey,\n dangerouslyAllowBrowser: true,\n defaultHeaders,\n ...base,\n })\n }\n // API-key path — only emit the beta header when the caller actually opted in.\n const defaultHeaders = filterReservedExtraHeaders(extraHeaders, API_KEY_RESERVED_EXTRA_HEADER_NAMES)\n if (betaHeader)\n defaultHeaders['anthropic-beta'] = betaHeader\n return new SDK({\n apiKey,\n ...(Object.keys(defaultHeaders).length > 0 ? { defaultHeaders } : {}),\n ...base,\n })\n}\n\ntype BudgetedThinkingLevel = Exclude<ThinkingLevel, 'off' | 'adaptive'>\n\n/**\n * Map `ThinkingLevel` budgeted tiers to Anthropic's `output_config.effort`\n * enum. `minimal` collapses to `low` (the closest equivalent — Anthropic does\n * not have a sub-`low` tier).\n */\nconst EFFORT_FOR_LEVEL: Record<BudgetedThinkingLevel, 'low' | 'medium' | 'high' | 'xhigh' | 'max'> = {\n minimal: 'low',\n low: 'low',\n medium: 'medium',\n high: 'high',\n xhigh: 'xhigh',\n max: 'max',\n}\n\n/**\n * Description of the Anthropic-side thinking configuration derived from a\n * ThinkingLevel. `null` means no thinking-related field should be set on the\n * request.\n *\n * Two shapes:\n * - `adaptive` — `thinking.type='adaptive'`. The model self-budgets per turn.\n * When `effort` is set, also emits `output_config.effort` to hint at the\n * level of reasoning the caller wants. This is the post-Claude-4.6 default\n * for level-based control and avoids the `thinking.type='enabled'`\n * deprecation warning. When `maxTokensCap` is set, the request builder\n * reduces `max_tokens` to `min(max_tokens, maxTokensCap)` — adaptive thinks\n * and replies inside one envelope, so capping the envelope soft-bounds the\n * thinking too.\n * - `enabled` — explicit-budget path: `thinking.type='enabled'` with an\n * explicit `budget_tokens`. `maxTokensBump` is added to the request's\n * `max_tokens` so the budget sits on top of the response cap. The only\n * way to pin a precise token budget; Anthropic emits a deprecation\n * warning for this shape on opus 4.6+, so we only route here when the\n * caller explicitly sets `behavior.thinkingBudget`.\n *\n * In both shapes Anthropic requires `temperature` to be `1`.\n */\nexport type AnthropicThinkingPlan\n = | { kind: 'enabled', budgetTokens: number, maxTokensBump: number }\n | { kind: 'adaptive', effort?: 'low' | 'medium' | 'high' | 'xhigh' | 'max', maxTokensCap?: number }\n\n/**\n * Decide how to translate a `ThinkingLevel` into Anthropic's request shape.\n * Pure / synchronous — exported so tests can assert routing without standing\n * up the SDK.\n *\n * Routing rules:\n * - `'off'` → `null` (no thinking field, no effort hint).\n * - `'adaptive'` → adaptive thinking with no effort hint (model decides\n * everything). When `customBudget` is set, it is carried as `maxTokensCap`\n * so the request builder caps `max_tokens` accordingly — adaptive has no\n * native budget knob, but capping the response envelope soft-bounds the\n * thinking that lives inside it.\n * - Budgeted levels → adaptive thinking with an `effort` hint, unless\n * `customBudget` is provided.\n * - Any level + `customBudget` → explicit-budget `enabled` path. The caller\n * has opted into precise budget control and accepts the Anthropic\n * deprecation warning that comes with it on opus 4.6+. `'adaptive'` is the\n * sole exception: it never falls back to enabled, since adaptive is the\n * current Anthropic API surface for self-budgeted thinking.\n */\nexport function planAnthropicThinking(\n level: ThinkingLevel,\n customBudget?: number,\n): AnthropicThinkingPlan | null {\n if (level === 'off')\n return null\n if (level === 'adaptive') {\n if (typeof customBudget === 'number' && customBudget > 0)\n return { kind: 'adaptive', maxTokensCap: customBudget }\n return { kind: 'adaptive' }\n }\n if (customBudget !== undefined) {\n return { kind: 'enabled', budgetTokens: customBudget, maxTokensBump: customBudget }\n }\n return { kind: 'adaptive', effort: EFFORT_FOR_LEVEL[level] }\n}\n\n/**\n * Map Anthropic's native `stop_reason` to the zidane `TurnFinishReason` union.\n *\n * `pause_turn` and `model_context_window_exceeded` are 4.6+ stop reasons that\n * pre-Z21 collapsed to `'other'` and silently terminated the run. They now\n * map to `'pause'` and `'length'` respectively, and the surrounding caller\n * adjusts the `done` flag so the loop can recover.\n */\nfunction mapStopReason(stopReason: string | null | undefined): TurnFinishReason | undefined {\n if (!stopReason)\n return undefined\n switch (stopReason) {\n case 'end_turn':\n case 'stop_sequence':\n return 'stop'\n case 'tool_use':\n return 'tool-calls'\n case 'max_tokens':\n case 'model_context_window_exceeded':\n // 4.6+: response bumped against the model's context window mid-stream.\n // Treat as a length-class stop so consumers can prune + retry; the\n // partial assistant response is preserved.\n return 'length'\n case 'refusal':\n return 'content-filter'\n // 4.6+: server-side mid-turn pause for long thinking. The loop\n // continues with a synthetic continue message rather than terminating.\n case 'pause_turn':\n return 'pause'\n default:\n return 'other'\n }\n}\n\n/**\n * Conservative per-family output ceiling, used ONLY as a fallback when pi-ai's\n * model registry doesn't know the model (or pi-ai is externalized) — see\n * {@link resolveAnthropicMaxOutput}, which is the primary path. These are\n * non-beta floors we can always send without a 400; the registry reports the\n * real (often much higher, e.g. Opus 4.x = 128K native) max when available.\n */\nexport function defaultAnthropicMaxTokens(modelId: string): number {\n const id = modelId.toLowerCase()\n if (id.includes('haiku'))\n return 8192\n if (id.includes('sonnet'))\n return 64000\n // opus + anything unrecognized → 32000: a safe floor when the registry is\n // unavailable. The registry lifts this to the model's true max when present.\n return 32000\n}\n\n/**\n * Lazily-loaded pi-ai model-registry getter (cached; keeps the multi-MB\n * registry out of the static bundle, mirroring `cost.ts`). Returns `null` when\n * pi-ai is externalized, in which case callers fall back to the family map.\n */\nconst loadPiModelGetter = lazyAsync(\n async (): Promise<((provider: string, model: string) => { maxTokens?: number } | undefined) | null> => {\n try {\n const mod = await import('@earendil-works/pi-ai/providers/all')\n return mod.getBuiltinModel as unknown as (provider: string, model: string) => { maxTokens?: number } | undefined\n }\n catch {\n return null\n }\n },\n)\n\n/**\n * Resolve a model's max output tokens. Prefers pi-ai's bundled registry — the\n * authoritative source the SDK itself uses to build requests, so e.g. Opus 4.x\n * reports its full native 128K (no extended-output beta required) — and falls\n * back to {@link defaultAnthropicMaxTokens} only when the registry lacks the\n * model. Used as BOTH the default (when the caller leaves `maxTokens` unset)\n * and a hard clamp, since a `max_tokens` above the model's max would 400.\n */\nexport async function resolveAnthropicMaxOutput(modelId: string): Promise<number> {\n const getModel = await loadPiModelGetter()\n const registryMax = getModel?.('anthropic', modelId)?.maxTokens\n return typeof registryMax === 'number' && registryMax > 0\n ? registryMax\n : defaultAnthropicMaxTokens(modelId)\n}\n\nconst EPHEMERAL: Anthropic.CacheControlEphemeral = { type: 'ephemeral' }\n\n/**\n * Mutate an Anthropic request in place to add cache breakpoints on the three\n * stable prefixes:\n * 1. System prompt — `cache_control` on the static prefix only (see below).\n * 2. Tool definitions — last tool.\n * 3. Conversation — last content block of the last message.\n *\n * Each breakpoint tells Anthropic to cache the prefix ending at that block;\n * subsequent turns reuse the cached prefix and pay only for the delta. Safe\n * no-op when any prefix is empty (no tools, empty system, etc.).\n *\n * System-prompt boundary handling (`SYSTEM_PROMPT_BOUNDARY`):\n *\n * - When `originalSystem` is provided AND carries the marker, the system\n * block is rewritten as a 2-block array: static (cached) then dynamic\n * (uncached). The caller is expected to have already stripped the marker\n * from `params.system` via {@link renderSystemForWire} — this helper\n * only re-splits to apply `cache_control` cleanly.\n * - When `originalSystem` is omitted or marker-free, the existing single-\n * block treatment applies: `cache_control` on the whole system string.\n * - Array system prompts: existing semantics — `cache_control` lands on the\n * last block. Callers building their own multi-block layout own the\n * breakpoint placement.\n */\nexport function applyAnthropicCacheBreakpoints(\n params: Anthropic.MessageCreateParamsStreaming,\n originalSystem?: string,\n): void {\n if (typeof params.system === 'string') {\n if (params.system.length > 0) {\n // `originalSystem` is the authoritative source for the boundary split\n // — `params.system` may have been pre-rendered by `renderSystemForWire`.\n // Fall back to `params.system` itself when no original was supplied\n // (or an empty original) so an external caller that embedded a marker\n // directly doesn't leak it into the cached block on the wire.\n const splitSource = originalSystem && originalSystem.length > 0\n ? originalSystem\n : params.system\n const parts = splitSystemPrompt(splitSource)\n if (parts.dynamic.length > 0) {\n const blocks: Anthropic.TextBlockParam[] = []\n if (parts.static.length > 0)\n blocks.push({ type: 'text', text: parts.static, cache_control: EPHEMERAL })\n blocks.push({ type: 'text', text: parts.dynamic })\n params.system = blocks\n }\n else {\n // No marker — wrap the (already marker-free) string in a single\n // cached block. `parts.static` equals `splitSource` in this branch.\n params.system = [{ type: 'text', text: parts.static, cache_control: EPHEMERAL }]\n }\n }\n }\n else if (Array.isArray(params.system) && params.system.length > 0) {\n const lastIdx = params.system.length - 1\n params.system = params.system.map((block, i) =>\n i === lastIdx ? { ...block, cache_control: EPHEMERAL } : block,\n )\n }\n\n if (params.tools && params.tools.length > 0) {\n const lastIdx = params.tools.length - 1\n params.tools = params.tools.map((tool, i) =>\n i === lastIdx ? { ...tool, cache_control: EPHEMERAL } : tool,\n ) as Anthropic.ToolUnion[]\n }\n\n if (params.messages.length === 0)\n return\n const lastMsgIdx = params.messages.length - 1\n const lastMsg = params.messages[lastMsgIdx]\n if (typeof lastMsg.content === 'string') {\n if (lastMsg.content.length === 0)\n return\n params.messages[lastMsgIdx] = {\n ...lastMsg,\n content: [{ type: 'text', text: lastMsg.content, cache_control: EPHEMERAL }],\n }\n return\n }\n if (!Array.isArray(lastMsg.content) || lastMsg.content.length === 0)\n return\n const blocks = lastMsg.content\n // `ThinkingBlockParam` and `RedactedThinkingBlockParam` do not accept `cache_control`\n // in the SDK types — walk backward past any trailing thinking blocks to find the\n // nearest cache-eligible block. Returns -1 when every block is a thinking variant,\n // which skips this breakpoint; system + tools still carry the cache prefix.\n let targetIdx = blocks.length - 1\n while (targetIdx >= 0 && isThinkingBlock(blocks[targetIdx]))\n targetIdx -= 1\n if (targetIdx < 0)\n return\n const nextBlocks = blocks.slice()\n nextBlocks[targetIdx] = { ...nextBlocks[targetIdx], cache_control: EPHEMERAL } as typeof nextBlocks[number]\n params.messages[lastMsgIdx] = { ...lastMsg, content: nextBlocks }\n}\n\nfunction isThinkingBlock(block: { type: string }): boolean {\n return block.type === 'thinking' || block.type === 'redacted_thinking'\n}\n\n/**\n * Duck-type check for an Anthropic SDK `APIError` — avoids a runtime dependency\n * on `@anthropic-ai/sdk` so `classifyError` stays usable even when the optional\n * peer dep isn't loaded (e.g. host code calling it on an unrelated provider's\n * error).\n *\n * Anthropic's APIError shape: `.status: number` + `.error` (parsed body, object\n * or null). Plain `Error`s don't have `.status` + `.error`.\n */\nfunction looksLikeAnthropicApiError(err: unknown): boolean {\n if (!err || typeof err !== 'object')\n return false\n const e = err as { status?: unknown, error?: unknown }\n return typeof e.status === 'number' && 'error' in e\n}\n\n/**\n * Anthropic SSE `error` event types that warrant a retry. `overloaded_error`\n * is the headline case (capacity event mid-stream); `api_error` and\n * `timeout_error` are both server-side transient failures. `invalid_request_error`,\n * `authentication_error`, `permission_error`, `not_found_error`, `billing_error`\n * are terminal — retrying them would just hammer a bad request.\n *\n * `rate_limit_error` here would be redundant: the SDK's pre-stream retry\n * handles 429, and a mid-stream `rate_limit_error` is exceedingly rare. We\n * include it anyway for symmetry with the HTTP-level retry policy.\n */\nconst SSE_RETRYABLE_ERROR_TYPES = new Set<string>([\n 'overloaded_error',\n 'api_error',\n 'timeout_error',\n 'rate_limit_error',\n])\n\n/**\n * Try to parse an Anthropic SSE `error` event payload out of a thrown error's\n * message. The SDK's `MessageStream` surfaces mid-stream error events by\n * throwing an `AnthropicError` whose `.message` contains the raw JSON shape:\n *\n * {\"type\":\"error\",\"error\":{\"type\":\"overloaded_error\",\"message\":\"Overloaded\"},\"request_id\":\"req_...\"}\n *\n * Returns the inner `{ type, message }` or `null` when the message isn't\n * the canonical SSE error shape. Tolerant: silently returns `null` for\n * non-string input, non-JSON content, or any shape mismatch.\n */\nfunction matchSseErrorEvent(message: unknown): { type: string, message: string } | null {\n if (typeof message !== 'string' || message.length === 0)\n return null\n // Cheap pre-check to avoid JSON.parse on every error message.\n if (!message.includes('\"type\":\"error\"') && !message.includes('\"type\": \"error\"'))\n return null\n // The message may be the JSON itself or have it embedded (some SDK paths\n // prefix a human prelude). Scan for the first `{` and try from there.\n const start = message.indexOf('{')\n if (start < 0)\n return null\n try {\n const parsed = JSON.parse(message.slice(start)) as unknown\n if (!parsed || typeof parsed !== 'object')\n return null\n const outer = parsed as { type?: unknown, error?: unknown }\n if (outer.type !== 'error' || !outer.error || typeof outer.error !== 'object')\n return null\n const inner = outer.error as { type?: unknown, message?: unknown }\n if (typeof inner.type !== 'string')\n return null\n return {\n type: inner.type,\n message: typeof inner.message === 'string' ? inner.message : inner.type,\n }\n }\n catch {\n return null\n }\n}\n\n/**\n * Classify an Anthropic SDK / HTTP error for typed-error wrapping.\n *\n * - `prompt is too long` (400 invalid_request_error) → `context_exceeded`.\n * - `'tool_use' ids were found without 'tool_result' blocks` /\n * `'tool_result must be preceded by a tool_call'` (400) →\n * `tool_pairing_corruption`. The local repair pass should have caught\n * this — the typed error tells consumers their pre-send pipeline is\n * shipping orphaned blocks somehow (custom `context:transform` hook,\n * bypassed loop, mid-stream session edit) so they can patch the root\n * cause instead of chasing 400s.\n * - Any other Anthropic `APIError`-shaped value → `provider_error` with the\n * native status/type code.\n * - Unknown errors → `null` (loop wraps in `AgentProviderError` generically).\n *\n * Anthropic's wire error shape is `{ type: 'error', error: { type, message } }` — the\n * SDK stores the parsed body on `err.error`. We walk both levels so callers see the\n * most specific `providerCode` we can find.\n */\nexport function classifyAnthropicError(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 interface InnerError { type?: string, message?: string }\n interface AnthError {\n name?: string\n message?: string\n status?: number\n error?: InnerError & { error?: InnerError }\n }\n const anyErr = err as AnthError\n\n // Mid-stream SSE error event — the SDK's `MessageStream` surfaces these by\n // throwing an `AnthropicError` (plain Error subclass, no `.status`) whose\n // `.message` carries the raw SSE error JSON, e.g.\n // `{\"type\":\"error\",\"error\":{\"type\":\"overloaded_error\",\"message\":\"Overloaded\"},\"request_id\":\"...\"}`.\n // The pre-stream HTTP retry inside the SDK can't help here — the response\n // already opened with a 200, then the server emitted `event: error`\n // mid-stream. Classify these so the loop-level retry kicks in.\n //\n // Guarded on `!looksLikeAnthropicApiError(err)` so we don't shadow the\n // structured `APIError` branch below (which also stringifies its body into\n // `.message` and would otherwise match this same pattern).\n if (!looksLikeAnthropicApiError(err)) {\n const sseError = matchSseErrorEvent(anyErr.message)\n if (sseError) {\n return {\n kind: 'provider_error',\n providerCode: sseError.type,\n message: sseError.message,\n retryable: SSE_RETRYABLE_ERROR_TYPES.has(sseError.type),\n }\n }\n return null\n }\n\n // Prefer the inner error when present (Anthropic's nested shape).\n const innerType = anyErr.error?.error?.type\n const outerType = anyErr.error?.type\n const nativeType = innerType && innerType !== 'error' ? innerType : outerType\n const message = anyErr.error?.error?.message\n ?? anyErr.error?.message\n ?? anyErr.message\n ?? ''\n\n if (matchesContextExceeded(message)) {\n return {\n kind: 'context_exceeded',\n providerCode: nativeType ?? 'invalid_request_error',\n message,\n }\n }\n\n if (matchesToolPairingError(message)) {\n return {\n kind: 'tool_pairing_corruption',\n providerCode: nativeType ?? 'invalid_request_error',\n message,\n }\n }\n\n const status = anyErr.status\n const retryable = typeof status === 'number'\n ? isRetryableHttpStatus(status)\n : undefined\n\n return {\n kind: 'provider_error',\n providerCode: nativeType ?? (status ? String(status) : undefined),\n message,\n ...(retryable !== undefined ? { retryable } : {}),\n }\n}\n\nfunction shouldFallbackFromFastMode(err: unknown): boolean {\n const classified = classifyAnthropicError(err)\n if (classified?.kind === 'provider_error' && classified.retryable === true)\n return true\n\n const message = err instanceof Error ? err.message : String(err)\n return /\\b(?:fast|speed)\\b/i.test(message)\n && /capacity|overload|rate|unavailable|unsupported|not supported|not permitted|extra inputs|invalid/i.test(message)\n}\n\n/**\n * Build a user `SessionMessage` from multimodal prompt parts.\n *\n * - Text parts → text blocks.\n * - Image parts → base64 image blocks.\n * - Document parts → canonical document blocks. `toAnthropic` emits native\n * document blocks only for base64 PDFs; text/non-PDF documents fall back to\n * attachment-tagged text to avoid invalid Anthropic source media types.\n *\n * The format mirrors `defaultPromptMessage`'s output on shape — Anthropic-specific\n * handling differs only in that `promptMessage` being present tells the agent loop\n * to route PromptPart[] through this function rather than throwing on base64 docs.\n */\nfunction anthropicPromptMessage(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 })\n continue\n }\n\n if (part.type === 'audio')\n throw unsupportedMediaError('audio', 'anthropic')\n if (part.type === 'video')\n throw unsupportedMediaError('video', 'anthropic')\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\n return { role: 'user', content }\n}\n\nexport function anthropic(\n anthropicParams?: AnthropicParams,\n): Provider {\n const configuredApiKey = getConfiguredApiKey(anthropicParams)\n // Factory-time snapshot — used only for the static `meta.isOAuth` flag.\n // Keys resolve per call (env / file creds can change between construction\n // and a turn), so `countTokens` / `stream` re-derive OAuth-ness from the\n // freshly resolved key instead of trusting this.\n const isOAuth = configuredApiKey.includes('sk-ant-oat')\n const defaultModel = anthropicParams?.defaultModel || 'claude-opus-4-8'\n let runtimeCredentials = extractRuntimeCredentials(anthropicParams)\n\n return {\n name: 'anthropic',\n meta: {\n defaultModel,\n isOAuth,\n // True when server-side context management is configured: the API may\n // clear old tool results mid-conversation without the client knowing.\n // The loop reads this to disable the read_file dedup, whose client-side\n // hash can't tell that a server-cleared body left the wire — otherwise\n // the \"unchanged\" replay stub would strand the model on missing content.\n clearsContextServerSide: Boolean(anthropicParams?.contextManagement),\n capabilities: {\n vision: true,\n imageInToolResult: true,\n documents: true,\n // Anthropic Messages API has no audio/video input content block.\n audio: false,\n video: false,\n // Anthropic Messages API supports `web_search_20250305` as a\n // server-side tool — the model issues searches and consumes\n // results without a client-visible tool_use round-trip. The\n // canonical `web_search` `ToolDef` ships as a cross-provider\n // fallback; when this capability is set, `formatTools` rewrites\n // it to the server-tool wire shape and drops the function-tool\n // version so the model uses the native path.\n nativeWebSearch: { maxUses: 5 },\n },\n },\n\n formatTools(tools: ToolSpec[]): Anthropic.ToolUnion[] {\n // Sanitize before forwarding — Anthropic 400s on the strict shapes\n // (root `$ref`, root `oneOf|anyOf|allOf`, `type: ['object', 'null']`,\n // missing `properties`, `nullable: true`) that show up in MCP-server\n // schemas. The strict `anthropic` profile coerces those without\n // changing semantics for valid schemas. Warnings are de-duped to\n // `console.warn` so the same bad schema doesn't spam the log every\n // turn.\n //\n // `web_search` is special-cased: when present, it's emitted as the\n // server-tool `web_search_20250305` block and removed from the\n // function-tools list. The model invokes it without a client-side\n // round-trip; Anthropic injects the search results into the\n // assistant message directly. See the `nativeWebSearch` capability\n // declared above.\n const nativeWebSearch = this.meta.capabilities?.nativeWebSearch\n // `hasWebSearch` is the existence check; `fnTools` is the filtered\n // list. Computing existence via `tools.some` (rather than comparing\n // pre/post-filter lengths) makes the rewrite intent obvious at a\n // glance.\n const hasWebSearch = !!nativeWebSearch && tools.some(t => t.name === 'web_search')\n const fnTools = hasWebSearch ? tools.filter(t => t.name !== 'web_search') : tools\n const sanitized = sanitizeToolSpecs(fnTools, { profile: 'anthropic' })\n const out: Anthropic.ToolUnion[] = sanitized.map(t => ({\n name: t.name,\n description: t.description,\n input_schema: t.inputSchema as Anthropic.Tool['input_schema'],\n }))\n if (hasWebSearch && nativeWebSearch) {\n // `max_uses` is optional — omit when the capability doesn't pin a value\n // so Anthropic's server-side default applies.\n const block: Anthropic.WebSearchTool20250305 = {\n type: 'web_search_20250305',\n name: 'web_search',\n }\n if (typeof nativeWebSearch.maxUses === 'number')\n block.max_uses = nativeWebSearch.maxUses\n out.push(block)\n }\n return out\n },\n\n userMessage(content: string): SessionMessage {\n return { role: 'user', content: [{ type: 'text', text: content }] }\n },\n\n assistantMessage(content: string): SessionMessage {\n return { role: 'assistant', content: [{ type: 'text', text: content }] }\n },\n\n 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 promptMessage: anthropicPromptMessage,\n\n classifyError: classifyAnthropicError,\n\n async countTokens(payload, signal): Promise<number | null> {\n try {\n const SDK = await loadAnthropicSdk()\n const apiKey = await resolveOAuthApiKey(\n {\n provider: 'anthropic',\n providerId: 'anthropic',\n params: runtimeCredentials ? { ...anthropicParams, ...runtimeCredentials } : anthropicParams,\n envKey: 'ANTHROPIC_API_KEY',\n missingError: 'No API key found. Run `bun run auth` first.',\n refreshError: reason => `Anthropic OAuth token refresh failed. ${reason}`,\n },\n )\n // Derive OAuth-ness from the freshly resolved key — auth mode can\n // change between factory construction and this call (env var set,\n // creds file rotated), and a stale flag would skew the count's\n // system-prompt folding relative to what `stream()` actually sends.\n const callIsOAuth = apiKey.includes('sk-ant-oat')\n const client = createClient(\n SDK,\n apiKey,\n callIsOAuth,\n anthropicParams?.baseURL,\n anthropicParams?.extraBetas,\n await resolveExtraHeaders(anthropicParams?.extraHeaders),\n )\n\n // Mirror `stream()`'s wire-payload build so the count matches what is\n // actually sent: OAuth folds the real system prompt into a synthetic\n // user/assistant pair and sends the canonical Claude Code prompt as\n // the system block.\n const wireSystem = renderSystemForWire(payload.system)\n const system = callIsOAuth\n ? `You are Claude Code, Anthropic's official CLI for Claude.`\n : wireSystem\n const messages: SessionMessage[] = callIsOAuth && payload.system\n ? [\n { role: 'user', content: [{ type: 'text' as const, text: wireSystem }] },\n { role: 'assistant', content: [{ type: 'text' as const, text: 'Understood. I will proceed with these instructions above the rest of my system prompt.' }] },\n ...payload.messages,\n ]\n : [...payload.messages]\n\n const res = await client.messages.countTokens(\n {\n model: payload.model as Model,\n system,\n tools: payload.tools as Anthropic.Tool[],\n messages: messages.map(m => toAnthropic(m)) as Anthropic.MessageParam[],\n },\n signal ? { signal } : undefined,\n )\n return res.input_tokens\n }\n catch (err) {\n // Unreachable under the active auth / network — caller falls back to\n // the heuristic. Surface the cause when debugging so a silent auth /\n // scope rejection (e.g. OAuth lacking the count endpoint) is visible.\n if (process.env.ZIDANE_DEBUG_COUNT)\n console.error('[anthropic.countTokens] failed:', err)\n return null\n }\n },\n\n async stream(options, callbacks: StreamCallbacks): Promise<TurnResult> {\n const SDK = await loadAnthropicSdk()\n const apiKey = await resolveOAuthApiKey(\n {\n provider: 'anthropic',\n providerId: 'anthropic',\n params: runtimeCredentials ? { ...anthropicParams, ...runtimeCredentials } : anthropicParams,\n envKey: 'ANTHROPIC_API_KEY',\n missingError: 'No API key found. Run `bun run auth` first.',\n refreshError: reason => `Anthropic OAuth token refresh failed. Run \\`bun run auth --anthropic\\` again. ${reason}`,\n },\n {\n ...callbacks,\n async onOAuthRefresh(ctx) {\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 // Fast mode (`speed: \"fast\"`) is gated behind the `fast-mode-2026-02-01`\n // beta — without the header Anthropic rejects the body field with\n // `speed: Extra inputs are not permitted`. Inject it here (not in the\n // static default betas) so the header is present exactly when the request\n // will carry `speed: \"fast\"`, keeping non-fast requests byte-stable for\n // the prompt cache.\n const requestBetas = options.modelOptions?.fast\n ? [...(anthropicParams?.extraBetas ?? []), FAST_MODE_BETA]\n : anthropicParams?.extraBetas\n // Per-call OAuth detection — see the matching note in `countTokens`.\n const callIsOAuth = apiKey.includes('sk-ant-oat')\n const client = createClient(\n SDK,\n apiKey,\n callIsOAuth,\n anthropicParams?.baseURL,\n requestBetas,\n await resolveExtraHeaders(anthropicParams?.extraHeaders),\n )\n\n // System prompt injection is handled by agent.ts — we just pass it through.\n // For OAuth, the Claude Code API requires the system block to start with\n // the canonical Claude Code prompt — we prepend it to the real system prompt.\n // For OAuth, the CC API requires the system block to start with the canonical\n // Claude Code prompt. The real system prompt is injected as user+assistant messages.\n //\n // The boundary marker (`SYSTEM_PROMPT_BOUNDARY`) is structural metadata\n // for `applyAnthropicCacheBreakpoints`; strip it before the bytes hit\n // the wire so it never reaches the model. The original (un-rendered)\n // string is preserved as `options.system` and forwarded to the cache\n // helper below for an accurate static/dynamic split.\n const wireSystem = renderSystemForWire(options.system)\n const system = callIsOAuth\n ? `You are Claude Code, Anthropic's official CLI for Claude.`\n : wireSystem\n const messages: SessionMessage[] = callIsOAuth && options.system\n ? [\n { role: 'user', content: [{ type: 'text' as const, text: wireSystem }] },\n { role: 'assistant', content: [{ type: 'text' as const, text: 'Understood. I will proceed with these instructions above the rest of my system prompt.' }] },\n ...options.messages,\n ]\n : [...options.messages]\n const thinking = options.thinking ?? 'off'\n\n const modelId = options.model as Model\n // The model's max output, sourced from pi-ai's registry (Opus 4.x = 128K\n // native; conservative family fallback when the registry misses). Used as\n // BOTH the default (when `maxTokens` is unset — the field is required) AND\n // a hard clamp, since a value above the model's max would 400.\n const modelMaxOutput = await resolveAnthropicMaxOutput(modelId)\n\n // `context_management` is an untyped beta in SDK v0.90; widen `params` with\n // an optional field so we don't have to double-cast through `unknown` to\n // attach it below.\n const params: Anthropic.MessageCreateParamsStreaming & {\n context_management?: AnthropicContextManagement\n speed?: 'fast' | 'standard'\n } = {\n // Forward-compat escape hatch for un-typed beta fields. Spread first so\n // the typed core (model / max_tokens / system / tools / messages /\n // stream) and the explicit `context_management` below override on\n // collision — explicit always wins.\n ...((anthropicParams?.extraBodyParams ?? {}) as Record<string, unknown>),\n model: modelId,\n // Required field — default to the model's full max output when unset.\n // The ceiling (incl. a caller cap above the model max) is enforced once,\n // after the thinking plan, since the `enabled` path adds budget on top.\n max_tokens: options.maxTokens ?? modelMaxOutput,\n system,\n tools: options.tools as Anthropic.ToolUnion[],\n messages: messages.map(m => toAnthropic(m)) as Anthropic.MessageParam[],\n stream: true,\n }\n\n // Server-side context management (beta `context-management-2025-06-27`).\n // Skipped when the caller didn't pass a config — leaving the field off\n // the wire keeps requests valid on accounts that don't have the beta enabled.\n if (anthropicParams?.contextManagement)\n params.context_management = anthropicParams.contextManagement\n\n // Prompt caching — inject `cache_control: { type: 'ephemeral' }` breakpoints\n // on the three largest stable prefixes: system prompt, tool definitions, and\n // the last message's final content block. Three of Anthropic's four allowed\n // breakpoints; leaves headroom for a future thinking-block breakpoint.\n //\n // On the API-key path, `params.system` was derived from\n // `renderSystemForWire(options.system)` so the cache helper can split\n // the original on the `SYSTEM_PROMPT_BOUNDARY` marker and apply\n // `cache_control` to the static prefix only — per-turn churn in the\n // dynamic suffix no longer invalidates the cached doctrine above.\n //\n // On the OAuth path, `params.system` is the canonical Claude Code\n // prompt (`\"You are Claude Code...\"`) — `options.system` lives in the\n // injected user message instead, so we do NOT forward it here. Passing\n // it would replace the CC prompt with the user's doctrine. The user\n // message is still naturally cached via the last-message breakpoint\n // applied below; per-turn churn there bypasses the boundary feature on\n // OAuth, but parity with the pre-feature behavior is preserved.\n //\n // Skipped when `options.cache === false` (opt-out) or on any shape that would\n // produce an invalid request (empty system string, zero tools, empty message list).\n if (options.cache !== false)\n applyAnthropicCacheBreakpoints(params, callIsOAuth ? undefined : options.system)\n\n // Enable thinking / extended thinking when requested. The default path\n // for level-based control is `thinking.type='adaptive'` plus an\n // `output_config.effort` hint — Anthropic deprecated the explicit\n // `thinking.type='enabled' + budget_tokens` shape on opus 4.6+ and\n // recommends adaptive instead. Callers who need a precise token budget\n // can opt into the explicit-budget path by setting\n // `behavior.thinkingBudget`, accepting Anthropic's deprecation\n // warning in exchange. Either shape requires `temperature=1`.\n //\n // `display: 'summarized'` is set explicitly on both shapes — Opus 4.7\n // and Claude Mythos Preview silently flipped the default to\n // `'omitted'` (signature-only, no text), which makes the SDK fire\n // `thinking` deltas with empty `thinking` content and breaks every\n // host that surfaces traces live. Older models still default to\n // `'summarized'` so this is a no-op there.\n const plan = planAnthropicThinking(thinking, options.thinkingBudget)\n if (plan) {\n if (plan.kind === 'enabled') {\n params.thinking = { type: 'enabled', budget_tokens: plan.budgetTokens, display: 'summarized' }\n params.max_tokens = plan.maxTokensBump + params.max_tokens\n }\n else {\n params.thinking = { type: 'adaptive', display: 'summarized' }\n if (plan.effort)\n params.output_config = { effort: plan.effort }\n // Adaptive has no native budget knob — soft-cap the response envelope\n // (`max_tokens`) so unbounded thinking can't run away. Reduce only;\n // never raise above the caller's request.\n if (typeof plan.maxTokensCap === 'number' && plan.maxTokensCap > 0)\n params.max_tokens = Math.min(params.max_tokens, plan.maxTokensCap)\n }\n params.temperature = 1\n }\n\n // The `enabled` thinking path adds the budget ON TOP of `max_tokens`,\n // which — now that the base defaults to the model's full max — can push\n // the envelope past the model's hard ceiling → 400. Clamp the final value\n // so the budget shares the envelope rather than overflowing it.\n params.max_tokens = Math.min(params.max_tokens, modelMaxOutput)\n\n // Fast mode (`speed: \"fast\"`) — ~2.5× output throughput at premium\n // pricing. Supported only on Opus 4.6 / 4.7 / 4.8; the model-options\n // surface only offers it on those, and Anthropic 400s a request with\n // `speed` on an unsupported model. `speed` is an untyped beta field in\n // SDK v0.90 (widened on `params` above).\n if (options.modelOptions?.fast)\n params.speed = 'fast'\n\n // Tool choice forcing\n if (options.toolChoice) {\n if (options.toolChoice.type === 'tool' && options.toolChoice.name)\n params.tool_choice = { type: 'tool', name: options.toolChoice.name }\n else if (options.toolChoice.type === 'required')\n params.tool_choice = { type: 'any' }\n else\n params.tool_choice = { type: 'auto' }\n }\n\n const runStream = async (\n requestParams: typeof params,\n fastRequested: boolean,\n ): Promise<TurnResult> => {\n const s = client.messages.stream(requestParams, {\n signal: options.signal,\n })\n\n let text = ''\n let observedOutput = false\n\n s.on('text', (delta) => {\n observedOutput = true\n text += delta\n callbacks.onText(delta)\n })\n\n if (callbacks.onThinking) {\n s.on('thinking', (delta) => {\n observedOutput = true\n callbacks.onThinking!(delta)\n })\n }\n\n // Liveness signal for tool-only turns: `inputJson` fires as a\n // `tool_use` block's arguments stream in. Without it, a turn that\n // emits only tool calls (no text/thinking) never trips the loop's\n // \"first response observed\" gate and the first-response watchdog can\n // abort an actively-progressing stream. (B2)\n if (callbacks.onToolCallDelta) {\n s.on('inputJson', () => {\n observedOutput = true\n callbacks.onToolCallDelta!()\n })\n }\n\n // Server-tool blocks (e.g. `web_search_20250305`) execute on Anthropic's\n // side and arrive in the assistant content stream — not via our tool\n // loop. The SDK fires `contentBlock` once per block as it closes, so\n // listen for the two we care about and dispatch generic callbacks.\n // Other block types (text, thinking, tool_use, …) have their own paths\n // above / in `finalMessage()` — ignored here to avoid double-emission.\n if (callbacks.onServerToolUse || callbacks.onServerToolResult) {\n s.on('contentBlock', (block) => {\n observedOutput = true\n if (block.type === 'server_tool_use') {\n callbacks.onServerToolUse?.({\n id: block.id,\n name: block.name,\n input: (block.input as Record<string, unknown> | undefined) ?? {},\n })\n }\n else if (block.type === 'web_search_tool_result') {\n callbacks.onServerToolResult?.({\n toolUseId: block.tool_use_id,\n toolName: 'web_search',\n content: block.content,\n })\n }\n })\n }\n\n let response: Anthropic.Message\n try {\n response = await s.finalMessage()\n }\n catch (err) {\n if (fastRequested && !observedOutput && shouldFallbackFromFastMode(err)) {\n const standardParams = { ...requestParams }\n delete standardParams.speed\n return await runStream(standardParams, false)\n }\n throw err\n }\n\n const toolCalls = response.content\n .filter((b): b is Anthropic.ToolUseBlock => b.type === 'tool_use')\n .map(b => ({ id: b.id, name: b.name, input: b.input as Record<string, unknown> }))\n\n const finishReason = mapStopReason(response.stop_reason)\n\n // `pause_turn` is *not* terminal — the loop has to inject a continue\n // message and run another turn. Without this branch, the previous\n // `toolCalls.length === 0` shortcut would mark the run done at the\n // pause and silently drop the work in flight.\n const isPause = response.stop_reason === 'pause_turn'\n\n const rawUsage: TurnUsage = {\n input: response.usage.input_tokens,\n output: response.usage.output_tokens,\n cacheCreation: response.usage.cache_creation_input_tokens ?? undefined,\n cacheRead: response.usage.cache_read_input_tokens ?? undefined,\n ...(finishReason ? { finishReason } : {}),\n modelId: response.model ?? (options.model as string),\n }\n // OAuth = subscription traffic (Claude Pro/Max via a Claude Code\n // account): usage is included in the flat plan, not billed per\n // token. Estimating API-list prices for it produces large dollar\n // figures the user never pays — so `cost` stays absent and cost\n // surfaces (footer counter, usage-by-model rows) show tokens\n // without a price, matching Claude Code's own /cost behavior.\n // API-key turns estimate as before; a mixed session (OAuth primary\n // with an API-key fallback, or vice versa) prices each turn by how\n // it was actually served.\n const usage = callIsOAuth\n ? rawUsage\n : await fillEstimatedCost(rawUsage, 'anthropic', fastRequested\n ? fastModeCostMultiplier(response.model ?? (options.model as string))\n : undefined)\n return {\n assistantMessage: fromAnthropic({ role: 'assistant', content: response.content }),\n text,\n toolCalls,\n done: !isPause && (response.stop_reason === 'end_turn' || toolCalls.length === 0),\n usage,\n }\n }\n\n return await runStream(params, options.modelOptions?.fast === true)\n },\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cache-telemetry-BKAwsNxm.js","names":[],"sources":["../src/cache-telemetry.ts"],"sourcesContent":["/**\n * Per-dimension cache-break detection.\n *\n * The agent surfaces `cacheRead` / `cacheCreation` in {@link TurnUsage}, but\n * doesn't answer the load-bearing diagnostic question: *which* dimension\n * busted the cache? When `cacheCreation > 0` on turn N+1 with no obvious\n * reason, you want to know whether the system prompt, the tool list, the\n * thinking config, or the model id changed — without re-reading the loop\n * source.\n *\n * This module hashes each cache-affecting dimension at `turn:before`, then\n * on `turn:after` checks whether the latest turn read its share of the\n * cached prefix. If not, it diffs the previous turn's dimension hashes\n * against the current ones and logs the responsible dimension(s).\n *\n * Wire contract:\n *\n * - Install via {@link installCacheBreakLogger} on the agent's hook bus.\n * Returns an uninstall fn. Idempotent — calling install twice on the\n * same bus leaves two listeners; callers manage lifecycle.\n * - Output sink defaults to `console.error`; pass `log` to redirect into a\n * {@link Logger}, span attribute writer, or test buffer.\n * - Heuristic only: a missing cache read on turn 1 is expected (nothing\n * cached yet); the logger only reports from turn 2 onward.\n * - Pure helper {@link diffCacheDimensions} is exported for tests so the\n * diff logic can be exercised without standing up the loop.\n *\n * The dimensions tracked are the inputs the provider treats as part of the\n * cache key — `system`, `tools`, `model`, thinking config. Tools are hashed\n * after `provider.formatTools`, so MCP catalogues showing up mid-run also\n * register as a break. Messages are NOT tracked: the conversation grows by\n * design, and the last-message breakpoint is meant to advance every turn.\n */\n\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from './agent'\nimport type { StreamOptions } from './providers'\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Snapshot of per-request dimensions that contribute to the provider-side\n * prompt cache key. Hashed at `turn:before` and stashed for diffing on the\n * next request.\n *\n * Hashes are FNV-1a 32-bit (cheap, no allocations on the hot path,\n * collision rate good enough for diff detection — we're not signing\n * anything). For verbose output the helper also captures byte lengths so\n * `system: 1024 → 1031` reads like a real diff line.\n */\nexport interface CacheDimensionSnapshot {\n /** Hash of `options.system` (with boundary marker preserved). */\n systemHash: number\n /** Byte length of `options.system`. */\n systemBytes: number\n /** Hash of the JSON-serialized `options.tools` array. */\n toolsHash: number\n /** Number of tools in the array. */\n toolsCount: number\n /** Model id. */\n model: string\n /** Thinking level — `'off'` when absent. */\n thinking: string\n /** Exact thinking budget — `0` when absent. */\n thinkingBudget: number\n}\n\n/** Result of {@link diffCacheDimensions}. */\nexport interface CacheDimensionDiff {\n /** Dimension names whose hashes differ between the two snapshots. */\n changed: readonly CacheDimensionName[]\n /** Per-dimension details — only filled for changed dimensions. */\n details: Readonly<Partial<Record<CacheDimensionName, string>>>\n}\n\n/** Cache-affecting dimension names. */\nexport type CacheDimensionName\n = | 'system'\n | 'tools'\n | 'model'\n | 'thinking'\n | 'thinkingBudget'\n\n// ---------------------------------------------------------------------------\n// Configuration\n// ---------------------------------------------------------------------------\n\nexport interface CacheBreakLoggerOptions {\n /**\n * Sink for log lines. Defaults to `console.error`. Use a {@link Logger} or\n * a test buffer here when you want the output to land somewhere other than\n * stderr.\n */\n log?: (line: string) => void\n /**\n * When `true`, emit a log line on EVERY turn (not just cache breaks) with\n * the per-dimension hashes. Useful for offline correlation; off by default\n * to keep the logger zero-noise on happy paths.\n */\n verbose?: boolean\n /**\n * Threshold for \"this turn missed cache\". The logger reports when\n * `cacheRead < expectedReadAtLeast` AND the dimensions differ from the\n * previous turn. Defaults to `1` — any cache read at all means the\n * provider matched some prefix, so a strict \"did we get any cache?\" check\n * is the right signal. Raise to detect partial-prefix breaks (e.g. the\n * tools cache hit but the system prompt didn't, which lowers `cacheRead`\n * without zeroing it).\n */\n expectedReadAtLeast?: number\n}\n\n// ---------------------------------------------------------------------------\n// Hashing\n// ---------------------------------------------------------------------------\n\nconst FNV_OFFSET = 0x811C9DC5\nconst FNV_PRIME = 0x01000193\n\n/**\n * FNV-1a-style 32-bit hash over UTF-16 code units. Pure, allocation-free,\n * ~10 ns per KB. Cryptographic strength is irrelevant here — we only need\n * change detection, and this has effectively zero collision risk for the\n * small number of dimensions tracked.\n *\n * Feeds the FULL char code into the mix (same as read-state's\n * `hashContent`) — masking to the low byte would collapse code points that\n * share a low byte, so a non-ASCII system-prompt change could hash\n * identically and silently hide a cache-dimension drift.\n *\n * Exported for tests; callers shouldn't typically import directly.\n */\nexport function fnv1a32(s: string): number {\n let h = FNV_OFFSET\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i)\n // Multiply by FNV_PRIME mod 2^32, using `Math.imul` to keep within\n // 32-bit signed range (avoid >>> 0 ping-pong on every iteration).\n h = Math.imul(h, FNV_PRIME)\n }\n // Map signed → unsigned 32-bit so the printed hex is stable.\n return h >>> 0\n}\n\n/**\n * Build a {@link CacheDimensionSnapshot} from a `turn:before` payload.\n *\n * Pure — exported so hosts can compute snapshots outside the logger (e.g.\n * stamping them onto custom telemetry spans). The hash is deterministic on\n * byte-identical inputs.\n */\nexport function snapshotCacheDimensions(options: StreamOptions): CacheDimensionSnapshot {\n const toolsJson = JSON.stringify(options.tools ?? [])\n return {\n systemHash: fnv1a32(options.system),\n systemBytes: options.system.length,\n toolsHash: fnv1a32(toolsJson),\n toolsCount: Array.isArray(options.tools) ? options.tools.length : 0,\n model: options.model,\n thinking: options.thinking ?? 'off',\n thinkingBudget: options.thinkingBudget ?? 0,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Diffing\n// ---------------------------------------------------------------------------\n\n/**\n * Compare two snapshots and return the list of dimensions whose bytes\n * differ. The `details` map carries a short human-readable description per\n * changed dimension (e.g. `system: 1024 → 1031 bytes`).\n *\n * Pure / no I/O. Use this in tests to assert the diff logic; the logger\n * itself just formats and emits the result.\n */\nexport function diffCacheDimensions(\n prev: CacheDimensionSnapshot,\n curr: CacheDimensionSnapshot,\n): CacheDimensionDiff {\n const changed: CacheDimensionName[] = []\n const details: Partial<Record<CacheDimensionName, string>> = {}\n\n if (prev.systemHash !== curr.systemHash) {\n changed.push('system')\n details.system = `${prev.systemBytes} → ${curr.systemBytes} bytes (hash ${hex(prev.systemHash)} → ${hex(curr.systemHash)})`\n }\n if (prev.toolsHash !== curr.toolsHash) {\n changed.push('tools')\n details.tools = `${prev.toolsCount} → ${curr.toolsCount} tools (hash ${hex(prev.toolsHash)} → ${hex(curr.toolsHash)})`\n }\n if (prev.model !== curr.model) {\n changed.push('model')\n details.model = `${prev.model} → ${curr.model}`\n }\n if (prev.thinking !== curr.thinking) {\n changed.push('thinking')\n details.thinking = `${prev.thinking} → ${curr.thinking}`\n }\n if (prev.thinkingBudget !== curr.thinkingBudget) {\n changed.push('thinkingBudget')\n details.thinkingBudget = `${prev.thinkingBudget} → ${curr.thinkingBudget}`\n }\n\n return { changed, details }\n}\n\nfunction hex(n: number): string {\n return `0x${n.toString(16).padStart(8, '0')}`\n}\n\n// ---------------------------------------------------------------------------\n// Hook installer\n// ---------------------------------------------------------------------------\n\n/**\n * Install the cache-break logger on an agent's hook bus.\n *\n * Subscribes to `turn:before` (snapshot) + `turn:after` (compare with prior\n * snapshot, log breaks). Returns an `uninstall` fn that detaches both\n * listeners. Idempotent w.r.t. agent lifecycle — installs/uninstalls cleanly\n * across runs.\n *\n * Recommended setup:\n *\n * ```ts\n * const uninstall = installCacheBreakLogger(agent.hooks, {\n * log: line => logger.debug(line),\n * verbose: !!process.env.ZIDANE_DEBUG?.includes('cache'),\n * })\n * // ...\n * uninstall()\n * ```\n *\n * Performance: hashing runs on each `turn:before` and is bounded by the\n * input size (system + tools serialization). Disabled callers pay nothing\n * (don't install).\n */\nexport function installCacheBreakLogger(\n hooks: Hookable<AgentHooks>,\n options: CacheBreakLoggerOptions = {},\n): () => void {\n const log = options.log ?? ((line: string) => console.error(line))\n const verbose = options.verbose === true\n const expectedReadAtLeast = options.expectedReadAtLeast ?? 1\n\n // Per-bus latest snapshot. Keyed implicitly by the bus instance —\n // installing on a fresh bus starts from empty. Across runs on the same\n // bus, the snapshot persists, which is what we want: the first turn of\n // run N+1 should hit cache if it shares a prefix with the last turn of\n // run N. The detector reports correctly across that boundary.\n let prev: CacheDimensionSnapshot | undefined\n let turnIndex = 0\n let pending: { snapshot: CacheDimensionSnapshot, turnIndex: number, turnId: string } | undefined\n\n function onTurnBefore(ctx: { turn: number, turnId: string, options: StreamOptions }): void {\n const curr = snapshotCacheDimensions(ctx.options)\n turnIndex += 1\n\n if (verbose) {\n log(formatVerboseLine({\n turn: turnIndex,\n turnId: ctx.turnId,\n snapshot: curr,\n }))\n }\n\n // Stash for the matching `turn:after`. We can't compute the diff here —\n // the cache-read result lands on `turn:after.usage`.\n pending = { snapshot: curr, turnIndex, turnId: ctx.turnId }\n }\n\n function onTurnAfter(ctx: { turn: number, turnId: string, usage: { cacheRead?: number, cacheCreation?: number } }): void {\n if (!pending || pending.turnId !== ctx.turnId)\n return\n const { snapshot: curr, turnIndex: tIdx } = pending\n pending = undefined\n\n // Skip break detection on providers that don't report `cacheRead` —\n // treating undefined as 0 would log a false break every turn on Codex,\n // Cerebras, or any host without a cache_read field. We can only diagnose\n // breaks when the provider tells us how much cache was actually used.\n if (prev && tIdx > 1 && typeof ctx.usage.cacheRead === 'number') {\n const cacheRead = ctx.usage.cacheRead\n if (cacheRead < expectedReadAtLeast) {\n const diff = diffCacheDimensions(prev, curr)\n if (diff.changed.length > 0) {\n log(formatBreakLine({\n turn: tIdx,\n turnId: ctx.turnId,\n cacheRead,\n cacheCreation: ctx.usage.cacheCreation ?? 0,\n diff,\n }))\n }\n }\n }\n\n prev = curr\n }\n\n // Hookable's `.hook()` returns its own unregister fn — keep both so\n // `uninstall()` is symmetric with the install path and survives a\n // future hookable rename of `removeHook`.\n const unregisterBefore = hooks.hook('turn:before', onTurnBefore)\n const unregisterAfter = hooks.hook('turn:after', onTurnAfter)\n\n return () => {\n unregisterBefore()\n unregisterAfter()\n }\n}\n\n// ---------------------------------------------------------------------------\n// Formatting\n// ---------------------------------------------------------------------------\n\nfunction formatBreakLine(input: {\n turn: number\n turnId: string\n cacheRead: number\n cacheCreation: number\n diff: CacheDimensionDiff\n}): string {\n const names = input.diff.changed.join(', ')\n const details = input.diff.changed\n .map(d => ` ${d}: ${input.diff.details[d]}`)\n .join('\\n')\n return [\n `[zidane:cache-break] turn=${input.turn} turnId=${input.turnId} cacheRead=${input.cacheRead} cacheCreation=${input.cacheCreation}`,\n ` changed: ${names}`,\n details,\n ].join('\\n')\n}\n\nfunction formatVerboseLine(input: {\n turn: number\n turnId: string\n snapshot: CacheDimensionSnapshot\n}): string {\n const s = input.snapshot\n return `[zidane:cache] turn=${input.turn} turnId=${input.turnId} system=${hex(s.systemHash)}(${s.systemBytes}b) tools=${hex(s.toolsHash)}(${s.toolsCount}) model=${s.model} thinking=${s.thinking}/${s.thinkingBudget}`\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"completion-core-CXYSVhZo.js","names":[],"sources":["../src/chat/completion-core.ts"],"sourcesContent":["/**\n * Prompt autocompletion framework — pure core.\n *\n * Provider contract types + the renderer-agnostic span/reference helpers\n * (`findActiveTrigger`, `applyInsert`, `mergeReferences`, `collectReferences`).\n * No React, no node — so a browser-context renderer imports these via\n * `zidane/chat/pure`. The `useCompletion` React hook lives in `./completion`\n * (main barrel) and re-exports everything here for back-compat.\n *\n * Providers plug in by registering a `trigger` character (e.g. `/` for skills,\n * `@` for files) and exposing two operations:\n *\n * 1. `suggest(query)` — return ranked items for the live query.\n * 2. `parseReferences(text)` — find all references to the provider's\n * items in arbitrary text. Used to highlight in-prompt mentions and\n * drive submit-time side effects (activate the skill, attach the\n * file, …).\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A discoverable, selectable thing — one row in the autocomplete popover.\n * `TItem` is provider-specific; consumers can inspect `data` when they\n * need the originating payload (e.g. the full `SkillConfig` for tooltips).\n */\nexport interface CompletionItem<TItem = unknown> {\n /** Stable identifier within the provider. Used as React key + selection equality. */\n id: string\n /** User-visible primary string (\"research\"). */\n label: string\n /** Optional one-line secondary string (\"In-depth research with citations\"). */\n description?: string\n /**\n * Text that replaces the active span (trigger + query) on commit. Usually\n * `${trigger}${label}` with a trailing space so the cursor lands ready\n * for the next token.\n */\n insertText: string\n /**\n * Optional section header for popovers that host several providers on one\n * trigger (e.g. `/` → Commands + Skills). When set, the renderer groups\n * items under this label; when absent, items render flat. The engine\n * defaults it to the producing provider's `label` in {@link mergeSuggestions}.\n */\n section?: string\n /** Original provider-specific payload. */\n data: TItem\n}\n\n/**\n * A reference to a provider item inside arbitrary prompt text. Producers:\n * `provider.parseReferences(text)`. Consumers: the TUI for highlighting,\n * the run flow for \"activate every referenced skill before agent.run()\".\n *\n * Spans are half-open `[start, end)` codepoint offsets into the source\n * string. Overlapping spans from the same or different providers are\n * caller-resolved — the helpers below ship a \"first wins\" merger.\n */\nexport interface CompletionReference<TItem = unknown> {\n providerId: string\n start: number\n end: number\n itemId: string\n data: TItem\n metadata?: Record<string, unknown>\n}\n\nexport interface CompletionSubmitContext<TItem = unknown> extends CompletionContext {\n references: readonly CompletionReference<TItem>[]\n}\n\n/**\n * Provider contract. Implementations decide their own ranking, fuzzy-match\n * rules, async loading behavior, and reference grammar.\n */\nexport interface CompletionProvider<TItem = unknown> {\n /** Stable id used for tagging references + React keys. */\n id: string\n /**\n * Single character that activates this provider. The engine considers a\n * trigger \"active\" when it appears at the start of the buffer or\n * immediately after whitespace, and is not closed by whitespace before\n * the cursor (so a trigger plus query is a contiguous token).\n */\n trigger: string\n /** Human-readable name. Reserved for future multi-provider popover headers. */\n label: string\n /**\n * Returns items for the active `query` (the text between the trigger and\n * the cursor, excluding the trigger itself). Synchronous return is the\n * common case; promises let providers paginate or hit a backend.\n *\n * `signal` is aborted when the query changes or the popover closes —\n * use it to cancel in-flight network calls.\n */\n suggest: (\n query: string,\n ctx: CompletionContext,\n signal: AbortSignal,\n ) => CompletionItem<TItem>[] | Promise<CompletionItem<TItem>[]>\n /**\n * Keep the trigger active across spaces (single line only — a newline\n * still closes it). Default false: a space ends the query token, which is\n * right for file/skill/chat mentions. The commands provider opts in so\n * `/deploy stag▌` can complete ARGUMENTS: its query becomes\n * `deploy stag` and it decides internally what to suggest. When the\n * query contains whitespace, only opted-in providers are consulted.\n */\n allowSpacesInQuery?: boolean\n /**\n * Find every reference to this provider's items in `text`. Pure: must not\n * mutate ctx. The TUI calls this on every keystroke for highlighting, so\n * keep it cheap (linear scan, no I/O).\n */\n parseReferences: (\n text: string,\n ctx: CompletionContext,\n ) => CompletionReference<TItem>[]\n /**\n * Optional submit-time resolver. Unlike `parseReferences`, this may do async\n * work and enrich references before the host turns them into attachments,\n * activated skills, or other side effects.\n *\n * Declared with method syntax (not a property arrow) on purpose: it makes the\n * `TItem`-typed params bivariant, so a heterogeneous `CompletionProvider`\n * collection (skills + files + extensions) stays assignable to\n * `CompletionProvider<unknown>[]`.\n */\n // eslint-disable-next-line ts/method-signature-style\n resolveReferences?(\n references: readonly CompletionReference<TItem>[],\n ctx: CompletionSubmitContext<TItem>,\n signal: AbortSignal,\n ): CompletionReference<TItem>[] | Promise<CompletionReference<TItem>[]>\n /** Optional submit-time side effect hook. Runs after references are resolved. */\n // eslint-disable-next-line ts/method-signature-style\n onSubmit?(\n submission: CompletionSubmitContext<TItem>,\n signal: AbortSignal,\n ): void | Promise<void>\n}\n\n/**\n * Read-only view of the active prompt buffer + cursor passed to provider\n * callbacks. Kept minimal so providers stay portable across renderers.\n */\nexport interface CompletionContext {\n /** Full prompt text. */\n text: string\n /** Codepoint offset of the cursor (0-based). */\n cursor: number\n}\n\n/**\n * Identified active trigger span. Returned by `findActiveTrigger` so\n * callers can show the popover, query the provider, and on commit replace\n * the span with the selected item's `insertText`.\n */\nexport interface ActiveTrigger<TItem = unknown> {\n provider: CompletionProvider<TItem>\n /** Substring after the trigger, up to the cursor. Empty if cursor sits right after the trigger. */\n query: string\n /** `[start, end)` — span covered by the trigger + query in the source. */\n span: { start: number, end: number }\n}\n\n// ---------------------------------------------------------------------------\n// Pure helpers — exported for unit tests + alternate renderers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the provider trigger active at `cursor`, or `null` when none fits.\n *\n * Rules:\n * - The trigger character must sit at position 0 of the buffer OR be\n * preceded by whitespace. This prevents `http://` from triggering the\n * `/`-bound skills provider mid-URL.\n * - The cursor must be at or past the trigger position.\n * - Nothing between the trigger and the cursor may be whitespace (the\n * query is one contiguous token).\n * - The query length is bounded — `maxQueryLength` defaults to 64 — so\n * a runaway buffer scan can't pin the renderer.\n */\nexport function findActiveTrigger<TItem>(\n text: string,\n cursor: number,\n providers: readonly CompletionProvider<TItem>[],\n options: { maxQueryLength?: number } = {},\n): ActiveTrigger<TItem> | null {\n const multi = findActiveTriggers(text, cursor, providers, options)\n if (!multi)\n return null\n // Back-compat: the first matching provider is exactly what the old\n // `providers.find(...)` returned (filter preserves order).\n return { provider: multi.providers[0], query: multi.query, span: multi.span }\n}\n\n/** Like {@link ActiveTrigger} but carries EVERY provider bound to the active trigger char. */\nexport interface ActiveTriggers<TItem = unknown> {\n /** All providers whose `trigger` matches the active char, in registration order. */\n providers: readonly CompletionProvider<TItem>[]\n query: string\n span: { start: number, end: number }\n}\n\n/**\n * Multi-provider variant of {@link findActiveTrigger}: resolves ALL providers\n * bound to the trigger char active at `cursor` (e.g. `/` → a commands provider\n * AND the skills provider), sharing one query + span. The same trigger rules\n * apply (start-of-buffer or whitespace-preceded, contiguous query, bounded\n * length). Returns `null` when no trigger fits.\n */\nexport function findActiveTriggers<TItem>(\n text: string,\n cursor: number,\n providers: readonly CompletionProvider<TItem>[],\n options: { maxQueryLength?: number } = {},\n): ActiveTriggers<TItem> | null {\n if (providers.length === 0)\n return null\n const max = options.maxQueryLength ?? 64\n const safeCursor = Math.max(0, Math.min(cursor, text.length))\n // Whitespace test mirrors regex `\\s` so this surface agrees with the\n // built-in providers' `parseReferences` regex (which uses `\\s`). Without\n // this, pasting `text\\u00a0@README.md` would parse as a ref but the popover\n // wouldn't reopen on retype because NBSP wasn't recognized as a boundary.\n const isWhitespace = (ch: string | undefined) => ch === undefined ? false : /\\s/.test(ch)\n // Walk backward from the cursor to the nearest trigger char. Whitespace\n // normally closes the query token and ends the walk — unless a provider\n // opted into `allowSpacesInQuery` (argument completion), in which case\n // the walk continues across spaces (never newlines) and, once a trigger\n // is found, only space-tolerant providers are consulted for a spaced\n // query. Non-opted-in providers keep byte-identical behavior.\n const anyAllowsSpaces = providers.some(p => p.allowSpacesInQuery === true)\n let sawSpace = false\n for (let i = safeCursor - 1; i >= 0 && safeCursor - i <= max + 1; i--) {\n const ch = text[i]\n if (isWhitespace(ch)) {\n if (ch === '\\n' || ch === '\\r' || !anyAllowsSpaces)\n return null\n sawSpace = true\n continue\n }\n const matching = providers.filter(p => p.trigger === ch && (!sawSpace || p.allowSpacesInQuery === true))\n if (matching.length === 0)\n continue\n // Trigger must be at start of buffer or follow whitespace.\n const before = i > 0 ? text[i - 1] : ''\n if (before !== '' && !isWhitespace(before))\n continue\n const query = text.slice(i + 1, safeCursor)\n return {\n providers: matching,\n query,\n span: { start: i, end: safeCursor },\n }\n }\n return null\n}\n\n/**\n * Flatten several providers' suggestion lists into one, defaulting each\n * item's `section` to its producing provider's `label` so a multi-provider\n * popover can render group headers. Items that already carry a `section`\n * are left as-is.\n */\nexport function mergeSuggestions<TItem>(\n groups: readonly { provider: CompletionProvider<TItem>, items: readonly CompletionItem<TItem>[] }[],\n): CompletionItem<TItem>[] {\n const out: CompletionItem<TItem>[] = []\n for (const { provider, items } of groups) {\n for (const item of items)\n out.push(item.section === undefined ? { ...item, section: provider.label } : item)\n }\n return out\n}\n\n/**\n * Replace `[span.start, span.end)` in `text` with `insertText`. Returns the\n * mutated text and the new cursor position (end of insertion).\n */\nexport function applyInsert(\n text: string,\n span: { start: number, end: number },\n insertText: string,\n): { text: string, cursor: number } {\n const next = text.slice(0, span.start) + insertText + text.slice(span.end)\n return { text: next, cursor: span.start + insertText.length }\n}\n\n/**\n * Merge reference lists from multiple providers into one ordered list with\n * earlier-start-wins disambiguation when spans overlap. Ties broken by\n * insertion order. Spans are sorted ascending so renderers can walk them\n * sequentially with a cursor through the source string.\n */\nexport function mergeReferences<TItem>(\n refs: readonly CompletionReference<TItem>[],\n): CompletionReference<TItem>[] {\n const sorted = [...refs].sort((a, b) => a.start - b.start)\n const merged: CompletionReference<TItem>[] = []\n let lastEnd = -1\n for (const ref of sorted) {\n if (ref.start < lastEnd)\n continue\n merged.push(ref)\n lastEnd = ref.end\n }\n return merged\n}\n\n/**\n * Collect every provider's references in one pass. Convenience wrapper —\n * the TUI textarea component calls this on every keystroke to highlight\n * in-prompt mentions.\n */\nexport function collectReferences<TItem>(\n text: string,\n providers: readonly CompletionProvider<TItem>[],\n cursor = text.length,\n): CompletionReference<TItem>[] {\n const ctx: CompletionContext = { text, cursor }\n const refs: CompletionReference<TItem>[] = []\n for (const p of providers) {\n for (const ref of p.parseReferences(text, ctx))\n refs.push(ref)\n }\n return mergeReferences(refs)\n}\n\nexport async function resolveCompletionSubmit<TItem>(\n text: string,\n providers: readonly CompletionProvider<TItem>[],\n options: { cursor?: number, signal?: AbortSignal } = {},\n): Promise<CompletionReference<TItem>[]> {\n const cursor = options.cursor ?? text.length\n const signal = options.signal ?? new AbortController().signal\n const byProvider = new Map<string, CompletionReference<TItem>[]>()\n for (const ref of collectReferences(text, providers, cursor)) {\n const bucket = byProvider.get(ref.providerId) ?? []\n bucket.push(ref)\n byProvider.set(ref.providerId, bucket)\n }\n const resolved: CompletionReference<TItem>[] = []\n for (const provider of providers) {\n const refs = byProvider.get(provider.id)\n if (!refs?.length)\n continue\n const ctx: CompletionSubmitContext<TItem> = { text, cursor, references: refs }\n const next = provider.resolveReferences\n ? await provider.resolveReferences(refs, ctx, signal)\n : refs\n const submitCtx: CompletionSubmitContext<TItem> = { text, cursor, references: next }\n await provider.onSubmit?.(submitCtx, signal)\n resolved.push(...next)\n }\n return mergeReferences(resolved)\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"generate-data-WL_yGVL6.js","names":[],"sources":["../src/chat/commands.ts","../src/zod.ts","../src/chat/generate-title.ts","../src/chat/generate-data.ts"],"sourcesContent":["/**\n * `/` command model — an extension-contributed workflow invoked by typing\n * `/<name> [args]` as the whole prompt. A command is an ordered list of\n * handler **steps**; a bare `run` is sugar for a single step. Steps share a\n * mutable `data` bag, emit local output blocks (rendered in the transcript,\n * never sent to the model), and can drive the agent via `ctx.runAgent` so a\n * workflow can interact with the LLM mid-flight.\n *\n * This module is renderer-free and node-free: the types are the authoring\n * surface extensions compile against, and `executeCommand` / the parse\n * helper are pure so they're unit-testable without a TUI. The host supplies\n * the side-effecting capabilities on the step context: `emit` (render +\n * persist local output), `runAgent` (interactive sub-run), `generateData`\n * (silent schema-pinned JSON), and the optional `session` (read-only view)\n * and `interaction` (confirm / forms / plan approval) surfaces.\n */\n\nimport type { Logger } from '../logger'\nimport type { SessionRun } from '../session'\nimport type { JsonValue, SessionTurn } from '../types'\nimport type { InferSchemaOutput, SchemaInput, StandardSchemaLike } from '../zod'\nimport type { AnswerValue, PlanPayload, PlanResponse, QuestionPayload } from './interactions'\nimport type { StreamEvent } from './types'\n\n/**\n * A block of local output a step renders into the chat.\n *\n * Plain blocks (`text` only) render as full markdown, both live and on\n * reload — use them for the output the user should read in full (e.g. a\n * final commit message). Blocks with `detail` render as a collapsed\n * `command-detail` row instead (dim `▸ label` + line-capped body with\n * \"… N more lines\") — use them for bulky supporting output (diffs, logs,\n * listings) that shouldn't drown the transcript.\n */\nexport interface CommandOutputBlock {\n /**\n * The block body — full markdown, the detail body, or (for `custom`\n * blocks) the MANDATORY fallback markdown rendered wherever the\n * renderer isn't available: extension uninstalled or disabled,\n * headless host, GUI, renderer threw. The fallback is what persists\n * as the block's output, so pre-custom hosts render it natively.\n */\n text: string\n /**\n * Render as a collapsed detail row (dim `▸ label` + line-capped body)\n * instead of full markdown. `syntax: 'diff'` additionally colors\n * `+N` / `−N` count tokens (added green / deleted red) in both label\n * and body — for diff-stat-style output.\n */\n detail?: { label?: string, syntax?: 'diff' }\n /**\n * Render through an extension-registered custom renderer\n * (`ctx.tui.registerRenderer`). `renderer` must be the NAMESPACED id\n * (`<extension>/<id>`) — `ctx.appendNote` auto-prefixes bare ids for\n * its own extension; command `emit` requires the full form. `data`\n * must be JSON-safe (it persists in the session). Takes precedence\n * over `detail` when both are set.\n */\n custom?: { renderer: string, data?: JsonValue }\n}\n\n/**\n * User-interaction surface for commands — the same prompt-slot primitives\n * the `ask_user` / `present_plan` TOOLS render, but WITHOUT the tools: a\n * command enqueues directly into the host's interaction queue. No tool\n * calls, no turns, no model involvement — the answer routes straight back\n * to the awaiting step. Every method rejects on cancel/esc, which the\n * executor classifies as a quiet abort.\n */\nexport interface CommandInteraction {\n /**\n * One-tap yes/no — sugar over a single `confirm`-type question:\n *\n * if (await ctx.interaction.confirm('Push to origin/main?')) …\n */\n confirm: (prompt: string, opts?: {\n /** Optional context shown above the question (Markdown). */\n intro?: string\n /** Label for the affirmative option. Default `'yes'`. */\n affirmLabel?: string\n /** Label for the negative option. Default `'no'`. */\n denyLabel?: string\n }) => Promise<boolean>\n /**\n * Show a form (text / textarea / select / confirm questions — identical\n * UI to the ask_user tool) and resolve with the answers keyed by\n * `Question.id` (`string` for text/select, `boolean` for confirm,\n * `string[]` of choice ids for `multi: true` selects).\n */\n askUser: (form: QuestionPayload) => Promise<Readonly<Record<string, AnswerValue>>>\n /**\n * Present a plan for the full approval ceremony (approve / reject /\n * revise + optional comment — identical UI to present_plan). For\n * lightweight gating prefer {@link confirm}.\n */\n presentPlan: (plan: PlanPayload) => Promise<PlanResponse>\n}\n\n/**\n * Read-only view over the session a command runs inside — live getters, so\n * turns appended mid-workflow (e.g. by a `runAgent` sub-run) are visible on\n * the next read. Deliberately projection-only: no mutators, so commands\n * can't bypass the display-only `command`-turn contract or the wire guard\n * (`isWireTurn`). Persisting output goes through `emit`; driving the\n * conversation goes through `runAgent`.\n */\nexport interface CommandSessionView {\n readonly id: string\n /** Full persisted history, including display-only `command` turns. */\n readonly turns: readonly SessionTurn[]\n /** Run records (status, usage, cost, `origin`, …), newest last. */\n readonly runs: readonly SessionRun[]\n readonly metadata: Readonly<Record<string, unknown>>\n}\n\n/** Options for `ctx.generateData` — a silent, schema-pinned model call. */\nexport interface CommandGenerateDataOptions {\n /** Instruction describing the value to produce. */\n prompt: string\n /**\n * Shape of the result — a zod v4 schema (return type inferred, no casts)\n * or a plain JSON Schema object (name the type via the generic).\n */\n schema: SchemaInput\n /** Optional system prompt override. */\n system?: string\n /**\n * Fold a compacted transcript of the recent conversation into the call as\n * context. Default false — most workflow values derive from data the\n * command already gathered itself.\n */\n includeConversation?: boolean\n /**\n * Model id to run the call on (must exist in the host's model catalog —\n * see `ctx.runtime.models()` on the extension context). Omit to use the\n * session's active model.\n */\n model?: string\n}\n\n/** One task for {@link CommandStepContext.spawnAgents} — an isolated child agent run. */\nexport interface CommandSpawnTask {\n /** Task prompt for the child agent. Brief it like a colleague — it has no conversation context. */\n prompt: string\n /**\n * Model id from the host catalog (`ctx.runtime.models()` on the extension\n * context). Omit to inherit the session's active model. Unknown ids reject\n * the task (`status: 'rejected'`) without running anything.\n */\n model?: string\n /** System prompt override for the child. Inherits the agent's system prompt when omitted. */\n system?: string\n /** Short display label carried back on the result (defaults to the child id). */\n label?: string\n}\n\n/** Outcome of one {@link CommandSpawnTask}. */\nexport interface CommandSpawnResult {\n /** The task's `label` (or the child id when none was given). */\n label: string\n /** Model the task asked for, when pinned. */\n model?: string\n /** Terminal status. `'rejected'` = refused before running (unknown model, caps). */\n status: 'completed' | 'aborted' | 'timeout' | 'error' | 'rejected'\n /** The child's final assistant text. Empty unless `status === 'completed'`. */\n text: string\n /** Full formatted result / diagnostic (always set; useful for non-completed statuses). */\n message: string\n}\n\n/** Result of driving the agent from within a step via `ctx.runAgent`. */\nexport interface CommandAgentResult {\n /** The assistant's final text for this sub-run. */\n text: string\n}\n\n/** Capabilities + inputs handed to every step. */\nexport interface CommandStepContext {\n /** Raw text after `/<name>` (trimmed of the leading space). */\n readonly args: string\n /** `args` split on whitespace, empties removed. */\n readonly argv: readonly string[]\n /** Mutable bag threaded across steps; a step's returned `data` is merged in. */\n readonly data: Record<string, unknown>\n /** Render + persist a local output block. */\n readonly emit: (block: CommandOutputBlock) => void\n /**\n * Drive one agent run to completion and return its result. The prompt is\n * injected as a real user turn (model-visible, unchanged on the wire) and\n * the response streams into the transcript. Serialized with the host's run\n * slot, so a workflow can interleave deterministic steps with LLM\n * interaction. Any output `emit`ted before this call is flushed first,\n * preserving transcript order.\n *\n * `opts.display` replaces the full-prompt echo in the transcript with a\n * short line (the full prompt still renders beneath it as a collapsed\n * detail row, and still reaches the model verbatim) — pass it when the\n * prompt embeds bulky material like a diff.\n */\n readonly runAgent: (prompt: string, opts?: { readonly display?: string }) => Promise<CommandAgentResult>\n /**\n * Generate schema-conforming JSON with ONE silent model call — no session\n * turns, no transcript output, nothing added to the conversation (the\n * workflow presents results itself via `emit`). Reach for this when a\n * step needs a VALUE (a commit message, a classification, extracted\n * fields); reach for `runAgent` when it needs an interactive sub-run\n * with tools and streaming.\n */\n readonly generateData: {\n /** zod v4 schema → the result type is inferred from the schema. */\n <S extends StandardSchemaLike>(opts: Omit<CommandGenerateDataOptions, 'schema'> & { schema: S }): Promise<InferSchemaOutput<S>>\n /** Plain JSON Schema → name the result type explicitly. */\n <T = unknown>(opts: CommandGenerateDataOptions): Promise<T>\n }\n /** Logger with a `command=<name>` binding, routed to the host's log funnel. */\n readonly logger: Logger\n /** Aborts when the user presses esc during execution. */\n readonly signal: AbortSignal\n /** Working directory the command runs against. */\n readonly cwd: string\n /**\n * The surrounding session, when the host runs commands inside one (the\n * TUI always does). ABSENT in session-less contexts — a command that\n * reads it must guard (`if (!ctx.session) …`) or degrade gracefully.\n */\n readonly session?: CommandSessionView\n /**\n * User interaction (confirm / forms / plan approval), when the host has\n * an interactive surface (the TUI always does). ABSENT on UI-less hosts\n * — guard (`if (!ctx.interaction) …`) and degrade gracefully.\n */\n readonly interaction?: CommandInteraction\n /**\n * Run one or more ISOLATED child agents — each with its own context\n * window and the full tool set, optionally pinned to a different model\n * — and resolve when the whole batch settles. Tasks run in parallel\n * (bounded by `opts.maxConcurrent`, default 3). Children stream and\n * persist into the transcript exactly like `spawn`-tool subagents, so\n * the user sees their activity live and on reload; their context stays\n * isolated from the conversation.\n *\n * Complements {@link runAgent}: `runAgent` drives THE conversation\n * (model-visible turns, active model, serialized); `spawnAgents` fans\n * work out to throwaway workers. Results come back to the command —\n * persist what matters via {@link emit} (user-facing) and\n * {@link appendContext} (model-facing).\n *\n * ABSENT on hosts without an agent (guard like `session`).\n */\n readonly spawnAgents?: (\n tasks: readonly CommandSpawnTask[],\n opts?: { readonly maxConcurrent?: number },\n ) => Promise<readonly CommandSpawnResult[]>\n /**\n * Persist a compact MODEL-VISIBLE context note — a real user-role turn,\n * sent on the wire unchanged from the next run onward, without\n * triggering a run now. This is how a command's outcome becomes part of\n * the conversation: the next user prompt sees what the workflow\n * found/did without replaying subagent transcripts.\n *\n * `opts.display` swaps the transcript echo for a short one-line marker —\n * live AND on reload (the marker persists on the turn; replay renders it\n * with the full note as a collapsed detail), so note bodies the command\n * also emitted never display twice. Keep notes compact regardless — they\n * occupy context for the rest of the conversation. ABSENT on session-less\n * hosts.\n */\n readonly appendContext?: (text: string, opts?: { readonly display?: string }) => Promise<void>\n}\n\n/** Optional per-step return — `data` is merged into the shared bag for later steps. */\nexport interface CommandStepResult {\n data?: Record<string, unknown>\n}\n\nexport type CommandStepFn = (\n ctx: CommandStepContext,\n) => void | CommandStepResult | Promise<void | CommandStepResult>\n\nexport interface CommandStep {\n run: CommandStepFn\n}\n\n/** One argument suggestion returned by {@link Command.completeArgs}. */\nexport interface CommandArgSuggestion {\n /** Text inserted after `/<name> ` on accept. */\n value: string\n /** Display label. Defaults to `value`. */\n label?: string\n description?: string\n}\n\n/** An extension-contributed `/` command. Provide EITHER `run` (one step) or `steps`. */\nexport interface Command {\n /** Invoked as `/<name>`. Lowercase, no whitespace. */\n name: string\n /** One-line summary shown in the completion popover. */\n description: string\n /** Single-step sugar. */\n run?: CommandStepFn\n /** Explicit ordered pipeline. Takes precedence over `run` when both are set. */\n steps?: readonly CommandStep[]\n /**\n * Argument completion — called while the user types `/<name> <prefix>▌`\n * with everything after the command name (may itself contain spaces).\n * Return suggestions to show in the `/` popover; return `[]`/`undefined`\n * for none. Sync or async.\n */\n completeArgs?: (argsPrefix: string) => readonly CommandArgSuggestion[] | undefined | Promise<readonly CommandArgSuggestion[] | undefined>\n}\n\n/**\n * Persisted form of output blocks inside a display-only `command` turn:\n * plain blocks → `text` content; `detail` blocks → `tool_result` content\n * with the label/syntax carried in namespaced metadata (rebuilt into the\n * collapsed rendering on reload). Shared by the command emit flush and\n * `ctx.appendNote` so both persist identically.\n */\nexport function commandBlocksToContent(blocks: readonly CommandOutputBlock[]): SessionTurn['content'] {\n return blocks.map((block) => {\n if (!block.detail && !block.custom)\n return { type: 'text' as const, text: block.text }\n // Custom blocks persist with the FALLBACK as the output — an older\n // zidane (or any host without the renderer) reads this turn as a\n // plain detail block and renders the fallback natively. The renderer\n // binding rides namespaced metadata, exactly like detail label/syntax.\n const commandMeta: Record<string, JsonValue> = block.custom\n ? { custom: { renderer: block.custom.renderer, ...(block.custom.data !== undefined ? { data: block.custom.data } : {}) } }\n : {\n ...(block.detail?.label ? { label: block.detail.label } : {}),\n ...(block.detail?.syntax ? { syntax: block.detail.syntax } : {}),\n }\n return {\n type: 'tool_result' as const,\n callId: `command_detail_${crypto.randomUUID()}`,\n output: block.text,\n ...(Object.keys(commandMeta).length > 0 ? { metadata: { command: commandMeta } } : {}),\n }\n })\n}\n\n/**\n * Map ONE command-output block to its LIVE transcript event — the\n * in-memory mirror of {@link commandBlocksToContent}'s persisted shape\n * (whose replay side lives in `eventsFromTurns`). One definition shared\n * by the command emit flush and the `ctx.appendNote` host impl so live\n * rendering can never drift from reloaded rendering — the exact drift\n * that shipped `custom` blocks rendering only after a reload.\n *\n * Precedence matches the persisted side: `custom` > `detail` > markdown.\n */\nexport function commandBlockToEvent(block: CommandOutputBlock): StreamEvent {\n if (block.custom) {\n return {\n kind: 'custom',\n text: block.text,\n custom: {\n renderer: block.custom.renderer,\n ...(block.custom.data !== undefined ? { data: block.custom.data } : {}),\n },\n }\n }\n if (block.detail) {\n return {\n kind: 'command-detail',\n text: block.text,\n ...(block.detail.label ? { label: block.detail.label } : {}),\n ...(block.detail.syntax ? { syntax: block.detail.syntax } : {}),\n }\n }\n return { kind: 'markdown', text: block.text, streaming: false }\n}\n\n/** Normalize a command to its ordered step list (`steps` wins over `run`). */\nexport function commandSteps(command: Command): readonly CommandStep[] {\n if (command.steps && command.steps.length > 0)\n return command.steps\n if (command.run)\n return [{ run: command.run }]\n return []\n}\n\n/** Parsed `/<name> [args]` invocation. */\nexport interface CommandInvocation {\n name: string\n /** Text after the name, trimmed. */\n args: string\n /** `args` split on whitespace. */\n argv: string[]\n}\n\n/**\n * Parse a prompt as a command invocation, but only when the command token is\n * the LEADING token of the whole prompt and names a known command. This is\n * what keeps mid-prompt `/skill` mentions routed to the skills provider:\n * `\"summarize /research\"` is not a command, `\"/research foo\"` is.\n *\n * `knownNames` is matched case-insensitively; the returned `name` is the\n * canonical (registered) casing.\n */\nexport function parseCommandInvocation(\n prompt: string,\n knownNames: readonly string[],\n): CommandInvocation | null {\n const body = prompt.trimStart()\n if (body[0] !== '/')\n return null\n // First token = the command name; the remainder (after the first run of\n // whitespace) is the args. Linear scan — no backtracking-prone regex.\n const afterSlash = body.slice(1)\n const wsIdx = afterSlash.search(/\\s/)\n const typed = (wsIdx === -1 ? afterSlash : afterSlash.slice(0, wsIdx)).toLowerCase()\n if (typed.length === 0)\n return null\n const canonical = knownNames.find(n => n.toLowerCase() === typed)\n if (canonical === undefined)\n return null\n const args = (wsIdx === -1 ? '' : afterSlash.slice(wsIdx + 1)).trim()\n return { name: canonical, args, argv: args.length > 0 ? args.split(/\\s+/) : [] }\n}\n\n/** Outcome of a command run. `error` is set when a step threw (pipeline stops). */\nexport interface CommandExecution {\n ok: boolean\n /** Number of steps that ran (including the one that threw). */\n ranSteps: number\n error?: unknown\n /** True when the run stopped because `ctx.signal` aborted. */\n aborted?: boolean\n}\n\n/**\n * Run a command's steps in order, threading `ctx.data`. Stops at the first\n * step that throws (error isolated + returned, not rethrown) or when the\n * signal aborts. Pure orchestration — all I/O lives in the host-supplied\n * `ctx` capabilities.\n */\nexport async function executeCommand(\n command: Command,\n ctx: CommandStepContext,\n): Promise<CommandExecution> {\n const steps = commandSteps(command)\n let ranSteps = 0\n for (const step of steps) {\n if (ctx.signal.aborted)\n return { ok: false, ranSteps, aborted: true }\n ranSteps++\n try {\n const result = await step.run(ctx)\n if (result && result.data)\n Object.assign(ctx.data, result.data)\n }\n catch (error) {\n // An esc mid-step usually surfaces as a thrown AbortError from whatever\n // I/O the handler had in flight. Classify by the signal so hosts can\n // stay quiet on user-initiated aborts instead of painting an error.\n if (ctx.signal.aborted)\n return { ok: false, ranSteps, aborted: true }\n return { ok: false, ranSteps, error }\n }\n }\n return { ok: true, ranSteps }\n}\n","/**\n * Zod v4 integration helpers.\n *\n * Zod is an OPTIONAL peer dependency, so this module never imports it at the\n * top level. Schema-accepting APIs (`ctx.generateData`, `generateStructuredData`)\n * take either a plain JSON Schema object or any Standard-Schema-carrying\n * value (https://standardschema.dev — zod v4 implements it). Zod schemas are\n * detected structurally by vendor and converted with a lazy\n * `import('zod')` — safe because whoever constructed the schema necessarily\n * has zod installed. Result types are inferred from the standard-schema\n * `types.output` slot, so callers get full typing without a single cast:\n *\n * const CommitMessage = z.object({ subject: z.string(), body: z.string().optional() })\n * const msg = await ctx.generateData({ prompt, schema: CommitMessage })\n * // ^? { subject: string, body?: string }\n */\n\n/**\n * Structural stand-in for a Standard Schema (zod v4 et al.) — enough surface\n * to detect the vendor and infer the output type WITHOUT importing zod's\n * types into our public API.\n */\nexport interface StandardSchemaLike<Output = unknown> {\n readonly '~standard': {\n readonly version: number\n readonly vendor: string\n /** Type-level only (undefined at runtime) — carries the inferred output type. */\n readonly types?: { readonly output: Output } | undefined\n }\n}\n\n/** Output type carried by a {@link StandardSchemaLike}; `unknown` for plain JSON Schema. */\nexport type InferSchemaOutput<S> = S extends StandardSchemaLike<infer O> ? O : unknown\n\n/** A schema-accepting input: plain JSON Schema, or a Standard Schema (e.g. zod v4). */\nexport type SchemaInput = Record<string, unknown> | StandardSchemaLike\n\nexport function isStandardSchema(value: unknown): value is StandardSchemaLike {\n if (typeof value !== 'object' || value === null)\n return false\n const marker = (value as Record<string, unknown>)['~standard']\n return typeof marker === 'object' && marker !== null\n && typeof (marker as Record<string, unknown>).vendor === 'string'\n}\n\n/**\n * Resolve a {@link SchemaInput} to the plain JSON Schema object providers\n * expect. Zod schemas convert via the lazily-imported `z.toJSONSchema`\n * (v4 native); other Standard Schema vendors are rejected with a clear\n * error — conversion is vendor-specific and only zod is wired today.\n */\nexport async function resolveSchemaInput(schema: SchemaInput): Promise<Record<string, unknown>> {\n if (!isStandardSchema(schema))\n return schema\n const vendor = schema['~standard'].vendor\n if (vendor !== 'zod') {\n throw new Error(\n `unsupported schema vendor \"${vendor}\" — pass a zod v4 schema or a plain JSON Schema object`,\n )\n }\n // Lazy + guarded: zod is an optional peer, but a zod schema in hand means\n // the caller's process has zod — this import only fails on truly broken\n // installs, and then with an actionable message.\n let toJSONSchema: (schema: unknown) => Record<string, unknown>\n try {\n const zod = await import('zod')\n toJSONSchema = zod.z.toJSONSchema as typeof toJSONSchema\n }\n catch {\n throw new Error('a zod schema was passed but the \"zod\" package could not be imported — install zod >= 4')\n }\n return zodToJsonSchema(toJSONSchema(schema))\n}\n\n/**\n * Normalize a JSON Schema (e.g. from zod v4's z.toJSONSchema()) for use\n * as a ToolSpec.inputSchema.\n *\n * Strips the $schema key that some providers reject.\n */\nexport function zodToJsonSchema(jsonSchema: Record<string, unknown>): Record<string, unknown> {\n const { $schema, ...rest } = jsonSchema\n return rest\n}\n","/**\n * One-shot title generation for an existing session — feeds the last N\n * turns to the provider with a \"write me a title\" system prompt and\n * returns the cleaned-up result.\n *\n * Provider-agnostic: works with anything implementing the renderer-\n * shared `Provider` interface. No tools, no session mutation, no\n * caching — this is a cheap, isolated completion that should be safe to\n * fire even mid-session without touching the live agent loop.\n */\n\nimport type { Provider } from '../providers'\nimport type { SessionTurn } from '../types'\n\n/** Hard cap on the result length. Anything longer is truncated client-side. */\nconst TITLE_MAX_CHARS = 60\n\n/** Default turn count fed to the model. 10 covers most exchanges without bloat. */\nconst DEFAULT_MAX_TURNS = 10\n\n/** Tokens budgeted for the model's reply. 64 fits any sane title with headroom. */\nconst TITLE_RESPONSE_MAX_TOKENS = 64\n\n/** Per-turn body cap when assembling the prompt — keeps the request payload tight. */\nconst PROMPT_TURN_CHAR_MAX = 2000\n\nexport interface GenerateSessionTitleOptions {\n provider: Provider\n /** Model id used for the call. */\n model: string\n /** Conversation history to summarize. Empty → throws synchronously. */\n turns: readonly SessionTurn[]\n /** Cap on how many trailing turns to feed the model. Defaults to 10. */\n maxTurns?: number\n /** Optional cancellation signal — forwarded to the provider's stream. */\n signal?: AbortSignal\n}\n\n/**\n * Drive the provider's `stream()` once and return a concise title for\n * the conversation represented by `turns`. Throws when:\n * - `turns` contains no text-bearing content (nothing to summarize),\n * - the provider stream completes with no output text,\n * - `signal` is aborted (rethrown verbatim).\n *\n * Output is trimmed, single-line, with surrounding quotes / trailing\n * punctuation stripped, and truncated to `TITLE_MAX_CHARS`.\n */\nexport async function generateSessionTitle({\n provider,\n model,\n turns,\n maxTurns = DEFAULT_MAX_TURNS,\n signal,\n}: GenerateSessionTitleOptions): Promise<string> {\n const slice = turns.slice(-Math.max(1, maxTurns))\n const transcript = renderTurnsForPrompt(slice)\n if (!transcript)\n throw new Error('No text content in the recent turns to summarize.')\n\n const system = [\n 'You generate concise, descriptive titles for chat conversations.',\n 'Reply with ONLY the title — no quotes, no markdown, no trailing punctuation,',\n 'no preamble (do not say \"Title:\" or similar).',\n `Aim for 3 to 7 words and stay under ${TITLE_MAX_CHARS} characters.`,\n ].join(' ')\n\n const userPrompt = `Generate a title for this conversation:\\n\\n${transcript}`\n\n let text = ''\n const result = await provider.stream(\n {\n model,\n system,\n tools: [],\n messages: [provider.userMessage(userPrompt)],\n maxTokens: TITLE_RESPONSE_MAX_TOKENS,\n ...(signal ? { signal } : {}),\n },\n {\n onText: (delta) => { text += delta },\n },\n )\n // Prefer the streamed-text accumulator (driven by deltas) but fall\n // back to the provider's already-concatenated `result.text` for\n // adapters that batch on the final frame instead of emitting deltas.\n const raw = text.trim().length > 0 ? text : result.text\n return cleanTitle(raw)\n}\n\n/**\n * Decide whether a session is ready for one-shot automatic titling.\n *\n * Returns `true` iff the conversation has had its first exchange — at\n * least two turns, i.e. the opening user message plus the assistant's\n * reply (this is exactly the \"N turns\" count the sessions list shows) —\n * AND no explicit title has been set yet. An existing `metadata.title`\n * (a previous auto-generation, or a manual title from the session-details\n * \"generate title\" action) is never clobbered.\n *\n * Pure + total. The runtime layers its own \"is one already in flight?\"\n * and \"is a provider picked?\" guards on top of this; the predicate only\n * encodes the content-level rule so it can be pinned in tests without a\n * mock provider — same rationale as the exported {@link cleanTitle}.\n */\nexport function shouldAutoGenerateTitle(\n turns: readonly SessionTurn[],\n metadata?: Record<string, unknown>,\n): boolean {\n const existing = typeof metadata?.title === 'string' ? metadata.title.trim() : ''\n if (existing.length > 0)\n return false\n return turns.length >= 2\n}\n\n/**\n * Compact a turn list into a transcript-style prompt:\n *\n * user: …\n * assistant: …\n * user: …\n *\n * Tool calls and tool results are summarized to keep the request\n * payload small — the model needs the rough flow of the conversation,\n * not full JSON blobs. Per-turn text is clipped at `PROMPT_TURN_CHAR_MAX`\n * so a single huge code dump doesn't blow the budget.\n *\n * Returns an empty string when the slice has no text-bearing content\n * (e.g. a turn list that's only tool plumbing).\n *\n * Exported for reuse by other silent one-shot helpers (`generate-data`'s\n * optional conversation context) so every side-channel inference compacts\n * history the same way.\n */\nexport function renderTurnsForPrompt(turns: readonly SessionTurn[]): string {\n const lines: string[] = []\n for (const turn of turns) {\n const body = textBodyOf(turn)\n if (!body)\n continue\n const role = turn.role === 'assistant' ? 'assistant' : turn.role === 'user' ? 'user' : 'system'\n lines.push(`${role}: ${clip(body, PROMPT_TURN_CHAR_MAX)}`)\n }\n return lines.join('\\n\\n')\n}\n\nfunction textBodyOf(turn: SessionTurn): string {\n const parts: string[] = []\n for (const block of turn.content) {\n if (block.type === 'text' && block.text.trim())\n parts.push(block.text.trim())\n else if (block.type === 'tool_call')\n parts.push(`[tool: ${block.name}]`)\n else if (block.type === 'tool_result')\n parts.push(`[tool result]`)\n }\n return parts.join(' ')\n}\n\n/**\n * Normalize a model-generated title into the shape we want to persist:\n *\n * - Collapse internal whitespace + take the first line only (some\n * models emit \"Title: foo\\n\\nReason: …\" despite instructions).\n * - Strip surrounding quote characters (`\"foo\"`, `'foo'`, `` `foo` ``).\n * - Strip a leading `Title:` / `title -` prefix if the model added one.\n * - Strip trailing punctuation (`.`, `!`, `?`) — titles read cleaner\n * without it.\n * - Truncate to `TITLE_MAX_CHARS` with a trailing `…` when over.\n *\n * Exported as `cleanTitle` so tests can pin the normalization rules\n * without going through a mock provider.\n */\nexport function cleanTitle(raw: string): string {\n let s = raw.split('\\n')[0]?.trim() ?? ''\n // Strip a \"Title:\" / \"Title -\" preamble the model sometimes adds.\n s = s.replace(/^\\s*title\\s*[:\\-–—]\\s*/i, '')\n // Strip surrounding matched quotes.\n if (s.length >= 2) {\n const first = s.charAt(0)\n const last = s.charAt(s.length - 1)\n const matched = (first === '\"' && last === '\"')\n || (first === '\\'' && last === '\\'')\n || (first === '`' && last === '`')\n || (first === '“' && last === '”')\n if (matched)\n s = s.slice(1, -1).trim()\n }\n // Trim trailing terminal punctuation — titles don't take periods.\n s = s.replace(/[.!?]+$/, '').trim()\n if (s.length === 0)\n throw new Error('Model returned an empty title.')\n return s.length > TITLE_MAX_CHARS ? `${s.slice(0, TITLE_MAX_CHARS - 1)}…` : s\n}\n\nfunction clip(text: string, max: number): string {\n return text.length > max ? `${text.slice(0, max)}…` : text\n}\n","/**\n * Silent structured-output generation — one provider call that returns\n * schema-conforming JSON without touching the session or the transcript.\n *\n * Modeled on the two proven side-channel patterns in the codebase:\n * - `generate-title.ts`: a single `provider.stream()` with no session\n * writes and no transcript events (optionally seeded with a compacted\n * conversation transcript), and\n * - the loop's `__output__` schema enforcement: a synthetic tool whose\n * `inputSchema` IS the requested JSON Schema, with `toolChoice` pinned\n * so the model must answer with a conforming payload.\n *\n * Used by `/` commands via `ctx.generateData` to derive values from the\n * model (or from the conversation) mid-workflow without bloating the chat\n * history — e.g. `/commit` generating `{ subject, body }` from a diff.\n * For genuinely interactive sub-runs (real turns, streaming, tools), use\n * `ctx.runAgent` instead.\n */\n\nimport type { Provider, ToolSpec } from '../providers'\nimport type { SessionTurn } from '../types'\nimport type { InferSchemaOutput, SchemaInput, StandardSchemaLike } from '../zod'\nimport { resolveSchemaInput } from '../zod'\nimport { renderTurnsForPrompt } from './generate-title'\n\n/** Synthetic forced-choice tool name (never persisted — the call is side-channel only). */\nconst DATA_TOOL_NAME = '__data__'\n\n/** Default cap on conversation turns folded into the context block. */\nconst DEFAULT_CONTEXT_TURNS = 12\n\nexport interface GenerateStructuredDataOptions {\n provider: Provider\n model: string\n /** Instruction describing the value to produce. */\n prompt: string\n /**\n * Shape of the result — a plain JSON Schema object, or a zod v4 schema\n * (converted automatically; the return type is then inferred from it).\n * Becomes the forced tool's inputSchema.\n */\n schema: SchemaInput\n /** Optional system prompt. Defaults to a terse data-extraction persona. */\n system?: string\n /**\n * Conversation turns to fold in as compacted context (same clipping as\n * session auto-titling). Omit or pass empty for a context-free call.\n */\n turns?: readonly SessionTurn[]\n /** Cap on turns rendered into the context block. Default 12 (most recent). */\n maxContextTurns?: number\n signal?: AbortSignal\n}\n\n/**\n * Drive one schema-pinned provider call and return the conforming payload.\n * Throws when the provider returns no structured payload (and rethrows\n * aborts verbatim). The caller owns presentation — nothing is rendered or\n * persisted here.\n *\n * Pass a zod v4 schema and the return type is inferred from it; pass a\n * plain JSON Schema object and name the type via the generic.\n */\nexport async function generateStructuredData<S extends StandardSchemaLike>(\n options: GenerateStructuredDataOptions & { schema: S },\n): Promise<InferSchemaOutput<S>>\nexport async function generateStructuredData<T = unknown>(\n options: GenerateStructuredDataOptions,\n): Promise<T>\nexport async function generateStructuredData(\n options: GenerateStructuredDataOptions,\n): Promise<unknown> {\n const { provider, model, prompt, signal } = options\n const spec: ToolSpec = {\n name: DATA_TOOL_NAME,\n description: 'Return the result. The input of this call IS the answer — it must conform to the schema.',\n inputSchema: await resolveSchemaInput(options.schema),\n }\n\n const contextTurns = options.turns && options.turns.length > 0\n ? options.turns.slice(-(options.maxContextTurns ?? DEFAULT_CONTEXT_TURNS))\n : []\n const transcript = contextTurns.length > 0 ? renderTurnsForPrompt(contextTurns) : ''\n const userPrompt = transcript\n ? `<conversation>\\n${transcript}\\n</conversation>\\n\\n${prompt}`\n : prompt\n\n const result = await provider.stream(\n {\n model,\n system: options.system\n ?? `You produce structured data. Call the ${DATA_TOOL_NAME} tool exactly once with the requested value. No prose.`,\n tools: provider.formatTools([spec]),\n messages: [provider.userMessage(userPrompt)],\n toolChoice: { type: 'tool', name: DATA_TOOL_NAME },\n ...(signal ? { signal } : {}),\n },\n {\n onText: () => {},\n },\n )\n\n const payload = result.toolCalls.find(tc => tc.name === DATA_TOOL_NAME)?.input\n if (payload === undefined)\n throw new Error('generateStructuredData: the model returned no structured payload')\n return payload\n}\n"],"mappings":"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"}
|