@aglyn/plugins-ai 1.0.0-beta.250 → 1.0.0-beta.253

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 (126) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-brief-dialog.component.js +15 -1
  3. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  4. package/src/lib/components/ai-job-links.js +1 -1
  5. package/src/lib/components/ai-job-links.js.map +1 -1
  6. package/src/lib/components/ai-job-plan.component.js +20 -2
  7. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  8. package/src/lib/components/ai-jobs-store.d.ts +11 -0
  9. package/src/lib/components/ai-jobs-store.js +22 -0
  10. package/src/lib/components/ai-jobs-store.js.map +1 -1
  11. package/src/lib/jobs/ai-job-crm-step.js +7 -4
  12. package/src/lib/jobs/ai-job-crm-step.js.map +1 -1
  13. package/src/lib/jobs/ai-job-drafts.d.ts +24 -0
  14. package/src/lib/jobs/ai-job-drafts.js +46 -0
  15. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  16. package/src/lib/jobs/ai-job-experiment-step.js +6 -1
  17. package/src/lib/jobs/ai-job-experiment-step.js.map +1 -1
  18. package/src/lib/jobs/ai-job-form-dataset.d.ts +6 -1
  19. package/src/lib/jobs/ai-job-form-dataset.js +7 -2
  20. package/src/lib/jobs/ai-job-form-dataset.js.map +1 -1
  21. package/src/lib/jobs/ai-job-form-reuse.d.ts +78 -0
  22. package/src/lib/jobs/ai-job-form-reuse.js +136 -0
  23. package/src/lib/jobs/ai-job-form-reuse.js.map +1 -0
  24. package/src/lib/jobs/ai-job-form-step.d.ts +17 -0
  25. package/src/lib/jobs/ai-job-form-step.js +63 -11
  26. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  27. package/src/lib/jobs/ai-job-insight-step.js +6 -1
  28. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  29. package/src/lib/jobs/ai-job-page-language.d.ts +10 -5
  30. package/src/lib/jobs/ai-job-page-language.js +10 -5
  31. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  32. package/src/lib/jobs/ai-job-plan-step.d.ts +32 -1
  33. package/src/lib/jobs/ai-job-plan-step.js +53 -2
  34. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  35. package/src/lib/jobs/ai-job-seo-step.js +5 -3
  36. package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-site-content.d.ts +7 -5
  38. package/src/lib/jobs/ai-job-site-content.js +7 -5
  39. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  40. package/src/lib/jobs/ai-job-site-datasets.d.ts +1 -1
  41. package/src/lib/jobs/ai-job-site-datasets.js +2 -2
  42. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  43. package/src/lib/jobs/ai-job-site-step.js +2 -1
  44. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  45. package/src/lib/jobs/ai-job-text-step.js +6 -3
  46. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  47. package/src/lib/jobs/ai-layout-stock-photos.d.ts +11 -7
  48. package/src/lib/jobs/ai-layout-stock-photos.js +25 -10
  49. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  50. package/src/lib/jobs/ai-stock-business-nouns.d.ts +62 -0
  51. package/src/lib/jobs/ai-stock-business-nouns.js +264 -0
  52. package/src/lib/jobs/ai-stock-business-nouns.js.map +1 -0
  53. package/src/lib/layout-language/ai-layout-language.js +5 -1
  54. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  55. package/src/lib/layout-language/ai-layout-links.js +5 -2
  56. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  57. package/src/lib/model/ai-build-plan.d.ts +7 -3
  58. package/src/lib/model/ai-build-plan.js +11 -1
  59. package/src/lib/model/ai-build-plan.js.map +1 -1
  60. package/src/lib/model/ai-credit-estimate.d.ts +39 -19
  61. package/src/lib/model/ai-credit-estimate.js +43 -19
  62. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  63. package/src/lib/model/ai-job-activity.d.ts +30 -0
  64. package/src/lib/model/ai-job-activity.js +35 -0
  65. package/src/lib/model/ai-job-activity.js.map +1 -1
  66. package/src/lib/model/ai-media-credits.d.ts +5 -3
  67. package/src/lib/model/ai-media-credits.js +7 -5
  68. package/src/lib/model/ai-media-credits.js.map +1 -1
  69. package/src/lib/model/ai-site-job.js +6 -3
  70. package/src/lib/model/ai-site-job.js.map +1 -1
  71. package/src/lib/model/ai-site-start-seo.d.ts +11 -2
  72. package/src/lib/model/ai-site-start-seo.js +29 -15
  73. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  74. package/src/lib/model/ai-site-store-pages.d.ts +5 -0
  75. package/src/lib/model/ai-site-store-pages.js +2 -0
  76. package/src/lib/model/ai-site-store-pages.js.map +1 -1
  77. package/src/lib/plugin-config.js +2 -2
  78. package/src/lib/plugin-config.js.map +1 -1
  79. package/src/lib/providers/anthropic-crm-wording-superseded.d.ts +29 -0
  80. package/src/lib/providers/anthropic-crm-wording-superseded.js +31 -0
  81. package/src/lib/providers/anthropic-crm-wording-superseded.js.map +1 -0
  82. package/src/lib/providers/anthropic.js +29 -7
  83. package/src/lib/providers/anthropic.js.map +1 -1
  84. package/src/lib/providers/catalog.d.ts +24 -17
  85. package/src/lib/providers/catalog.js +63 -30
  86. package/src/lib/providers/catalog.js.map +1 -1
  87. package/src/lib/providers/contract.d.ts +19 -1
  88. package/src/lib/providers/contract.js +10 -0
  89. package/src/lib/providers/contract.js.map +1 -1
  90. package/src/lib/providers/model-choice.d.ts +1 -1
  91. package/src/lib/providers/model-choice.js +39 -30
  92. package/src/lib/providers/model-choice.js.map +1 -1
  93. package/src/lib/providers/routing.d.ts +20 -1
  94. package/src/lib/providers/routing.js +71 -12
  95. package/src/lib/providers/routing.js.map +1 -1
  96. package/src/lib/runtime/ai-dev-env.d.ts +58 -1
  97. package/src/lib/runtime/ai-dev-env.js +86 -6
  98. package/src/lib/runtime/ai-dev-env.js.map +1 -1
  99. package/src/lib/runtime/ai-dev-replay.d.ts +4 -0
  100. package/src/lib/runtime/ai-dev-replay.js +4 -0
  101. package/src/lib/runtime/ai-dev-replay.js.map +1 -1
  102. package/src/lib/runtime/ai-doctrine-validators.js +13 -3
  103. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  104. package/src/lib/runtime/ai-doctrine.js +8 -4
  105. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  106. package/src/lib/runtime/ai-eval-live.d.ts +43 -1
  107. package/src/lib/runtime/ai-eval-live.js +79 -9
  108. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  109. package/src/lib/runtime/ai-live-batch.d.ts +20 -4
  110. package/src/lib/runtime/ai-live-batch.js +57 -10
  111. package/src/lib/runtime/ai-live-batch.js.map +1 -1
  112. package/src/lib/runtime/ai-palette.generated.js +4 -0
  113. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  114. package/src/lib/runtime/ai-runtime.js +21 -4
  115. package/src/lib/runtime/ai-runtime.js.map +1 -1
  116. package/src/lib/runtime/seo-fields.js +4 -3
  117. package/src/lib/runtime/seo-fields.js.map +1 -1
  118. package/src/lib/server/ai-assist.js +3 -2
  119. package/src/lib/server/ai-assist.js.map +1 -1
  120. package/src/lib/server/ai-media-image.d.ts +5 -1
  121. package/src/lib/server/ai-media-image.js +6 -2
  122. package/src/lib/server/ai-media-image.js.map +1 -1
  123. package/src/lib/server/ai-media-svg.js +4 -0
  124. package/src/lib/server/ai-media-svg.js.map +1 -1
  125. package/src/lib/usage/ai-allotment-alerts.js +2 -5
  126. package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
@@ -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. Only a development run asks for `1h`\n * (`runtime/ai-dev-replay.ts`); it changes no answer, only what a second\n * request within the hour pays for the prefix.\n */\n cacheTtl?: '1h'\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 `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 /** The most a batch is waited for before every request in it fails; an hour when absent. */\n timeoutMs?: number\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","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 breakpoints write should live, where the\n * provider offers a choice: the provider's default (five minutes on the\n * Messages API) when absent. Only a development run asks for `1h`\n * (`runtime/ai-dev-replay.ts`); it changes no answer, only what a second\n * request within the hour pays for the prefix.\n */\n cacheTtl?: '1h'\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 `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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"}
@@ -78,7 +78,7 @@ export interface AiModelBounds {
78
78
  /** The org-wide restriction; `null` for none. */
79
79
  orgModels?: readonly string[] | null;
80
80
  }
81
- /** The catalog models a manual pick may name on a provider under these bounds. */
81
+ /** The current catalog models a manual pick may name on a provider under these bounds. */
82
82
  export declare function aiModelsSelectable(providerId: string, bounds: AiModelBounds): AiCatalogEntry[];
