@aglyn/plugins-ai 1.0.0-beta.277 → 1.0.0-beta.282

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.
Files changed (76) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.d.ts +2 -26
  3. package/src/lib/activity/ai-activity-actions.js +5 -42
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/activity/ai-activity-words.d.ts +42 -0
  6. package/src/lib/activity/ai-activity-words.js +63 -0
  7. package/src/lib/activity/ai-activity-words.js.map +1 -0
  8. package/src/lib/components/ai-insight-card.component.d.ts +2 -6
  9. package/src/lib/components/ai-insight-card.component.js +2 -6
  10. package/src/lib/components/ai-insight-card.component.js.map +1 -1
  11. package/src/lib/components/ai-insight-dialog.component.js +22 -32
  12. package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
  13. package/src/lib/components/ai-job-links.js +15 -0
  14. package/src/lib/components/ai-job-links.js.map +1 -1
  15. package/src/lib/components/assist-panel.component.js +3 -1
  16. package/src/lib/components/assist-panel.component.js.map +1 -1
  17. package/src/lib/jobs/ai-build-capabilities.js +23 -0
  18. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  19. package/src/lib/jobs/ai-job-build-step.js +2 -1
  20. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  21. package/src/lib/jobs/ai-job-import-step.d.ts +1 -0
  22. package/src/lib/jobs/ai-job-import-step.js +17 -11
  23. package/src/lib/jobs/ai-job-import-step.js.map +1 -1
  24. package/src/lib/jobs/ai-job-plan-step.js +3 -2
  25. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  26. package/src/lib/jobs/ai-job-privacy-step.d.ts +184 -0
  27. package/src/lib/jobs/ai-job-privacy-step.js +471 -0
  28. package/src/lib/jobs/ai-job-privacy-step.js.map +1 -0
  29. package/src/lib/jobs/ai-site-inspiration.d.ts +10 -0
  30. package/src/lib/jobs/ai-site-inspiration.js +15 -0
  31. package/src/lib/jobs/ai-site-inspiration.js.map +1 -1
  32. package/src/lib/model/ai-insight-ask.d.ts +57 -0
  33. package/src/lib/model/ai-insight-ask.js +64 -0
  34. package/src/lib/model/ai-insight-ask.js.map +1 -0
  35. package/src/lib/model/ai-insight.d.ts +1 -6
  36. package/src/lib/model/ai-insight.js +2 -8
  37. package/src/lib/model/ai-insight.js.map +1 -1
  38. package/src/lib/model/ai-job-activity.js +1 -0
  39. package/src/lib/model/ai-job-activity.js.map +1 -1
  40. package/src/lib/model/ai-jobs.types.d.ts +5 -1
  41. package/src/lib/model/ai-jobs.types.js +1 -0
  42. package/src/lib/model/ai-jobs.types.js.map +1 -1
  43. package/src/lib/model/ai-permissions.js +6 -2
  44. package/src/lib/model/ai-permissions.js.map +1 -1
  45. package/src/lib/model/ai-site-import.d.ts +2 -0
  46. package/src/lib/model/ai-site-import.js +2 -0
  47. package/src/lib/model/ai-site-import.js.map +1 -1
  48. package/src/lib/model/ai-site-kinds.d.ts +10 -28
  49. package/src/lib/model/ai-site-kinds.js +12 -496
  50. package/src/lib/model/ai-site-kinds.js.map +1 -1
  51. package/src/lib/providers/anthropic.js +23 -14
  52. package/src/lib/providers/anthropic.js.map +1 -1
  53. package/src/lib/providers/contract.d.ts +39 -4
  54. package/src/lib/providers/contract.js +28 -0
  55. package/src/lib/providers/contract.js.map +1 -1
  56. package/src/lib/providers/routing.d.ts +3 -0
  57. package/src/lib/providers/routing.js +4 -3
  58. package/src/lib/providers/routing.js.map +1 -1
  59. package/src/lib/runtime/ai-dev-env.d.ts +3 -2
  60. package/src/lib/runtime/ai-dev-env.js +3 -2
  61. package/src/lib/runtime/ai-dev-env.js.map +1 -1
  62. package/src/lib/runtime/ai-page-links.d.ts +5 -2
  63. package/src/lib/runtime/ai-page-links.js +11 -3
  64. package/src/lib/runtime/ai-page-links.js.map +1 -1
  65. package/src/lib/runtime/ai-runtime.js +5 -2
  66. package/src/lib/runtime/ai-runtime.js.map +1 -1
  67. package/src/lib/server/assist-chat.js +58 -30
  68. package/src/lib/server/assist-chat.js.map +1 -1
  69. package/src/lib/server.js +2 -0
  70. package/src/lib/server.js.map +1 -1
  71. package/src/lib/site-import/fixtures/site-import-crawl-recordings.d.ts +3 -1
  72. package/src/lib/site-import/fixtures/site-import-crawl-recordings.js +19 -1
  73. package/src/lib/site-import/fixtures/site-import-crawl-recordings.js.map +1 -1
  74. package/src/lib/site-import/site-import-page.d.ts +2 -2
  75. package/src/lib/site-import/site-import-page.js +11 -1
  76. package/src/lib/site-import/site-import-page.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/providers/anthropic.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { aiBilledRatesForModel, aiCatalogEntry, aiModelIdsForProvider, estimateAiBilledUsd } from './catalog'\nimport { aiCacheWriteHourSurchargeUsd } from './cache-write-hour'\nimport {\n AiBatchUnfinishedError,\n AiUpstreamError,\n type AiAccountProblem,\n type AiBatchOptions,\n type AiEffort,\n aiRawOutputOf,\n aiStoppedAtCeiling,\n aiTokenCount,\n aiToolInputOf,\n type AiMessage,\n type AiModelDescriptor,\n type AiProvider,\n type AiProviderRequest,\n type AiResult,\n type AiStreamEvent,\n type AiToolSchemaLimits,\n type AiToolUse,\n type AiUsage,\n} from './contract'\n\n/**\n * The Anthropic adapter (AGL-2903, behind the contract since AGL-2939):\n * the Messages API over raw `fetch`. No provider SDK is installed, and the\n * meter prices by model id from the catalog, so nothing an SDK would add\n * is used here. This file and the catalog are the only places in the\n * plugin that name the vendor's URL, header or model ids.\n *\n * ── Errors (AGL-2815) ─────────────────────────────────────────────────────\n *\n * Provider error text describes OUR account with the vendor — a key, a rate\n * limit, a balance — and names the vendor to a white-label org's users. It\n * never leaves this module as a message: a non-2xx answer becomes an\n * `AiUpstreamError` carrying the status, whether the failure is retryable\n * and a fixed customer-safe sentence; the provider's body goes to\n * `console.error` beside the request id, where an operator can find it.\n * A `refusal` stop is not an error at all — the tokens were spent and the\n * model answered — so it comes back as a typed `AiRefusal` result.\n */\n\nexport const ANTHROPIC_PROVIDER_ID = 'anthropic'\nexport const ANTHROPIC_API_KEY_ENV = 'ANTHROPIC_API_KEY'\nconst ANTHROPIC_HOST = 'api.anthropic.com'\nconst ANTHROPIC_URL = `https://${ANTHROPIC_HOST}/v1/messages`\nconst ANTHROPIC_BATCHES_URL = `https://${ANTHROPIC_HOST}/v1/messages/batches`\nconst ANTHROPIC_VERSION = '2023-06-01'\n\n/** The Messages API accepts at most this many `cache_control` markers. */\nexport const ANTHROPIC_MAX_CACHE_BREAKPOINTS = 4\n\n/**\n * What the Messages API compiles of one request's strict schemas, as its\n * structured-outputs documentation states the limits: 20 strict tools, 24\n * optional parameters and 16 parameters with union types, each a total over\n * every strict schema the request carries. Past any of them the request is\n * refused with a 400 before the model runs.\n *\n * `compiledSchemaBytes` is not one of those. The API also refuses a request\n * whose schemas compile to too large a grammar — \"Simplify your tool schemas\n * or reduce the number of strict tools\" — and states no size for it, so this\n * bound is measured rather than read (AGL-3096). Its evidence is two live\n * requests for the automation draft, the largest strict schema anything here\n * sends:\n *\n * - 8,757 bytes, refused for grammar size (`req_011CfC6Fif9iJvmKPTRgtLdp`,\n * 2026-09-19), with 1 strict tool, 0 optional parameters and 2 unions:\n * every published count was green;\n * - 3,807 bytes, served (`req_011CfELn62WVLeaNzwK4TWgb`, 2026-09-20), the\n * same tool with the guard lifted out of the steps' union and one variant\n * per set of fields.\n *\n * Everything else the plugin sends is smaller still, the next largest being\n * the theme tool at 2,410 bytes. The bound sits at what has been served, so a\n * schema that grows past it has to be proved live before the bound moves.\n */\nexport const ANTHROPIC_TOOL_SCHEMA_LIMITS: AiToolSchemaLimits = {\n strictTools: 20,\n optionalParameters: 24,\n unionParameters: 16,\n compiledSchemaBytes: 3_807,\n}\n\n/**\n * Whether a provider failure is the kind that clears on its own. Matched on\n * the status AND the error type, because the in-stream `error` event has no\n * status to go by.\n */\nexport function anthropicFailureIsRetryable(\n status: number | null,\n errorType: string | undefined,\n): boolean {\n return (\n status === 429 ||\n status === 529 ||\n errorType === 'rate_limit_error' ||\n errorType === 'overloaded_error'\n )\n}\n\n/**\n * Whether a failure is about the platform's Anthropic account rather than the\n * request (AGL-3377): a refused key, or a balance too low to answer.\n */\nexport function anthropicAccountProblem(\n status: number | null,\n error: { type?: string; message?: string } | null | undefined,\n): AiAccountProblem | null {\n if (status === 401 || status === 403) return 'credentials'\n if (error?.type === 'authentication_error' || error?.type === 'permission_error') {\n return 'credentials'\n }\n if (error?.type === 'billing_error' || /credit balance/i.test(error?.message ?? '')) {\n return 'credit'\n }\n return null\n}\n\n/** Anthropic's `usage` object, in the meter's shape. Missing fields read 0. */\nexport function anthropicUsageFrom(usage: unknown): AiUsage {\n const record = (usage ?? {}) as Record<string, unknown>\n // Part of `output_tokens`, and carried only where the response breaks them\n // down, so an answer that reports none says nothing rather than zero.\n const details = record['output_tokens_details']\n const thinking =\n details && typeof details === 'object'\n ? (details as Record<string, unknown>)['thinking_tokens']\n : undefined\n // The writes split by lifetime (AGL-3830). Only an hour-long write is kept,\n // because it is the one billed above the rate table's write rate, and only\n // where there was one, so every five-minute exchange keeps its shape.\n const creation = record['cache_creation']\n const hour =\n creation && typeof creation === 'object'\n ? aiTokenCount((creation as Record<string, unknown>)['ephemeral_1h_input_tokens'])\n : 0\n return {\n inputTokens: aiTokenCount(record['input_tokens']),\n outputTokens: aiTokenCount(record['output_tokens']),\n cacheReadTokens: aiTokenCount(record['cache_read_input_tokens']),\n cacheWriteTokens: aiTokenCount(record['cache_creation_input_tokens']),\n ...(hour > 0 ? { cacheWriteHourTokens: hour } : {}),\n ...(thinking === undefined ? {} : { thinkingTokens: aiTokenCount(thinking) }),\n }\n}\n\n/** What an exchange draws, its hour-long cache writes included (AGL-3830). */\nfunction anthropicBilledUsd(usage: AiUsage, model: string): number {\n const usd = estimateAiBilledUsd(usage, model) + aiCacheWriteHourSurchargeUsd(usage, aiBilledRatesForModel(model))\n return Math.round(usd * 1_000_000) / 1_000_000\n}\n\n/**\n * How \"thinking off\" is said to a model that refuses `{type: \"disabled\"}`.\n * Claude Sonnet 5.5 turns it off only through `between_tools` (accepted at\n * effort high or below, which is every effort the contract names); Claude\n * Opus 5.5 cannot turn it off at all, so the field is left out and the\n * model's adaptive thinking runs. Every other model takes `disabled`.\n */\nconst ANTHROPIC_THINKING_OFF: Readonly<Record<string, 'between_tools' | 'omit'>> = {\n 'claude-sonnet-5-5': 'between_tools',\n 'claude-opus-5-5': 'omit',\n}\n\n/**\n * The effort a request that names none is sent, for a model whose own\n * default is lower than its siblings': Claude Opus 5.5 defaults to medium,\n * where Claude Opus 5 defaults to high.\n */\nconst ANTHROPIC_DEFAULT_EFFORT: Readonly<Record<string, AiEffort>> = {\n 'claude-opus-5-5': 'high',\n}\n\n/**\n * The Messages API body for a request. Exported so a spec can assert the\n * wire shape without a network. The cache-prefix rule is enforced by the\n * runtime before the request reaches any adapter; this maps the surviving\n * breakpoints onto `cache_control` and refuses only the count the API\n * itself refuses.\n */\nexport function buildAnthropicRequestBody(\n input: Omit<AiProviderRequest, 'apiKey' | 'signal'> & { stream: boolean },\n): Record<string, unknown> {\n const conversation = Boolean(input.cacheConversation) && input.messages.length > 0\n const breakpoints = input.system.filter((block) => block.cacheBreakpoint).length + (conversation ? 1 : 0)\n if (breakpoints > ANTHROPIC_MAX_CACHE_BREAKPOINTS) {\n throw new Error(\n `${breakpoints} cache breakpoints; the Messages API allows ${ANTHROPIC_MAX_CACHE_BREAKPOINTS}`,\n )\n }\n // One lifetime for every breakpoint: the API refuses a longer-lived entry\n // after a shorter one, and a request asks for one or the other.\n const cacheControl = input.cacheTtl === '1h' ? { type: 'ephemeral', ttl: '1h' } : { type: 'ephemeral' }\n const system = input.system.map((block) => ({\n type: 'text',\n text: block.text,\n ...(block.cacheBreakpoint ? { cache_control: cacheControl } : {}),\n }))\n const tools = input.tools?.map((tool) => ({\n name: tool.name,\n description: tool.description,\n input_schema: { ...tool.inputSchema, additionalProperties: false },\n strict: true,\n }))\n // A model that rejects an explicit thinking setting gets none, whatever\n // the door asked for, and no effort either: effort rides the same rule\n // (see the contract), and the catalog says which models those are.\n const settable = aiCatalogEntry(input.model)?.capabilities.thinking !== false\n const thinking = settable ? input.thinking : undefined\n const effort = settable ? (input.effort ?? ANTHROPIC_DEFAULT_EFFORT[input.model]) : undefined\n const thinkingOff = ANTHROPIC_THINKING_OFF[input.model] ?? 'disabled'\n return {\n model: input.model,\n max_tokens: input.maxTokens,\n ...(input.stream ? { stream: true } : {}),\n ...(thinking === 'off'\n ? thinkingOff === 'omit'\n ? {}\n : { thinking: { type: thinkingOff } }\n : thinking === 'adaptive'\n ? { thinking: { type: 'adaptive' } }\n : {}),\n ...(effort ? { output_config: { effort } } : {}),\n ...(system.length ? { system } : {}),\n messages: input.messages.map((message, index) => ({\n role: message.role,\n content: conversation\n ? anthropicBlocksOf(message, index === input.messages.length - 1 ? cacheControl : null)\n : anthropicContentOf(message),\n })),\n ...(tools?.length ? { tools, tool_choice: { type: 'auto' } } : {}),\n }\n}\n\n/**\n * A turn's content in the Messages API's shape: a text-only turn as the\n * string it is, and a turn with pictures as content blocks in order, each\n * picture a base64 image block (AGL-2916).\n */\nfunction anthropicContentOf(message: AiMessage): string | Array<Record<string, unknown>> {\n if (typeof message.content === 'string') return message.content\n return message.content.map((part) =>\n part.type === 'image'\n ? { type: 'image', source: { type: 'base64', media_type: part.mediaType, data: part.data } }\n : { type: 'text', text: part.text },\n )\n}\n\n/**\n * A turn as content blocks whatever its shape, the last block carrying the\n * conversation's breakpoint when it is the request's last turn (AGL-3809).\n * Every turn of a request that caches its conversation is sent as blocks, so\n * a turn reads the same on the call that marked it and on every call after,\n * where it is no longer last.\n */\nfunction anthropicBlocksOf(\n message: AiMessage,\n cacheControl: Record<string, unknown> | null,\n): Array<Record<string, unknown>> {\n const content = anthropicContentOf(message)\n const blocks = typeof content === 'string' ? [{ type: 'text', text: content }] : content\n if (!cacheControl || !blocks.length) return blocks\n const last = blocks.length - 1\n return blocks.map((block, index) => (index === last ? { ...block, cache_control: cacheControl } : block))\n}\n\n/** The provider's request id, when it sent one — the log's join key. */\nfunction requestIdOf(response: Response): string | null {\n return response.headers?.get?.('request-id') ?? null\n}\n\nasync function send(\n input: AiProviderRequest,\n stream: boolean,\n): Promise<Response> {\n const body = buildAnthropicRequestBody({ ...input, stream })\n const response = await fetch(ANTHROPIC_URL, {\n method: 'POST',\n headers: {\n 'x-api-key': input.apiKey,\n 'anthropic-version': ANTHROPIC_VERSION,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n ...(input.signal ? { signal: input.signal } : {}),\n })\n if (!response.ok || (stream && !response.body)) {\n const requestId = requestIdOf(response)\n const payload = (await response.json().catch(() => null)) as {\n error?: { type?: string; message?: string }\n } | null\n console.error('ai upstream error', {\n provider: ANTHROPIC_PROVIDER_ID,\n status: response.status,\n requestId,\n model: input.model,\n error: payload?.error ?? null,\n })\n throw new AiUpstreamError(\n response.status,\n anthropicFailureIsRetryable(response.status, payload?.error?.type),\n requestId,\n anthropicAccountProblem(response.status, payload?.error),\n )\n }\n return response\n}\n\nasync function complete(input: AiProviderRequest): Promise<AiResult> {\n const response = await send(input, false)\n return anthropicResultFrom(await response.json(), input.model)\n}\n\n/**\n * A Messages API answer as the contract's result: the shape `complete` reads\n * off the wire and a batch's succeeded entry carries. `priceFactor` is what a\n * discount leaves of the price (a batch's half).\n */\nexport function anthropicResultFrom(raw: unknown, model: string, priceFactor = 1): AiResult {\n const payload = raw as {\n content?: Array<Record<string, unknown>>\n stop_reason?: unknown\n usage?: unknown\n } | null\n const blocks = Array.isArray(payload?.content) ? payload.content : []\n const text = blocks\n .filter((block) => block?.['type'] === 'text')\n .map((block) => String(block['text'] ?? ''))\n .join('')\n const toolUse: AiToolUse[] = blocks\n .filter((block) => block?.['type'] === 'tool_use')\n .map((block) => ({\n name: String(block['name'] ?? ''),\n input: aiToolInputOf(block['input']),\n }))\n const usage = anthropicUsageFrom(payload?.usage)\n const estCostUsd = Math.round(anthropicBilledUsd(usage, model) * priceFactor * 1_000_000) / 1_000_000\n const stopReason =\n typeof payload?.stop_reason === 'string' && payload.stop_reason\n ? payload.stop_reason\n : null\n if (stopReason === 'refusal') {\n return { kind: 'refusal', text, usage, estCostUsd, stopReason }\n }\n return {\n kind: 'completion',\n text,\n toolUse,\n usage,\n estCostUsd,\n stopReason,\n // The blocks as the provider answered them, for a call its ceiling cut\n // off (AGL-3143): `toolUse` holds only what still parsed out of them.\n ...(aiStoppedAtCeiling(stopReason) ? { rawOutput: aiRawOutputOf(blocks) } : {}),\n }\n}\n\nasync function stream(input: AiProviderRequest): Promise<AsyncIterable<AiStreamEvent>> {\n const response = await send(input, true)\n return streamEvents(\n response.body as ReadableStream<Uint8Array>,\n input.model,\n requestIdOf(response),\n )\n}\n\n/**\n * Anthropic's SSE stream as contract events. Usage is taken from\n * `message_start` (input and cache figures) and `message_delta` (the\n * output count, and any cumulative input figures a newer API sends); the\n * `done` event carries whatever was known when the body ended, so a stream\n * cut short still meters what it cost.\n */\nasync function* streamEvents(\n body: ReadableStream<Uint8Array>,\n model: string,\n requestId: string | null,\n): AsyncGenerator<AiStreamEvent> {\n const decoder = new TextDecoder()\n const reader = body.getReader()\n const usage: AiUsage = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n }\n let stopReason: string | null = null\n /** Tool-use blocks in flight, by content index; input arrives in pieces. */\n const tools = new Map<number, { name: string; json: string }>()\n /**\n * What each tool call was actually written in, in the order its block\n * opened (AGL-3143). The `tool` events below carry only as much of these\n * as `aiToolInputOf` could still parse, so for a call the ceiling cut off\n * this is the one place the emitted bytes survive.\n */\n const written: string[] = []\n let buffer = ''\n let drained = false\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) {\n drained = true\n break\n }\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue\n let event: Record<string, unknown>\n try {\n event = JSON.parse(line.slice('data: '.length))\n } catch {\n continue\n }\n const index = Number(event['index'])\n switch (event['type']) {\n case 'message_start': {\n const start = anthropicUsageFrom(\n (event['message'] as { usage?: unknown } | undefined)?.usage,\n )\n usage.inputTokens = start.inputTokens\n usage.cacheReadTokens = start.cacheReadTokens\n usage.cacheWriteTokens = start.cacheWriteTokens\n if (start.cacheWriteHourTokens) usage.cacheWriteHourTokens = start.cacheWriteHourTokens\n break\n }\n case 'content_block_start': {\n const block = event['content_block'] as\n | { type?: string; name?: string }\n | undefined\n if (block?.type === 'tool_use') {\n tools.set(index, { name: String(block.name ?? ''), json: '' })\n }\n break\n }\n case 'content_block_delta': {\n const delta = event['delta'] as\n | { type?: string; text?: string; partial_json?: string }\n | undefined\n if (delta?.type === 'text_delta' && delta.text) {\n yield { type: 'delta', text: delta.text }\n } else if (delta?.type === 'input_json_delta') {\n const tool = tools.get(index)\n if (tool) tool.json += delta.partial_json ?? ''\n }\n break\n }\n case 'content_block_stop': {\n const tool = tools.get(index)\n if (tool) {\n tools.delete(index)\n written.push(tool.json)\n yield { type: 'tool', name: tool.name, input: aiToolInputOf(tool.json) }\n }\n break\n }\n case 'message_delta': {\n const delta = anthropicUsageFrom(event['usage'])\n usage.outputTokens = delta.outputTokens || usage.outputTokens\n usage.inputTokens = delta.inputTokens || usage.inputTokens\n usage.cacheReadTokens = delta.cacheReadTokens || usage.cacheReadTokens\n usage.cacheWriteTokens = delta.cacheWriteTokens || usage.cacheWriteTokens\n // Kept where the breakdown is reported and left absent where it\n // is not (AGL-3143), never set to a zero the provider did not\n // send. A stream needs it for the reason a completion does: it\n // is the figure that accounts for a spent ceiling first, and it\n // arrives in the same usage object as the four above.\n if (delta.thinkingTokens !== undefined) usage.thinkingTokens = delta.thinkingTokens\n const reason = (event['delta'] as { stop_reason?: unknown } | undefined)\n ?.stop_reason\n if (typeof reason === 'string' && reason) stopReason = reason\n break\n }\n case 'error': {\n const error = event['error'] as\n | { type?: string; message?: string }\n | undefined\n console.error('ai upstream stream error', {\n provider: ANTHROPIC_PROVIDER_ID,\n requestId,\n model,\n error: error ?? null,\n })\n yield { type: 'error', retryable: anthropicFailureIsRetryable(null, error?.type) }\n break\n }\n default:\n break\n }\n }\n }\n } finally {\n // A consumer that stops iterating early — a client that hung up — must\n // not leave the provider generating into a socket nobody reads: cancel\n // tells the upstream to stop, and the tokens it has already produced are\n // the only ones billed.\n if (!drained) await reader.cancel().catch(() => undefined)\n reader.releaseLock()\n }\n // A message the ceiling ends mid-block never reaches that block's stop\n // event, so what it had written is still held in flight.\n for (const tool of tools.values()) written.push(tool.json)\n yield {\n type: 'done',\n usage,\n estCostUsd: anthropicBilledUsd(usage, model),\n stopReason,\n // What the model emitted into its tool calls, for a stream its ceiling\n // cut off (AGL-3143): the `tool` events carry only what parsed out.\n ...(aiStoppedAtCeiling(stopReason) ? { rawOutput: aiRawOutputOf(written.join('')) } : {}),\n }\n}\n\n/** What a batch's discount leaves of a request's price. */\nexport const ANTHROPIC_BATCH_PRICE_FACTOR = 0.5\n\nfunction batchHeaders(apiKey: string): Record<string, string> {\n return {\n 'x-api-key': apiKey,\n 'anthropic-version': ANTHROPIC_VERSION,\n 'Content-Type': 'application/json',\n }\n}\n\n/** A batch call that did not answer 2xx, as the contract's error. */\nasync function batchFailure(response: Response, model: string): Promise<AiUpstreamError> {\n const requestId = requestIdOf(response)\n const payload = (await response.json().catch(() => null)) as {\n error?: { type?: string; message?: string }\n } | null\n console.error('ai upstream batch error', {\n provider: ANTHROPIC_PROVIDER_ID,\n status: response.status,\n requestId,\n model,\n error: payload?.error ?? null,\n })\n return new AiUpstreamError(\n response.status,\n anthropicFailureIsRetryable(response.status, payload?.error?.type),\n requestId,\n anthropicAccountProblem(response.status, payload?.error),\n )\n}\n\n/** The batch as its create and poll calls describe it. */\ninterface AnthropicBatch {\n id: string\n processing_status?: string\n results_url?: string | null\n}\n\n/** Results a batch can end a request with that answer nothing yet: asked again, not failed (AGL-3781). */\nconst ANTHROPIC_BATCH_UNFINISHED = new Set(['canceled', 'expired'])\n\n/**\n * Many requests through the Message Batches API (AGL-3660): created as one\n * batch, polled until it has ended, its results read by `custom_id` — they\n * come back in any order — and each turned into the result `complete` would\n * have returned, at half the price. Development only: the live evals' batch\n * mode is the one caller, and no door waits minutes for an answer.\n *\n * A batch still processing when its wait runs out is canceled, and read once\n * the cancel has ended it (AGL-3781): a cancel keeps every answer the batch\n * already has, so those come back as results, and only the requests it never\n * reached come back as `AiBatchUnfinishedError`, for the caller to ask on\n * their own. Throwing away a round's paid-for answers because one batch was\n * slow is how a whole golden run lost its grades.\n */\nasync function completeBatch(\n requests: readonly AiProviderRequest[],\n options: AiBatchOptions = {},\n): Promise<Array<AiResult | Error>> {\n if (!requests.length) return []\n const apiKey = requests[0].apiKey\n const model = requests[0].model\n const pollMs = options.pollMs ?? 10_000\n const timeoutMs = options.timeoutMs ?? 60 * 60_000\n const cancelGraceMs = options.cancelGraceMs ?? 10 * 60_000\n const unfinished = (id: string, reason: string) => requests.map(() => new AiBatchUnfinishedError(id, reason))\n const created = await fetch(ANTHROPIC_BATCHES_URL, {\n method: 'POST',\n headers: batchHeaders(apiKey),\n body: JSON.stringify({\n requests: requests.map((request, index) => {\n const { apiKey: _key, signal: _signal, ...rest } = request\n return { custom_id: `r${index}`, params: buildAnthropicRequestBody({ ...rest, stream: false }) }\n }),\n }),\n })\n if (!created.ok) throw await batchFailure(created, model)\n let batch = (await created.json()) as AnthropicBatch\n const deadline = Date.now() + timeoutMs\n let cancelDeadline: number | null = null\n while (batch.processing_status !== 'ended') {\n if (cancelDeadline === null && Date.now() >= deadline) {\n console.warn(`[ai] batch ${batch.id} has not ended after ${timeoutMs} ms; canceling it to read what it answered`)\n await fetch(`${ANTHROPIC_BATCHES_URL}/${batch.id}/cancel`, { method: 'POST', headers: batchHeaders(apiKey) }).catch(\n () => undefined,\n )\n cancelDeadline = Date.now() + cancelGraceMs\n }\n // A cancel that never ends leaves no results to read; every request goes back unanswered.\n if (cancelDeadline !== null && Date.now() >= cancelDeadline) return unfinished(batch.id, 'cancel did not end')\n await new Promise((resolve) => setTimeout(resolve, pollMs))\n const polled = await fetch(`${ANTHROPIC_BATCHES_URL}/${batch.id}`, { headers: batchHeaders(apiKey) })\n if (!polled.ok) throw await batchFailure(polled, model)\n batch = (await polled.json()) as AnthropicBatch\n }\n if (!batch.results_url) {\n if (cancelDeadline !== null) return unfinished(batch.id, 'canceled with no results')\n throw new Error(`batch ${batch.id} ended with no results`)\n }\n const results = await fetch(batch.results_url, { headers: batchHeaders(apiKey) })\n if (!results.ok) throw await batchFailure(results, model)\n const byId = new Map<string, { type?: string; message?: unknown; error?: unknown }>()\n for (const line of (await results.text()).split('\\n')) {\n if (!line.trim()) continue\n const entry = JSON.parse(line) as { custom_id: string; result?: { type?: string; message?: unknown; error?: unknown } }\n byId.set(entry.custom_id, entry.result ?? {})\n }\n return requests.map((request, index) => {\n const result = byId.get(`r${index}`)\n if (result?.type === 'succeeded') {\n return anthropicResultFrom(result.message, request.model, ANTHROPIC_BATCH_PRICE_FACTOR)\n }\n const type = result?.type ?? 'missing'\n if (ANTHROPIC_BATCH_UNFINISHED.has(type) || type === 'missing') return new AiBatchUnfinishedError(batch.id, type)\n console.error('ai batch request not answered', {\n provider: ANTHROPIC_PROVIDER_ID,\n batchId: batch.id,\n customId: `r${index}`,\n result: type,\n error: result?.error ?? null,\n })\n return new AiUpstreamError(null, type === 'errored', batch.id, null)\n })\n}\n\nexport const anthropicProvider: AiProvider = {\n id: ANTHROPIC_PROVIDER_ID,\n label: 'Anthropic',\n apiKeyEnv: ANTHROPIC_API_KEY_ENV,\n readApiKey: () => process.env.ANTHROPIC_API_KEY?.trim() || undefined,\n endpointHost: ANTHROPIC_HOST,\n toolSchemaLimits: ANTHROPIC_TOOL_SCHEMA_LIMITS,\n models(): readonly AiModelDescriptor[] {\n return aiModelIdsForProvider(ANTHROPIC_PROVIDER_ID)\n .map((id) => aiCatalogEntry(id))\n .filter((entry): entry is NonNullable<typeof entry> => Boolean(entry))\n },\n complete,\n stream,\n completeBatch,\n}\n"],"names":["aiBilledRatesForModel","aiCatalogEntry","aiModelIdsForProvider","estimateAiBilledUsd","aiCacheWriteHourSurchargeUsd","AiBatchUnfinishedError","AiUpstreamError","aiRawOutputOf","aiStoppedAtCeiling","aiTokenCount","aiToolInputOf","ANTHROPIC_PROVIDER_ID","ANTHROPIC_API_KEY_ENV","ANTHROPIC_HOST","ANTHROPIC_URL","ANTHROPIC_BATCHES_URL","ANTHROPIC_VERSION","ANTHROPIC_MAX_CACHE_BREAKPOINTS","ANTHROPIC_TOOL_SCHEMA_LIMITS","strictTools","optionalParameters","unionParameters","compiledSchemaBytes","anthropicFailureIsRetryable","status","errorType","anthropicAccountProblem","error","type","test","message","anthropicUsageFrom","usage","record","details","thinking","undefined","creation","hour","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","cacheWriteHourTokens","thinkingTokens","anthropicBilledUsd","model","usd","Math","round","ANTHROPIC_THINKING_OFF","ANTHROPIC_DEFAULT_EFFORT","buildAnthropicRequestBody","input","conversation","Boolean","cacheConversation","messages","length","breakpoints","system","filter","block","cacheBreakpoint","Error","cacheControl","cacheTtl","ttl","map","text","cache_control","tools","tool","name","description","input_schema","inputSchema","additionalProperties","strict","settable","capabilities","effort","thinkingOff","max_tokens","maxTokens","stream","output_config","index","role","content","anthropicBlocksOf","anthropicContentOf","tool_choice","part","source","media_type","mediaType","data","blocks","last","requestIdOf","response","headers","get","send","body","fetch","method","apiKey","JSON","stringify","signal","ok","payload","requestId","json","catch","console","provider","complete","anthropicResultFrom","raw","priceFactor","Array","isArray","String","join","toolUse","estCostUsd","stopReason","stop_reason","kind","rawOutput","streamEvents","decoder","TextDecoder","reader","getReader","Map","written","buffer","drained","lines","done","value","read","decode","split","pop","line","startsWith","event","parse","slice","Number","start","set","delta","partial_json","delete","push","reason","retryable","cancel","releaseLock","values","ANTHROPIC_BATCH_PRICE_FACTOR","batchHeaders","batchFailure","ANTHROPIC_BATCH_UNFINISHED","Set","completeBatch","requests","options","pollMs","timeoutMs","cancelGraceMs","unfinished","id","created","request","_key","_signal","rest","custom_id","params","batch","deadline","Date","now","cancelDeadline","processing_status","warn","Promise","resolve","setTimeout","polled","results_url","results","byId","entry","trim","result","has","batchId","customId","anthropicProvider","label","apiKeyEnv","readApiKey","process","env","ANTHROPIC_API_KEY","endpointHost","toolSchemaLimits","models"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/providers/anthropic.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { aiBilledRatesForModel, aiCatalogEntry, aiModelIdsForProvider, estimateAiBilledUsd } from './catalog'\nimport { aiCacheWriteHourSurchargeUsd } from './cache-write-hour'\nimport {\n AiBatchUnfinishedError,\n AiUpstreamError,\n aiCacheMarkerTtls,\n type AiAccountProblem,\n type AiBatchOptions,\n type AiCacheTtl,\n type AiEffort,\n aiRawOutputOf,\n aiStoppedAtCeiling,\n aiTokenCount,\n aiToolInputOf,\n type AiMessage,\n type AiModelDescriptor,\n type AiProvider,\n type AiProviderRequest,\n type AiResult,\n type AiStreamEvent,\n type AiToolSchemaLimits,\n type AiToolUse,\n type AiUsage,\n} from './contract'\n\n/**\n * The Anthropic adapter (AGL-2903, behind the contract since AGL-2939):\n * the Messages API over raw `fetch`. No provider SDK is installed, and the\n * meter prices by model id from the catalog, so nothing an SDK would add\n * is used here. This file and the catalog are the only places in the\n * plugin that name the vendor's URL, header or model ids.\n *\n * ── Errors (AGL-2815) ─────────────────────────────────────────────────────\n *\n * Provider error text describes OUR account with the vendor — a key, a rate\n * limit, a balance — and names the vendor to a white-label org's users. It\n * never leaves this module as a message: a non-2xx answer becomes an\n * `AiUpstreamError` carrying the status, whether the failure is retryable\n * and a fixed customer-safe sentence; the provider's body goes to\n * `console.error` beside the request id, where an operator can find it.\n * A `refusal` stop is not an error at all — the tokens were spent and the\n * model answered — so it comes back as a typed `AiRefusal` result.\n */\n\nexport const ANTHROPIC_PROVIDER_ID = 'anthropic'\nexport const ANTHROPIC_API_KEY_ENV = 'ANTHROPIC_API_KEY'\nconst ANTHROPIC_HOST = 'api.anthropic.com'\nconst ANTHROPIC_URL = `https://${ANTHROPIC_HOST}/v1/messages`\nconst ANTHROPIC_BATCHES_URL = `https://${ANTHROPIC_HOST}/v1/messages/batches`\nconst ANTHROPIC_VERSION = '2023-06-01'\n\n/** The Messages API accepts at most this many `cache_control` markers. */\nexport const ANTHROPIC_MAX_CACHE_BREAKPOINTS = 4\n\n/**\n * What the Messages API compiles of one request's strict schemas, as its\n * structured-outputs documentation states the limits: 20 strict tools, 24\n * optional parameters and 16 parameters with union types, each a total over\n * every strict schema the request carries. Past any of them the request is\n * refused with a 400 before the model runs.\n *\n * `compiledSchemaBytes` is not one of those. The API also refuses a request\n * whose schemas compile to too large a grammar — \"Simplify your tool schemas\n * or reduce the number of strict tools\" — and states no size for it, so this\n * bound is measured rather than read (AGL-3096). Its evidence is two live\n * requests for the automation draft, the largest strict schema anything here\n * sends:\n *\n * - 8,757 bytes, refused for grammar size (`req_011CfC6Fif9iJvmKPTRgtLdp`,\n * 2026-09-19), with 1 strict tool, 0 optional parameters and 2 unions:\n * every published count was green;\n * - 3,807 bytes, served (`req_011CfELn62WVLeaNzwK4TWgb`, 2026-09-20), the\n * same tool with the guard lifted out of the steps' union and one variant\n * per set of fields.\n *\n * Everything else the plugin sends is smaller still, the next largest being\n * the theme tool at 2,410 bytes. The bound sits at what has been served, so a\n * schema that grows past it has to be proved live before the bound moves.\n */\nexport const ANTHROPIC_TOOL_SCHEMA_LIMITS: AiToolSchemaLimits = {\n strictTools: 20,\n optionalParameters: 24,\n unionParameters: 16,\n compiledSchemaBytes: 3_807,\n}\n\n/**\n * Whether a provider failure is the kind that clears on its own. Matched on\n * the status AND the error type, because the in-stream `error` event has no\n * status to go by.\n */\nexport function anthropicFailureIsRetryable(\n status: number | null,\n errorType: string | undefined,\n): boolean {\n return (\n status === 429 ||\n status === 529 ||\n errorType === 'rate_limit_error' ||\n errorType === 'overloaded_error'\n )\n}\n\n/**\n * Whether a failure is about the platform's Anthropic account rather than the\n * request (AGL-3377): a refused key, or a balance too low to answer.\n */\nexport function anthropicAccountProblem(\n status: number | null,\n error: { type?: string; message?: string } | null | undefined,\n): AiAccountProblem | null {\n if (status === 401 || status === 403) return 'credentials'\n if (error?.type === 'authentication_error' || error?.type === 'permission_error') {\n return 'credentials'\n }\n if (error?.type === 'billing_error' || /credit balance/i.test(error?.message ?? '')) {\n return 'credit'\n }\n return null\n}\n\n/** Anthropic's `usage` object, in the meter's shape. Missing fields read 0. */\nexport function anthropicUsageFrom(usage: unknown): AiUsage {\n const record = (usage ?? {}) as Record<string, unknown>\n // Part of `output_tokens`, and carried only where the response breaks them\n // down, so an answer that reports none says nothing rather than zero.\n const details = record['output_tokens_details']\n const thinking =\n details && typeof details === 'object'\n ? (details as Record<string, unknown>)['thinking_tokens']\n : undefined\n // The writes split by lifetime (AGL-3830). Only an hour-long write is kept,\n // because it is the one billed above the rate table's write rate, and only\n // where there was one, so every five-minute exchange keeps its shape.\n const creation = record['cache_creation']\n const hour =\n creation && typeof creation === 'object'\n ? aiTokenCount((creation as Record<string, unknown>)['ephemeral_1h_input_tokens'])\n : 0\n return {\n inputTokens: aiTokenCount(record['input_tokens']),\n outputTokens: aiTokenCount(record['output_tokens']),\n cacheReadTokens: aiTokenCount(record['cache_read_input_tokens']),\n cacheWriteTokens: aiTokenCount(record['cache_creation_input_tokens']),\n ...(hour > 0 ? { cacheWriteHourTokens: hour } : {}),\n ...(thinking === undefined ? {} : { thinkingTokens: aiTokenCount(thinking) }),\n }\n}\n\n/** What an exchange draws, its hour-long cache writes included (AGL-3830). */\nfunction anthropicBilledUsd(usage: AiUsage, model: string): number {\n const usd = estimateAiBilledUsd(usage, model) + aiCacheWriteHourSurchargeUsd(usage, aiBilledRatesForModel(model))\n return Math.round(usd * 1_000_000) / 1_000_000\n}\n\n/**\n * How \"thinking off\" is said to a model that refuses `{type: \"disabled\"}`.\n * Claude Sonnet 5.5 turns it off only through `between_tools` (accepted at\n * effort high or below, which is every effort the contract names); Claude\n * Opus 5.5 cannot turn it off at all, so the field is left out and the\n * model's adaptive thinking runs. Every other model takes `disabled`.\n */\nconst ANTHROPIC_THINKING_OFF: Readonly<Record<string, 'between_tools' | 'omit'>> = {\n 'claude-sonnet-5-5': 'between_tools',\n 'claude-opus-5-5': 'omit',\n}\n\n/**\n * The effort a request that names none is sent, for a model whose own\n * default is lower than its siblings': Claude Opus 5.5 defaults to medium,\n * where Claude Opus 5 defaults to high.\n */\nconst ANTHROPIC_DEFAULT_EFFORT: Readonly<Record<string, AiEffort>> = {\n 'claude-opus-5-5': 'high',\n}\n\n/**\n * The Messages API body for a request. Exported so a spec can assert the\n * wire shape without a network. The cache-prefix rule is enforced by the\n * runtime before the request reaches any adapter; this maps the surviving\n * breakpoints onto `cache_control` and refuses only the count the API\n * itself refuses.\n */\nexport function buildAnthropicRequestBody(\n input: Omit<AiProviderRequest, 'apiKey' | 'signal'> & { stream: boolean },\n): Record<string, unknown> {\n const conversation = Boolean(input.cacheConversation) && input.messages.length > 0\n const breakpoints = input.system.filter((block) => block.cacheBreakpoint).length + (conversation ? 1 : 0)\n if (breakpoints > ANTHROPIC_MAX_CACHE_BREAKPOINTS) {\n throw new Error(\n `${breakpoints} cache breakpoints; the Messages API allows ${ANTHROPIC_MAX_CACHE_BREAKPOINTS}`,\n )\n }\n // A lifetime per breakpoint (AGL-4050): the shared prompt's markers at the\n // route's lifetime, a site's block and the conversation at five minutes,\n // and never an hour after five minutes, which the API refuses.\n const ttls = aiCacheMarkerTtls({ ...input, cacheConversation: conversation })\n const system = input.system.map((block, index) => {\n const ttl = ttls.system[index]\n return { type: 'text', text: block.text, ...(ttl ? { cache_control: anthropicCacheControl(ttl) } : {}) }\n })\n const tools = input.tools?.map((tool) => ({\n name: tool.name,\n description: tool.description,\n input_schema: { ...tool.inputSchema, additionalProperties: false },\n strict: true,\n }))\n // A model that rejects an explicit thinking setting gets none, whatever\n // the door asked for, and no effort either: effort rides the same rule\n // (see the contract), and the catalog says which models those are.\n const settable = aiCatalogEntry(input.model)?.capabilities.thinking !== false\n const thinking = settable ? input.thinking : undefined\n const effort = settable ? (input.effort ?? ANTHROPIC_DEFAULT_EFFORT[input.model]) : undefined\n const thinkingOff = ANTHROPIC_THINKING_OFF[input.model] ?? 'disabled'\n return {\n model: input.model,\n max_tokens: input.maxTokens,\n ...(input.stream ? { stream: true } : {}),\n ...(thinking === 'off'\n ? thinkingOff === 'omit'\n ? {}\n : { thinking: { type: thinkingOff } }\n : thinking === 'adaptive'\n ? { thinking: { type: 'adaptive' } }\n : {}),\n ...(effort ? { output_config: { effort } } : {}),\n ...(system.length ? { system } : {}),\n messages: input.messages.map((message, index) => ({\n role: message.role,\n content: conversation\n ? anthropicBlocksOf(\n message,\n index === input.messages.length - 1 && ttls.conversation ? anthropicCacheControl(ttls.conversation) : null,\n )\n : anthropicContentOf(message),\n })),\n ...(tools?.length ? { tools, tool_choice: { type: 'auto' } } : {}),\n }\n}\n\n/** One marker's `cache_control`: the five minutes are the API's default, so only the hour is named. */\nfunction anthropicCacheControl(ttl: AiCacheTtl): Record<string, unknown> {\n return ttl === '1h' ? { type: 'ephemeral', ttl: '1h' } : { type: 'ephemeral' }\n}\n\n/**\n * A turn's content in the Messages API's shape: a text-only turn as the\n * string it is, and a turn with pictures as content blocks in order, each\n * picture a base64 image block (AGL-2916).\n */\nfunction anthropicContentOf(message: AiMessage): string | Array<Record<string, unknown>> {\n if (typeof message.content === 'string') return message.content\n return message.content.map((part) =>\n part.type === 'image'\n ? { type: 'image', source: { type: 'base64', media_type: part.mediaType, data: part.data } }\n : { type: 'text', text: part.text },\n )\n}\n\n/**\n * A turn as content blocks whatever its shape, the last block carrying the\n * conversation's breakpoint when it is the request's last turn (AGL-3809).\n * Every turn of a request that caches its conversation is sent as blocks, so\n * a turn reads the same on the call that marked it and on every call after,\n * where it is no longer last.\n */\nfunction anthropicBlocksOf(\n message: AiMessage,\n cacheControl: Record<string, unknown> | null,\n): Array<Record<string, unknown>> {\n const content = anthropicContentOf(message)\n const blocks = typeof content === 'string' ? [{ type: 'text', text: content }] : content\n if (!cacheControl || !blocks.length) return blocks\n const last = blocks.length - 1\n return blocks.map((block, index) => (index === last ? { ...block, cache_control: cacheControl } : block))\n}\n\n/** The provider's request id, when it sent one — the log's join key. */\nfunction requestIdOf(response: Response): string | null {\n return response.headers?.get?.('request-id') ?? null\n}\n\nasync function send(\n input: AiProviderRequest,\n stream: boolean,\n): Promise<Response> {\n const body = buildAnthropicRequestBody({ ...input, stream })\n const response = await fetch(ANTHROPIC_URL, {\n method: 'POST',\n headers: {\n 'x-api-key': input.apiKey,\n 'anthropic-version': ANTHROPIC_VERSION,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n ...(input.signal ? { signal: input.signal } : {}),\n })\n if (!response.ok || (stream && !response.body)) {\n const requestId = requestIdOf(response)\n const payload = (await response.json().catch(() => null)) as {\n error?: { type?: string; message?: string }\n } | null\n console.error('ai upstream error', {\n provider: ANTHROPIC_PROVIDER_ID,\n status: response.status,\n requestId,\n model: input.model,\n error: payload?.error ?? null,\n })\n throw new AiUpstreamError(\n response.status,\n anthropicFailureIsRetryable(response.status, payload?.error?.type),\n requestId,\n anthropicAccountProblem(response.status, payload?.error),\n )\n }\n return response\n}\n\nasync function complete(input: AiProviderRequest): Promise<AiResult> {\n const response = await send(input, false)\n return anthropicResultFrom(await response.json(), input.model)\n}\n\n/**\n * A Messages API answer as the contract's result: the shape `complete` reads\n * off the wire and a batch's succeeded entry carries. `priceFactor` is what a\n * discount leaves of the price (a batch's half).\n */\nexport function anthropicResultFrom(raw: unknown, model: string, priceFactor = 1): AiResult {\n const payload = raw as {\n content?: Array<Record<string, unknown>>\n stop_reason?: unknown\n usage?: unknown\n } | null\n const blocks = Array.isArray(payload?.content) ? payload.content : []\n const text = blocks\n .filter((block) => block?.['type'] === 'text')\n .map((block) => String(block['text'] ?? ''))\n .join('')\n const toolUse: AiToolUse[] = blocks\n .filter((block) => block?.['type'] === 'tool_use')\n .map((block) => ({\n name: String(block['name'] ?? ''),\n input: aiToolInputOf(block['input']),\n }))\n const usage = anthropicUsageFrom(payload?.usage)\n const estCostUsd = Math.round(anthropicBilledUsd(usage, model) * priceFactor * 1_000_000) / 1_000_000\n const stopReason =\n typeof payload?.stop_reason === 'string' && payload.stop_reason\n ? payload.stop_reason\n : null\n if (stopReason === 'refusal') {\n return { kind: 'refusal', text, usage, estCostUsd, stopReason }\n }\n return {\n kind: 'completion',\n text,\n toolUse,\n usage,\n estCostUsd,\n stopReason,\n // The blocks as the provider answered them, for a call its ceiling cut\n // off (AGL-3143): `toolUse` holds only what still parsed out of them.\n ...(aiStoppedAtCeiling(stopReason) ? { rawOutput: aiRawOutputOf(blocks) } : {}),\n }\n}\n\nasync function stream(input: AiProviderRequest): Promise<AsyncIterable<AiStreamEvent>> {\n const response = await send(input, true)\n return streamEvents(\n response.body as ReadableStream<Uint8Array>,\n input.model,\n requestIdOf(response),\n )\n}\n\n/**\n * Anthropic's SSE stream as contract events. Usage is taken from\n * `message_start` (input and cache figures) and `message_delta` (the\n * output count, and any cumulative input figures a newer API sends); the\n * `done` event carries whatever was known when the body ended, so a stream\n * cut short still meters what it cost.\n */\nasync function* streamEvents(\n body: ReadableStream<Uint8Array>,\n model: string,\n requestId: string | null,\n): AsyncGenerator<AiStreamEvent> {\n const decoder = new TextDecoder()\n const reader = body.getReader()\n const usage: AiUsage = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n }\n let stopReason: string | null = null\n /** Tool-use blocks in flight, by content index; input arrives in pieces. */\n const tools = new Map<number, { name: string; json: string }>()\n /**\n * What each tool call was actually written in, in the order its block\n * opened (AGL-3143). The `tool` events below carry only as much of these\n * as `aiToolInputOf` could still parse, so for a call the ceiling cut off\n * this is the one place the emitted bytes survive.\n */\n const written: string[] = []\n let buffer = ''\n let drained = false\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) {\n drained = true\n break\n }\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue\n let event: Record<string, unknown>\n try {\n event = JSON.parse(line.slice('data: '.length))\n } catch {\n continue\n }\n const index = Number(event['index'])\n switch (event['type']) {\n case 'message_start': {\n const start = anthropicUsageFrom(\n (event['message'] as { usage?: unknown } | undefined)?.usage,\n )\n usage.inputTokens = start.inputTokens\n usage.cacheReadTokens = start.cacheReadTokens\n usage.cacheWriteTokens = start.cacheWriteTokens\n if (start.cacheWriteHourTokens) usage.cacheWriteHourTokens = start.cacheWriteHourTokens\n break\n }\n case 'content_block_start': {\n const block = event['content_block'] as\n | { type?: string; name?: string }\n | undefined\n if (block?.type === 'tool_use') {\n tools.set(index, { name: String(block.name ?? ''), json: '' })\n }\n break\n }\n case 'content_block_delta': {\n const delta = event['delta'] as\n | { type?: string; text?: string; partial_json?: string }\n | undefined\n if (delta?.type === 'text_delta' && delta.text) {\n yield { type: 'delta', text: delta.text }\n } else if (delta?.type === 'input_json_delta') {\n const tool = tools.get(index)\n if (tool) tool.json += delta.partial_json ?? ''\n }\n break\n }\n case 'content_block_stop': {\n const tool = tools.get(index)\n if (tool) {\n tools.delete(index)\n written.push(tool.json)\n yield { type: 'tool', name: tool.name, input: aiToolInputOf(tool.json) }\n }\n break\n }\n case 'message_delta': {\n const delta = anthropicUsageFrom(event['usage'])\n usage.outputTokens = delta.outputTokens || usage.outputTokens\n usage.inputTokens = delta.inputTokens || usage.inputTokens\n usage.cacheReadTokens = delta.cacheReadTokens || usage.cacheReadTokens\n usage.cacheWriteTokens = delta.cacheWriteTokens || usage.cacheWriteTokens\n // Kept where the breakdown is reported and left absent where it\n // is not (AGL-3143), never set to a zero the provider did not\n // send. A stream needs it for the reason a completion does: it\n // is the figure that accounts for a spent ceiling first, and it\n // arrives in the same usage object as the four above.\n if (delta.thinkingTokens !== undefined) usage.thinkingTokens = delta.thinkingTokens\n const reason = (event['delta'] as { stop_reason?: unknown } | undefined)\n ?.stop_reason\n if (typeof reason === 'string' && reason) stopReason = reason\n break\n }\n case 'error': {\n const error = event['error'] as\n | { type?: string; message?: string }\n | undefined\n console.error('ai upstream stream error', {\n provider: ANTHROPIC_PROVIDER_ID,\n requestId,\n model,\n error: error ?? null,\n })\n yield { type: 'error', retryable: anthropicFailureIsRetryable(null, error?.type) }\n break\n }\n default:\n break\n }\n }\n }\n } finally {\n // A consumer that stops iterating early — a client that hung up — must\n // not leave the provider generating into a socket nobody reads: cancel\n // tells the upstream to stop, and the tokens it has already produced are\n // the only ones billed.\n if (!drained) await reader.cancel().catch(() => undefined)\n reader.releaseLock()\n }\n // A message the ceiling ends mid-block never reaches that block's stop\n // event, so what it had written is still held in flight.\n for (const tool of tools.values()) written.push(tool.json)\n yield {\n type: 'done',\n usage,\n estCostUsd: anthropicBilledUsd(usage, model),\n stopReason,\n // What the model emitted into its tool calls, for a stream its ceiling\n // cut off (AGL-3143): the `tool` events carry only what parsed out.\n ...(aiStoppedAtCeiling(stopReason) ? { rawOutput: aiRawOutputOf(written.join('')) } : {}),\n }\n}\n\n/** What a batch's discount leaves of a request's price. */\nexport const ANTHROPIC_BATCH_PRICE_FACTOR = 0.5\n\nfunction batchHeaders(apiKey: string): Record<string, string> {\n return {\n 'x-api-key': apiKey,\n 'anthropic-version': ANTHROPIC_VERSION,\n 'Content-Type': 'application/json',\n }\n}\n\n/** A batch call that did not answer 2xx, as the contract's error. */\nasync function batchFailure(response: Response, model: string): Promise<AiUpstreamError> {\n const requestId = requestIdOf(response)\n const payload = (await response.json().catch(() => null)) as {\n error?: { type?: string; message?: string }\n } | null\n console.error('ai upstream batch error', {\n provider: ANTHROPIC_PROVIDER_ID,\n status: response.status,\n requestId,\n model,\n error: payload?.error ?? null,\n })\n return new AiUpstreamError(\n response.status,\n anthropicFailureIsRetryable(response.status, payload?.error?.type),\n requestId,\n anthropicAccountProblem(response.status, payload?.error),\n )\n}\n\n/** The batch as its create and poll calls describe it. */\ninterface AnthropicBatch {\n id: string\n processing_status?: string\n results_url?: string | null\n}\n\n/** Results a batch can end a request with that answer nothing yet: asked again, not failed (AGL-3781). */\nconst ANTHROPIC_BATCH_UNFINISHED = new Set(['canceled', 'expired'])\n\n/**\n * Many requests through the Message Batches API (AGL-3660): created as one\n * batch, polled until it has ended, its results read by `custom_id` — they\n * come back in any order — and each turned into the result `complete` would\n * have returned, at half the price. Development only: the live evals' batch\n * mode is the one caller, and no door waits minutes for an answer.\n *\n * A batch still processing when its wait runs out is canceled, and read once\n * the cancel has ended it (AGL-3781): a cancel keeps every answer the batch\n * already has, so those come back as results, and only the requests it never\n * reached come back as `AiBatchUnfinishedError`, for the caller to ask on\n * their own. Throwing away a round's paid-for answers because one batch was\n * slow is how a whole golden run lost its grades.\n */\nasync function completeBatch(\n requests: readonly AiProviderRequest[],\n options: AiBatchOptions = {},\n): Promise<Array<AiResult | Error>> {\n if (!requests.length) return []\n const apiKey = requests[0].apiKey\n const model = requests[0].model\n const pollMs = options.pollMs ?? 10_000\n const timeoutMs = options.timeoutMs ?? 60 * 60_000\n const cancelGraceMs = options.cancelGraceMs ?? 10 * 60_000\n const unfinished = (id: string, reason: string) => requests.map(() => new AiBatchUnfinishedError(id, reason))\n const created = await fetch(ANTHROPIC_BATCHES_URL, {\n method: 'POST',\n headers: batchHeaders(apiKey),\n body: JSON.stringify({\n requests: requests.map((request, index) => {\n const { apiKey: _key, signal: _signal, ...rest } = request\n return { custom_id: `r${index}`, params: buildAnthropicRequestBody({ ...rest, stream: false }) }\n }),\n }),\n })\n if (!created.ok) throw await batchFailure(created, model)\n let batch = (await created.json()) as AnthropicBatch\n const deadline = Date.now() + timeoutMs\n let cancelDeadline: number | null = null\n while (batch.processing_status !== 'ended') {\n if (cancelDeadline === null && Date.now() >= deadline) {\n console.warn(`[ai] batch ${batch.id} has not ended after ${timeoutMs} ms; canceling it to read what it answered`)\n await fetch(`${ANTHROPIC_BATCHES_URL}/${batch.id}/cancel`, { method: 'POST', headers: batchHeaders(apiKey) }).catch(\n () => undefined,\n )\n cancelDeadline = Date.now() + cancelGraceMs\n }\n // A cancel that never ends leaves no results to read; every request goes back unanswered.\n if (cancelDeadline !== null && Date.now() >= cancelDeadline) return unfinished(batch.id, 'cancel did not end')\n await new Promise((resolve) => setTimeout(resolve, pollMs))\n const polled = await fetch(`${ANTHROPIC_BATCHES_URL}/${batch.id}`, { headers: batchHeaders(apiKey) })\n if (!polled.ok) throw await batchFailure(polled, model)\n batch = (await polled.json()) as AnthropicBatch\n }\n if (!batch.results_url) {\n if (cancelDeadline !== null) return unfinished(batch.id, 'canceled with no results')\n throw new Error(`batch ${batch.id} ended with no results`)\n }\n const results = await fetch(batch.results_url, { headers: batchHeaders(apiKey) })\n if (!results.ok) throw await batchFailure(results, model)\n const byId = new Map<string, { type?: string; message?: unknown; error?: unknown }>()\n for (const line of (await results.text()).split('\\n')) {\n if (!line.trim()) continue\n const entry = JSON.parse(line) as { custom_id: string; result?: { type?: string; message?: unknown; error?: unknown } }\n byId.set(entry.custom_id, entry.result ?? {})\n }\n return requests.map((request, index) => {\n const result = byId.get(`r${index}`)\n if (result?.type === 'succeeded') {\n return anthropicResultFrom(result.message, request.model, ANTHROPIC_BATCH_PRICE_FACTOR)\n }\n const type = result?.type ?? 'missing'\n if (ANTHROPIC_BATCH_UNFINISHED.has(type) || type === 'missing') return new AiBatchUnfinishedError(batch.id, type)\n console.error('ai batch request not answered', {\n provider: ANTHROPIC_PROVIDER_ID,\n batchId: batch.id,\n customId: `r${index}`,\n result: type,\n error: result?.error ?? null,\n })\n return new AiUpstreamError(null, type === 'errored', batch.id, null)\n })\n}\n\nexport const anthropicProvider: AiProvider = {\n id: ANTHROPIC_PROVIDER_ID,\n label: 'Anthropic',\n apiKeyEnv: ANTHROPIC_API_KEY_ENV,\n readApiKey: () => process.env.ANTHROPIC_API_KEY?.trim() || undefined,\n endpointHost: ANTHROPIC_HOST,\n toolSchemaLimits: ANTHROPIC_TOOL_SCHEMA_LIMITS,\n models(): readonly AiModelDescriptor[] {\n return aiModelIdsForProvider(ANTHROPIC_PROVIDER_ID)\n .map((id) => aiCatalogEntry(id))\n .filter((entry): entry is NonNullable<typeof entry> => Boolean(entry))\n },\n complete,\n stream,\n completeBatch,\n}\n"],"names":["aiBilledRatesForModel","aiCatalogEntry","aiModelIdsForProvider","estimateAiBilledUsd","aiCacheWriteHourSurchargeUsd","AiBatchUnfinishedError","AiUpstreamError","aiCacheMarkerTtls","aiRawOutputOf","aiStoppedAtCeiling","aiTokenCount","aiToolInputOf","ANTHROPIC_PROVIDER_ID","ANTHROPIC_API_KEY_ENV","ANTHROPIC_HOST","ANTHROPIC_URL","ANTHROPIC_BATCHES_URL","ANTHROPIC_VERSION","ANTHROPIC_MAX_CACHE_BREAKPOINTS","ANTHROPIC_TOOL_SCHEMA_LIMITS","strictTools","optionalParameters","unionParameters","compiledSchemaBytes","anthropicFailureIsRetryable","status","errorType","anthropicAccountProblem","error","type","test","message","anthropicUsageFrom","usage","record","details","thinking","undefined","creation","hour","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","cacheWriteHourTokens","thinkingTokens","anthropicBilledUsd","model","usd","Math","round","ANTHROPIC_THINKING_OFF","ANTHROPIC_DEFAULT_EFFORT","buildAnthropicRequestBody","input","conversation","Boolean","cacheConversation","messages","length","breakpoints","system","filter","block","cacheBreakpoint","Error","ttls","map","index","ttl","text","cache_control","anthropicCacheControl","tools","tool","name","description","input_schema","inputSchema","additionalProperties","strict","settable","capabilities","effort","thinkingOff","max_tokens","maxTokens","stream","output_config","role","content","anthropicBlocksOf","anthropicContentOf","tool_choice","part","source","media_type","mediaType","data","cacheControl","blocks","last","requestIdOf","response","headers","get","send","body","fetch","method","apiKey","JSON","stringify","signal","ok","payload","requestId","json","catch","console","provider","complete","anthropicResultFrom","raw","priceFactor","Array","isArray","String","join","toolUse","estCostUsd","stopReason","stop_reason","kind","rawOutput","streamEvents","decoder","TextDecoder","reader","getReader","Map","written","buffer","drained","lines","done","value","read","decode","split","pop","line","startsWith","event","parse","slice","Number","start","set","delta","partial_json","delete","push","reason","retryable","cancel","releaseLock","values","ANTHROPIC_BATCH_PRICE_FACTOR","batchHeaders","batchFailure","ANTHROPIC_BATCH_UNFINISHED","Set","completeBatch","requests","options","pollMs","timeoutMs","cancelGraceMs","unfinished","id","created","request","_key","_signal","rest","custom_id","params","batch","deadline","Date","now","cancelDeadline","processing_status","warn","Promise","resolve","setTimeout","polled","results_url","results","byId","entry","trim","result","has","batchId","customId","anthropicProvider","label","apiKeyEnv","readApiKey","process","env","ANTHROPIC_API_KEY","endpointHost","toolSchemaLimits","models"],"mappings":"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"}
@@ -213,13 +213,15 @@ export interface AiRequestBase {
213
213
  thinking?: AiThinking;
214
214
  signal?: AbortSignal;
215
215
  /**
216
- * How long a cache entry the breakpoints write should live, where the
217
- * provider offers a choice: the provider's default (five minutes on the
216
+ * How long a cache entry the SHARED breakpoints write should live, where
217
+ * the provider offers a choice: the provider's default (five minutes on the
218
218
  * Messages API) when absent. A step a site job runs asks for `1h` through
219
219
  * its routing row (`AiRoutingRow.cacheTtl`, AGL-3830), in production too,
220
220
  * and a development live run asks for it on every request
221
- * (`runtime/ai-dev-env.ts`). It changes no answer, only what a request
222
- * within the hour pays for the prefix, so a replay key does not read it.
221
+ * (`runtime/ai-dev-env.ts`). A job's own markers keep five minutes whatever
222
+ * this says (`aiCacheMarkerTtls`, AGL-4050). It changes no answer, only what
223
+ * a request within the hour pays for the prefix, so a replay key does not
224
+ * read it.
223
225
  */
224
226
  cacheTtl?: '1h';
225
227
  /**
@@ -238,6 +240,39 @@ export interface AiProviderRequest extends AiRequestBase {
238
240
  /** The provider's own key, resolved by the runtime from the environment or the org's setting. */
239
241
  apiKey: string;
240
242
  }
243
+ /** How long one cache marker's entry lives: the provider's five minutes, or the hour. */
244
+ export type AiCacheTtl = '5m' | '1h';
245
+ /**
246
+ * Who reads a cache marker's entry (AGL-4050). A `shared` marker closes a
247
+ * span of the platform's own prompt (the tools, the doctrine, a door's
248
+ * rules), the same bytes for every org and every job, so the next job can
249
+ * read what this one wrote: it lives as long as the request's `cacheTtl`
250
+ * asks. A `job` marker closes bytes only one job reads: a site's block
251
+ * (`site`) or the request's own conversation (`cacheConversation`). That job
252
+ * reads it again within minutes and no other job ever does, so it keeps the
253
+ * provider's five minutes whatever the route asks; at the hour, its 2x write
254
+ * would never be read back.
255
+ */
256
+ export type AiCacheScope = 'shared' | 'job';
257
+ /** A system block's marker scope: a site's block is one job's, every other block the platform's. */
258
+ export declare function aiCacheScopeOf(block: Pick<AiSystemBlock, 'site'>): AiCacheScope;
259
+ /**
260
+ * The lifetime of every cache marker a request carries (AGL-4050), in prefix
261
+ * order: one per system block, `null` where the block marks none, then the
262
+ * conversation's, `null` where it is not cached. Tools carry no marker of
263
+ * their own; they sit ahead of the first system marker and live as long as it.
264
+ *
265
+ * A provider refuses a request whose longer-lived entry comes after a
266
+ * shorter-lived one, so once a marker is five minutes every marker after it
267
+ * is too. A shared marker behind a job's is downgraded, never sent at the
268
+ * hour. The runtime already keeps a site's block last among the system
269
+ * breakpoints, so a door's request never meets the downgrade; it is the
270
+ * adapter's guarantee all the same.
271
+ */
272
+ export declare function aiCacheMarkerTtls(request: Pick<AiRequestBase, 'system' | 'messages' | 'cacheTtl' | 'cacheConversation'>): {
273
+ system: Array<AiCacheTtl | null>;
274
+ conversation: AiCacheTtl | null;
275
+ };
241
276
  /**
242
277
  * Token usage in the meter's shape. Cache reads and writes are separate
243
278
  * because they are priced separately; a provider with no cache reports 0.
@@ -174,6 +174,34 @@ function countSchema(node, root, counts, resolving) {
174
174
  const padding = data.endsWith('==') ? 2 : data.endsWith('=') ? 1 : 0;
175
175
  return Math.max(0, Math.floor(data.length * 3 / 4) - padding);
176
176
  }
177
+ /** A system block's marker scope: a site's block is one job's, every other block the platform's. */ export function aiCacheScopeOf(block) {
178
+ return block.site ? 'job' : 'shared';
179
+ }
180
+ /**
181
+ * The lifetime of every cache marker a request carries (AGL-4050), in prefix
182
+ * order: one per system block, `null` where the block marks none, then the
183
+ * conversation's, `null` where it is not cached. Tools carry no marker of
184
+ * their own; they sit ahead of the first system marker and live as long as it.
185
+ *
186
+ * A provider refuses a request whose longer-lived entry comes after a
187
+ * shorter-lived one, so once a marker is five minutes every marker after it
188
+ * is too. A shared marker behind a job's is downgraded, never sent at the
189
+ * hour. The runtime already keeps a site's block last among the system
190
+ * breakpoints, so a door's request never meets the downgrade; it is the
191
+ * adapter's guarantee all the same.
192
+ */ export function aiCacheMarkerTtls(request) {
193
+ let ceiling = request.cacheTtl === '1h' ? '1h' : '5m';
194
+ const lifetime = (scope)=>{
195
+ if (scope === 'job') ceiling = '5m';
196
+ return ceiling;
197
+ };
198
+ const system = request.system.map((block)=>block.cacheBreakpoint ? lifetime(aiCacheScopeOf(block)) : null);
199
+ const conversation = request.cacheConversation && request.messages.length > 0 ? lifetime('job') : null;
200
+ return {
201
+ system,
202
+ conversation
203
+ };
204
+ }
177
205
  /**
178
206
  * Whether a model call stopped because it reached the output ceiling it was
179
207
  * asked for (AGL-3042). `max_tokens` is the contract's word, and the
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/providers/contract.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { UpstreamServiceError } from '@aglyn/shared-util-errors'\n\n/**\n * The provider contract (AGL-2939): the one shape every AI door speaks, and\n * the one thing an adapter has to implement. The runtime, the gate, the\n * jobs, the doctrine and the usage code import this and never a vendor\n * URL, header or model literal — Anthropic is one adapter behind it, and\n * an OpenAI-compatible endpoint is the second, which is what proves the\n * seam holds.\n *\n * ── Prompt caching, by construction ───────────────────────────────────────\n *\n * `system` is an ordered list of blocks, and caching is a PREFIX match: a\n * breakpoint covers every block before it, and one byte that differs per\n * request anywhere inside that prefix gives every distinct value its own\n * copy of it. The AGL-2352 rule is that no per-org byte may sit inside a\n * cached prefix — a white-label brand name interpolated into a cached block\n * multiplies the prefix count by the number of brands, each written off its\n * own traffic and expired before it is read. So a block marked `volatile`\n * may not sit at or before the last `cacheBreakpoint`, and the runtime\n * refuses the request before a byte leaves the process. A provider with no\n * cache honors the order and ignores the markers.\n *\n * ── Structured output ─────────────────────────────────────────────────────\n *\n * A `tools` entry is sent strict, with additional properties forbidden on\n * its schema, so a tool call's `input` validates against the schema\n * exactly. Tool choice stays automatic — forced tool use is not portable\n * across models — and the caller's system text names the tool it wants. An\n * adapter delivers `input` as an object; a stream assembles it from\n * fragments and parses the whole once, at the block's end. Nothing\n * string-matches the serialized form: models differ in how they escape it.\n * A provider bounds how much strict schema one request may carry, and an\n * adapter declares those bounds as `toolSchemaLimits`: three bounds a vendor\n * publishes and counts, and a fourth, the size of the grammar the schemas\n * compile to, that none publishes and only a live request settles.\n *\n * ── Thinking ──────────────────────────────────────────────────────────────\n *\n * `thinking: 'off'` and `'adaptive'` are sent when the provider has the\n * control; an omitted field sends NOTHING, leaving the model's own default\n * in force. The omission is deliberate rather than a gap: some models\n * reject an explicit setting, and the copy assistant serves its element\n * mode on such a model. `effort` rides the same rule.\n *\n * ── Pictures ──────────────────────────────────────────────────────────────\n *\n * A user turn may carry pictures beside its text (AGL-2916): `content` is\n * then an ordered list of parts, and a turn with nothing but text stays a\n * string, which every adapter passes through as it always has. A picture is\n * base64 bytes of one of `AI_IMAGE_MEDIA_TYPES`, never a URL, so no provider\n * fetches anything on the platform's behalf. The runtime refuses a picture\n * on an assistant turn, a picture for a model whose descriptor does not say\n * `vision`, and a picture past the size or count bounds, all before any\n * network is touched. Each adapter maps the parts onto its vendor's shape.\n */\n\n/**\n * One system block. `cacheBreakpoint` marks the end of a cacheable prefix;\n * `volatile` declares that the text carries a per-request or per-org byte\n * and therefore may never sit inside one.\n *\n * `site` declares a block that describes ONE site (AGL-3661): its business\n * profile, status and remembered preferences. As a breakpoint it is cached\n * once per site, so it must close the cached span — every platform-wide\n * block sits ahead of it, where every workspace still shares one entry —\n * and the runtime refuses a request that puts another breakpoint after it.\n */\nexport interface AiSystemBlock {\n text: string\n cacheBreakpoint?: true\n volatile?: true\n site?: true\n}\n\n/** A structured-output tool. `strict` is required, not optional. */\nexport interface AiTool {\n name: string\n description: string\n /** A JSON schema of `type: 'object'`; `additionalProperties` is forced off. */\n inputSchema: Record<string, unknown>\n strict: true\n}\n\n/**\n * How much of a request's strict tools a provider can compile (AGL-3096). A\n * provider that constrains decoding to a tool's schema compiles every strict\n * schema of a request together, before the model runs, and refuses a request\n * whose schemas are past its bounds. That refusal is permanent: the schema is\n * the same on every retry. Each bound is a TOTAL over every strict tool one\n * request sends, as `aiToolSchemaCounts` counts them. A bound left out is one\n * the provider does not state.\n *\n * A provider may also refuse a request whose schemas compile to too large a\n * grammar, which none of the published counts can see: a request counting 1\n * strict tool, 0 optional parameters and 1 union was refused for exactly that\n * (AGL-3096). `compiledSchemaBytes` stands in for it, on live evidence.\n */\nexport interface AiToolSchemaLimits {\n /** The most strict tools one request may send. */\n strictTools?: number\n /** The most properties, across every strict schema, left out of their object's `required`. */\n optionalParameters?: number\n /** The most schemas, across every strict schema, that are a union: an `anyOf` or `oneOf`, or a list of types. */\n unionParameters?: number\n /**\n * The most bytes of schema a request's grammar may be compiled from, as\n * `aiToolSchemaCompiledBytes` measures them.\n *\n * Unlike the bounds above, this one is EMPIRICAL. A provider refuses a\n * request whose compiled grammar is too large without publishing the size,\n * so the number here is not read off a vendor's page: it is the largest a\n * live request has been watched to carry, and every number above it is\n * unknown rather than safe. Raising it on an offline measurement alone\n * puts the guard back where it could not see the break.\n */\n compiledSchemaBytes?: number\n}\n\n/** A request's strict tools, counted on the measures `AiToolSchemaLimits` bounds. */\nexport type AiToolSchemaCounts = Required<AiToolSchemaLimits>\n\n/**\n * One schema as a grammar compiler reads it: every local `$ref` written out\n * at each place it is used, and every description, title and `$defs` dropped,\n * since none of them constrains a single token. A schema that refers to\n * itself is written out once around and then left as the reference it is.\n */\nfunction compiledForm(node: unknown, root: Record<string, unknown>, resolving: ReadonlySet<string>): unknown {\n if (Array.isArray(node)) return node.map((entry) => compiledForm(entry, root, resolving))\n if (!isSchemaObject(node)) return node\n const ref = node['$ref']\n if (typeof ref === 'string') {\n if (resolving.has(ref)) return { $ref: ref }\n return compiledForm(localSchemaAt(root, ref), root, new Set([...resolving, ref]))\n }\n const written: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(node)) {\n if (key === 'description' || key === 'title' || key === '$defs') continue\n written[key] = compiledForm(value, root, resolving)\n }\n return written\n}\n\n/**\n * The bytes of schema a request's grammar is compiled from, over all its\n * strict tools. It is a stand-in for the size of the grammar itself, which\n * nothing outside the provider can measure, and it errs toward more: a `$ref`\n * counts at each place it is used, because a compiler that writes it out\n * there pays for it there.\n */\nexport function aiToolSchemaCompiledBytes(tools: readonly AiTool[]): number {\n return tools.reduce(\n (bytes, tool) => bytes + JSON.stringify(compiledForm(tool.inputSchema, tool.inputSchema, new Set())).length,\n 0,\n )\n}\n\nfunction isSchemaObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** The schema a local `$ref` (`#/…`) points at, or `undefined`. */\nfunction localSchemaAt(root: Record<string, unknown>, ref: string): unknown {\n if (!ref.startsWith('#/')) return undefined\n return ref\n .slice(2)\n .split('/')\n .map((key) => key.replace(/~1/g, '/').replace(/~0/g, '~'))\n .reduce<unknown>((node, key) => (isSchemaObject(node) ? node[key] : undefined), root)\n}\n\nfunction countSchema(\n node: unknown,\n root: Record<string, unknown>,\n counts: AiToolSchemaCounts,\n resolving: ReadonlySet<string>,\n): void {\n if (!isSchemaObject(node)) return\n if (Array.isArray(node['anyOf']) || Array.isArray(node['oneOf']) || Array.isArray(node['type'])) {\n counts.unionParameters += 1\n }\n const ref = node['$ref']\n if (typeof ref === 'string' && !resolving.has(ref)) {\n countSchema(localSchemaAt(root, ref), root, counts, new Set([...resolving, ref]))\n }\n const properties = node['properties']\n if (isSchemaObject(properties)) {\n const required = Array.isArray(node['required']) ? node['required'] : []\n for (const [key, property] of Object.entries(properties)) {\n if (!required.includes(key)) counts.optionalParameters += 1\n countSchema(property, root, counts, resolving)\n }\n }\n const items = node['items']\n for (const item of Array.isArray(items) ? items : [items]) countSchema(item, root, counts, resolving)\n for (const key of ['anyOf', 'allOf', 'oneOf']) {\n const branches = node[key]\n if (Array.isArray(branches)) for (const branch of branches) countSchema(branch, root, counts, resolving)\n }\n}\n\n/**\n * The strict tools of one request, counted on each measure a provider bounds.\n * Every tool the contract sends is strict. Each schema is walked whole: every\n * property, every `items`, every branch of an `anyOf`, `allOf` or `oneOf`, and\n * a local `$ref` at each place it is used. A union nested in an array's items\n * or in another union's branch counts on its own, as it does for a provider\n * that compiles it; a `$ref` counted where it is used errs toward more.\n */\nexport function aiToolSchemaCounts(tools: readonly AiTool[]): AiToolSchemaCounts {\n const counts: AiToolSchemaCounts = {\n strictTools: tools.length,\n optionalParameters: 0,\n unionParameters: 0,\n compiledSchemaBytes: aiToolSchemaCompiledBytes(tools),\n }\n for (const tool of tools) countSchema(tool.inputSchema, tool.inputSchema, counts, new Set())\n return counts\n}\n\n/**\n * Each bound, as one, as many, and where its number comes from — a published\n * bound reads as the provider's own, a measured one as what has been proved.\n */\nconst TOOL_SCHEMA_MEASURES: ReadonlyArray<[keyof AiToolSchemaLimits, string, string, string]> = [\n ['strictTools', 'strict tool', 'strict tools', 'the provider compiles'],\n ['optionalParameters', 'optional parameter', 'optional parameters', 'the provider compiles'],\n ['unionParameters', 'union-typed parameter', 'union-typed parameters', 'the provider compiles'],\n ['compiledSchemaBytes', 'byte of compiled schema', 'bytes of compiled schema', 'a live request has proved'],\n]\n\n/** Each bound a request's tools are past, as a sentence; empty when every one holds. */\nexport function aiToolSchemaBreaches(\n tools: readonly AiTool[],\n limits: AiToolSchemaLimits | undefined,\n): string[] {\n if (!limits) return []\n const counts = aiToolSchemaCounts(tools)\n return TOOL_SCHEMA_MEASURES.flatMap(([measure, one, many, source]) => {\n const limit = limits[measure]\n const count = counts[measure]\n return limit !== undefined && count > limit\n ? [`${count} ${count === 1 ? one : many} in one request, over the ${limit} ${source}`]\n : []\n })\n}\n\nexport type AiEffort = 'low' | 'medium' | 'high'\nexport type AiThinking = 'off' | 'adaptive'\n\n/**\n * The picture formats a request may carry (AGL-2916): the four every model\n * the catalog marks as reading images accepts. A door converts anything else\n * — an AVIF, an SVG — before it attaches the picture, or attaches none.\n */\nexport const AI_IMAGE_MEDIA_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'] as const\nexport type AiImageMediaType = (typeof AI_IMAGE_MEDIA_TYPES)[number]\n\n/**\n * The largest picture one part may carry, in decoded bytes: the ceiling the\n * providers behind the contract accept for one image. A door that attaches a\n * picture resizes it far below this; the runtime refuses anything above it\n * before a byte leaves the process, rather than letting the provider do it\n * after the request was paid for in time.\n */\nexport const AI_IMAGE_MAX_BYTES = 5 * 1024 * 1024\n\n/** The most pictures one request may carry. */\nexport const AI_REQUEST_MAX_IMAGES = 8\n\n/** Text in a message made of parts. */\nexport interface AiTextPart {\n type: 'text'\n text: string\n}\n\n/**\n * A picture in a message (AGL-2916): its bytes, base64-encoded, and their\n * format. Only a USER turn carries one, and only to a model whose descriptor\n * says it reads images; the runtime refuses any other request before a byte\n * leaves the process, so a door cannot send a picture by accident to a model\n * that would reject it, or to a turn the model wrote.\n */\nexport interface AiImagePart {\n type: 'image'\n mediaType: AiImageMediaType\n /** The picture's bytes, base64-encoded, with no `data:` prefix. */\n data: string\n}\n\nexport type AiMessagePart = AiTextPart | AiImagePart\n\nexport interface AiMessage {\n role: 'user' | 'assistant'\n /**\n * The turn's text, or its parts in order — text and pictures — for a door\n * that shows the model an image. A door with nothing but text sends a\n * string, which every adapter passes through as it always has.\n */\n content: string | readonly AiMessagePart[]\n}\n\n/** A message's text: its parts' text joined, with every picture left out. */\nexport function aiMessageText(message: Pick<AiMessage, 'content'>): string {\n if (typeof message.content === 'string') return message.content\n return message.content\n .filter((part): part is AiTextPart => part.type === 'text')\n .map((part) => part.text)\n .join('\\n\\n')\n}\n\n/** Every picture a list of messages carries, in order. */\nexport function aiMessageImages(messages: readonly Pick<AiMessage, 'content'>[]): AiImagePart[] {\n return messages.flatMap((message) =>\n typeof message.content === 'string'\n ? []\n : message.content.filter((part): part is AiImagePart => part.type === 'image'),\n )\n}\n\n/** The decoded size of a base64 string, without decoding it. */\nexport function aiBase64Bytes(data: string): number {\n const padding = data.endsWith('==') ? 2 : data.endsWith('=') ? 1 : 0\n return Math.max(0, Math.floor((data.length * 3) / 4) - padding)\n}\n\n/** What every door hands the runtime; `provider` and `model` resolve there. */\nexport interface AiRequestBase {\n model: string\n system: AiSystemBlock[]\n messages: AiMessage[]\n tools?: AiTool[]\n maxTokens: number\n effort?: AiEffort\n thinking?: AiThinking\n signal?: AbortSignal\n /**\n * How long a cache entry the breakpoints write should live, where the\n * provider offers a choice: the provider's default (five minutes on the\n * Messages API) when absent. A step a site job runs asks for `1h` through\n * its routing row (`AiRoutingRow.cacheTtl`, AGL-3830), in production too,\n * and a development live run asks for it on every request\n * (`runtime/ai-dev-env.ts`). It changes no answer, only what a request\n * within the hour pays for the prefix, so a replay key does not read it.\n */\n cacheTtl?: '1h'\n /**\n * Cache the conversation through its last message as well as the system\n * blocks (AGL-3809), for a request the door expects to continue: a\n * generation that looks the site up or is asked again re-sends its whole\n * first turn, which without this is billed at the full input rate on every\n * continuation. One more breakpoint, on the last message, counted against\n * the same limit as the system's; a provider without a cache ignores it.\n * It changes no answer, so a replay key does not read it.\n */\n cacheConversation?: boolean\n}\n\n/** The request an adapter receives: the runtime has already validated it. */\nexport interface AiProviderRequest extends AiRequestBase {\n /** The provider's own key, resolved by the runtime from the environment or the org's setting. */\n apiKey: string\n}\n\n/**\n * Token usage in the meter's shape. Cache reads and writes are separate\n * because they are priced separately; a provider with no cache reports 0.\n */\nexport interface AiUsage {\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheWriteTokens: number\n /**\n * How many of `cacheWriteTokens` went to an hour-long cache entry\n * (AGL-3830), where the provider breaks the writes down; absent where none\n * did or the provider does not say.\n *\n * Inside `cacheWriteTokens`, never beside it, like `thinkingTokens` below.\n * An hour-long write is billed at twice the input price where a rate\n * table's `cacheWritePerToken` is the five-minute 1.25 times, so a price\n * reads this to add the difference (`aiCacheWriteHourSurchargeUsd`).\n */\n cacheWriteHourTokens?: number\n /**\n * How many of `outputTokens` the model spent thinking, where the provider\n * reports it; absent where it does not (AGL-3143).\n *\n * Inside `outputTokens`, never beside it: a rate table prices the four\n * figures above and would charge these twice if it read this one.\n *\n * It is here because it is the figure that says where a generation's\n * output went. A tool call that comes back far smaller than the tokens it\n * cost has three shapes, and only this separates the first from the other\n * two: the model thought and then answered briefly, which is a model\n * working as asked; a runaway string the partial parse discarded; or\n * decoding that wrote nothing usable. Kept on every answer, because a\n * generation burns its budget thinking without ever reaching its ceiling,\n * and `rawOutput` is only there when it does.\n */\n thinkingTokens?: number\n}\n\nexport interface AiToolUse {\n name: string\n input: Record<string, unknown>\n}\n\n/** A completed, non-streaming answer. */\nexport interface AiCompletion {\n kind: 'completion'\n /** Every text block, joined. */\n text: string\n toolUse: AiToolUse[]\n usage: AiUsage\n /**\n * The exchange at the serving model's BILLED rates — what a customer's\n * credits are drawn from, and never a margin's cost side (AGL-3015). What\n * the same exchange cost us is `estimateAiProviderCostUsd`, recorded by\n * the meter; a result travels no further than the credit path, so it\n * carries the one figure that path needs.\n */\n estCostUsd: number\n stopReason: string | null\n /**\n * WHERE THE OUTPUT WENT (AGL-3143). The provider's own output for this\n * call, as it answered it, kept ONLY when the call stopped at its ceiling\n * and cut to `AI_RAW_OUTPUT_MAX_CHARS`.\n *\n * A tool call truncated mid-answer reaches `toolUse` as whatever the\n * provider could still parse, and the parsed input alone cannot say where\n * the tokens went: a runaway string the partial parse discarded and a\n * decoding stall that wrote nothing usable both leave a small input\n * against a spent ceiling. This is what tells them apart. These are the\n * model's own words, so they belong to the trace alone: never to a reader,\n * and never to a log, which carries the figures that separate the two\n * shapes — `outputTokens`, `parsedChars`, `rawChars`.\n */\n rawOutput?: string\n}\n\n/**\n * Whether a model call stopped because it reached the output ceiling it was\n * asked for (AGL-3042). `max_tokens` is the contract's word, and the\n * OpenAI-compatible adapter maps its endpoint's `length` onto it; `length` is\n * read as well, for an adapter that passes its provider's own word through.\n */\nexport function aiStoppedAtCeiling(stopReason: string | null): boolean {\n return stopReason === 'max_tokens' || stopReason === 'length'\n}\n\n/** The most characters of raw output a call cut off at its ceiling keeps. */\nexport const AI_RAW_OUTPUT_MAX_CHARS = 20_000\n\n/**\n * A cut-off call's output as the trace keeps it: what the provider answered,\n * serialized where it is not already text, and cut to the bound with an\n * ellipsis so a reader can tell a cut trace from a short answer.\n */\nexport function aiRawOutputOf(output: unknown): string {\n const text = typeof output === 'string' ? output : (JSON.stringify(output) ?? '')\n return text.length > AI_RAW_OUTPUT_MAX_CHARS ? `${text.slice(0, AI_RAW_OUTPUT_MAX_CHARS)}…` : text\n}\n\n/**\n * WHERE THE OUTPUT WENT, as a log may carry it (AGL-3143): the figures that\n * separate the shapes a call cut off at its ceiling comes in, and nothing\n * else.\n *\n * Thinking accounts for the spend first, because the thinking budget is\n * drawn from the same ceiling as the answer: a call can spend nearly all of\n * one thinking and still be cut off with a correct, tiny tool input, which\n * is a ceiling to raise rather than a decoder to fix. What thinking does not\n * account for is told apart by how much was written — long, and a runaway\n * string the partial parse discarded; short, and decoding wrote nothing\n * usable.\n *\n * It takes the raw output rather than a count on purpose. The bytes are the\n * model's own words and therefore the site's content, so they belong to the\n * trace alone and never to a log; passing them through one function that can\n * only ever return their LENGTH is what holds that rule at every door,\n * instead of asking each one to remember it.\n */\nexport function aiCutOffFigures(call: {\n usage: AiUsage\n /** The tool input that still parsed, or null where none did. */\n parsed: unknown\n rawOutput: string | null | undefined\n}): {\n outputTokens: number\n thinkingTokens: number | null\n parsedChars: number\n rawChars: number | null\n} {\n const parsed =\n call.parsed === null || call.parsed === undefined ? '' : (JSON.stringify(call.parsed) ?? '')\n return {\n outputTokens: call.usage.outputTokens,\n thinkingTokens: call.usage.thinkingTokens ?? null,\n parsedChars: parsed.length,\n rawChars: call.rawOutput?.length ?? null,\n }\n}\n\n/**\n * The model declined. Tokens were spent, so the usage is real and the\n * caller meters it; the text is whatever partial answer preceded the stop,\n * usually nothing. Every adapter maps its provider's own decline signal —\n * a refusal stop, a content filter — onto this one kind.\n */\nexport interface AiRefusal {\n kind: 'refusal'\n text: string\n usage: AiUsage\n estCostUsd: number\n stopReason: 'refusal'\n}\n\nexport type AiResult = AiCompletion | AiRefusal\n\nexport type AiStreamEvent =\n | { type: 'delta'; text: string }\n | { type: 'tool'; name: string; input: Record<string, unknown> }\n /**\n * The provider's in-stream error. The stream is still open and ends with\n * `done`, so the caller decides what the reader is told; the provider's\n * own words went to the log, not here.\n */\n | { type: 'error'; retryable: boolean }\n | {\n type: 'done'\n usage: AiUsage\n estCostUsd: number\n stopReason: string | null\n /**\n * WHERE THE OUTPUT WENT, for a stream (AGL-3143): the bytes the\n * stream's tool calls were written in, kept ONLY when the stream\n * stopped at its ceiling and cut to `AI_RAW_OUTPUT_MAX_CHARS`. The\n * streaming counterpart of `AiCompletion.rawOutput`.\n *\n * A `tool` event carries what `aiToolInputOf` could still parse, which\n * for a call cut mid-answer is a prefix of the object or nothing at\n * all; the fragments those events were assembled from are the only\n * record of what the model actually emitted, and without them a\n * runaway string and a decoding stall look identical from the\n * outside. These are the model's own words, so they belong to the\n * trace alone: never to a reader, and never to a log, which carries\n * the figures that separate the two shapes.\n */\n rawOutput?: string\n }\n\n/** What a provider says about one of its models; the catalog carries both rates. */\nexport interface AiModelDescriptor {\n id: string\n /** The provider id the model is served by. */\n provider: string\n label: string\n capabilities: {\n streaming: boolean\n tools: boolean\n /** Whether the provider accepts an explicit `thinking` setting for this model. */\n thinking: boolean\n /** Whether the provider caches a system prefix for this model. */\n promptCache: boolean\n /**\n * Whether the model reads a picture in a user turn (AGL-2916). Absent\n * reads as no, so a provider registered before the field existed is never\n * sent an image it did not say it can read.\n */\n vision?: boolean\n }\n}\n\n/** The one sentence a customer may read when a provider fails. */\nexport const AI_UPSTREAM_FAILURE_COPY = 'The AI request failed — try again.'\n\n/**\n * A non-2xx answer from the provider. `message` is the fixed customer-safe\n * sentence; the provider's payload is in the log under `requestId`.\n */\nexport class AiUpstreamError extends UpstreamServiceError {\n override readonly name = 'AiUpstreamError'\n /**\n * A failure about the PLATFORM's account with the provider rather than the\n * request (AGL-3377): the key was refused, or the account is out of\n * credit. Every AI feature on the install fails until the operator fixes\n * it, so the runtime tells them. Each adapter classifies its own vendor's\n * answers; absent is an ordinary failure.\n */\n readonly accountProblem: AiAccountProblem | null\n\n constructor(\n status: number | null,\n retryable: boolean,\n requestId: string | null,\n accountProblem: AiAccountProblem | null = null,\n ) {\n super(AI_UPSTREAM_FAILURE_COPY, { status, retryable, requestId })\n this.accountProblem = accountProblem\n }\n}\n\n/** See {@link AiUpstreamError.accountProblem}. */\nexport type AiAccountProblem = 'credentials' | 'credit'\n\n/**\n * A request the runtime refuses to send. Thrown synchronously before any\n * network is touched, and it names the block at fault: this is a\n * programming error in the caller's prompt assembly, not a condition to\n * handle at runtime.\n */\nexport class AiRequestShapeError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'AiRequestShapeError'\n }\n}\n\n/**\n * A provider: one vendor's wire protocol behind the contract. `id` is the\n * routing key (`anthropic`, `openai-compatible`); `models` is what the\n * provider can serve; `complete` and `stream` are the two shapes every door\n * uses. An adapter maps its vendor's errors onto `AiUpstreamError` and its\n * declines onto `AiRefusal`, and reports usage in the meter's shape.\n */\nexport interface AiProvider {\n readonly id: string\n readonly label: string\n /** The environment variable that carries this provider's key, for operator messages. */\n readonly apiKeyEnv: string\n /**\n * The key from the server environment, or `undefined` when the provider\n * is not configured to answer. Each adapter reads its own variable by\n * name in its own module, so every key read is one the key-exposure\n * guard can see (AGL-2240) and no door names a vendor's variable.\n */\n readApiKey(): string | undefined\n /** The host the provider is reached at, for the subprocessor inventory. */\n readonly endpointHost: string\n /**\n * How much of one request's strict tools this provider compiles, where it\n * states a bound. The doors never read it: a spec holds every tool set a\n * door sends to every registered provider's limits, so a schema a provider\n * would refuse is red before it ships rather than a 400 on every request.\n */\n readonly toolSchemaLimits?: AiToolSchemaLimits\n models(): readonly AiModelDescriptor[]\n complete(request: AiProviderRequest): Promise<AiResult>\n /**\n * Resolves once the provider has ACCEPTED the request — after the\n * headers, before the first token — with an iterator of events. A non-2xx\n * answer rejects with `AiUpstreamError` before the iterator exists, so a\n * door can still send a plain error status and hand back its reservation.\n */\n stream(request: AiProviderRequest): Promise<AsyncIterable<AiStreamEvent>>\n /**\n * Many requests answered together, asynchronously, at the provider's batch\n * discount, where it has one: each entry is that request's result, or the\n * error it failed with, in the order asked. Only a development run's live\n * eval calls it (`runtime/ai-live-batch.ts`); no door does.\n */\n completeBatch?(requests: readonly AiProviderRequest[], options?: AiBatchOptions): Promise<Array<AiResult | Error>>\n}\n\n/** How a batch is waited on. */\nexport interface AiBatchOptions {\n /** Between two looks at a batch still processing; 10 s when absent. */\n pollMs?: number\n /**\n * The most a batch is waited for; an hour when absent. Past it the batch\n * is canceled, which keeps every answer it already has, and each request\n * it did not answer comes back as an `AiBatchUnfinishedError` (AGL-3781).\n */\n timeoutMs?: number\n /** How long a canceled batch is given to end, so its answers can be read; 10 minutes when absent. */\n cancelGraceMs?: number\n}\n\n/**\n * A request a batch did not answer (AGL-3781): canceled when its wait ran\n * out, expired, errored, or missing from the results. Not the request's own\n * failure — the caller answers it with a request of its own rather than\n * losing it, and a failure that request meets is the real one.\n */\nexport class AiBatchUnfinishedError extends Error {\n override readonly name = 'AiBatchUnfinishedError'\n constructor(\n readonly batchId: string,\n readonly reason: string,\n ) {\n super(`batch ${batchId} did not answer this request (${reason})`)\n }\n}\n\n/** A count off the wire: finite, non-negative, whole; anything else is 0. */\nexport function aiTokenCount(value: unknown): number {\n const parsed = Number(value ?? 0)\n return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0\n}\n\n/**\n * A tool call's `input` as an object. A provider sends an object or, for\n * the one shape a stream assembles by hand, a JSON string.\n *\n * A string that does not parse to an object is an EMPTY input, not a throw.\n * The case that produces one is a stream cut off mid-call by the output\n * ceiling, and a throw there would end the stream before its `done` event —\n * the event that carries the usage a door must meter for tokens already\n * spent. An empty input is one every door's own validation refuses.\n */\nexport function aiToolInputOf(value: unknown): Record<string, unknown> {\n if (typeof value === 'string') {\n if (!value.trim()) return {}\n try {\n const parsed: unknown = JSON.parse(value)\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {}\n } catch {\n return {}\n }\n }\n return value && typeof value === 'object' ? (value as Record<string, unknown>) : {}\n}\n"],"names":["UpstreamServiceError","compiledForm","node","root","resolving","Array","isArray","map","entry","isSchemaObject","ref","has","$ref","localSchemaAt","Set","written","key","value","Object","entries","aiToolSchemaCompiledBytes","tools","reduce","bytes","tool","JSON","stringify","inputSchema","length","startsWith","undefined","slice","split","replace","countSchema","counts","unionParameters","properties","required","property","includes","optionalParameters","items","item","branches","branch","aiToolSchemaCounts","strictTools","compiledSchemaBytes","TOOL_SCHEMA_MEASURES","aiToolSchemaBreaches","limits","flatMap","measure","one","many","source","limit","count","AI_IMAGE_MEDIA_TYPES","AI_IMAGE_MAX_BYTES","AI_REQUEST_MAX_IMAGES","aiMessageText","message","content","filter","part","type","text","join","aiMessageImages","messages","aiBase64Bytes","data","padding","endsWith","Math","max","floor","aiStoppedAtCeiling","stopReason","AI_RAW_OUTPUT_MAX_CHARS","aiRawOutputOf","output","aiCutOffFigures","call","parsed","outputTokens","usage","thinkingTokens","parsedChars","rawChars","rawOutput","AI_UPSTREAM_FAILURE_COPY","AiUpstreamError","status","retryable","requestId","accountProblem","name","AiRequestShapeError","Error","AiBatchUnfinishedError","batchId","reason","aiTokenCount","Number","isFinite","aiToolInputOf","trim","parse"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/providers/contract.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { UpstreamServiceError } from '@aglyn/shared-util-errors'\n\n/**\n * The provider contract (AGL-2939): the one shape every AI door speaks, and\n * the one thing an adapter has to implement. The runtime, the gate, the\n * jobs, the doctrine and the usage code import this and never a vendor\n * URL, header or model literal — Anthropic is one adapter behind it, and\n * an OpenAI-compatible endpoint is the second, which is what proves the\n * seam holds.\n *\n * ── Prompt caching, by construction ───────────────────────────────────────\n *\n * `system` is an ordered list of blocks, and caching is a PREFIX match: a\n * breakpoint covers every block before it, and one byte that differs per\n * request anywhere inside that prefix gives every distinct value its own\n * copy of it. The AGL-2352 rule is that no per-org byte may sit inside a\n * cached prefix — a white-label brand name interpolated into a cached block\n * multiplies the prefix count by the number of brands, each written off its\n * own traffic and expired before it is read. So a block marked `volatile`\n * may not sit at or before the last `cacheBreakpoint`, and the runtime\n * refuses the request before a byte leaves the process. A provider with no\n * cache honors the order and ignores the markers.\n *\n * ── Structured output ─────────────────────────────────────────────────────\n *\n * A `tools` entry is sent strict, with additional properties forbidden on\n * its schema, so a tool call's `input` validates against the schema\n * exactly. Tool choice stays automatic — forced tool use is not portable\n * across models — and the caller's system text names the tool it wants. An\n * adapter delivers `input` as an object; a stream assembles it from\n * fragments and parses the whole once, at the block's end. Nothing\n * string-matches the serialized form: models differ in how they escape it.\n * A provider bounds how much strict schema one request may carry, and an\n * adapter declares those bounds as `toolSchemaLimits`: three bounds a vendor\n * publishes and counts, and a fourth, the size of the grammar the schemas\n * compile to, that none publishes and only a live request settles.\n *\n * ── Thinking ──────────────────────────────────────────────────────────────\n *\n * `thinking: 'off'` and `'adaptive'` are sent when the provider has the\n * control; an omitted field sends NOTHING, leaving the model's own default\n * in force. The omission is deliberate rather than a gap: some models\n * reject an explicit setting, and the copy assistant serves its element\n * mode on such a model. `effort` rides the same rule.\n *\n * ── Pictures ──────────────────────────────────────────────────────────────\n *\n * A user turn may carry pictures beside its text (AGL-2916): `content` is\n * then an ordered list of parts, and a turn with nothing but text stays a\n * string, which every adapter passes through as it always has. A picture is\n * base64 bytes of one of `AI_IMAGE_MEDIA_TYPES`, never a URL, so no provider\n * fetches anything on the platform's behalf. The runtime refuses a picture\n * on an assistant turn, a picture for a model whose descriptor does not say\n * `vision`, and a picture past the size or count bounds, all before any\n * network is touched. Each adapter maps the parts onto its vendor's shape.\n */\n\n/**\n * One system block. `cacheBreakpoint` marks the end of a cacheable prefix;\n * `volatile` declares that the text carries a per-request or per-org byte\n * and therefore may never sit inside one.\n *\n * `site` declares a block that describes ONE site (AGL-3661): its business\n * profile, status and remembered preferences. As a breakpoint it is cached\n * once per site, so it must close the cached span — every platform-wide\n * block sits ahead of it, where every workspace still shares one entry —\n * and the runtime refuses a request that puts another breakpoint after it.\n */\nexport interface AiSystemBlock {\n text: string\n cacheBreakpoint?: true\n volatile?: true\n site?: true\n}\n\n/** A structured-output tool. `strict` is required, not optional. */\nexport interface AiTool {\n name: string\n description: string\n /** A JSON schema of `type: 'object'`; `additionalProperties` is forced off. */\n inputSchema: Record<string, unknown>\n strict: true\n}\n\n/**\n * How much of a request's strict tools a provider can compile (AGL-3096). A\n * provider that constrains decoding to a tool's schema compiles every strict\n * schema of a request together, before the model runs, and refuses a request\n * whose schemas are past its bounds. That refusal is permanent: the schema is\n * the same on every retry. Each bound is a TOTAL over every strict tool one\n * request sends, as `aiToolSchemaCounts` counts them. A bound left out is one\n * the provider does not state.\n *\n * A provider may also refuse a request whose schemas compile to too large a\n * grammar, which none of the published counts can see: a request counting 1\n * strict tool, 0 optional parameters and 1 union was refused for exactly that\n * (AGL-3096). `compiledSchemaBytes` stands in for it, on live evidence.\n */\nexport interface AiToolSchemaLimits {\n /** The most strict tools one request may send. */\n strictTools?: number\n /** The most properties, across every strict schema, left out of their object's `required`. */\n optionalParameters?: number\n /** The most schemas, across every strict schema, that are a union: an `anyOf` or `oneOf`, or a list of types. */\n unionParameters?: number\n /**\n * The most bytes of schema a request's grammar may be compiled from, as\n * `aiToolSchemaCompiledBytes` measures them.\n *\n * Unlike the bounds above, this one is EMPIRICAL. A provider refuses a\n * request whose compiled grammar is too large without publishing the size,\n * so the number here is not read off a vendor's page: it is the largest a\n * live request has been watched to carry, and every number above it is\n * unknown rather than safe. Raising it on an offline measurement alone\n * puts the guard back where it could not see the break.\n */\n compiledSchemaBytes?: number\n}\n\n/** A request's strict tools, counted on the measures `AiToolSchemaLimits` bounds. */\nexport type AiToolSchemaCounts = Required<AiToolSchemaLimits>\n\n/**\n * One schema as a grammar compiler reads it: every local `$ref` written out\n * at each place it is used, and every description, title and `$defs` dropped,\n * since none of them constrains a single token. A schema that refers to\n * itself is written out once around and then left as the reference it is.\n */\nfunction compiledForm(node: unknown, root: Record<string, unknown>, resolving: ReadonlySet<string>): unknown {\n if (Array.isArray(node)) return node.map((entry) => compiledForm(entry, root, resolving))\n if (!isSchemaObject(node)) return node\n const ref = node['$ref']\n if (typeof ref === 'string') {\n if (resolving.has(ref)) return { $ref: ref }\n return compiledForm(localSchemaAt(root, ref), root, new Set([...resolving, ref]))\n }\n const written: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(node)) {\n if (key === 'description' || key === 'title' || key === '$defs') continue\n written[key] = compiledForm(value, root, resolving)\n }\n return written\n}\n\n/**\n * The bytes of schema a request's grammar is compiled from, over all its\n * strict tools. It is a stand-in for the size of the grammar itself, which\n * nothing outside the provider can measure, and it errs toward more: a `$ref`\n * counts at each place it is used, because a compiler that writes it out\n * there pays for it there.\n */\nexport function aiToolSchemaCompiledBytes(tools: readonly AiTool[]): number {\n return tools.reduce(\n (bytes, tool) => bytes + JSON.stringify(compiledForm(tool.inputSchema, tool.inputSchema, new Set())).length,\n 0,\n )\n}\n\nfunction isSchemaObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** The schema a local `$ref` (`#/…`) points at, or `undefined`. */\nfunction localSchemaAt(root: Record<string, unknown>, ref: string): unknown {\n if (!ref.startsWith('#/')) return undefined\n return ref\n .slice(2)\n .split('/')\n .map((key) => key.replace(/~1/g, '/').replace(/~0/g, '~'))\n .reduce<unknown>((node, key) => (isSchemaObject(node) ? node[key] : undefined), root)\n}\n\nfunction countSchema(\n node: unknown,\n root: Record<string, unknown>,\n counts: AiToolSchemaCounts,\n resolving: ReadonlySet<string>,\n): void {\n if (!isSchemaObject(node)) return\n if (Array.isArray(node['anyOf']) || Array.isArray(node['oneOf']) || Array.isArray(node['type'])) {\n counts.unionParameters += 1\n }\n const ref = node['$ref']\n if (typeof ref === 'string' && !resolving.has(ref)) {\n countSchema(localSchemaAt(root, ref), root, counts, new Set([...resolving, ref]))\n }\n const properties = node['properties']\n if (isSchemaObject(properties)) {\n const required = Array.isArray(node['required']) ? node['required'] : []\n for (const [key, property] of Object.entries(properties)) {\n if (!required.includes(key)) counts.optionalParameters += 1\n countSchema(property, root, counts, resolving)\n }\n }\n const items = node['items']\n for (const item of Array.isArray(items) ? items : [items]) countSchema(item, root, counts, resolving)\n for (const key of ['anyOf', 'allOf', 'oneOf']) {\n const branches = node[key]\n if (Array.isArray(branches)) for (const branch of branches) countSchema(branch, root, counts, resolving)\n }\n}\n\n/**\n * The strict tools of one request, counted on each measure a provider bounds.\n * Every tool the contract sends is strict. Each schema is walked whole: every\n * property, every `items`, every branch of an `anyOf`, `allOf` or `oneOf`, and\n * a local `$ref` at each place it is used. A union nested in an array's items\n * or in another union's branch counts on its own, as it does for a provider\n * that compiles it; a `$ref` counted where it is used errs toward more.\n */\nexport function aiToolSchemaCounts(tools: readonly AiTool[]): AiToolSchemaCounts {\n const counts: AiToolSchemaCounts = {\n strictTools: tools.length,\n optionalParameters: 0,\n unionParameters: 0,\n compiledSchemaBytes: aiToolSchemaCompiledBytes(tools),\n }\n for (const tool of tools) countSchema(tool.inputSchema, tool.inputSchema, counts, new Set())\n return counts\n}\n\n/**\n * Each bound, as one, as many, and where its number comes from — a published\n * bound reads as the provider's own, a measured one as what has been proved.\n */\nconst TOOL_SCHEMA_MEASURES: ReadonlyArray<[keyof AiToolSchemaLimits, string, string, string]> = [\n ['strictTools', 'strict tool', 'strict tools', 'the provider compiles'],\n ['optionalParameters', 'optional parameter', 'optional parameters', 'the provider compiles'],\n ['unionParameters', 'union-typed parameter', 'union-typed parameters', 'the provider compiles'],\n ['compiledSchemaBytes', 'byte of compiled schema', 'bytes of compiled schema', 'a live request has proved'],\n]\n\n/** Each bound a request's tools are past, as a sentence; empty when every one holds. */\nexport function aiToolSchemaBreaches(\n tools: readonly AiTool[],\n limits: AiToolSchemaLimits | undefined,\n): string[] {\n if (!limits) return []\n const counts = aiToolSchemaCounts(tools)\n return TOOL_SCHEMA_MEASURES.flatMap(([measure, one, many, source]) => {\n const limit = limits[measure]\n const count = counts[measure]\n return limit !== undefined && count > limit\n ? [`${count} ${count === 1 ? one : many} in one request, over the ${limit} ${source}`]\n : []\n })\n}\n\nexport type AiEffort = 'low' | 'medium' | 'high'\nexport type AiThinking = 'off' | 'adaptive'\n\n/**\n * The picture formats a request may carry (AGL-2916): the four every model\n * the catalog marks as reading images accepts. A door converts anything else\n * — an AVIF, an SVG — before it attaches the picture, or attaches none.\n */\nexport const AI_IMAGE_MEDIA_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'] as const\nexport type AiImageMediaType = (typeof AI_IMAGE_MEDIA_TYPES)[number]\n\n/**\n * The largest picture one part may carry, in decoded bytes: the ceiling the\n * providers behind the contract accept for one image. A door that attaches a\n * picture resizes it far below this; the runtime refuses anything above it\n * before a byte leaves the process, rather than letting the provider do it\n * after the request was paid for in time.\n */\nexport const AI_IMAGE_MAX_BYTES = 5 * 1024 * 1024\n\n/** The most pictures one request may carry. */\nexport const AI_REQUEST_MAX_IMAGES = 8\n\n/** Text in a message made of parts. */\nexport interface AiTextPart {\n type: 'text'\n text: string\n}\n\n/**\n * A picture in a message (AGL-2916): its bytes, base64-encoded, and their\n * format. Only a USER turn carries one, and only to a model whose descriptor\n * says it reads images; the runtime refuses any other request before a byte\n * leaves the process, so a door cannot send a picture by accident to a model\n * that would reject it, or to a turn the model wrote.\n */\nexport interface AiImagePart {\n type: 'image'\n mediaType: AiImageMediaType\n /** The picture's bytes, base64-encoded, with no `data:` prefix. */\n data: string\n}\n\nexport type AiMessagePart = AiTextPart | AiImagePart\n\nexport interface AiMessage {\n role: 'user' | 'assistant'\n /**\n * The turn's text, or its parts in order — text and pictures — for a door\n * that shows the model an image. A door with nothing but text sends a\n * string, which every adapter passes through as it always has.\n */\n content: string | readonly AiMessagePart[]\n}\n\n/** A message's text: its parts' text joined, with every picture left out. */\nexport function aiMessageText(message: Pick<AiMessage, 'content'>): string {\n if (typeof message.content === 'string') return message.content\n return message.content\n .filter((part): part is AiTextPart => part.type === 'text')\n .map((part) => part.text)\n .join('\\n\\n')\n}\n\n/** Every picture a list of messages carries, in order. */\nexport function aiMessageImages(messages: readonly Pick<AiMessage, 'content'>[]): AiImagePart[] {\n return messages.flatMap((message) =>\n typeof message.content === 'string'\n ? []\n : message.content.filter((part): part is AiImagePart => part.type === 'image'),\n )\n}\n\n/** The decoded size of a base64 string, without decoding it. */\nexport function aiBase64Bytes(data: string): number {\n const padding = data.endsWith('==') ? 2 : data.endsWith('=') ? 1 : 0\n return Math.max(0, Math.floor((data.length * 3) / 4) - padding)\n}\n\n/** What every door hands the runtime; `provider` and `model` resolve there. */\nexport interface AiRequestBase {\n model: string\n system: AiSystemBlock[]\n messages: AiMessage[]\n tools?: AiTool[]\n maxTokens: number\n effort?: AiEffort\n thinking?: AiThinking\n signal?: AbortSignal\n /**\n * How long a cache entry the SHARED breakpoints write should live, where\n * the provider offers a choice: the provider's default (five minutes on the\n * Messages API) when absent. A step a site job runs asks for `1h` through\n * its routing row (`AiRoutingRow.cacheTtl`, AGL-3830), in production too,\n * and a development live run asks for it on every request\n * (`runtime/ai-dev-env.ts`). A job's own markers keep five minutes whatever\n * this says (`aiCacheMarkerTtls`, AGL-4050). It changes no answer, only what\n * a request within the hour pays for the prefix, so a replay key does not\n * read it.\n */\n cacheTtl?: '1h'\n /**\n * Cache the conversation through its last message as well as the system\n * blocks (AGL-3809), for a request the door expects to continue: a\n * generation that looks the site up or is asked again re-sends its whole\n * first turn, which without this is billed at the full input rate on every\n * continuation. One more breakpoint, on the last message, counted against\n * the same limit as the system's; a provider without a cache ignores it.\n * It changes no answer, so a replay key does not read it.\n */\n cacheConversation?: boolean\n}\n\n/** The request an adapter receives: the runtime has already validated it. */\nexport interface AiProviderRequest extends AiRequestBase {\n /** The provider's own key, resolved by the runtime from the environment or the org's setting. */\n apiKey: string\n}\n\n/** How long one cache marker's entry lives: the provider's five minutes, or the hour. */\nexport type AiCacheTtl = '5m' | '1h'\n\n/**\n * Who reads a cache marker's entry (AGL-4050). A `shared` marker closes a\n * span of the platform's own prompt (the tools, the doctrine, a door's\n * rules), the same bytes for every org and every job, so the next job can\n * read what this one wrote: it lives as long as the request's `cacheTtl`\n * asks. A `job` marker closes bytes only one job reads: a site's block\n * (`site`) or the request's own conversation (`cacheConversation`). That job\n * reads it again within minutes and no other job ever does, so it keeps the\n * provider's five minutes whatever the route asks; at the hour, its 2x write\n * would never be read back.\n */\nexport type AiCacheScope = 'shared' | 'job'\n\n/** A system block's marker scope: a site's block is one job's, every other block the platform's. */\nexport function aiCacheScopeOf(block: Pick<AiSystemBlock, 'site'>): AiCacheScope {\n return block.site ? 'job' : 'shared'\n}\n\n/**\n * The lifetime of every cache marker a request carries (AGL-4050), in prefix\n * order: one per system block, `null` where the block marks none, then the\n * conversation's, `null` where it is not cached. Tools carry no marker of\n * their own; they sit ahead of the first system marker and live as long as it.\n *\n * A provider refuses a request whose longer-lived entry comes after a\n * shorter-lived one, so once a marker is five minutes every marker after it\n * is too. A shared marker behind a job's is downgraded, never sent at the\n * hour. The runtime already keeps a site's block last among the system\n * breakpoints, so a door's request never meets the downgrade; it is the\n * adapter's guarantee all the same.\n */\nexport function aiCacheMarkerTtls(\n request: Pick<AiRequestBase, 'system' | 'messages' | 'cacheTtl' | 'cacheConversation'>,\n): { system: Array<AiCacheTtl | null>; conversation: AiCacheTtl | null } {\n let ceiling: AiCacheTtl = request.cacheTtl === '1h' ? '1h' : '5m'\n const lifetime = (scope: AiCacheScope): AiCacheTtl => {\n if (scope === 'job') ceiling = '5m'\n return ceiling\n }\n const system = request.system.map((block) => (block.cacheBreakpoint ? lifetime(aiCacheScopeOf(block)) : null))\n const conversation = request.cacheConversation && request.messages.length > 0 ? lifetime('job') : null\n return { system, conversation }\n}\n\n/**\n * Token usage in the meter's shape. Cache reads and writes are separate\n * because they are priced separately; a provider with no cache reports 0.\n */\nexport interface AiUsage {\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheWriteTokens: number\n /**\n * How many of `cacheWriteTokens` went to an hour-long cache entry\n * (AGL-3830), where the provider breaks the writes down; absent where none\n * did or the provider does not say.\n *\n * Inside `cacheWriteTokens`, never beside it, like `thinkingTokens` below.\n * An hour-long write is billed at twice the input price where a rate\n * table's `cacheWritePerToken` is the five-minute 1.25 times, so a price\n * reads this to add the difference (`aiCacheWriteHourSurchargeUsd`).\n */\n cacheWriteHourTokens?: number\n /**\n * How many of `outputTokens` the model spent thinking, where the provider\n * reports it; absent where it does not (AGL-3143).\n *\n * Inside `outputTokens`, never beside it: a rate table prices the four\n * figures above and would charge these twice if it read this one.\n *\n * It is here because it is the figure that says where a generation's\n * output went. A tool call that comes back far smaller than the tokens it\n * cost has three shapes, and only this separates the first from the other\n * two: the model thought and then answered briefly, which is a model\n * working as asked; a runaway string the partial parse discarded; or\n * decoding that wrote nothing usable. Kept on every answer, because a\n * generation burns its budget thinking without ever reaching its ceiling,\n * and `rawOutput` is only there when it does.\n */\n thinkingTokens?: number\n}\n\nexport interface AiToolUse {\n name: string\n input: Record<string, unknown>\n}\n\n/** A completed, non-streaming answer. */\nexport interface AiCompletion {\n kind: 'completion'\n /** Every text block, joined. */\n text: string\n toolUse: AiToolUse[]\n usage: AiUsage\n /**\n * The exchange at the serving model's BILLED rates — what a customer's\n * credits are drawn from, and never a margin's cost side (AGL-3015). What\n * the same exchange cost us is `estimateAiProviderCostUsd`, recorded by\n * the meter; a result travels no further than the credit path, so it\n * carries the one figure that path needs.\n */\n estCostUsd: number\n stopReason: string | null\n /**\n * WHERE THE OUTPUT WENT (AGL-3143). The provider's own output for this\n * call, as it answered it, kept ONLY when the call stopped at its ceiling\n * and cut to `AI_RAW_OUTPUT_MAX_CHARS`.\n *\n * A tool call truncated mid-answer reaches `toolUse` as whatever the\n * provider could still parse, and the parsed input alone cannot say where\n * the tokens went: a runaway string the partial parse discarded and a\n * decoding stall that wrote nothing usable both leave a small input\n * against a spent ceiling. This is what tells them apart. These are the\n * model's own words, so they belong to the trace alone: never to a reader,\n * and never to a log, which carries the figures that separate the two\n * shapes — `outputTokens`, `parsedChars`, `rawChars`.\n */\n rawOutput?: string\n}\n\n/**\n * Whether a model call stopped because it reached the output ceiling it was\n * asked for (AGL-3042). `max_tokens` is the contract's word, and the\n * OpenAI-compatible adapter maps its endpoint's `length` onto it; `length` is\n * read as well, for an adapter that passes its provider's own word through.\n */\nexport function aiStoppedAtCeiling(stopReason: string | null): boolean {\n return stopReason === 'max_tokens' || stopReason === 'length'\n}\n\n/** The most characters of raw output a call cut off at its ceiling keeps. */\nexport const AI_RAW_OUTPUT_MAX_CHARS = 20_000\n\n/**\n * A cut-off call's output as the trace keeps it: what the provider answered,\n * serialized where it is not already text, and cut to the bound with an\n * ellipsis so a reader can tell a cut trace from a short answer.\n */\nexport function aiRawOutputOf(output: unknown): string {\n const text = typeof output === 'string' ? output : (JSON.stringify(output) ?? '')\n return text.length > AI_RAW_OUTPUT_MAX_CHARS ? `${text.slice(0, AI_RAW_OUTPUT_MAX_CHARS)}…` : text\n}\n\n/**\n * WHERE THE OUTPUT WENT, as a log may carry it (AGL-3143): the figures that\n * separate the shapes a call cut off at its ceiling comes in, and nothing\n * else.\n *\n * Thinking accounts for the spend first, because the thinking budget is\n * drawn from the same ceiling as the answer: a call can spend nearly all of\n * one thinking and still be cut off with a correct, tiny tool input, which\n * is a ceiling to raise rather than a decoder to fix. What thinking does not\n * account for is told apart by how much was written — long, and a runaway\n * string the partial parse discarded; short, and decoding wrote nothing\n * usable.\n *\n * It takes the raw output rather than a count on purpose. The bytes are the\n * model's own words and therefore the site's content, so they belong to the\n * trace alone and never to a log; passing them through one function that can\n * only ever return their LENGTH is what holds that rule at every door,\n * instead of asking each one to remember it.\n */\nexport function aiCutOffFigures(call: {\n usage: AiUsage\n /** The tool input that still parsed, or null where none did. */\n parsed: unknown\n rawOutput: string | null | undefined\n}): {\n outputTokens: number\n thinkingTokens: number | null\n parsedChars: number\n rawChars: number | null\n} {\n const parsed =\n call.parsed === null || call.parsed === undefined ? '' : (JSON.stringify(call.parsed) ?? '')\n return {\n outputTokens: call.usage.outputTokens,\n thinkingTokens: call.usage.thinkingTokens ?? null,\n parsedChars: parsed.length,\n rawChars: call.rawOutput?.length ?? null,\n }\n}\n\n/**\n * The model declined. Tokens were spent, so the usage is real and the\n * caller meters it; the text is whatever partial answer preceded the stop,\n * usually nothing. Every adapter maps its provider's own decline signal —\n * a refusal stop, a content filter — onto this one kind.\n */\nexport interface AiRefusal {\n kind: 'refusal'\n text: string\n usage: AiUsage\n estCostUsd: number\n stopReason: 'refusal'\n}\n\nexport type AiResult = AiCompletion | AiRefusal\n\nexport type AiStreamEvent =\n | { type: 'delta'; text: string }\n | { type: 'tool'; name: string; input: Record<string, unknown> }\n /**\n * The provider's in-stream error. The stream is still open and ends with\n * `done`, so the caller decides what the reader is told; the provider's\n * own words went to the log, not here.\n */\n | { type: 'error'; retryable: boolean }\n | {\n type: 'done'\n usage: AiUsage\n estCostUsd: number\n stopReason: string | null\n /**\n * WHERE THE OUTPUT WENT, for a stream (AGL-3143): the bytes the\n * stream's tool calls were written in, kept ONLY when the stream\n * stopped at its ceiling and cut to `AI_RAW_OUTPUT_MAX_CHARS`. The\n * streaming counterpart of `AiCompletion.rawOutput`.\n *\n * A `tool` event carries what `aiToolInputOf` could still parse, which\n * for a call cut mid-answer is a prefix of the object or nothing at\n * all; the fragments those events were assembled from are the only\n * record of what the model actually emitted, and without them a\n * runaway string and a decoding stall look identical from the\n * outside. These are the model's own words, so they belong to the\n * trace alone: never to a reader, and never to a log, which carries\n * the figures that separate the two shapes.\n */\n rawOutput?: string\n }\n\n/** What a provider says about one of its models; the catalog carries both rates. */\nexport interface AiModelDescriptor {\n id: string\n /** The provider id the model is served by. */\n provider: string\n label: string\n capabilities: {\n streaming: boolean\n tools: boolean\n /** Whether the provider accepts an explicit `thinking` setting for this model. */\n thinking: boolean\n /** Whether the provider caches a system prefix for this model. */\n promptCache: boolean\n /**\n * Whether the model reads a picture in a user turn (AGL-2916). Absent\n * reads as no, so a provider registered before the field existed is never\n * sent an image it did not say it can read.\n */\n vision?: boolean\n }\n}\n\n/** The one sentence a customer may read when a provider fails. */\nexport const AI_UPSTREAM_FAILURE_COPY = 'The AI request failed — try again.'\n\n/**\n * A non-2xx answer from the provider. `message` is the fixed customer-safe\n * sentence; the provider's payload is in the log under `requestId`.\n */\nexport class AiUpstreamError extends UpstreamServiceError {\n override readonly name = 'AiUpstreamError'\n /**\n * A failure about the PLATFORM's account with the provider rather than the\n * request (AGL-3377): the key was refused, or the account is out of\n * credit. Every AI feature on the install fails until the operator fixes\n * it, so the runtime tells them. Each adapter classifies its own vendor's\n * answers; absent is an ordinary failure.\n */\n readonly accountProblem: AiAccountProblem | null\n\n constructor(\n status: number | null,\n retryable: boolean,\n requestId: string | null,\n accountProblem: AiAccountProblem | null = null,\n ) {\n super(AI_UPSTREAM_FAILURE_COPY, { status, retryable, requestId })\n this.accountProblem = accountProblem\n }\n}\n\n/** See {@link AiUpstreamError.accountProblem}. */\nexport type AiAccountProblem = 'credentials' | 'credit'\n\n/**\n * A request the runtime refuses to send. Thrown synchronously before any\n * network is touched, and it names the block at fault: this is a\n * programming error in the caller's prompt assembly, not a condition to\n * handle at runtime.\n */\nexport class AiRequestShapeError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'AiRequestShapeError'\n }\n}\n\n/**\n * A provider: one vendor's wire protocol behind the contract. `id` is the\n * routing key (`anthropic`, `openai-compatible`); `models` is what the\n * provider can serve; `complete` and `stream` are the two shapes every door\n * uses. An adapter maps its vendor's errors onto `AiUpstreamError` and its\n * declines onto `AiRefusal`, and reports usage in the meter's shape.\n */\nexport interface AiProvider {\n readonly id: string\n readonly label: string\n /** The environment variable that carries this provider's key, for operator messages. */\n readonly apiKeyEnv: string\n /**\n * The key from the server environment, or `undefined` when the provider\n * is not configured to answer. Each adapter reads its own variable by\n * name in its own module, so every key read is one the key-exposure\n * guard can see (AGL-2240) and no door names a vendor's variable.\n */\n readApiKey(): string | undefined\n /** The host the provider is reached at, for the subprocessor inventory. */\n readonly endpointHost: string\n /**\n * How much of one request's strict tools this provider compiles, where it\n * states a bound. The doors never read it: a spec holds every tool set a\n * door sends to every registered provider's limits, so a schema a provider\n * would refuse is red before it ships rather than a 400 on every request.\n */\n readonly toolSchemaLimits?: AiToolSchemaLimits\n models(): readonly AiModelDescriptor[]\n complete(request: AiProviderRequest): Promise<AiResult>\n /**\n * Resolves once the provider has ACCEPTED the request — after the\n * headers, before the first token — with an iterator of events. A non-2xx\n * answer rejects with `AiUpstreamError` before the iterator exists, so a\n * door can still send a plain error status and hand back its reservation.\n */\n stream(request: AiProviderRequest): Promise<AsyncIterable<AiStreamEvent>>\n /**\n * Many requests answered together, asynchronously, at the provider's batch\n * discount, where it has one: each entry is that request's result, or the\n * error it failed with, in the order asked. Only a development run's live\n * eval calls it (`runtime/ai-live-batch.ts`); no door does.\n */\n completeBatch?(requests: readonly AiProviderRequest[], options?: AiBatchOptions): Promise<Array<AiResult | Error>>\n}\n\n/** How a batch is waited on. */\nexport interface AiBatchOptions {\n /** Between two looks at a batch still processing; 10 s when absent. */\n pollMs?: number\n /**\n * The most a batch is waited for; an hour when absent. Past it the batch\n * is canceled, which keeps every answer it already has, and each request\n * it did not answer comes back as an `AiBatchUnfinishedError` (AGL-3781).\n */\n timeoutMs?: number\n /** How long a canceled batch is given to end, so its answers can be read; 10 minutes when absent. */\n cancelGraceMs?: number\n}\n\n/**\n * A request a batch did not answer (AGL-3781): canceled when its wait ran\n * out, expired, errored, or missing from the results. Not the request's own\n * failure — the caller answers it with a request of its own rather than\n * losing it, and a failure that request meets is the real one.\n */\nexport class AiBatchUnfinishedError extends Error {\n override readonly name = 'AiBatchUnfinishedError'\n constructor(\n readonly batchId: string,\n readonly reason: string,\n ) {\n super(`batch ${batchId} did not answer this request (${reason})`)\n }\n}\n\n/** A count off the wire: finite, non-negative, whole; anything else is 0. */\nexport function aiTokenCount(value: unknown): number {\n const parsed = Number(value ?? 0)\n return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0\n}\n\n/**\n * A tool call's `input` as an object. A provider sends an object or, for\n * the one shape a stream assembles by hand, a JSON string.\n *\n * A string that does not parse to an object is an EMPTY input, not a throw.\n * The case that produces one is a stream cut off mid-call by the output\n * ceiling, and a throw there would end the stream before its `done` event —\n * the event that carries the usage a door must meter for tokens already\n * spent. An empty input is one every door's own validation refuses.\n */\nexport function aiToolInputOf(value: unknown): Record<string, unknown> {\n if (typeof value === 'string') {\n if (!value.trim()) return {}\n try {\n const parsed: unknown = JSON.parse(value)\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {}\n } catch {\n return {}\n }\n }\n return value && typeof value === 'object' ? (value as Record<string, unknown>) : {}\n}\n"],"names":["UpstreamServiceError","compiledForm","node","root","resolving","Array","isArray","map","entry","isSchemaObject","ref","has","$ref","localSchemaAt","Set","written","key","value","Object","entries","aiToolSchemaCompiledBytes","tools","reduce","bytes","tool","JSON","stringify","inputSchema","length","startsWith","undefined","slice","split","replace","countSchema","counts","unionParameters","properties","required","property","includes","optionalParameters","items","item","branches","branch","aiToolSchemaCounts","strictTools","compiledSchemaBytes","TOOL_SCHEMA_MEASURES","aiToolSchemaBreaches","limits","flatMap","measure","one","many","source","limit","count","AI_IMAGE_MEDIA_TYPES","AI_IMAGE_MAX_BYTES","AI_REQUEST_MAX_IMAGES","aiMessageText","message","content","filter","part","type","text","join","aiMessageImages","messages","aiBase64Bytes","data","padding","endsWith","Math","max","floor","aiCacheScopeOf","block","site","aiCacheMarkerTtls","request","ceiling","cacheTtl","lifetime","scope","system","cacheBreakpoint","conversation","cacheConversation","aiStoppedAtCeiling","stopReason","AI_RAW_OUTPUT_MAX_CHARS","aiRawOutputOf","output","aiCutOffFigures","call","parsed","outputTokens","usage","thinkingTokens","parsedChars","rawChars","rawOutput","AI_UPSTREAM_FAILURE_COPY","AiUpstreamError","status","retryable","requestId","accountProblem","name","AiRequestShapeError","Error","AiBatchUnfinishedError","batchId","reason","aiTokenCount","Number","isFinite","aiToolInputOf","trim","parse"],"mappings":"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"}
@@ -82,6 +82,9 @@ export interface AiRoutingRow {
82
82
  * so a five-minute entry is gone before the next job could read it; at an
83
83
  * hour the next site job reads what the last one wrote. The write costs 2x
84
84
  * the input price instead of 1.25x, so a step no job runs leaves it out.
85
+ * The hour is the SHARED markers' alone (AGL-4050): a site's block and the
86
+ * plan's own conversation are read by one job only, so they keep five
87
+ * minutes on every row (`aiCacheMarkerTtls` in `providers/contract.ts`).
85
88
  * Read by `runValidatedGeneration` into the request's `cacheTtl`; a door
86
89
  * that sends its own request on the same step (the copy assistant's blog
87
90
  * draft) keeps five minutes.
@@ -81,9 +81,10 @@ import { aiProviderById, defaultAiProvider } from "./registry.js";
81
81
  } : undefined;
82
82
  }
83
83
  /**
84
- * The cache lifetime every step a site job runs asks for (AGL-3830): the
85
- * plan, the look, the header and footer, every page and its listing, the
86
- * creations, the first posts and products, and the welcome email.
84
+ * The cache lifetime every step a site job runs asks for its shared prompt
85
+ * (AGL-3830, AGL-4050): the plan, the look, the header and footer, every page
86
+ * and its listing, the creations, the first posts and products, and the
87
+ * welcome email.
87
88
  */ const AI_SITE_JOB_CACHE_TTL = '1h';
88
89
  /**
89
90
  * THE ROUTING TABLE (AGL-2937): what each step kind asks of the model, in one