83
83
  export interface AiModelChoice {
84
84
  /** The catalog id the request runs, and is metered, on. */
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */ import { assistCreditsFromUsd } from "../usage/assist-credits.js";
17
17
  import { AI_MODEL_CATALOG, AI_STEP_TIERS, aiCatalogEntry, aiBilledRatesForModel, estimateAiBilledUsd } from "./catalog.js";
18
- import { resolveAiRoute } from "./routing.js";
18
+ import { aiServingModelId, resolveAiRoute } from "./routing.js";
19
19
  /**
20
20
  * THE MODEL SWITCH (AGL-2942): what a person may pick, what "Auto" means,
21
21
  * and what a pick costs.
@@ -105,9 +105,11 @@ import { resolveAiRoute } from "./routing.js";
105
105
  cacheReadTokens: 1050,
106
106
  cacheWriteTokens: 0
107
107
  },
108
+ // One element's rewrite on the fast tier, counted as Claude Haiku 5.5 counts
109
+ // it: about 30% more tokens than Haiku 4.5 for the same text (AGL-3781).
108
110
  'copy.element': {
109
- inputTokens: 350,
110
- outputTokens: 120,
111
+ inputTokens: 455,
112
+ outputTokens: 156,
111
113
  cacheReadTokens: 0,
112
114
  cacheWriteTokens: 0
113
115
  },
@@ -169,16 +171,17 @@ import { resolveAiRoute } from "./routing.js";
169
171
  cacheReadTokens: 5800,
170
172
  cacheWriteTokens: 0
171
173
  },
172
- // The CRM step's exchange (AGL-2917). NOTHING is cached: the step runs on
173
- // the fast tier, whose cacheable minimum is far above what its prompt
174
- // reaches — the doctrine's field rules, the CRM rules and the tool come to
175
- // 650 to 913 tokens, as `runtime/ai-prompt-cache.spec.ts` measures — so
176
- // every byte is billed as input. A record's facts ride with them, a
177
- // timeline cut to its newest twelve entries, and the typical answer is a
178
- // summary and a next step of a few sentences, with no thinking.
174
+ // The CRM step's exchange (AGL-2917), counted as the fast tier's Claude
175
+ // Haiku 5.5 counts it, about 30% more tokens than Haiku 4.5 (AGL-3781). The
176
+ // doctrine's field rules, the CRM rules and the tool come to 650 to 913
177
+ // tokens, as `runtime/ai-prompt-cache.spec.ts` measures them, which caches on
178
+ // that model; one question about a record writes it, so it is counted as
179
+ // input. A record's facts ride with them, a timeline cut to its newest twelve
180
+ // entries, and the typical answer is a summary and a next step of a few
181
+ // sentences, with no thinking.
179
182
  'job.crm': {
180
- inputTokens: 1800,
181
- outputTokens: 250,
183
+ inputTokens: 2340,
184
+ outputTokens: 325,
182
185
  cacheReadTokens: 0,
183
186
  cacheWriteTokens: 0
184
187
  },
@@ -228,19 +231,20 @@ import { resolveAiRoute } from "./routing.js";
228
231
  cacheWriteTokens: 0
229
232
  },
230
233
  // The SEO step's listing exchange, which a page's or a product's listing is
231
- // exactly one of. NOTHING is cached here, whatever the breakpoints say: the
232
- // step runs on the fast tier, whose cacheable minimum is four times what
233
- // this prompt reaches, so every static byte is billed as input on every
234
- // attempt — the doctrine's block in its field scope, the listing rules and
235
- // the tool's schema, about 760 tokens together. The page text, capped at
236
- // 2,000 characters, rides with the current listing and a dozen other titles
237
- // for about another 750. The largest answer the tool accepts is about 600
238
- // characters of JSON, and the request asks for no thinking. An audit pass
239
- // is heavier: eight pages' findings and text, about 9,000 characters, with
240
- // up to about 8,000 back.
234
+ // exactly one of: the median of 146 production listings of October 8–14
235
+ // (2,255 input and 213 output tokens on Claude Haiku 4.5), counted as the
236
+ // fast tier's Claude Haiku 5.5 counts them, about 30% more (AGL-3781). The
237
+ // doctrine's block in its field scope, the listing rules and the tool's
238
+ // schema are its prefix, which caches on that model and which a site's next
239
+ // listing reads; a lone listing writes it, so it is counted as input. The
240
+ // page text, capped at 2,000 characters, rides with the current listing and a
241
+ // dozen other titles. The largest answer the tool accepts is about 600
242
+ // characters of JSON, and the request asks for no thinking. An audit pass is
243
+ // heavier: eight pages' findings and text, about 9,000 characters, with up to
244
+ // about 8,000 back.
241
245
  'job.seo': {
242
- inputTokens: 1500,
243
- outputTokens: 250,
246
+ inputTokens: 2930,
247
+ outputTokens: 277,
244
248
  cacheReadTokens: 0,
245
249
  cacheWriteTokens: 0
246
250
  },
@@ -331,16 +335,19 @@ import { resolveAiRoute } from "./routing.js";
331
335
  }
332
336
  };
333
337
  /** The fewest measured exchanges a median is taken over. */ export const AI_MEASURED_MEDIAN_MIN_SAMPLES = 3;
338
+ /** Whether a list names a model, a retired id on it naming its successor (AGL-3781). */ function names(list, id) {
339
+ return list.some((listed)=>aiServingModelId(listed) === id);
340
+ }
334
341
  function onLists(entry, bounds) {
335
342
  var _bounds_orgModels;
336
- if (((_bounds_orgModels = bounds.orgModels) == null ? void 0 : _bounds_orgModels.length) && !bounds.orgModels.includes(entry.id)) return false;
337
- if (bounds.allotmentModels && !bounds.allotmentModels.includes(entry.id)) return false;
343
+ if (((_bounds_orgModels = bounds.orgModels) == null ? void 0 : _bounds_orgModels.length) && !names(bounds.orgModels, entry.id)) return false;
344
+ if (bounds.allotmentModels && !names(bounds.allotmentModels, entry.id)) return false;
338
345
  return true;
339
346
  }
340
- /** The catalog models a manual pick may name on a provider under these bounds. */ export function aiModelsSelectable(providerId, bounds) {
347
+ /** The current catalog models a manual pick may name on a provider under these bounds. */ export function aiModelsSelectable(providerId, bounds) {
341
348
  var _AI_PLAN_MODEL_TIERS_bounds_plan;
342
349
  const tiers = (_AI_PLAN_MODEL_TIERS_bounds_plan = AI_PLAN_MODEL_TIERS[bounds.plan]) != null ? _AI_PLAN_MODEL_TIERS_bounds_plan : [];
343
- return AI_MODEL_CATALOG.filter((entry)=>entry.provider === providerId && tiers.includes(entry.tier) && onLists(entry, bounds));
350
+ return AI_MODEL_CATALOG.filter((entry)=>entry.provider === providerId && !entry.successor && tiers.includes(entry.tier) && onLists(entry, bounds));
344
351
  }
345
352
  /**
346
353
  * A model's blended BILLED rate, for "the cheapest allowed" and nothing
@@ -362,7 +369,9 @@ function onLists(entry, bounds) {
362
369
  var _bounds_orgModels;
363
370
  const route = resolveAiRoute(kind, settings);
364
371
  if (!route) return null;
365
- const wanted = typeof requested === 'string' && requested.trim() && requested.trim() !== AI_MODEL_AUTO ? requested.trim() : null;
372
+ // A pick of a retired model, from a job made before it retired, is a pick
373
+ // of the model that serves in its place.
374
+ const wanted = typeof requested === 'string' && requested.trim() && requested.trim() !== AI_MODEL_AUTO ? aiServingModelId(requested.trim()) : null;
366
375
  if (wanted && aiModelsSelectable(route.provider.id, bounds).some((entry)=>entry.id === wanted)) {
367
376
  return {
368
377
  model: wanted,
@@ -373,7 +382,7 @@ function onLists(entry, bounds) {
373
382
  const restricted = Boolean((_bounds_orgModels = bounds.orgModels) == null ? void 0 : _bounds_orgModels.length) || Boolean(bounds.allotmentModels);
374
383
  let model = route.model;
375
384
  if (restricted) {
376
- const allowed = AI_MODEL_CATALOG.filter((entry)=>entry.provider === route.provider.id && onLists(entry, bounds));
385
+ const allowed = AI_MODEL_CATALOG.filter((entry)=>entry.provider === route.provider.id && !entry.successor && onLists(entry, bounds));
377
386
  if (allowed.length && !allowed.some((entry)=>entry.id === model)) {
378
387
  var _ref;
379
388
  var _aiCatalogEntry;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/providers/model-choice.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 { assistCreditsFromUsd } from '../usage/assist-credits'\nimport type { OrgPlan } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport {\n AI_MODEL_CATALOG,\n AI_STEP_TIERS,\n aiCatalogEntry,\n aiBilledRatesForModel,\n estimateAiBilledUsd,\n type AiCatalogEntry,\n type AiStepKind,\n} from './catalog'\nimport type { AiUsage } from './contract'\nimport { resolveAiRoute, type AiPluginSettings } from './routing'\n\n/**\n * THE MODEL SWITCH (AGL-2942): what a person may pick, what \"Auto\" means,\n * and what a pick costs.\n *\n * ## Auto is the routing table\n *\n * A request that names no model — or names one it may not have — runs on\n * `resolveAiRoute(kind)`, the table that maps a step kind to a catalog model\n * (AGL-2937). Picking a model is an override of that one row for one request,\n * never a second table.\n *\n * ## Three bounds, one intersection\n *\n * A manual pick must be served by the active provider, in a tier the org's\n * plan offers (`AI_PLAN_MODEL_TIERS`), on the org's own restriction when it\n * set one, and on the allotment allowlists that apply to the request. The\n * two allowlists bound Auto too: when the routing table's answer is not on\n * them, Auto runs on the allowed model of the same tier, else the cheapest\n * allowed one. The plan bounds only a pick — Auto is the platform's choice,\n * made for the step, and a plan never makes it worse.\n *\n * An allowlist that leaves nothing this deployment serves is not a way to\n * switch AI off by mistake: the choice falls back to the routing table, and\n * the permission keys are how AI is switched off on purpose.\n *\n * ## The price beside each option\n *\n * Credits per typical request at each model's list rates, over the step's\n * measured median exchange when the workspace has enough of them, and over\n * `AI_STEP_NOMINAL_USAGE` until it does — with the multiple of what Auto\n * would cost for the same request. Every figure is credits, never dollars.\n */\n\n/** The value a request sends for \"let the platform route it\". */\nexport const AI_MODEL_AUTO = 'auto'\n\n/**\n * The catalog tiers a MANUAL pick may name, per plan. Free runs on Auto only:\n * its taste is a wall, and a pick there would spend the account's credits at\n * a rate nobody chose on purpose. The dearest tier opens from Scale up, where\n * the band is large enough that a slower, deeper model is a real choice\n * rather than a fast way to end the month.\n */\nexport const AI_PLAN_MODEL_TIERS: Record<OrgPlan, readonly AiCatalogEntry['tier'][]> = {\n free: [],\n starter: ['fast', 'balanced'],\n pro: ['fast', 'balanced'],\n business: ['fast', 'balanced'],\n scale: ['fast', 'balanced', 'deep'],\n advanced: ['fast', 'balanced', 'deep'],\n agency: ['fast', 'balanced', 'deep'],\n enterprise: ['fast', 'balanced', 'deep'],\n}\n\n/** The plans whose admins may restrict the model set org-wide. */\nexport const AI_MODEL_RESTRICTION_PLANS: readonly OrgPlan[] = ['agency', 'enterprise']\n\n/**\n * A typical exchange per step kind, before a workspace has measured one: the\n * doors' own prompt sizes and output ceilings, read conservatively. Replaced\n * by the workspace's measured median as soon as `aiMedianUsage` has enough\n * samples, and only ever used for the price beside an option.\n */\nexport const AI_STEP_NOMINAL_USAGE: Record<AiStepKind, AiUsage> = {\n 'assist.chat': { inputTokens: 900, outputTokens: 450, cacheReadTokens: 1_050, cacheWriteTokens: 0 },\n 'copy.element': { inputTokens: 350, outputTokens: 120, cacheReadTokens: 0, cacheWriteTokens: 0 },\n 'copy.section': { inputTokens: 700, outputTokens: 1_400, cacheReadTokens: 0, cacheWriteTokens: 0 },\n 'copy.blog': { inputTokens: 600, outputTokens: 1_100, cacheReadTokens: 0, cacheWriteTokens: 0 },\n 'generate.section': { inputTokens: 700, outputTokens: 1_400, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The text step's rules are about 500 characters — far under the balanced\n // tier's cacheable minimum, so they are billed as input every time rather\n // than read from a cache (`runtime/ai-prompt-cache.spec.ts` measures it).\n 'job.text': { inputTokens: 450, outputTokens: 500, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The theme step: the tool's schema and both system blocks (about 7,500\n // characters) are the cached prefix, the site's inventory and the brief ride\n // uncached, and a full-palette answer is about 1,300 characters of JSON with\n // as much again to think in.\n 'job.theme': { inputTokens: 500, outputTokens: 850, cacheReadTokens: 2_500, cacheWriteTokens: 0 },\n // The plan step: the tool's schema, the doctrine and the plan instructions\n // (about 10,000 characters) are the cached prefix, the site inventory and the\n // brief ride uncached, and a plan answer is about 600 characters of JSON with\n // as much again to think in.\n 'job.plan': { inputTokens: 500, outputTokens: 400, cacheReadTokens: 3_600, cacheWriteTokens: 0 },\n // The component step: the doctrine, the component instructions, the\n // component surface's catalog and the tool are the cached prefix, the site\n // inventory, the brief and the confirmed plan ride uncached, and a card-sized\n // answer — its tree and declared props as JSON — is about 1,700 characters.\n // The component step's spec measures the prefix and the answer against this.\n 'job.component': { inputTokens: 700, outputTokens: 600, cacheReadTokens: 5_800, cacheWriteTokens: 0 },\n // The CRM step's exchange (AGL-2917). NOTHING is cached: the step runs on\n // the fast tier, whose cacheable minimum is far above what its prompt\n // reaches — the doctrine's field rules, the CRM rules and the tool come to\n // 650 to 913 tokens, as `runtime/ai-prompt-cache.spec.ts` measures — so\n // every byte is billed as input. A record's facts ride with them, a\n // timeline cut to its newest twelve entries, and the typical answer is a\n // summary and a next step of a few sentences, with no thinking.\n 'job.crm': { inputTokens: 1_800, outputTokens: 250, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // A layout: a tree layout's 1,000 output tokens until AGL-3660, now a\n // site's header and footer in the layout language — 442 to 583 output\n // tokens live on 2026-10-07 — with up to 1,500 more for the adaptive\n // thinking it is asked with, an estimate until a workspace measures it.\n 'job.layout': { inputTokens: 700, outputTokens: 2_000, cacheReadTokens: 5_600, cacheWriteTokens: 0 },\n 'job.template': { inputTokens: 1_200, outputTokens: 700, cacheReadTokens: 6_400, cacheWriteTokens: 0 },\n // One page section's exchange (AGL-2907), from the modules' own text: the\n // doctrine (6,281 characters), the page instructions (1,524), the screen\n // palette catalog (8,353) and the section tool (484) are the cached prefix;\n // the site inventory (711 on the median golden site), the brief, the plan\n // and the section line ride uncached; and a golden section answer is 901\n // characters at the median, asked for with no thinking.\n // Since AGL-3660 a site's and a build's pages are one answer a page in\n // the layout language: 683 to 1,745 output tokens live on 2026-10-07, about\n // 1,150 at the median, with up to 1,500 more for the adaptive thinking it\n // is asked with — an estimate until a workspace measures it, read high so\n // the quote beside a model never undersells the exchange.\n 'job.page': { inputTokens: 600, outputTokens: 2_650, cacheReadTokens: 4_400, cacheWriteTokens: 0 },\n // One product's copy (AGL-2916), the exchange a bulk job makes once a\n // product: the doctrine, the copy rules and the tool (about 9,500 characters)\n // are the cached prefix; the product's facts, the site's categories and a\n // photo at most 768 pixels on its long edge (about 790 tokens) ride\n // uncached; and the answer is a description with its search listing, tags,\n // categories and option names, about 1,300 characters of JSON.\n 'job.products': { inputTokens: 1_500, outputTokens: 450, cacheReadTokens: 2_400, cacheWriteTokens: 0 },\n // The SEO step's listing exchange, which a page's or a product's listing is\n // exactly one of. NOTHING is cached here, whatever the breakpoints say: the\n // step runs on the fast tier, whose cacheable minimum is four times what\n // this prompt reaches, so every static byte is billed as input on every\n // attempt — the doctrine's block in its field scope, the listing rules and\n // the tool's schema, about 760 tokens together. The page text, capped at\n // 2,000 characters, rides with the current listing and a dozen other titles\n // for about another 750. The largest answer the tool accepts is about 600\n // characters of JSON, and the request asks for no thinking. An audit pass\n // is heavier: eight pages' findings and text, about 9,000 characters, with\n // up to about 8,000 back.\n 'job.seo': { inputTokens: 1_500, outputTokens: 250, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The experiment step (AGL-2914). NOTHING is cached: the A/B test rules\n // and the acceptable-use block come to about 2,300 characters, under the\n // balanced tier's cacheable minimum, so they are billed as input every\n // time. The copy under test, or a test's arms with its verdict, rides with\n // them, and a four-variant answer is about 800 characters of JSON.\n 'job.experiment': { inputTokens: 900, outputTokens: 300, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The form step: the doctrine, the form instructions, the form surface's\n // catalog (about 150 tokens) and the tool's schema are the cached prefix, the\n // site inventory and the brief ride uncached, and a form's answer is 200 to\n // 400 tokens of JSON with no extended thinking.\n 'job.form': { inputTokens: 700, outputTokens: 450, cacheReadTokens: 3_900, cacheWriteTokens: 0 },\n // An insight (AGL-2915): two exchanges, and NOTHING cached — the rules and\n // the acceptable-use block are under the balanced tier's minimum, so they\n // are billed as input on both. The read call carries the question and the\n // readers' catalog (about 2,000 characters) and answers a few reads; the\n // answer call carries four tables of about ten rows each (about 4,000\n // characters) and answers three to five insights as JSON.\n 'job.insight': { inputTokens: 2_800, outputTokens: 650, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The email step, and the campaign step that shares its generation: the\n // doctrine, the email instructions, the email palette catalog and the\n // tool's schema (about 10,300 characters) are the cached prefix, the site\n // inventory and the brief ride uncached, and the answer is one email's\n // node map with three subject lines and three preheaders.\n 'job.email': { inputTokens: 600, outputTokens: 1_500, cacheReadTokens: 3_600, cacheWriteTokens: 0 },\n 'job.campaign': { inputTokens: 600, outputTokens: 1_500, cacheReadTokens: 3_600, cacheWriteTokens: 0 },\n // The workflow step's draft (AGL-2919): the doctrine, the automation\n // vocabulary and the tool's schema are the cached prefix (4,564 tokens, as\n // `runtime/ai-prompt-cache.spec.ts` measures it); what the workspace can\n // run, the site's forms and datasets and the description ride uncached; and\n // an automation of a few steps is about 1,300 characters of JSON with as\n // much again to think in. An explanation caches half as much.\n 'job.workflow': { inputTokens: 500, outputTokens: 1_000, cacheReadTokens: 4_500, cacheWriteTokens: 0 },\n // The logic step (AGL-3603): the fields doctrine, one rules block and the\n // tool's schema are the cached prefix (1,556 tokens for a function, as\n // `runtime/ai-prompt-cache.spec.ts` measures it); the site's variables and\n // the request ride uncached, and a function of a few operations is about\n // 1,200 characters of JSON with as much again to think in.\n 'job.logic': { inputTokens: 700, outputTokens: 900, cacheReadTokens: 1_500, cacheWriteTokens: 0 },\n // The edit step (AGL-3616): the document doctrine, the job's rules and the\n // edit rung's protocol and catalog for the document's kind are the cached\n // prefix (5,970 tokens for a page, as `runtime/ai-prompt-cache.spec.ts`\n // measures it); the outline of up to sixty elements with their settings\n // and the request ride uncached, and a change of a few operations is about\n // 1,500 characters of tool call with as much again to think in.\n 'job.edit': { inputTokens: 2_500, outputTokens: 1_000, cacheReadTokens: 6_000, cacheWriteTokens: 0 },\n}\n\n/** The fewest measured exchanges a median is taken over. */\nexport const AI_MEASURED_MEDIAN_MIN_SAMPLES = 3\n\nexport interface AiModelBounds {\n plan: OrgPlan\n /** The allotment allowlists that apply, intersected; `null` for none. */\n allotmentModels?: readonly string[] | null\n /** The org-wide restriction; `null` for none. */\n orgModels?: readonly string[] | null\n}\n\nfunction onLists(entry: AiCatalogEntry, bounds: AiModelBounds): boolean {\n if (bounds.orgModels?.length && !bounds.orgModels.includes(entry.id)) return false\n if (bounds.allotmentModels && !bounds.allotmentModels.includes(entry.id)) return false\n return true\n}\n\n/** The catalog models a manual pick may name on a provider under these bounds. */\nexport function aiModelsSelectable(\n providerId: string,\n bounds: AiModelBounds,\n): AiCatalogEntry[] {\n const tiers = AI_PLAN_MODEL_TIERS[bounds.plan] ?? []\n return AI_MODEL_CATALOG.filter(\n (entry) =>\n entry.provider === providerId && tiers.includes(entry.tier) && onLists(entry, bounds),\n )\n}\n\n/**\n * A model's blended BILLED rate, for \"the cheapest allowed\" and nothing\n * else.\n *\n * Billed and not provider (AGL-3015): the substitution below happens when a\n * workspace asked for a model its bounds do not allow, and the party it\n * falls back for is the one whose credits are drawn. \"Cheapest\" has to mean\n * the same thing here as in the selector this substitution stands in for,\n * which sorts on `creditsPerRequest`.\n */\nconst blendedRate = (id: string): number => {\n const rates = aiBilledRatesForModel(id)\n return rates.inputPerToken + rates.outputPerToken\n}\n\nexport interface AiModelChoice {\n /** The catalog id the request runs, and is metered, on. */\n model: string\n /** True when the routing table decided: Auto, or a pick that was declined. */\n auto: boolean\n /** A pick that was asked for and is not allowed, for the envelope. */\n declined: string | null\n}\n\n/**\n * The model one request runs on, or `null` when no provider is registered —\n * which every door has refused before it asks.\n */\nexport function resolveAiModelChoice(\n kind: AiStepKind,\n requested: unknown,\n bounds: AiModelBounds,\n settings?: AiPluginSettings,\n): AiModelChoice | null {\n const route = resolveAiRoute(kind, settings)\n if (!route) return null\n const wanted =\n typeof requested === 'string' && requested.trim() && requested.trim() !== AI_MODEL_AUTO\n ? requested.trim()\n : null\n if (wanted && aiModelsSelectable(route.provider.id, bounds).some((entry) => entry.id === wanted)) {\n return { model: wanted, auto: false, declined: null }\n }\n const restricted = Boolean(bounds.orgModels?.length) || Boolean(bounds.allotmentModels)\n let model = route.model\n if (restricted) {\n const allowed = AI_MODEL_CATALOG.filter(\n (entry) => entry.provider === route.provider.id && onLists(entry, bounds),\n )\n if (allowed.length && !allowed.some((entry) => entry.id === model)) {\n const tier = aiCatalogEntry(model)?.tier ?? AI_STEP_TIERS[kind]\n const sameTier = allowed.find((entry) => entry.tier === tier)\n const cheapest = [...allowed].sort((a, b) => blendedRate(a.id) - blendedRate(b.id))[0]\n model = (sameTier ?? cheapest).id\n }\n }\n return { model, auto: true, declined: wanted }\n}\n\n/** The median exchange over measured samples, or `null` below the minimum. */\nexport function aiMedianUsage(samples: readonly AiUsage[]): AiUsage | null {\n if (samples.length < AI_MEASURED_MEDIAN_MIN_SAMPLES) return null\n const median = (field: keyof AiUsage): number => {\n const values = samples\n .map((sample) => Number(sample[field] ?? 0))\n .filter((value) => Number.isFinite(value) && value >= 0)\n .sort((a, b) => a - b)\n if (!values.length) return 0\n const middle = Math.floor(values.length / 2)\n return values.length % 2\n ? values[middle]\n : Math.round((values[middle - 1] + values[middle]) / 2)\n }\n return {\n inputTokens: median('inputTokens'),\n outputTokens: median('outputTokens'),\n cacheReadTokens: median('cacheReadTokens'),\n cacheWriteTokens: median('cacheWriteTokens'),\n }\n}\n\nexport interface AiModelOption {\n /** A catalog id, or `AI_MODEL_AUTO`. */\n id: string\n label: string\n tier: AiCatalogEntry['tier']\n /** Credits a typical request of this kind costs on the option. */\n creditsPerRequest: number\n /** That cost as a multiple of Auto's, to one decimal. */\n multiplier: number\n}\n\nexport interface AiModelOptions {\n /** Auto, labeled with the model it routes this kind to under these bounds. */\n auto: AiModelOption & { model: string }\n /** The manual picks, cheapest first; empty where the plan offers none. */\n options: AiModelOption[]\n /** Whether the price came from the workspace's own measured exchanges. */\n measured: boolean\n}\n\n/** What the selector lists for one step kind, or `null` without a provider. */\nexport function aiModelOptions(\n kind: AiStepKind,\n bounds: AiModelBounds,\n measured: AiUsage | null,\n settings?: AiPluginSettings,\n): AiModelOptions | null {\n const route = resolveAiRoute(kind, settings)\n const auto = resolveAiModelChoice(kind, null, bounds, settings)\n if (!route || !auto) return null\n const typical = measured ?? AI_STEP_NOMINAL_USAGE[kind]\n // Billed rates throughout (AGL-3015): every figure below is what the\n // workspace will be charged for picking this model — credits per request\n // and the multiple of Auto those credits come to. Our own cost for the\n // same request is not this selector's subject and would misstate both.\n const autoCost = estimateAiBilledUsd(typical, auto.model)\n const priced = (entry: AiCatalogEntry): AiModelOption => {\n const cost = estimateAiBilledUsd(typical, entry.id)\n return {\n id: entry.id,\n label: entry.label,\n tier: entry.tier,\n creditsPerRequest: assistCreditsFromUsd(cost),\n multiplier: autoCost > 0 ? Math.round((cost / autoCost) * 10) / 10 : 1,\n }\n }\n const autoEntry = aiCatalogEntry(auto.model)\n return {\n auto: {\n id: AI_MODEL_AUTO,\n label: autoEntry ? `Auto (${autoEntry.label})` : 'Auto',\n tier: autoEntry?.tier ?? AI_STEP_TIERS[kind],\n creditsPerRequest: assistCreditsFromUsd(autoCost),\n multiplier: 1,\n model: auto.model,\n },\n options: aiModelsSelectable(route.provider.id, bounds)\n .map(priced)\n .sort((a, b) => a.creditsPerRequest - b.creditsPerRequest || a.label.localeCompare(b.label)),\n measured: measured !== null,\n }\n}\n"],"names":["assistCreditsFromUsd","AI_MODEL_CATALOG","AI_STEP_TIERS","aiCatalogEntry","aiBilledRatesForModel","estimateAiBilledUsd","resolveAiRoute","AI_MODEL_AUTO","AI_PLAN_MODEL_TIERS","free","starter","pro","business","scale","advanced","agency","enterprise","AI_MODEL_RESTRICTION_PLANS","AI_STEP_NOMINAL_USAGE","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","AI_MEASURED_MEDIAN_MIN_SAMPLES","onLists","entry","bounds","orgModels","length","includes","id","allotmentModels","aiModelsSelectable","providerId","tiers","plan","filter","provider","tier","blendedRate","rates","inputPerToken","outputPerToken","resolveAiModelChoice","kind","requested","settings","route","wanted","trim","some","model","auto","declined","restricted","Boolean","allowed","sameTier","find","cheapest","sort","a","b","aiMedianUsage","samples","median","field","values","map","sample","Number","value","isFinite","middle","Math","floor","round","aiModelOptions","measured","typical","autoCost","priced","cost","label","creditsPerRequest","multiplier","autoEntry","options","localeCompare"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/providers/model-choice.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 { assistCreditsFromUsd } from '../usage/assist-credits'\nimport type { OrgPlan } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport {\n AI_MODEL_CATALOG,\n AI_STEP_TIERS,\n aiCatalogEntry,\n aiBilledRatesForModel,\n estimateAiBilledUsd,\n type AiCatalogEntry,\n type AiStepKind,\n} from './catalog'\nimport type { AiUsage } from './contract'\nimport { aiServingModelId, resolveAiRoute, type AiPluginSettings } from './routing'\n\n/**\n * THE MODEL SWITCH (AGL-2942): what a person may pick, what \"Auto\" means,\n * and what a pick costs.\n *\n * ## Auto is the routing table\n *\n * A request that names no model — or names one it may not have — runs on\n * `resolveAiRoute(kind)`, the table that maps a step kind to a catalog model\n * (AGL-2937). Picking a model is an override of that one row for one request,\n * never a second table.\n *\n * ## Three bounds, one intersection\n *\n * A manual pick must be served by the active provider, in a tier the org's\n * plan offers (`AI_PLAN_MODEL_TIERS`), on the org's own restriction when it\n * set one, and on the allotment allowlists that apply to the request. The\n * two allowlists bound Auto too: when the routing table's answer is not on\n * them, Auto runs on the allowed model of the same tier, else the cheapest\n * allowed one. The plan bounds only a pick — Auto is the platform's choice,\n * made for the step, and a plan never makes it worse.\n *\n * An allowlist that leaves nothing this deployment serves is not a way to\n * switch AI off by mistake: the choice falls back to the routing table, and\n * the permission keys are how AI is switched off on purpose.\n *\n * ## The price beside each option\n *\n * Credits per typical request at each model's list rates, over the step's\n * measured median exchange when the workspace has enough of them, and over\n * `AI_STEP_NOMINAL_USAGE` until it does — with the multiple of what Auto\n * would cost for the same request. Every figure is credits, never dollars.\n */\n\n/** The value a request sends for \"let the platform route it\". */\nexport const AI_MODEL_AUTO = 'auto'\n\n/**\n * The catalog tiers a MANUAL pick may name, per plan. Free runs on Auto only:\n * its taste is a wall, and a pick there would spend the account's credits at\n * a rate nobody chose on purpose. The dearest tier opens from Scale up, where\n * the band is large enough that a slower, deeper model is a real choice\n * rather than a fast way to end the month.\n */\nexport const AI_PLAN_MODEL_TIERS: Record<OrgPlan, readonly AiCatalogEntry['tier'][]> = {\n free: [],\n starter: ['fast', 'balanced'],\n pro: ['fast', 'balanced'],\n business: ['fast', 'balanced'],\n scale: ['fast', 'balanced', 'deep'],\n advanced: ['fast', 'balanced', 'deep'],\n agency: ['fast', 'balanced', 'deep'],\n enterprise: ['fast', 'balanced', 'deep'],\n}\n\n/** The plans whose admins may restrict the model set org-wide. */\nexport const AI_MODEL_RESTRICTION_PLANS: readonly OrgPlan[] = ['agency', 'enterprise']\n\n/**\n * A typical exchange per step kind, before a workspace has measured one: the\n * doors' own prompt sizes and output ceilings, read conservatively. Replaced\n * by the workspace's measured median as soon as `aiMedianUsage` has enough\n * samples, and only ever used for the price beside an option.\n */\nexport const AI_STEP_NOMINAL_USAGE: Record<AiStepKind, AiUsage> = {\n 'assist.chat': { inputTokens: 900, outputTokens: 450, cacheReadTokens: 1_050, cacheWriteTokens: 0 },\n // One element's rewrite on the fast tier, counted as Claude Haiku 5.5 counts\n // it: about 30% more tokens than Haiku 4.5 for the same text (AGL-3781).\n 'copy.element': { inputTokens: 455, outputTokens: 156, cacheReadTokens: 0, cacheWriteTokens: 0 },\n 'copy.section': { inputTokens: 700, outputTokens: 1_400, cacheReadTokens: 0, cacheWriteTokens: 0 },\n 'copy.blog': { inputTokens: 600, outputTokens: 1_100, cacheReadTokens: 0, cacheWriteTokens: 0 },\n 'generate.section': { inputTokens: 700, outputTokens: 1_400, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The text step's rules are about 500 characters — far under the balanced\n // tier's cacheable minimum, so they are billed as input every time rather\n // than read from a cache (`runtime/ai-prompt-cache.spec.ts` measures it).\n 'job.text': { inputTokens: 450, outputTokens: 500, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The theme step: the tool's schema and both system blocks (about 7,500\n // characters) are the cached prefix, the site's inventory and the brief ride\n // uncached, and a full-palette answer is about 1,300 characters of JSON with\n // as much again to think in.\n 'job.theme': { inputTokens: 500, outputTokens: 850, cacheReadTokens: 2_500, cacheWriteTokens: 0 },\n // The plan step: the tool's schema, the doctrine and the plan instructions\n // (about 10,000 characters) are the cached prefix, the site inventory and the\n // brief ride uncached, and a plan answer is about 600 characters of JSON with\n // as much again to think in.\n 'job.plan': { inputTokens: 500, outputTokens: 400, cacheReadTokens: 3_600, cacheWriteTokens: 0 },\n // The component step: the doctrine, the component instructions, the\n // component surface's catalog and the tool are the cached prefix, the site\n // inventory, the brief and the confirmed plan ride uncached, and a card-sized\n // answer — its tree and declared props as JSON — is about 1,700 characters.\n // The component step's spec measures the prefix and the answer against this.\n 'job.component': { inputTokens: 700, outputTokens: 600, cacheReadTokens: 5_800, cacheWriteTokens: 0 },\n // The CRM step's exchange (AGL-2917), counted as the fast tier's Claude\n // Haiku 5.5 counts it, about 30% more tokens than Haiku 4.5 (AGL-3781). The\n // doctrine's field rules, the CRM rules and the tool come to 650 to 913\n // tokens, as `runtime/ai-prompt-cache.spec.ts` measures them, which caches on\n // that model; one question about a record writes it, so it is counted as\n // input. A record's facts ride with them, a timeline cut to its newest twelve\n // entries, and the typical answer is a summary and a next step of a few\n // sentences, with no thinking.\n 'job.crm': { inputTokens: 2_340, outputTokens: 325, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // A layout: a tree layout's 1,000 output tokens until AGL-3660, now a\n // site's header and footer in the layout language — 442 to 583 output\n // tokens live on 2026-10-07 — with up to 1,500 more for the adaptive\n // thinking it is asked with, an estimate until a workspace measures it.\n 'job.layout': { inputTokens: 700, outputTokens: 2_000, cacheReadTokens: 5_600, cacheWriteTokens: 0 },\n 'job.template': { inputTokens: 1_200, outputTokens: 700, cacheReadTokens: 6_400, cacheWriteTokens: 0 },\n // One page section's exchange (AGL-2907), from the modules' own text: the\n // doctrine (6,281 characters), the page instructions (1,524), the screen\n // palette catalog (8,353) and the section tool (484) are the cached prefix;\n // the site inventory (711 on the median golden site), the brief, the plan\n // and the section line ride uncached; and a golden section answer is 901\n // characters at the median, asked for with no thinking.\n // Since AGL-3660 a site's and a build's pages are one answer a page in\n // the layout language: 683 to 1,745 output tokens live on 2026-10-07, about\n // 1,150 at the median, with up to 1,500 more for the adaptive thinking it\n // is asked with — an estimate until a workspace measures it, read high so\n // the quote beside a model never undersells the exchange.\n 'job.page': { inputTokens: 600, outputTokens: 2_650, cacheReadTokens: 4_400, cacheWriteTokens: 0 },\n // One product's copy (AGL-2916), the exchange a bulk job makes once a\n // product: the doctrine, the copy rules and the tool (about 9,500 characters)\n // are the cached prefix; the product's facts, the site's categories and a\n // photo at most 768 pixels on its long edge (about 790 tokens) ride\n // uncached; and the answer is a description with its search listing, tags,\n // categories and option names, about 1,300 characters of JSON.\n 'job.products': { inputTokens: 1_500, outputTokens: 450, cacheReadTokens: 2_400, cacheWriteTokens: 0 },\n // The SEO step's listing exchange, which a page's or a product's listing is\n // exactly one of: the median of 146 production listings of October 8–14\n // (2,255 input and 213 output tokens on Claude Haiku 4.5), counted as the\n // fast tier's Claude Haiku 5.5 counts them, about 30% more (AGL-3781). The\n // doctrine's block in its field scope, the listing rules and the tool's\n // schema are its prefix, which caches on that model and which a site's next\n // listing reads; a lone listing writes it, so it is counted as input. The\n // page text, capped at 2,000 characters, rides with the current listing and a\n // dozen other titles. The largest answer the tool accepts is about 600\n // characters of JSON, and the request asks for no thinking. An audit pass is\n // heavier: eight pages' findings and text, about 9,000 characters, with up to\n // about 8,000 back.\n 'job.seo': { inputTokens: 2_930, outputTokens: 277, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The experiment step (AGL-2914). NOTHING is cached: the A/B test rules\n // and the acceptable-use block come to about 2,300 characters, under the\n // balanced tier's cacheable minimum, so they are billed as input every\n // time. The copy under test, or a test's arms with its verdict, rides with\n // them, and a four-variant answer is about 800 characters of JSON.\n 'job.experiment': { inputTokens: 900, outputTokens: 300, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The form step: the doctrine, the form instructions, the form surface's\n // catalog (about 150 tokens) and the tool's schema are the cached prefix, the\n // site inventory and the brief ride uncached, and a form's answer is 200 to\n // 400 tokens of JSON with no extended thinking.\n 'job.form': { inputTokens: 700, outputTokens: 450, cacheReadTokens: 3_900, cacheWriteTokens: 0 },\n // An insight (AGL-2915): two exchanges, and NOTHING cached — the rules and\n // the acceptable-use block are under the balanced tier's minimum, so they\n // are billed as input on both. The read call carries the question and the\n // readers' catalog (about 2,000 characters) and answers a few reads; the\n // answer call carries four tables of about ten rows each (about 4,000\n // characters) and answers three to five insights as JSON.\n 'job.insight': { inputTokens: 2_800, outputTokens: 650, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The email step, and the campaign step that shares its generation: the\n // doctrine, the email instructions, the email palette catalog and the\n // tool's schema (about 10,300 characters) are the cached prefix, the site\n // inventory and the brief ride uncached, and the answer is one email's\n // node map with three subject lines and three preheaders.\n 'job.email': { inputTokens: 600, outputTokens: 1_500, cacheReadTokens: 3_600, cacheWriteTokens: 0 },\n 'job.campaign': { inputTokens: 600, outputTokens: 1_500, cacheReadTokens: 3_600, cacheWriteTokens: 0 },\n // The workflow step's draft (AGL-2919): the doctrine, the automation\n // vocabulary and the tool's schema are the cached prefix (4,564 tokens, as\n // `runtime/ai-prompt-cache.spec.ts` measures it); what the workspace can\n // run, the site's forms and datasets and the description ride uncached; and\n // an automation of a few steps is about 1,300 characters of JSON with as\n // much again to think in. An explanation caches half as much.\n 'job.workflow': { inputTokens: 500, outputTokens: 1_000, cacheReadTokens: 4_500, cacheWriteTokens: 0 },\n // The logic step (AGL-3603): the fields doctrine, one rules block and the\n // tool's schema are the cached prefix (1,556 tokens for a function, as\n // `runtime/ai-prompt-cache.spec.ts` measures it); the site's variables and\n // the request ride uncached, and a function of a few operations is about\n // 1,200 characters of JSON with as much again to think in.\n 'job.logic': { inputTokens: 700, outputTokens: 900, cacheReadTokens: 1_500, cacheWriteTokens: 0 },\n // The edit step (AGL-3616): the document doctrine, the job's rules and the\n // edit rung's protocol and catalog for the document's kind are the cached\n // prefix (5,970 tokens for a page, as `runtime/ai-prompt-cache.spec.ts`\n // measures it); the outline of up to sixty elements with their settings\n // and the request ride uncached, and a change of a few operations is about\n // 1,500 characters of tool call with as much again to think in.\n 'job.edit': { inputTokens: 2_500, outputTokens: 1_000, cacheReadTokens: 6_000, cacheWriteTokens: 0 },\n}\n\n/** The fewest measured exchanges a median is taken over. */\nexport const AI_MEASURED_MEDIAN_MIN_SAMPLES = 3\n\nexport interface AiModelBounds {\n plan: OrgPlan\n /** The allotment allowlists that apply, intersected; `null` for none. */\n allotmentModels?: readonly string[] | null\n /** The org-wide restriction; `null` for none. */\n orgModels?: readonly string[] | null\n}\n\n/** Whether a list names a model, a retired id on it naming its successor (AGL-3781). */\nfunction names(list: readonly string[], id: string): boolean {\n return list.some((listed) => aiServingModelId(listed) === id)\n}\n\nfunction onLists(entry: AiCatalogEntry, bounds: AiModelBounds): boolean {\n if (bounds.orgModels?.length && !names(bounds.orgModels, entry.id)) return false\n if (bounds.allotmentModels && !names(bounds.allotmentModels, entry.id)) return false\n return true\n}\n\n/** The current catalog models a manual pick may name on a provider under these bounds. */\nexport function aiModelsSelectable(\n providerId: string,\n bounds: AiModelBounds,\n): AiCatalogEntry[] {\n const tiers = AI_PLAN_MODEL_TIERS[bounds.plan] ?? []\n return AI_MODEL_CATALOG.filter(\n (entry) =>\n entry.provider === providerId &&\n !entry.successor &&\n tiers.includes(entry.tier) &&\n onLists(entry, bounds),\n )\n}\n\n/**\n * A model's blended BILLED rate, for \"the cheapest allowed\" and nothing\n * else.\n *\n * Billed and not provider (AGL-3015): the substitution below happens when a\n * workspace asked for a model its bounds do not allow, and the party it\n * falls back for is the one whose credits are drawn. \"Cheapest\" has to mean\n * the same thing here as in the selector this substitution stands in for,\n * which sorts on `creditsPerRequest`.\n */\nconst blendedRate = (id: string): number => {\n const rates = aiBilledRatesForModel(id)\n return rates.inputPerToken + rates.outputPerToken\n}\n\nexport interface AiModelChoice {\n /** The catalog id the request runs, and is metered, on. */\n model: string\n /** True when the routing table decided: Auto, or a pick that was declined. */\n auto: boolean\n /** A pick that was asked for and is not allowed, for the envelope. */\n declined: string | null\n}\n\n/**\n * The model one request runs on, or `null` when no provider is registered —\n * which every door has refused before it asks.\n */\nexport function resolveAiModelChoice(\n kind: AiStepKind,\n requested: unknown,\n bounds: AiModelBounds,\n settings?: AiPluginSettings,\n): AiModelChoice | null {\n const route = resolveAiRoute(kind, settings)\n if (!route) return null\n // A pick of a retired model, from a job made before it retired, is a pick\n // of the model that serves in its place.\n const wanted =\n typeof requested === 'string' && requested.trim() && requested.trim() !== AI_MODEL_AUTO\n ? aiServingModelId(requested.trim())\n : null\n if (wanted && aiModelsSelectable(route.provider.id, bounds).some((entry) => entry.id === wanted)) {\n return { model: wanted, auto: false, declined: null }\n }\n const restricted = Boolean(bounds.orgModels?.length) || Boolean(bounds.allotmentModels)\n let model = route.model\n if (restricted) {\n const allowed = AI_MODEL_CATALOG.filter(\n (entry) => entry.provider === route.provider.id && !entry.successor && onLists(entry, bounds),\n )\n if (allowed.length && !allowed.some((entry) => entry.id === model)) {\n const tier = aiCatalogEntry(model)?.tier ?? AI_STEP_TIERS[kind]\n const sameTier = allowed.find((entry) => entry.tier === tier)\n const cheapest = [...allowed].sort((a, b) => blendedRate(a.id) - blendedRate(b.id))[0]\n model = (sameTier ?? cheapest).id\n }\n }\n return { model, auto: true, declined: wanted }\n}\n\n/** The median exchange over measured samples, or `null` below the minimum. */\nexport function aiMedianUsage(samples: readonly AiUsage[]): AiUsage | null {\n if (samples.length < AI_MEASURED_MEDIAN_MIN_SAMPLES) return null\n const median = (field: keyof AiUsage): number => {\n const values = samples\n .map((sample) => Number(sample[field] ?? 0))\n .filter((value) => Number.isFinite(value) && value >= 0)\n .sort((a, b) => a - b)\n if (!values.length) return 0\n const middle = Math.floor(values.length / 2)\n return values.length % 2\n ? values[middle]\n : Math.round((values[middle - 1] + values[middle]) / 2)\n }\n return {\n inputTokens: median('inputTokens'),\n outputTokens: median('outputTokens'),\n cacheReadTokens: median('cacheReadTokens'),\n cacheWriteTokens: median('cacheWriteTokens'),\n }\n}\n\nexport interface AiModelOption {\n /** A catalog id, or `AI_MODEL_AUTO`. */\n id: string\n label: string\n tier: AiCatalogEntry['tier']\n /** Credits a typical request of this kind costs on the option. */\n creditsPerRequest: number\n /** That cost as a multiple of Auto's, to one decimal. */\n multiplier: number\n}\n\nexport interface AiModelOptions {\n /** Auto, labeled with the model it routes this kind to under these bounds. */\n auto: AiModelOption & { model: string }\n /** The manual picks, cheapest first; empty where the plan offers none. */\n options: AiModelOption[]\n /** Whether the price came from the workspace's own measured exchanges. */\n measured: boolean\n}\n\n/** What the selector lists for one step kind, or `null` without a provider. */\nexport function aiModelOptions(\n kind: AiStepKind,\n bounds: AiModelBounds,\n measured: AiUsage | null,\n settings?: AiPluginSettings,\n): AiModelOptions | null {\n const route = resolveAiRoute(kind, settings)\n const auto = resolveAiModelChoice(kind, null, bounds, settings)\n if (!route || !auto) return null\n const typical = measured ?? AI_STEP_NOMINAL_USAGE[kind]\n // Billed rates throughout (AGL-3015): every figure below is what the\n // workspace will be charged for picking this model — credits per request\n // and the multiple of Auto those credits come to. Our own cost for the\n // same request is not this selector's subject and would misstate both.\n const autoCost = estimateAiBilledUsd(typical, auto.model)\n const priced = (entry: AiCatalogEntry): AiModelOption => {\n const cost = estimateAiBilledUsd(typical, entry.id)\n return {\n id: entry.id,\n label: entry.label,\n tier: entry.tier,\n creditsPerRequest: assistCreditsFromUsd(cost),\n multiplier: autoCost > 0 ? Math.round((cost / autoCost) * 10) / 10 : 1,\n }\n }\n const autoEntry = aiCatalogEntry(auto.model)\n return {\n auto: {\n id: AI_MODEL_AUTO,\n label: autoEntry ? `Auto (${autoEntry.label})` : 'Auto',\n tier: autoEntry?.tier ?? AI_STEP_TIERS[kind],\n creditsPerRequest: assistCreditsFromUsd(autoCost),\n multiplier: 1,\n model: auto.model,\n },\n options: aiModelsSelectable(route.provider.id, bounds)\n .map(priced)\n .sort((a, b) => a.creditsPerRequest - b.creditsPerRequest || a.label.localeCompare(b.label)),\n measured: measured !== null,\n }\n}\n"],"names":["assistCreditsFromUsd","AI_MODEL_CATALOG","AI_STEP_TIERS","aiCatalogEntry","aiBilledRatesForModel","estimateAiBilledUsd","aiServingModelId","resolveAiRoute","AI_MODEL_AUTO","AI_PLAN_MODEL_TIERS","free","starter","pro","business","scale","advanced","agency","enterprise","AI_MODEL_RESTRICTION_PLANS","AI_STEP_NOMINAL_USAGE","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","AI_MEASURED_MEDIAN_MIN_SAMPLES","names","list","id","some","listed","onLists","entry","bounds","orgModels","length","allotmentModels","aiModelsSelectable","providerId","tiers","plan","filter","provider","successor","includes","tier","blendedRate","rates","inputPerToken","outputPerToken","resolveAiModelChoice","kind","requested","settings","route","wanted","trim","model","auto","declined","restricted","Boolean","allowed","sameTier","find","cheapest","sort","a","b","aiMedianUsage","samples","median","field","values","map","sample","Number","value","isFinite","middle","Math","floor","round","aiModelOptions","measured","typical","autoCost","priced","cost","label","creditsPerRequest","multiplier","autoEntry","options","localeCompare"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,oBAAoB,QAAQ,6BAAyB;AAE9D,SACEC,gBAAgB,EAChBC,aAAa,EACbC,cAAc,EACdC,qBAAqB,EACrBC,mBAAmB,QAGd,eAAW;AAElB,SAASC,gBAAgB,EAAEC,cAAc,QAA+B,eAAW;AAEnF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BC,GAED,+DAA+D,GAC/D,OAAO,MAAMC,gBAAgB,OAAM;AAEnC;;;;;;CAMC,GACD,OAAO,MAAMC,sBAA0E;IACrFC,MAAM,EAAE;IACRC,SAAS;QAAC;QAAQ;KAAW;IAC7BC,KAAK;QAAC;QAAQ;KAAW;IACzBC,UAAU;QAAC;QAAQ;KAAW;IAC9BC,OAAO;QAAC;QAAQ;QAAY;KAAO;IACnCC,UAAU;QAAC;QAAQ;QAAY;KAAO;IACtCC,QAAQ;QAAC;QAAQ;QAAY;KAAO;IACpCC,YAAY;QAAC;QAAQ;QAAY;KAAO;AAC1C,EAAC;AAED,gEAAgE,GAChE,OAAO,MAAMC,6BAAiD;IAAC;IAAU;CAAa,CAAA;AAEtF;;;;;CAKC,GACD,OAAO,MAAMC,wBAAqD;IAChE,eAAe;QAAEC,aAAa;QAAKC,cAAc;QAAKC,iBAAiB;QAAOC,kBAAkB;IAAE;IAClG,6EAA6E;IAC7E,yEAAyE;IACzE,gBAAgB;QAAEH,aAAa;QAAKC,cAAc;QAAKC,iBAAiB;QAAGC,kBAAkB;IAAE;IAC/F,gBAAgB;QAAEH,aAAa;QAAKC,cAAc;QAAOC,iBAAiB;QAAGC,kBAAkB;IAAE;IACjG,aAAa;QAAEH,aAAa;QAAKC,cAAc;QAAOC,iBAAiB;QAAGC,kBAAkB;IAAE;IAC9F,oBAAoB;QAAEH,aAAa;QAAKC,cAAc;QAAOC,iBAAiB;QAAGC,kBAAkB;IAAE;IACrG,0EAA0E;IAC1E,0EAA0E;IAC1E,0EAA0E;IAC1E,YAAY;QAAEH,aAAa;QAAKC,cAAc;QAAKC,iBAAiB;QAAGC,kBAAkB;IAAE;IAC3F,wEAAwE;IACxE,6EAA6E;IAC7E,6EAA6E;IAC7E,6BAA6B;IAC7B,aAAa;QAAEH,aAAa;QAAKC,cAAc;QAAKC,iBAAiB;QAAOC,kBAAkB;IAAE;IAChG,2EAA2E;IAC3E,8EAA8E;IAC9E,8EAA8E;IAC9E,6BAA6B;IAC7B,YAAY;QAAEH,aAAa;QAAKC,cAAc;QAAKC,iBAAiB;QAAOC,kBAAkB;IAAE;IAC/F,oEAAoE;IACpE,2EAA2E;IAC3E,8EAA8E;IAC9E,4EAA4E;IAC5E,6EAA6E;IAC7E,iBAAiB;QAAEH,aAAa;QAAKC,cAAc;QAAKC,iBAAiB;QAAOC,kBAAkB;IAAE;IACpG,wEAAwE;IACxE,4EAA4E;IAC5E,wEAAwE;IACxE,8EAA8E;IAC9E,yEAAyE;IACzE,8EAA8E;IAC9E,wEAAwE;IACxE,+BAA+B;IAC/B,WAAW;QAAEH,aAAa;QAAOC,cAAc;QAAKC,iBAAiB;QAAGC,kBAAkB;IAAE;IAC5F,sEAAsE;IACtE,sEAAsE;IACtE,qEAAqE;IACrE,wEAAwE;IACxE,cAAc;QAAEH,aAAa;QAAKC,cAAc;QAAOC,iBAAiB;QAAOC,kBAAkB;IAAE;IACnG,gBAAgB;QAAEH,aAAa;QAAOC,cAAc;QAAKC,iBAAiB;QAAOC,kBAAkB;IAAE;IACrG,0EAA0E;IAC1E,yEAAyE;IACzE,4EAA4E;IAC5E,0EAA0E;IAC1E,yEAAyE;IACzE,wDAAwD;IACxD,uEAAuE;IACvE,4EAA4E;IAC5E,0EAA0E;IAC1E,0EAA0E;IAC1E,0DAA0D;IAC1D,YAAY;QAAEH,aAAa;QAAKC,cAAc;QAAOC,iBAAiB;QAAOC,kBAAkB;IAAE;IACjG,sEAAsE;IACtE,8EAA8E;IAC9E,0EAA0E;IAC1E,oEAAoE;IACpE,2EAA2E;IAC3E,+DAA+D;IAC/D,gBAAgB;QAAEH,aAAa;QAAOC,cAAc;QAAKC,iBAAiB;QAAOC,kBAAkB;IAAE;IACrG,4EAA4E;IAC5E,wEAAwE;IACxE,0EAA0E;IAC1E,2EAA2E;IAC3E,wEAAwE;IACxE,4EAA4E;IAC5E,0EAA0E;IAC1E,8EAA8E;IAC9E,uEAAuE;IACvE,6EAA6E;IAC7E,8EAA8E;IAC9E,oBAAoB;IACpB,WAAW;QAAEH,aAAa;QAAOC,cAAc;QAAKC,iBAAiB;QAAGC,kBAAkB;IAAE;IAC5F,wEAAwE;IACxE,yEAAyE;IACzE,uEAAuE;IACvE,2EAA2E;IAC3E,mEAAmE;IACnE,kBAAkB;QAAEH,aAAa;QAAKC,cAAc;QAAKC,iBAAiB;QAAGC,kBAAkB;IAAE;IACjG,yEAAyE;IACzE,8EAA8E;IAC9E,4EAA4E;IAC5E,gDAAgD;IAChD,YAAY;QAAEH,aAAa;QAAKC,cAAc;QAAKC,iBAAiB;QAAOC,kBAAkB;IAAE;IAC/F,2EAA2E;IAC3E,0EAA0E;IAC1E,0EAA0E;IAC1E,yEAAyE;IACzE,sEAAsE;IACtE,0DAA0D;IAC1D,eAAe;QAAEH,aAAa;QAAOC,cAAc;QAAKC,iBAAiB;QAAGC,kBAAkB;IAAE;IAChG,wEAAwE;IACxE,sEAAsE;IACtE,0EAA0E;IAC1E,uEAAuE;IACvE,0DAA0D;IAC1D,aAAa;QAAEH,aAAa;QAAKC,cAAc;QAAOC,iBAAiB;QAAOC,kBAAkB;IAAE;IAClG,gBAAgB;QAAEH,aAAa;QAAKC,cAAc;QAAOC,iBAAiB;QAAOC,kBAAkB;IAAE;IACrG,qEAAqE;IACrE,2EAA2E;IAC3E,yEAAyE;IACzE,4EAA4E;IAC5E,yEAAyE;IACzE,8DAA8D;IAC9D,gBAAgB;QAAEH,aAAa;QAAKC,cAAc;QAAOC,iBAAiB;QAAOC,kBAAkB;IAAE;IACrG,0EAA0E;IAC1E,uEAAuE;IACvE,2EAA2E;IAC3E,yEAAyE;IACzE,2DAA2D;IAC3D,aAAa;QAAEH,aAAa;QAAKC,cAAc;QAAKC,iBAAiB;QAAOC,kBAAkB;IAAE;IAChG,2EAA2E;IAC3E,0EAA0E;IAC1E,wEAAwE;IACxE,wEAAwE;IACxE,2EAA2E;IAC3E,gEAAgE;IAChE,YAAY;QAAEH,aAAa;QAAOC,cAAc;QAAOC,iBAAiB;QAAOC,kBAAkB;IAAE;AACrG,EAAC;AAED,0DAA0D,GAC1D,OAAO,MAAMC,iCAAiC,EAAC;AAU/C,sFAAsF,GACtF,SAASC,MAAMC,IAAuB,EAAEC,EAAU;IAChD,OAAOD,KAAKE,IAAI,CAAC,CAACC,SAAWvB,iBAAiBuB,YAAYF;AAC5D;AAEA,SAASG,QAAQC,KAAqB,EAAEC,MAAqB;QACvDA;IAAJ,IAAIA,EAAAA,oBAAAA,OAAOC,SAAS,qBAAhBD,kBAAkBE,MAAM,KAAI,CAACT,MAAMO,OAAOC,SAAS,EAAEF,MAAMJ,EAAE,GAAG,OAAO;IAC3E,IAAIK,OAAOG,eAAe,IAAI,CAACV,MAAMO,OAAOG,eAAe,EAAEJ,MAAMJ,EAAE,GAAG,OAAO;IAC/E,OAAO;AACT;AAEA,wFAAwF,GACxF,OAAO,SAASS,mBACdC,UAAkB,EAClBL,MAAqB;QAEPvB;IAAd,MAAM6B,SAAQ7B,mCAAAA,mBAAmB,CAACuB,OAAOO,IAAI,CAAC,YAAhC9B,mCAAoC,EAAE;IACpD,OAAOR,iBAAiBuC,MAAM,CAC5B,CAACT,QACCA,MAAMU,QAAQ,KAAKJ,cACnB,CAACN,MAAMW,SAAS,IAChBJ,MAAMK,QAAQ,CAACZ,MAAMa,IAAI,KACzBd,QAAQC,OAAOC;AAErB;AAEA;;;;;;;;;CASC,GACD,MAAMa,cAAc,CAAClB;IACnB,MAAMmB,QAAQ1C,sBAAsBuB;IACpC,OAAOmB,MAAMC,aAAa,GAAGD,MAAME,cAAc;AACnD;AAWA;;;CAGC,GACD,OAAO,SAASC,qBACdC,IAAgB,EAChBC,SAAkB,EAClBnB,MAAqB,EACrBoB,QAA2B;QAaApB;IAX3B,MAAMqB,QAAQ9C,eAAe2C,MAAME;IACnC,IAAI,CAACC,OAAO,OAAO;IACnB,0EAA0E;IAC1E,yCAAyC;IACzC,MAAMC,SACJ,OAAOH,cAAc,YAAYA,UAAUI,IAAI,MAAMJ,UAAUI,IAAI,OAAO/C,gBACtEF,iBAAiB6C,UAAUI,IAAI,MAC/B;IACN,IAAID,UAAUlB,mBAAmBiB,MAAMZ,QAAQ,CAACd,EAAE,EAAEK,QAAQJ,IAAI,CAAC,CAACG,QAAUA,MAAMJ,EAAE,KAAK2B,SAAS;QAChG,OAAO;YAAEE,OAAOF;YAAQG,MAAM;YAAOC,UAAU;QAAK;IACtD;IACA,MAAMC,aAAaC,SAAQ5B,oBAAAA,OAAOC,SAAS,qBAAhBD,kBAAkBE,MAAM,KAAK0B,QAAQ5B,OAAOG,eAAe;IACtF,IAAIqB,QAAQH,MAAMG,KAAK;IACvB,IAAIG,YAAY;QACd,MAAME,UAAU5D,iBAAiBuC,MAAM,CACrC,CAACT,QAAUA,MAAMU,QAAQ,KAAKY,MAAMZ,QAAQ,CAACd,EAAE,IAAI,CAACI,MAAMW,SAAS,IAAIZ,QAAQC,OAAOC;QAExF,IAAI6B,QAAQ3B,MAAM,IAAI,CAAC2B,QAAQjC,IAAI,CAAC,CAACG,QAAUA,MAAMJ,EAAE,KAAK6B,QAAQ;;gBACrDrD;YAAb,MAAMyC,gBAAOzC,kBAAAA,eAAeqD,2BAAfrD,gBAAuByC,IAAI,mBAAI1C,aAAa,CAACgD,KAAK;YAC/D,MAAMY,WAAWD,QAAQE,IAAI,CAAC,CAAChC,QAAUA,MAAMa,IAAI,KAAKA;YACxD,MAAMoB,WAAW;mBAAIH;aAAQ,CAACI,IAAI,CAAC,CAACC,GAAGC,IAAMtB,YAAYqB,EAAEvC,EAAE,IAAIkB,YAAYsB,EAAExC,EAAE,EAAE,CAAC,EAAE;YACtF6B,QAAQ,CAACM,mBAAAA,WAAYE,UAAUrC,EAAE;QACnC;IACF;IACA,OAAO;QAAE6B;QAAOC,MAAM;QAAMC,UAAUJ;IAAO;AAC/C;AAEA,4EAA4E,GAC5E,OAAO,SAASc,cAAcC,OAA2B;IACvD,IAAIA,QAAQnC,MAAM,GAAGV,gCAAgC,OAAO;IAC5D,MAAM8C,SAAS,CAACC;QACd,MAAMC,SAASH,QACZI,GAAG,CAAC,CAACC;gBAAkBA;mBAAPC,QAAOD,gBAAAA,MAAM,CAACH,MAAM,YAAbG,gBAAiB;WACxClC,MAAM,CAAC,CAACoC,QAAUD,OAAOE,QAAQ,CAACD,UAAUA,SAAS,GACrDX,IAAI,CAAC,CAACC,GAAGC,IAAMD,IAAIC;QACtB,IAAI,CAACK,OAAOtC,MAAM,EAAE,OAAO;QAC3B,MAAM4C,SAASC,KAAKC,KAAK,CAACR,OAAOtC,MAAM,GAAG;QAC1C,OAAOsC,OAAOtC,MAAM,GAAG,IACnBsC,MAAM,CAACM,OAAO,GACdC,KAAKE,KAAK,CAAC,AAACT,CAAAA,MAAM,CAACM,SAAS,EAAE,GAAGN,MAAM,CAACM,OAAO,AAAD,IAAK;IACzD;IACA,OAAO;QACL1D,aAAakD,OAAO;QACpBjD,cAAciD,OAAO;QACrBhD,iBAAiBgD,OAAO;QACxB/C,kBAAkB+C,OAAO;IAC3B;AACF;AAsBA,6EAA6E,GAC7E,OAAO,SAASY,eACdhC,IAAgB,EAChBlB,MAAqB,EACrBmD,QAAwB,EACxB/B,QAA2B;;IAE3B,MAAMC,QAAQ9C,eAAe2C,MAAME;IACnC,MAAMK,OAAOR,qBAAqBC,MAAM,MAAMlB,QAAQoB;IACtD,IAAI,CAACC,SAAS,CAACI,MAAM,OAAO;IAC5B,MAAM2B,UAAUD,mBAAAA,WAAYhE,qBAAqB,CAAC+B,KAAK;IACvD,qEAAqE;IACrE,yEAAyE;IACzE,uEAAuE;IACvE,uEAAuE;IACvE,MAAMmC,WAAWhF,oBAAoB+E,SAAS3B,KAAKD,KAAK;IACxD,MAAM8B,SAAS,CAACvD;QACd,MAAMwD,OAAOlF,oBAAoB+E,SAASrD,MAAMJ,EAAE;QAClD,OAAO;YACLA,IAAII,MAAMJ,EAAE;YACZ6D,OAAOzD,MAAMyD,KAAK;YAClB5C,MAAMb,MAAMa,IAAI;YAChB6C,mBAAmBzF,qBAAqBuF;YACxCG,YAAYL,WAAW,IAAIN,KAAKE,KAAK,CAAC,AAACM,OAAOF,WAAY,MAAM,KAAK;QACvE;IACF;IACA,MAAMM,YAAYxF,eAAesD,KAAKD,KAAK;IAC3C,OAAO;QACLC,MAAM;YACJ9B,IAAInB;YACJgF,OAAOG,YAAY,CAAC,MAAM,EAAEA,UAAUH,KAAK,CAAC,CAAC,CAAC,GAAG;YACjD5C,IAAI,UAAE+C,6BAAAA,UAAW/C,IAAI,mBAAI1C,aAAa,CAACgD,KAAK;YAC5CuC,mBAAmBzF,qBAAqBqF;YACxCK,YAAY;YACZlC,OAAOC,KAAKD,KAAK;QACnB;QACAoC,SAASxD,mBAAmBiB,MAAMZ,QAAQ,CAACd,EAAE,EAAEK,QAC5CyC,GAAG,CAACa,QACJrB,IAAI,CAAC,CAACC,GAAGC,IAAMD,EAAEuB,iBAAiB,GAAGtB,EAAEsB,iBAAiB,IAAIvB,EAAEsB,KAAK,CAACK,aAAa,CAAC1B,EAAEqB,KAAK;QAC5FL,UAAUA,aAAa;IACzB;AACF"}
@@ -37,6 +37,12 @@ export declare const AI_DEFAULT_MODEL_ENV = "AI_DEFAULT_MODEL";
37
37
  export declare const ASSIST_MODEL_ENV = "ASSIST_MODEL";
38
38
  /** The org's resolved `pluginSettings/ai` values, as `getPluginConfig` merges them. */
39
39
  export type AiPluginSettings = Record<string, unknown>;
40
+ /**
41
+ * The model that serves a catalog id (AGL-3781): the id itself, or for a
42
+ * retired row the successor it names, followed to a current row. An id the
43
+ * catalog does not know is returned as it is, for the caller to refuse.
44
+ */
45
+ export declare function aiServingModelId(modelId: string): string;
40
46
  export interface AiRoute {
41
47
  provider: AiProvider;
42
48
  model: string;
@@ -56,7 +62,11 @@ export declare function resolveAiRoute(kind: AiStepKind, settings?: AiPluginSett
56
62
  * briefs (AGL-2937).
57
63
  */
58
64
  export interface AiRoutingRow {
59
- /** `null` sends no setting: the model's own default, and all a fast-tier model accepts. */
65
+ /**
66
+ * `null` sends no setting: the model's own default, which on every current
67
+ * model is adaptive thinking. A row whose ceiling holds the answer alone
68
+ * says `off` (AGL-3781).
69
+ */
60
70
  thinking: AiThinking | null;
61
71
  effort: AiEffort | null;
62
72
  /** The answer ceiling, thinking included. */
@@ -94,5 +104,14 @@ export interface AiRoutingEval {
94
104
  * tokenizer that splits finer than four.
95
105
  */
96
106
  export declare const AI_ROUTING_TABLE: Readonly<Record<AiStepKind, AiRoutingRow>>;
107
+ /**
108
+ * What a step kind's row asks of its model, as the fields a request carries:
109
+ * only those the row names, so a row that names none sends none and the
110
+ * model's own default stands.
111
+ */
112
+ export declare function aiRouteSettings(kind: AiStepKind): {
113
+ thinking?: AiThinking;
114
+ effort?: AiEffort;
115
+ };
97
116
  /** The model id alone — what a door records on the meter and the job. */
98
117
  export declare function aiModelForStep(kind: AiStepKind, settings?: AiPluginSettings): string;