@aglyn/plugins-ai 1.0.0-beta.257 → 1.0.0-beta.258

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 (84) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/assist-panel.component.js +21 -8
  3. package/src/lib/components/assist-panel.component.js.map +1 -1
  4. package/src/lib/insights/ai-figure-readers.js +15 -8
  5. package/src/lib/insights/ai-figure-readers.js.map +1 -1
  6. package/src/lib/jobs/ai-job-build-step.d.ts +9 -0
  7. package/src/lib/jobs/ai-job-build-step.js +19 -3
  8. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  9. package/src/lib/jobs/ai-job-insight-step.js +1 -1
  10. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  11. package/src/lib/jobs/ai-job-page-language.d.ts +19 -0
  12. package/src/lib/jobs/ai-job-page-language.js +24 -4
  13. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  14. package/src/lib/jobs/ai-job-page-sections.js +12 -1
  15. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  16. package/src/lib/jobs/ai-job-page-step.d.ts +14 -0
  17. package/src/lib/jobs/ai-job-page-step.js +69 -20
  18. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  19. package/src/lib/jobs/ai-job-seo-step.js +3 -2
  20. package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
  21. package/src/lib/jobs/ai-layout-stock-photos.d.ts +10 -5
  22. package/src/lib/jobs/ai-layout-stock-photos.js +89 -7
  23. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  24. package/src/lib/jobs/fixtures/ai-stock-photo-copper-kettle-recordings.d.ts +21 -0
  25. package/src/lib/jobs/fixtures/ai-stock-photo-copper-kettle-recordings.js +126 -0
  26. package/src/lib/jobs/fixtures/ai-stock-photo-copper-kettle-recordings.js.map +1 -0
  27. package/src/lib/layout-language/ai-layout-compiler.js +52 -52
  28. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  29. package/src/lib/layout-language/ai-layout-copy.d.ts +9 -5
  30. package/src/lib/layout-language/ai-layout-copy.js +22 -14
  31. package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
  32. package/src/lib/layout-language/ai-layout-frame.js +4 -4
  33. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  34. package/src/lib/layout-language/ai-layout-pictures.d.ts +44 -15
  35. package/src/lib/layout-language/ai-layout-pictures.js +29 -8
  36. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  37. package/src/lib/model/ai-build-job.js +14 -0
  38. package/src/lib/model/ai-build-job.js.map +1 -1
  39. package/src/lib/model/ai-host-publish-context.d.ts +6 -0
  40. package/src/lib/model/ai-host-publish-context.js +15 -2
  41. package/src/lib/model/ai-host-publish-context.js.map +1 -1
  42. package/src/lib/model/assist-follow-up.d.ts +35 -0
  43. package/src/lib/model/assist-follow-up.js +44 -4
  44. package/src/lib/model/assist-follow-up.js.map +1 -1
  45. package/src/lib/providers/anthropic.js +17 -4
  46. package/src/lib/providers/anthropic.js.map +1 -1
  47. package/src/lib/providers/cache-write-hour.d.ts +37 -0
  48. package/src/lib/providers/cache-write-hour.js +46 -0
  49. package/src/lib/providers/cache-write-hour.js.map +1 -0
  50. package/src/lib/providers/contract.d.ts +16 -3
  51. package/src/lib/providers/contract.js.map +1 -1
  52. package/src/lib/providers/routing.d.ts +13 -0
  53. package/src/lib/providers/routing.js +27 -11
  54. package/src/lib/providers/routing.js.map +1 -1
  55. package/src/lib/runtime/ai-dev-env.d.ts +13 -3
  56. package/src/lib/runtime/ai-dev-env.js +17 -3
  57. package/src/lib/runtime/ai-dev-env.js.map +1 -1
  58. package/src/lib/runtime/ai-dev-replay.js +4 -2
  59. package/src/lib/runtime/ai-dev-replay.js.map +1 -1
  60. package/src/lib/runtime/ai-doctrine-validators.d.ts +41 -1
  61. package/src/lib/runtime/ai-doctrine-validators.js +263 -2
  62. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  63. package/src/lib/runtime/ai-doctrine.js +7 -1
  64. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  65. package/src/lib/runtime/ai-eval-live.js +1 -1
  66. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  67. package/src/lib/runtime/ai-eval.d.ts +1 -1
  68. package/src/lib/runtime/ai-eval.js +18 -1
  69. package/src/lib/runtime/ai-eval.js.map +1 -1
  70. package/src/lib/runtime/ai-placeholder-tokens.d.ts +70 -0
  71. package/src/lib/runtime/ai-placeholder-tokens.js +169 -0
  72. package/src/lib/runtime/ai-placeholder-tokens.js.map +1 -0
  73. package/src/lib/runtime/ai-repeated-items.js +5 -2
  74. package/src/lib/runtime/ai-repeated-items.js.map +1 -1
  75. package/src/lib/runtime/ai-runtime.js +2 -3
  76. package/src/lib/runtime/ai-runtime.js.map +1 -1
  77. package/src/lib/server/assist-chat.js +8 -3
  78. package/src/lib/server/assist-chat.js.map +1 -1
  79. package/src/lib/server/assist-view-context.d.ts +5 -1
  80. package/src/lib/server/assist-view-context.js +8 -3
  81. package/src/lib/server/assist-view-context.js.map +1 -1
  82. package/src/lib/usage/assist-usage.d.ts +2 -0
  83. package/src/lib/usage/assist-usage.js +8 -3
  84. package/src/lib/usage/assist-usage.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-host-publish-context.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 { liveCustomDomain, TENANT_APEX } from '@aglyn/aglyn/app-utils/host-naming'\nimport type { AiInsightTable } from './ai-insight'\n\n/**\n * Whether a site is published and where, read off its host document — the\n * facts an AI job must be told rather than left to infer. Pure: no read, so\n * every job that already holds the host can use it, and the site inventory\n * every AI job will get (AGL-3661) absorbs it rather than duplicating it.\n */\nexport interface AiHostPublishContext {\n /** At least one page a visitor can open. */\n published: boolean\n /** The address visitors use: a custom domain only once it serves, else the platform subdomain. */\n liveUrl: string | null\n /** The published pages' paths, `/` first. */\n publishedPages: string[]\n /** When the first page went live, in ms, when the screens passed say so. */\n firstPublishedAt: number | null\n}\n\nconst millis = (value: unknown): number | null => {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (value && typeof (value as { toMillis?: unknown }).toMillis === 'function') {\n return (value as { toMillis: () => number }).toMillis()\n }\n if (typeof value === 'string') {\n const parsed = Date.parse(value)\n return Number.isNaN(parsed) ? null : parsed\n }\n return null\n}\n\n/**\n * `host.screens` is the routing map publishing writes — screen id to path —\n * so its entries are exactly the pages a visitor can reach. `screens`, when a\n * caller has the page documents, adds when the first one went live.\n */\nexport function aiHostPublishContext(\n host: Record<string, unknown> | null | undefined,\n screens?: ReadonlyArray<{ publishedAt?: unknown; deletedAt?: unknown }>,\n): AiHostPublishContext {\n const routes = host?.['screens']\n const paths =\n routes && typeof routes === 'object'\n ? [...new Set(Object.values(routes as Record<string, unknown>).filter((path): path is string => typeof path === 'string' && path.startsWith('/')))]\n : []\n paths.sort((a, b) => (a === '/' ? -1 : b === '/' ? 1 : a.localeCompare(b)))\n const domain = host ? liveCustomDomain(host as Parameters<typeof liveCustomDomain>[0]) : undefined\n const subdomain = typeof host?.['subdomain'] === 'string' ? (host['subdomain'] as string) : ''\n const liveUrl = domain ? `https://${domain}` : subdomain ? `https://${subdomain}.${TENANT_APEX}` : null\n const firsts = (screens ?? [])\n .filter((screen) => !screen.deletedAt)\n .map((screen) => millis(screen.publishedAt))\n .filter((ms): ms is number => ms !== null)\n return {\n published: paths.length > 0,\n liveUrl,\n publishedPages: paths,\n firstPublishedAt: firsts.length ? Math.min(...firsts) : null,\n }\n}\n\n/** The reader id the site-status table carries. Code writes it; the model never chooses it. */\nexport const AI_SITE_STATUS_READER = 'site.status'\n\n/** At most this many paths are named; the rest are counted after them. */\nconst SITE_STATUS_MAX_PAGES = 12\n\n/**\n * The site's publish state as a table an insight can cite (AGL-2915's trace\n * holds it like any other). Without it, a question about launch was answered\n * from traffic alone, and \"no views yet\" was read as \"not live\" for a site\n * that had been live for a minute.\n */\nexport function aiSiteStatusTable(context: AiHostPublishContext, ref: string): AiInsightTable {\n const pages = context.publishedPages.slice(0, SITE_STATUS_MAX_PAGES)\n const more = context.publishedPages.length - pages.length\n return {\n ref,\n reader: AI_SITE_STATUS_READER,\n days: 0,\n scope: 'site',\n title: 'Site status',\n source: { label: 'Pages', path: 'screens' },\n period: null,\n columns: [\n { key: 'figure', label: 'Figure', kind: 'text' },\n { key: 'value', label: 'Now', kind: 'text' },\n ],\n rows: [\n { figure: 'Published', value: context.published ? 'Yes: visitors can open the site now' : 'No: no page is published yet' },\n { figure: 'Address', value: context.liveUrl ?? 'None yet' },\n {\n figure: 'Published pages',\n value: pages.length ? `${pages.join(', ')}${more > 0 ? `, and ${more} more` : ''}` : 'None',\n },\n ],\n omitted: 0,\n notes: [\n 'Page views count visits, not whether the site is up: a published site with no views yet is still live.',\n ],\n }\n}\n"],"names":["liveCustomDomain","TENANT_APEX","millis","value","Number","isFinite","toMillis","parsed","Date","parse","isNaN","aiHostPublishContext","host","screens","routes","paths","Set","Object","values","filter","path","startsWith","sort","a","b","localeCompare","domain","undefined","subdomain","liveUrl","firsts","screen","deletedAt","map","publishedAt","ms","published","length","publishedPages","firstPublishedAt","Math","min","AI_SITE_STATUS_READER","SITE_STATUS_MAX_PAGES","aiSiteStatusTable","context","ref","pages","slice","more","reader","days","scope","title","source","label","period","columns","key","kind","rows","figure","join","omitted","notes"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-host-publish-context.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 { liveCustomDomain, TENANT_APEX } from '@aglyn/aglyn/app-utils/host-naming'\nimport { SCREEN_ROOT_PATH, screenRoutePathToUrl } from '@aglyn/aglyn/app-utils/screen-route'\nimport type { AiInsightTable } from './ai-insight'\n\n/**\n * Whether a site is published and where, read off its host document — the\n * facts an AI job must be told rather than left to infer. Pure: no read, so\n * every job that already holds the host can use it, and the site inventory\n * every AI job will get (AGL-3661) absorbs it rather than duplicating it.\n */\nexport interface AiHostPublishContext {\n /** At least one page a visitor can open. */\n published: boolean\n /** The address visitors use: a custom domain only once it serves, else the platform subdomain. */\n liveUrl: string | null\n /** The published pages' paths, `/` first. */\n publishedPages: string[]\n /** When the first page went live, in ms, when the screens passed say so. */\n firstPublishedAt: number | null\n}\n\nconst millis = (value: unknown): number | null => {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (value && typeof (value as { toMillis?: unknown }).toMillis === 'function') {\n return (value as { toMillis: () => number }).toMillis()\n }\n if (typeof value === 'string') {\n const parsed = Date.parse(value)\n return Number.isNaN(parsed) ? null : parsed\n }\n return null\n}\n\n/** A routing-map path as the URL path a visitor opens, or `null` for an entry that names none. */\nfunction routeUrl(path: unknown): string | null {\n if (typeof path !== 'string') return null\n const slug = path.trim().replace(/^\\/+|\\/+$/g, '')\n if (slug) return screenRoutePathToUrl(slug)\n return path.trim() === SCREEN_ROOT_PATH ? SCREEN_ROOT_PATH : null\n}\n\n/**\n * `host.screens` is the routing map publishing writes — screen id to path —\n * so its entries are exactly the pages a visitor can reach. `screens`, when a\n * caller has the page documents, adds when the first one went live.\n *\n * The map holds routing paths, not URLs: `/` for the home page and `menu` or\n * `services/pricing` for the rest. Each is read as the URL a visitor opens.\n * Kept only when it already began with `/`, every page but the home was\n * dropped, and a site publishing five pages was described as publishing\n * only `/` (AGL-3834).\n */\nexport function aiHostPublishContext(\n host: Record<string, unknown> | null | undefined,\n screens?: ReadonlyArray<{ publishedAt?: unknown; deletedAt?: unknown }>,\n): AiHostPublishContext {\n const routes = host?.['screens']\n const paths =\n routes && typeof routes === 'object'\n ? [\n ...new Set(\n Object.values(routes as Record<string, unknown>)\n .map(routeUrl)\n .filter((path): path is string => path !== null),\n ),\n ]\n : []\n paths.sort((a, b) => (a === SCREEN_ROOT_PATH ? -1 : b === SCREEN_ROOT_PATH ? 1 : a.localeCompare(b)))\n const domain = host ? liveCustomDomain(host as Parameters<typeof liveCustomDomain>[0]) : undefined\n const subdomain = typeof host?.['subdomain'] === 'string' ? (host['subdomain'] as string) : ''\n const liveUrl = domain ? `https://${domain}` : subdomain ? `https://${subdomain}.${TENANT_APEX}` : null\n const firsts = (screens ?? [])\n .filter((screen) => !screen.deletedAt)\n .map((screen) => millis(screen.publishedAt))\n .filter((ms): ms is number => ms !== null)\n return {\n published: paths.length > 0,\n liveUrl,\n publishedPages: paths,\n firstPublishedAt: firsts.length ? Math.min(...firsts) : null,\n }\n}\n\n/** The reader id the site-status table carries. Code writes it; the model never chooses it. */\nexport const AI_SITE_STATUS_READER = 'site.status'\n\n/** At most this many paths are named; the rest are counted after them. */\nconst SITE_STATUS_MAX_PAGES = 12\n\n/**\n * The site's publish state as a table an insight can cite (AGL-2915's trace\n * holds it like any other). Without it, a question about launch was answered\n * from traffic alone, and \"no views yet\" was read as \"not live\" for a site\n * that had been live for a minute.\n */\nexport function aiSiteStatusTable(context: AiHostPublishContext, ref: string): AiInsightTable {\n const pages = context.publishedPages.slice(0, SITE_STATUS_MAX_PAGES)\n const more = context.publishedPages.length - pages.length\n return {\n ref,\n reader: AI_SITE_STATUS_READER,\n days: 0,\n scope: 'site',\n title: 'Site status',\n source: { label: 'Pages', path: 'screens' },\n period: null,\n columns: [\n { key: 'figure', label: 'Figure', kind: 'text' },\n { key: 'value', label: 'Now', kind: 'text' },\n ],\n rows: [\n { figure: 'Published', value: context.published ? 'Yes: visitors can open the site now' : 'No: no page is published yet' },\n { figure: 'Address', value: context.liveUrl ?? 'None yet' },\n {\n figure: 'Published pages',\n value: pages.length ? `${pages.join(', ')}${more > 0 ? `, and ${more} more` : ''}` : 'None',\n },\n ],\n omitted: 0,\n notes: [\n 'Page views count visits, not whether the site is up: a published site with no views yet is still live.',\n ],\n }\n}\n"],"names":["liveCustomDomain","TENANT_APEX","SCREEN_ROOT_PATH","screenRoutePathToUrl","millis","value","Number","isFinite","toMillis","parsed","Date","parse","isNaN","routeUrl","path","slug","trim","replace","aiHostPublishContext","host","screens","routes","paths","Set","Object","values","map","filter","sort","a","b","localeCompare","domain","undefined","subdomain","liveUrl","firsts","screen","deletedAt","publishedAt","ms","published","length","publishedPages","firstPublishedAt","Math","min","AI_SITE_STATUS_READER","SITE_STATUS_MAX_PAGES","aiSiteStatusTable","context","ref","pages","slice","more","reader","days","scope","title","source","label","period","columns","key","kind","rows","figure","join","omitted","notes"],"mappings":"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"}
@@ -50,6 +50,12 @@ export interface AssistBuildDraft {
50
50
  label: string;
51
51
  /** What kind of thing it is, in a word a person reads. */
52
52
  noun: string;
53
+ /**
54
+ * The draft whose canvas is open in the editor the request was asked from
55
+ * (AGL-3833). Set only when the request also carries that canvas: the
56
+ * answer edits it there, and never offers to open it again.
57
+ */
58
+ open?: true;
53
59
  }
54
60
  /** A draft the panel can open, with the address it opens at. */
55
61
  export interface AssistBuildDraftLink extends AssistBuildDraft {
@@ -73,6 +79,16 @@ export declare function assistDraftLabel(value: unknown): string;
73
79
  * malformed entry is dropped, never repaired.
74
80
  */
75
81
  export declare function parseAssistBuildDrafts(raw: unknown): AssistBuildDraft[];
82
+ /**
83
+ * The drafts one turn works with: none away from a site, where opening one has
84
+ * no destination; and the `open` mark only on a turn that reads the canvas
85
+ * the request carried (AGL-3833). Without that canvas the mark would tell the
86
+ * chat to edit a document it cannot see.
87
+ */
88
+ export declare function assistDraftsForTurn(drafts: readonly AssistBuildDraft[], turn: {
89
+ onSite: boolean;
90
+ canvasRead: boolean;
91
+ }): AssistBuildDraft[];
76
92
  /**
77
93
  * The lines the chat is told about the drafts, after every cache breakpoint:
78
94
  * what each ref is, and that a request to change one is a navigation to it,
@@ -90,3 +106,22 @@ export interface AssistPendingFollowUp {
90
106
  export declare function isLiveAssistFollowUp(value: unknown, now: number): value is AssistPendingFollowUp;
91
107
  /** The version a Besigner address names, for the open editor to match. */
92
108
  export declare function assistBesignerVersionOf(path: string): string | null;
109
+ /** Where the panel stands when it decides whether to ask a follow-up again. */
110
+ export interface AssistFollowUpStand {
111
+ /** The route the panel is on. */
112
+ path: string;
113
+ /** The version the open editor session reports, or null with none open. */
114
+ versionId: string | null;
115
+ /** Whether the canvas holds the open document's nodes yet. */
116
+ canvasLoaded: boolean;
117
+ }
118
+ /**
119
+ * Whether a follow-up is asked again now (AGL-3833): on its draft's address,
120
+ * with the editor reporting that draft's version AND its canvas loaded.
121
+ *
122
+ * The editor reports its version from the route the moment it mounts, well
123
+ * before the document's nodes reach the canvas. Asked in that gap, the request
124
+ * carried no canvas, so the chat could not see that the draft was open and
125
+ * offered to open it again: a loop that spent credits on every turn.
126
+ */
127
+ export declare function isAssistFollowUpDue(followUp: AssistPendingFollowUp, stand: AssistFollowUpStand): boolean;
@@ -1,3 +1,4 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
1
2
  /**
2
3
  * @license
3
4
  * Copyright 2026 Aglyn LLC
@@ -95,24 +96,50 @@ const NOUN_WORDS = new Set([
95
96
  const noun = typeof record['noun'] === 'string' && NOUN_WORDS.has(record['noun']) ? record['noun'] : 'draft';
96
97
  if (!REF.test(ref) || refs.has(ref) || !label) continue;
97
98
  refs.add(ref);
98
- drafts.push({
99
+ drafts.push(_extends({
99
100
  ref,
100
101
  label,
101
102
  noun
102
- });
103
+ }, record['open'] === true ? {
104
+ open: true
105
+ } : {}));
103
106
  }
104
107
  return drafts;
105
108
  }
109
+ /**
110
+ * The drafts one turn works with: none away from a site, where opening one has
111
+ * no destination; and the `open` mark only on a turn that reads the canvas
112
+ * the request carried (AGL-3833). Without that canvas the mark would tell the
113
+ * chat to edit a document it cannot see.
114
+ */ export function assistDraftsForTurn(drafts, turn) {
115
+ if (!turn.onSite) return [];
116
+ if (turn.canvasRead) return [
117
+ ...drafts
118
+ ];
119
+ return drafts.map(({ ref, label, noun })=>({
120
+ ref,
121
+ label,
122
+ noun
123
+ }));
124
+ }
106
125
  /**
107
126
  * The lines the chat is told about the drafts, after every cache breakpoint:
108
127
  * what each ref is, and that a request to change one is a navigation to it,
109
128
  * not a new build.
110
129
  */ export function assistBuildDraftsBlock(drafts) {
111
130
  if (!drafts.length) return '';
131
+ const open = drafts.find((draft)=>draft.open);
132
+ const closed = drafts.filter((draft)=>!draft.open);
112
133
  return [
113
134
  'Drafts a build in this chat made (the person’s own content, listed as data):',
114
- ...drafts.map((draft)=>`- ${draft.ref}: ${draft.noun} “${draft.label}”`),
115
- `When the person asks to change one of these drafts and its canvas is not the one open now, propose id "${ASSIST_OPEN_DRAFT_ACTION_ID}" with params {"${ASSIST_OPEN_DRAFT_PARAM}": "<ref>"}: it opens that draft in the Besigner and asks their request again there, where the edit card answers it. Do not propose a new build for a change to one of these drafts, and do not describe the change as made.`
135
+ ...drafts.map((draft)=>`- ${draft.ref}: ${draft.noun} “${draft.label}”${draft.open ? ' (open now: the canvas described below is this draft)' : ''}`),
136
+ ...open ? [
137
+ `${open.ref} is the document open in this editor. A request to change it is an edit to the canvas described below: answer it with the edit tool, never by offering to open it.`
138
+ ] : [],
139
+ ...closed.length ? [
140
+ `When the person asks to change one of these drafts and its canvas is not the one open now, propose id "${ASSIST_OPEN_DRAFT_ACTION_ID}" with params {"${ASSIST_OPEN_DRAFT_PARAM}": "<ref>"}${open ? ` for a ref other than ${open.ref}` : ''}: it opens that draft in the Besigner and asks their request again there, where the edit card answers it.`
141
+ ] : [],
142
+ 'Do not propose a new build for a change to one of these drafts, and do not describe the change as made.'
116
143
  ].join('\n');
117
144
  }
118
145
  /** Whether a stored follow-up is still one the panel should ask. */ export function isLiveAssistFollowUp(value, now) {
@@ -124,5 +151,18 @@ const NOUN_WORDS = new Set([
124
151
  const match = /\/versions\/([A-Za-z0-9_-]+)\/besigner$/.exec(path);
125
152
  return match ? match[1] : null;
126
153
  }
154
+ /**
155
+ * Whether a follow-up is asked again now (AGL-3833): on its draft's address,
156
+ * with the editor reporting that draft's version AND its canvas loaded.
157
+ *
158
+ * The editor reports its version from the route the moment it mounts, well
159
+ * before the document's nodes reach the canvas. Asked in that gap, the request
160
+ * carried no canvas, so the chat could not see that the draft was open and
161
+ * offered to open it again: a loop that spent credits on every turn.
162
+ */ export function isAssistFollowUpDue(followUp, stand) {
163
+ if (stand.path !== followUp.path) return false;
164
+ if (!stand.versionId || stand.versionId !== assistBesignerVersionOf(followUp.path)) return false;
165
+ return stand.canvasLoaded;
166
+ }
127
167
 
128
168
  //# sourceMappingURL=assist-follow-up.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/assist-follow-up.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 { AI_JOB_BESIGNER_SEGMENT } from './ai-job-notice'\nimport type { AiJobOutput } from './ai-jobs.types'\n\n/**\n * A follow-up on what a build made (AGL-3616): \"make the about page shorter\"\n * after Assist built the About page.\n *\n * The edit card works only on the document open in the Besigner. So when a\n * request is about a draft the build in this chat made, Assist proposes\n * OPENING that draft's Besigner, and the panel asks the same request again\n * once the draft's canvas is open — where the edit rung answers it with an\n * edit card the author applies. Nothing is changed by the proposal itself.\n *\n * The panel names the drafts it can open by a short ref (`d1`, `d2`, …) and a\n * label; the ids and the address stay in the panel, which got them from the\n * job door. The chat sees only refs and labels, and a proposal it makes is\n * held to a ref the request listed.\n *\n * Shared by the chat door and the panel. Pure: no request, no React.\n */\n\n/** The navigation's id, in the proposal channel and the analytics event. */\nexport const ASSIST_OPEN_DRAFT_ACTION_ID = 'open.build.draft'\n\n/** The one parameter the navigation takes: the ref of the draft to open. */\nexport const ASSIST_OPEN_DRAFT_PARAM = 'draft'\n\n/** The most drafts one request lists: a build makes at most 16 units. */\nexport const ASSIST_BUILD_DRAFTS_MAX = 16\n\n/** The longest label a request carries for one draft. */\nexport const ASSIST_BUILD_DRAFT_LABEL_MAX = 80\n\n/** How long a follow-up waits for its canvas before it is dropped. */\nexport const ASSIST_FOLLOW_UP_TTL_MS = 2 * 60_000\n\n/** A draft the chat may offer to open, as the request names it. */\nexport interface AssistBuildDraft {\n /** `d1`, `d2`, …: what the model chooses from. */\n ref: string\n /** What the draft is called: the page's title, the layout's name. */\n label: string\n /** What kind of thing it is, in a word a person reads. */\n noun: string\n}\n\n/** A draft the panel can open, with the address it opens at. */\nexport interface AssistBuildDraftLink extends AssistBuildDraft {\n href: string\n}\n\n/** What one Besigner-backed output is called in a sentence. */\nconst NOUNS: Partial<Record<AiJobOutput['resource'], string>> = {\n screen: 'page',\n reusableComponent: 'component',\n layout: 'layout',\n template: 'page template',\n emailScreen: 'email design',\n}\n\n/** The Besigner address a job's output opens at, or `null` where it has none. */\nexport function assistDraftBesignerHref(output: AiJobOutput, orgSlug: string): string | null {\n const segment = AI_JOB_BESIGNER_SEGMENT[output.resource]\n if (!segment || !orgSlug || !output.hostSubdomain || !output.versionId) return null\n return `/${orgSlug}/hosts/${output.hostSubdomain}/${segment}/${output.id}/versions/${output.versionId}/besigner`\n}\n\n/**\n * The drafts the panel can offer, newest build first: every output of the\n * given jobs that opens in the Besigner on the version the job wrote, once\n * each, at most {@link ASSIST_BUILD_DRAFTS_MAX}.\n */\nexport function assistBuildDraftLinks(\n jobs: ReadonlyArray<{ outputs: readonly AiJobOutput[] }>,\n orgSlug: string,\n): AssistBuildDraftLink[] {\n const links: AssistBuildDraftLink[] = []\n const seen = new Set<string>()\n for (const job of jobs) {\n for (const output of job.outputs) {\n if (links.length >= ASSIST_BUILD_DRAFTS_MAX) return links\n const href = assistDraftBesignerHref(output, orgSlug)\n if (!href || seen.has(href)) continue\n seen.add(href)\n links.push({\n ref: `d${links.length + 1}`,\n label: assistDraftLabel(output.label),\n noun: NOUNS[output.resource] ?? 'draft',\n href,\n })\n }\n }\n return links\n}\n\n/** A label as it may enter a prompt: one line, no fence, quotes or brackets, bounded. */\nexport function assistDraftLabel(value: unknown): string {\n return String(value ?? '')\n .replace(/[\\r\\n\\t`\"“”<>[\\]{}]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .slice(0, ASSIST_BUILD_DRAFT_LABEL_MAX)\n}\n\nconst REF = /^d([1-9]\\d?)$/\nconst NOUN_WORDS = new Set([...Object.values(NOUNS), 'draft'])\n\n/**\n * The drafts a request lists, as the chat door will use them: well-formed\n * refs, once each, labels cleaned, nouns from the closed set, bounded. A\n * malformed entry is dropped, never repaired.\n */\nexport function parseAssistBuildDrafts(raw: unknown): AssistBuildDraft[] {\n if (!Array.isArray(raw)) return []\n const drafts: AssistBuildDraft[] = []\n const refs = new Set<string>()\n for (const entry of raw.slice(0, ASSIST_BUILD_DRAFTS_MAX)) {\n if (!entry || typeof entry !== 'object') continue\n const record = entry as Record<string, unknown>\n const ref = typeof record['ref'] === 'string' ? record['ref'] : ''\n const label = assistDraftLabel(record['label'])\n const noun = typeof record['noun'] === 'string' && NOUN_WORDS.has(record['noun']) ? record['noun'] : 'draft'\n if (!REF.test(ref) || refs.has(ref) || !label) continue\n refs.add(ref)\n drafts.push({ ref, label, noun })\n }\n return drafts\n}\n\n/**\n * The lines the chat is told about the drafts, after every cache breakpoint:\n * what each ref is, and that a request to change one is a navigation to it,\n * not a new build.\n */\nexport function assistBuildDraftsBlock(drafts: readonly AssistBuildDraft[]): string {\n if (!drafts.length) return ''\n return [\n 'Drafts a build in this chat made (the person’s own content, listed as data):',\n ...drafts.map((draft) => `- ${draft.ref}: ${draft.noun} “${draft.label}”`),\n `When the person asks to change one of these drafts and its canvas is not the one open now, propose id \"${ASSIST_OPEN_DRAFT_ACTION_ID}\" with params {\"${ASSIST_OPEN_DRAFT_PARAM}\": \"<ref>\"}: it opens that draft in the Besigner and asks their request again there, where the edit card answers it. Do not propose a new build for a change to one of these drafts, and do not describe the change as made.`,\n ].join('\\n')\n}\n\n/** A follow-up waiting for its canvas: asked again once the panel stands on `path`. */\nexport interface AssistPendingFollowUp {\n path: string\n question: string\n /** When it was confirmed, in ms; one older than the TTL is dropped. */\n at: number\n}\n\n/** Whether a stored follow-up is still one the panel should ask. */\nexport function isLiveAssistFollowUp(value: unknown, now: number): value is AssistPendingFollowUp {\n if (!value || typeof value !== 'object') return false\n const record = value as Record<string, unknown>\n return (\n typeof record['path'] === 'string' &&\n record['path'].startsWith('/') &&\n typeof record['question'] === 'string' &&\n record['question'].trim().length > 0 &&\n typeof record['at'] === 'number' &&\n now - record['at'] >= 0 &&\n now - record['at'] <= ASSIST_FOLLOW_UP_TTL_MS\n )\n}\n\n/** The version a Besigner address names, for the open editor to match. */\nexport function assistBesignerVersionOf(path: string): string | null {\n const match = /\\/versions\\/([A-Za-z0-9_-]+)\\/besigner$/.exec(path)\n return match ? match[1] : null\n}\n"],"names":["AI_JOB_BESIGNER_SEGMENT","ASSIST_OPEN_DRAFT_ACTION_ID","ASSIST_OPEN_DRAFT_PARAM","ASSIST_BUILD_DRAFTS_MAX","ASSIST_BUILD_DRAFT_LABEL_MAX","ASSIST_FOLLOW_UP_TTL_MS","NOUNS","screen","reusableComponent","layout","template","emailScreen","assistDraftBesignerHref","output","orgSlug","segment","resource","hostSubdomain","versionId","id","assistBuildDraftLinks","jobs","links","seen","Set","job","outputs","length","href","has","add","push","ref","label","assistDraftLabel","noun","value","String","replace","trim","slice","REF","NOUN_WORDS","Object","values","parseAssistBuildDrafts","raw","Array","isArray","drafts","refs","entry","record","test","assistBuildDraftsBlock","map","draft","join","isLiveAssistFollowUp","now","startsWith","assistBesignerVersionOf","path","match","exec"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/assist-follow-up.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 { AI_JOB_BESIGNER_SEGMENT } from './ai-job-notice'\nimport type { AiJobOutput } from './ai-jobs.types'\n\n/**\n * A follow-up on what a build made (AGL-3616): \"make the about page shorter\"\n * after Assist built the About page.\n *\n * The edit card works only on the document open in the Besigner. So when a\n * request is about a draft the build in this chat made, Assist proposes\n * OPENING that draft's Besigner, and the panel asks the same request again\n * once the draft's canvas is open — where the edit rung answers it with an\n * edit card the author applies. Nothing is changed by the proposal itself.\n *\n * The panel names the drafts it can open by a short ref (`d1`, `d2`, …) and a\n * label; the ids and the address stay in the panel, which got them from the\n * job door. The chat sees only refs and labels, and a proposal it makes is\n * held to a ref the request listed.\n *\n * Shared by the chat door and the panel. Pure: no request, no React.\n */\n\n/** The navigation's id, in the proposal channel and the analytics event. */\nexport const ASSIST_OPEN_DRAFT_ACTION_ID = 'open.build.draft'\n\n/** The one parameter the navigation takes: the ref of the draft to open. */\nexport const ASSIST_OPEN_DRAFT_PARAM = 'draft'\n\n/** The most drafts one request lists: a build makes at most 16 units. */\nexport const ASSIST_BUILD_DRAFTS_MAX = 16\n\n/** The longest label a request carries for one draft. */\nexport const ASSIST_BUILD_DRAFT_LABEL_MAX = 80\n\n/** How long a follow-up waits for its canvas before it is dropped. */\nexport const ASSIST_FOLLOW_UP_TTL_MS = 2 * 60_000\n\n/** A draft the chat may offer to open, as the request names it. */\nexport interface AssistBuildDraft {\n /** `d1`, `d2`, …: what the model chooses from. */\n ref: string\n /** What the draft is called: the page's title, the layout's name. */\n label: string\n /** What kind of thing it is, in a word a person reads. */\n noun: string\n /**\n * The draft whose canvas is open in the editor the request was asked from\n * (AGL-3833). Set only when the request also carries that canvas: the\n * answer edits it there, and never offers to open it again.\n */\n open?: true\n}\n\n/** A draft the panel can open, with the address it opens at. */\nexport interface AssistBuildDraftLink extends AssistBuildDraft {\n href: string\n}\n\n/** What one Besigner-backed output is called in a sentence. */\nconst NOUNS: Partial<Record<AiJobOutput['resource'], string>> = {\n screen: 'page',\n reusableComponent: 'component',\n layout: 'layout',\n template: 'page template',\n emailScreen: 'email design',\n}\n\n/** The Besigner address a job's output opens at, or `null` where it has none. */\nexport function assistDraftBesignerHref(output: AiJobOutput, orgSlug: string): string | null {\n const segment = AI_JOB_BESIGNER_SEGMENT[output.resource]\n if (!segment || !orgSlug || !output.hostSubdomain || !output.versionId) return null\n return `/${orgSlug}/hosts/${output.hostSubdomain}/${segment}/${output.id}/versions/${output.versionId}/besigner`\n}\n\n/**\n * The drafts the panel can offer, newest build first: every output of the\n * given jobs that opens in the Besigner on the version the job wrote, once\n * each, at most {@link ASSIST_BUILD_DRAFTS_MAX}.\n */\nexport function assistBuildDraftLinks(\n jobs: ReadonlyArray<{ outputs: readonly AiJobOutput[] }>,\n orgSlug: string,\n): AssistBuildDraftLink[] {\n const links: AssistBuildDraftLink[] = []\n const seen = new Set<string>()\n for (const job of jobs) {\n for (const output of job.outputs) {\n if (links.length >= ASSIST_BUILD_DRAFTS_MAX) return links\n const href = assistDraftBesignerHref(output, orgSlug)\n if (!href || seen.has(href)) continue\n seen.add(href)\n links.push({\n ref: `d${links.length + 1}`,\n label: assistDraftLabel(output.label),\n noun: NOUNS[output.resource] ?? 'draft',\n href,\n })\n }\n }\n return links\n}\n\n/** A label as it may enter a prompt: one line, no fence, quotes or brackets, bounded. */\nexport function assistDraftLabel(value: unknown): string {\n return String(value ?? '')\n .replace(/[\\r\\n\\t`\"“”<>[\\]{}]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .slice(0, ASSIST_BUILD_DRAFT_LABEL_MAX)\n}\n\nconst REF = /^d([1-9]\\d?)$/\nconst NOUN_WORDS = new Set([...Object.values(NOUNS), 'draft'])\n\n/**\n * The drafts a request lists, as the chat door will use them: well-formed\n * refs, once each, labels cleaned, nouns from the closed set, bounded. A\n * malformed entry is dropped, never repaired.\n */\nexport function parseAssistBuildDrafts(raw: unknown): AssistBuildDraft[] {\n if (!Array.isArray(raw)) return []\n const drafts: AssistBuildDraft[] = []\n const refs = new Set<string>()\n for (const entry of raw.slice(0, ASSIST_BUILD_DRAFTS_MAX)) {\n if (!entry || typeof entry !== 'object') continue\n const record = entry as Record<string, unknown>\n const ref = typeof record['ref'] === 'string' ? record['ref'] : ''\n const label = assistDraftLabel(record['label'])\n const noun = typeof record['noun'] === 'string' && NOUN_WORDS.has(record['noun']) ? record['noun'] : 'draft'\n if (!REF.test(ref) || refs.has(ref) || !label) continue\n refs.add(ref)\n drafts.push({ ref, label, noun, ...(record['open'] === true ? { open: true as const } : {}) })\n }\n return drafts\n}\n\n/**\n * The drafts one turn works with: none away from a site, where opening one has\n * no destination; and the `open` mark only on a turn that reads the canvas\n * the request carried (AGL-3833). Without that canvas the mark would tell the\n * chat to edit a document it cannot see.\n */\nexport function assistDraftsForTurn(\n drafts: readonly AssistBuildDraft[],\n turn: { onSite: boolean; canvasRead: boolean },\n): AssistBuildDraft[] {\n if (!turn.onSite) return []\n if (turn.canvasRead) return [...drafts]\n return drafts.map(({ ref, label, noun }) => ({ ref, label, noun }))\n}\n\n/**\n * The lines the chat is told about the drafts, after every cache breakpoint:\n * what each ref is, and that a request to change one is a navigation to it,\n * not a new build.\n */\nexport function assistBuildDraftsBlock(drafts: readonly AssistBuildDraft[]): string {\n if (!drafts.length) return ''\n const open = drafts.find((draft) => draft.open)\n const closed = drafts.filter((draft) => !draft.open)\n return [\n 'Drafts a build in this chat made (the person’s own content, listed as data):',\n ...drafts.map(\n (draft) =>\n `- ${draft.ref}: ${draft.noun} “${draft.label}”${draft.open ? ' (open now: the canvas described below is this draft)' : ''}`,\n ),\n ...(open\n ? [\n `${open.ref} is the document open in this editor. A request to change it is an edit to the canvas described below: answer it with the edit tool, never by offering to open it.`,\n ]\n : []),\n ...(closed.length\n ? [\n `When the person asks to change one of these drafts and its canvas is not the one open now, propose id \"${ASSIST_OPEN_DRAFT_ACTION_ID}\" with params {\"${ASSIST_OPEN_DRAFT_PARAM}\": \"<ref>\"}${open ? ` for a ref other than ${open.ref}` : ''}: it opens that draft in the Besigner and asks their request again there, where the edit card answers it.`,\n ]\n : []),\n 'Do not propose a new build for a change to one of these drafts, and do not describe the change as made.',\n ].join('\\n')\n}\n\n/** A follow-up waiting for its canvas: asked again once the panel stands on `path`. */\nexport interface AssistPendingFollowUp {\n path: string\n question: string\n /** When it was confirmed, in ms; one older than the TTL is dropped. */\n at: number\n}\n\n/** Whether a stored follow-up is still one the panel should ask. */\nexport function isLiveAssistFollowUp(value: unknown, now: number): value is AssistPendingFollowUp {\n if (!value || typeof value !== 'object') return false\n const record = value as Record<string, unknown>\n return (\n typeof record['path'] === 'string' &&\n record['path'].startsWith('/') &&\n typeof record['question'] === 'string' &&\n record['question'].trim().length > 0 &&\n typeof record['at'] === 'number' &&\n now - record['at'] >= 0 &&\n now - record['at'] <= ASSIST_FOLLOW_UP_TTL_MS\n )\n}\n\n/** The version a Besigner address names, for the open editor to match. */\nexport function assistBesignerVersionOf(path: string): string | null {\n const match = /\\/versions\\/([A-Za-z0-9_-]+)\\/besigner$/.exec(path)\n return match ? match[1] : null\n}\n\n/** Where the panel stands when it decides whether to ask a follow-up again. */\nexport interface AssistFollowUpStand {\n /** The route the panel is on. */\n path: string\n /** The version the open editor session reports, or null with none open. */\n versionId: string | null\n /** Whether the canvas holds the open document's nodes yet. */\n canvasLoaded: boolean\n}\n\n/**\n * Whether a follow-up is asked again now (AGL-3833): on its draft's address,\n * with the editor reporting that draft's version AND its canvas loaded.\n *\n * The editor reports its version from the route the moment it mounts, well\n * before the document's nodes reach the canvas. Asked in that gap, the request\n * carried no canvas, so the chat could not see that the draft was open and\n * offered to open it again: a loop that spent credits on every turn.\n */\nexport function isAssistFollowUpDue(followUp: AssistPendingFollowUp, stand: AssistFollowUpStand): boolean {\n if (stand.path !== followUp.path) return false\n if (!stand.versionId || stand.versionId !== assistBesignerVersionOf(followUp.path)) return false\n return stand.canvasLoaded\n}\n"],"names":["AI_JOB_BESIGNER_SEGMENT","ASSIST_OPEN_DRAFT_ACTION_ID","ASSIST_OPEN_DRAFT_PARAM","ASSIST_BUILD_DRAFTS_MAX","ASSIST_BUILD_DRAFT_LABEL_MAX","ASSIST_FOLLOW_UP_TTL_MS","NOUNS","screen","reusableComponent","layout","template","emailScreen","assistDraftBesignerHref","output","orgSlug","segment","resource","hostSubdomain","versionId","id","assistBuildDraftLinks","jobs","links","seen","Set","job","outputs","length","href","has","add","push","ref","label","assistDraftLabel","noun","value","String","replace","trim","slice","REF","NOUN_WORDS","Object","values","parseAssistBuildDrafts","raw","Array","isArray","drafts","refs","entry","record","test","open","assistDraftsForTurn","turn","onSite","canvasRead","map","assistBuildDraftsBlock","find","draft","closed","filter","join","isLiveAssistFollowUp","now","startsWith","assistBesignerVersionOf","path","match","exec","isAssistFollowUpDue","followUp","stand","canvasLoaded"],"mappings":"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"}
@@ -17,7 +17,8 @@ import { _ as _wrap_async_generator } from "@swc/helpers/_/_wrap_async_generator
17
17
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
18
  * See the License for the specific language governing permissions and
19
19
  * limitations under the License.
20
- */ import { aiModelIdsForProvider, estimateAiBilledUsd, aiCatalogEntry } from "./catalog.js";
20
+ */ import { aiBilledRatesForModel, aiCatalogEntry, aiModelIdsForProvider, estimateAiBilledUsd } from "./catalog.js";
21
+ import { aiCacheWriteHourSurchargeUsd } from "./cache-write-hour.js";
21
22
  import { AiBatchUnfinishedError, AiUpstreamError, aiRawOutputOf, aiStoppedAtCeiling, aiTokenCount, aiToolInputOf } from "./contract.js";
22
23
  /**
23
24
  * The Anthropic adapter (AGL-2903, behind the contract since AGL-2939):
@@ -100,15 +101,26 @@ const ANTHROPIC_VERSION = '2023-06-01';
100
101
  // down, so an answer that reports none says nothing rather than zero.
101
102
  const details = record['output_tokens_details'];
102
103
  const thinking = details && typeof details === 'object' ? details['thinking_tokens'] : undefined;
104
+ // The writes split by lifetime (AGL-3830). Only an hour-long write is kept,
105
+ // because it is the one billed above the rate table's write rate, and only
106
+ // where there was one, so every five-minute exchange keeps its shape.
107
+ const creation = record['cache_creation'];
108
+ const hour = creation && typeof creation === 'object' ? aiTokenCount(creation['ephemeral_1h_input_tokens']) : 0;
103
109
  return _extends({
104
110
  inputTokens: aiTokenCount(record['input_tokens']),
105
111
  outputTokens: aiTokenCount(record['output_tokens']),
106
112
  cacheReadTokens: aiTokenCount(record['cache_read_input_tokens']),
107
113
  cacheWriteTokens: aiTokenCount(record['cache_creation_input_tokens'])
108
- }, thinking === undefined ? {} : {
114
+ }, hour > 0 ? {
115
+ cacheWriteHourTokens: hour
116
+ } : {}, thinking === undefined ? {} : {
109
117
  thinkingTokens: aiTokenCount(thinking)
110
118
  });
111
119
  }
120
+ /** What an exchange draws, its hour-long cache writes included (AGL-3830). */ function anthropicBilledUsd(usage, model) {
121
+ const usd = estimateAiBilledUsd(usage, model) + aiCacheWriteHourSurchargeUsd(usage, aiBilledRatesForModel(model));
122
+ return Math.round(usd * 1000000) / 1000000;
123
+ }
112
124
  /**
113
125
  * How "thinking off" is said to a model that refuses `{type: "disabled"}`.
114
126
  * Claude Sonnet 5.5 turns it off only through `between_tools` (accepted at
@@ -297,7 +309,7 @@ async function complete(input) {
297
309
  };
298
310
  });
299
311
  const usage = anthropicUsageFrom(payload == null ? void 0 : payload.usage);
300
- const estCostUsd = Math.round(estimateAiBilledUsd(usage, model) * priceFactor * 1000000) / 1000000;
312
+ const estCostUsd = Math.round(anthropicBilledUsd(usage, model) * priceFactor * 1000000) / 1000000;
301
313
  const stopReason = typeof (payload == null ? void 0 : payload.stop_reason) === 'string' && payload.stop_reason ? payload.stop_reason : null;
302
314
  if (stopReason === 'refusal') {
303
315
  return {
@@ -379,6 +391,7 @@ async function stream(input) {
379
391
  usage.inputTokens = start.inputTokens;
380
392
  usage.cacheReadTokens = start.cacheReadTokens;
381
393
  usage.cacheWriteTokens = start.cacheWriteTokens;
394
+ if (start.cacheWriteHourTokens) usage.cacheWriteHourTokens = start.cacheWriteHourTokens;
382
395
  break;
383
396
  }
384
397
  case 'content_block_start':
@@ -474,7 +487,7 @@ async function stream(input) {
474
487
  yield _extends({
475
488
  type: 'done',
476
489
  usage,
477
- estCostUsd: estimateAiBilledUsd(usage, model),
490
+ estCostUsd: anthropicBilledUsd(usage, model),
478
491
  stopReason
479
492
  }, aiStoppedAtCeiling(stopReason) ? {
480
493
  rawOutput: aiRawOutputOf(written.join(''))
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/providers/anthropic.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { aiModelIdsForProvider, estimateAiBilledUsd, aiCatalogEntry } from './catalog'\nimport {\n AiBatchUnfinishedError,\n AiUpstreamError,\n type AiAccountProblem,\n type AiBatchOptions,\n type AiEffort,\n aiRawOutputOf,\n aiStoppedAtCeiling,\n aiTokenCount,\n aiToolInputOf,\n type AiMessage,\n type AiModelDescriptor,\n type AiProvider,\n type AiProviderRequest,\n type AiResult,\n type AiStreamEvent,\n type AiToolSchemaLimits,\n type AiToolUse,\n type AiUsage,\n} from './contract'\n\n/**\n * The Anthropic adapter (AGL-2903, behind the contract since AGL-2939):\n * the Messages API over raw `fetch`. No provider SDK is installed, and the\n * meter prices by model id from the catalog, so nothing an SDK would add\n * is used here. This file and the catalog are the only places in the\n * plugin that name the vendor's URL, header or model ids.\n *\n * ── Errors (AGL-2815) ─────────────────────────────────────────────────────\n *\n * Provider error text describes OUR account with the vendor — a key, a rate\n * limit, a balance — and names the vendor to a white-label org's users. It\n * never leaves this module as a message: a non-2xx answer becomes an\n * `AiUpstreamError` carrying the status, whether the failure is retryable\n * and a fixed customer-safe sentence; the provider's body goes to\n * `console.error` beside the request id, where an operator can find it.\n * A `refusal` stop is not an error at all — the tokens were spent and the\n * model answered — so it comes back as a typed `AiRefusal` result.\n */\n\nexport const ANTHROPIC_PROVIDER_ID = 'anthropic'\nexport const ANTHROPIC_API_KEY_ENV = 'ANTHROPIC_API_KEY'\nconst ANTHROPIC_HOST = 'api.anthropic.com'\nconst ANTHROPIC_URL = `https://${ANTHROPIC_HOST}/v1/messages`\nconst ANTHROPIC_BATCHES_URL = `https://${ANTHROPIC_HOST}/v1/messages/batches`\nconst ANTHROPIC_VERSION = '2023-06-01'\n\n/** The Messages API accepts at most this many `cache_control` markers. */\nexport const ANTHROPIC_MAX_CACHE_BREAKPOINTS = 4\n\n/**\n * What the Messages API compiles of one request's strict schemas, as its\n * structured-outputs documentation states the limits: 20 strict tools, 24\n * optional parameters and 16 parameters with union types, each a total over\n * every strict schema the request carries. Past any of them the request is\n * refused with a 400 before the model runs.\n *\n * `compiledSchemaBytes` is not one of those. The API also refuses a request\n * whose schemas compile to too large a grammar — \"Simplify your tool schemas\n * or reduce the number of strict tools\" — and states no size for it, so this\n * bound is measured rather than read (AGL-3096). Its evidence is two live\n * requests for the automation draft, the largest strict schema anything here\n * sends:\n *\n * - 8,757 bytes, refused for grammar size (`req_011CfC6Fif9iJvmKPTRgtLdp`,\n * 2026-09-19), with 1 strict tool, 0 optional parameters and 2 unions:\n * every published count was green;\n * - 3,807 bytes, served (`req_011CfELn62WVLeaNzwK4TWgb`, 2026-09-20), the\n * same tool with the guard lifted out of the steps' union and one variant\n * per set of fields.\n *\n * Everything else the plugin sends is smaller still, the next largest being\n * the theme tool at 2,410 bytes. The bound sits at what has been served, so a\n * schema that grows past it has to be proved live before the bound moves.\n */\nexport const ANTHROPIC_TOOL_SCHEMA_LIMITS: AiToolSchemaLimits = {\n strictTools: 20,\n optionalParameters: 24,\n unionParameters: 16,\n compiledSchemaBytes: 3_807,\n}\n\n/**\n * Whether a provider failure is the kind that clears on its own. Matched on\n * the status AND the error type, because the in-stream `error` event has no\n * status to go by.\n */\nexport function anthropicFailureIsRetryable(\n status: number | null,\n errorType: string | undefined,\n): boolean {\n return (\n status === 429 ||\n status === 529 ||\n errorType === 'rate_limit_error' ||\n errorType === 'overloaded_error'\n )\n}\n\n/**\n * Whether a failure is about the platform's Anthropic account rather than the\n * request (AGL-3377): a refused key, or a balance too low to answer.\n */\nexport function anthropicAccountProblem(\n status: number | null,\n error: { type?: string; message?: string } | null | undefined,\n): AiAccountProblem | null {\n if (status === 401 || status === 403) return 'credentials'\n if (error?.type === 'authentication_error' || error?.type === 'permission_error') {\n return 'credentials'\n }\n if (error?.type === 'billing_error' || /credit balance/i.test(error?.message ?? '')) {\n return 'credit'\n }\n return null\n}\n\n/** Anthropic's `usage` object, in the meter's shape. Missing fields read 0. */\nexport function anthropicUsageFrom(usage: unknown): AiUsage {\n const record = (usage ?? {}) as Record<string, unknown>\n // Part of `output_tokens`, and carried only where the response breaks them\n // down, so an answer that reports none says nothing rather than zero.\n const details = record['output_tokens_details']\n const thinking =\n details && typeof details === 'object'\n ? (details as Record<string, unknown>)['thinking_tokens']\n : undefined\n return {\n inputTokens: aiTokenCount(record['input_tokens']),\n outputTokens: aiTokenCount(record['output_tokens']),\n cacheReadTokens: aiTokenCount(record['cache_read_input_tokens']),\n cacheWriteTokens: aiTokenCount(record['cache_creation_input_tokens']),\n ...(thinking === undefined ? {} : { thinkingTokens: aiTokenCount(thinking) }),\n }\n}\n\n/**\n * How \"thinking off\" is said to a model that refuses `{type: \"disabled\"}`.\n * Claude Sonnet 5.5 turns it off only through `between_tools` (accepted at\n * effort high or below, which is every effort the contract names); Claude\n * Opus 5.5 cannot turn it off at all, so the field is left out and the\n * model's adaptive thinking runs. Every other model takes `disabled`.\n */\nconst ANTHROPIC_THINKING_OFF: Readonly<Record<string, 'between_tools' | 'omit'>> = {\n 'claude-sonnet-5-5': 'between_tools',\n 'claude-opus-5-5': 'omit',\n}\n\n/**\n * The effort a request that names none is sent, for a model whose own\n * default is lower than its siblings': Claude Opus 5.5 defaults to medium,\n * where Claude Opus 5 defaults to high.\n */\nconst ANTHROPIC_DEFAULT_EFFORT: Readonly<Record<string, AiEffort>> = {\n 'claude-opus-5-5': 'high',\n}\n\n/**\n * The Messages API body for a request. Exported so a spec can assert the\n * wire shape without a network. The cache-prefix rule is enforced by the\n * runtime before the request reaches any adapter; this maps the surviving\n * breakpoints onto `cache_control` and refuses only the count the API\n * itself refuses.\n */\nexport function buildAnthropicRequestBody(\n input: Omit<AiProviderRequest, 'apiKey' | 'signal'> & { stream: boolean },\n): Record<string, unknown> {\n const conversation = Boolean(input.cacheConversation) && input.messages.length > 0\n const breakpoints = input.system.filter((block) => block.cacheBreakpoint).length + (conversation ? 1 : 0)\n if (breakpoints > ANTHROPIC_MAX_CACHE_BREAKPOINTS) {\n throw new Error(\n `${breakpoints} cache breakpoints; the Messages API allows ${ANTHROPIC_MAX_CACHE_BREAKPOINTS}`,\n )\n }\n // One lifetime for every breakpoint: the API refuses a longer-lived entry\n // after a shorter one, and a request asks for one or the other.\n const cacheControl = input.cacheTtl === '1h' ? { type: 'ephemeral', ttl: '1h' } : { type: 'ephemeral' }\n const system = input.system.map((block) => ({\n type: 'text',\n text: block.text,\n ...(block.cacheBreakpoint ? { cache_control: cacheControl } : {}),\n }))\n const tools = input.tools?.map((tool) => ({\n name: tool.name,\n description: tool.description,\n input_schema: { ...tool.inputSchema, additionalProperties: false },\n strict: true,\n }))\n // A model that rejects an explicit thinking setting gets none, whatever\n // the door asked for, and no effort either: effort rides the same rule\n // (see the contract), and the catalog says which models those are.\n const settable = aiCatalogEntry(input.model)?.capabilities.thinking !== false\n const thinking = settable ? input.thinking : undefined\n const effort = settable ? (input.effort ?? ANTHROPIC_DEFAULT_EFFORT[input.model]) : undefined\n const thinkingOff = ANTHROPIC_THINKING_OFF[input.model] ?? 'disabled'\n return {\n model: input.model,\n max_tokens: input.maxTokens,\n ...(input.stream ? { stream: true } : {}),\n ...(thinking === 'off'\n ? thinkingOff === 'omit'\n ? {}\n : { thinking: { type: thinkingOff } }\n : thinking === 'adaptive'\n ? { thinking: { type: 'adaptive' } }\n : {}),\n ...(effort ? { output_config: { effort } } : {}),\n ...(system.length ? { system } : {}),\n messages: input.messages.map((message, index) => ({\n role: message.role,\n content: conversation\n ? anthropicBlocksOf(message, index === input.messages.length - 1 ? cacheControl : null)\n : anthropicContentOf(message),\n })),\n ...(tools?.length ? { tools, tool_choice: { type: 'auto' } } : {}),\n }\n}\n\n/**\n * A turn's content in the Messages API's shape: a text-only turn as the\n * string it is, and a turn with pictures as content blocks in order, each\n * picture a base64 image block (AGL-2916).\n */\nfunction anthropicContentOf(message: AiMessage): string | Array<Record<string, unknown>> {\n if (typeof message.content === 'string') return message.content\n return message.content.map((part) =>\n part.type === 'image'\n ? { type: 'image', source: { type: 'base64', media_type: part.mediaType, data: part.data } }\n : { type: 'text', text: part.text },\n )\n}\n\n/**\n * A turn as content blocks whatever its shape, the last block carrying the\n * conversation's breakpoint when it is the request's last turn (AGL-3809).\n * Every turn of a request that caches its conversation is sent as blocks, so\n * a turn reads the same on the call that marked it and on every call after,\n * where it is no longer last.\n */\nfunction anthropicBlocksOf(\n message: AiMessage,\n cacheControl: Record<string, unknown> | null,\n): Array<Record<string, unknown>> {\n const content = anthropicContentOf(message)\n const blocks = typeof content === 'string' ? [{ type: 'text', text: content }] : content\n if (!cacheControl || !blocks.length) return blocks\n const last = blocks.length - 1\n return blocks.map((block, index) => (index === last ? { ...block, cache_control: cacheControl } : block))\n}\n\n/** The provider's request id, when it sent one — the log's join key. */\nfunction requestIdOf(response: Response): string | null {\n return response.headers?.get?.('request-id') ?? null\n}\n\nasync function send(\n input: AiProviderRequest,\n stream: boolean,\n): Promise<Response> {\n const body = buildAnthropicRequestBody({ ...input, stream })\n const response = await fetch(ANTHROPIC_URL, {\n method: 'POST',\n headers: {\n 'x-api-key': input.apiKey,\n 'anthropic-version': ANTHROPIC_VERSION,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n ...(input.signal ? { signal: input.signal } : {}),\n })\n if (!response.ok || (stream && !response.body)) {\n const requestId = requestIdOf(response)\n const payload = (await response.json().catch(() => null)) as {\n error?: { type?: string; message?: string }\n } | null\n console.error('ai upstream error', {\n provider: ANTHROPIC_PROVIDER_ID,\n status: response.status,\n requestId,\n model: input.model,\n error: payload?.error ?? null,\n })\n throw new AiUpstreamError(\n response.status,\n anthropicFailureIsRetryable(response.status, payload?.error?.type),\n requestId,\n anthropicAccountProblem(response.status, payload?.error),\n )\n }\n return response\n}\n\nasync function complete(input: AiProviderRequest): Promise<AiResult> {\n const response = await send(input, false)\n return anthropicResultFrom(await response.json(), input.model)\n}\n\n/**\n * A Messages API answer as the contract's result: the shape `complete` reads\n * off the wire and a batch's succeeded entry carries. `priceFactor` is what a\n * discount leaves of the price (a batch's half).\n */\nexport function anthropicResultFrom(raw: unknown, model: string, priceFactor = 1): AiResult {\n const payload = raw as {\n content?: Array<Record<string, unknown>>\n stop_reason?: unknown\n usage?: unknown\n } | null\n const blocks = Array.isArray(payload?.content) ? payload.content : []\n const text = blocks\n .filter((block) => block?.['type'] === 'text')\n .map((block) => String(block['text'] ?? ''))\n .join('')\n const toolUse: AiToolUse[] = blocks\n .filter((block) => block?.['type'] === 'tool_use')\n .map((block) => ({\n name: String(block['name'] ?? ''),\n input: aiToolInputOf(block['input']),\n }))\n const usage = anthropicUsageFrom(payload?.usage)\n const estCostUsd = Math.round(estimateAiBilledUsd(usage, model) * priceFactor * 1_000_000) / 1_000_000\n const stopReason =\n typeof payload?.stop_reason === 'string' && payload.stop_reason\n ? payload.stop_reason\n : null\n if (stopReason === 'refusal') {\n return { kind: 'refusal', text, usage, estCostUsd, stopReason }\n }\n return {\n kind: 'completion',\n text,\n toolUse,\n usage,\n estCostUsd,\n stopReason,\n // The blocks as the provider answered them, for a call its ceiling cut\n // off (AGL-3143): `toolUse` holds only what still parsed out of them.\n ...(aiStoppedAtCeiling(stopReason) ? { rawOutput: aiRawOutputOf(blocks) } : {}),\n }\n}\n\nasync function stream(input: AiProviderRequest): Promise<AsyncIterable<AiStreamEvent>> {\n const response = await send(input, true)\n return streamEvents(\n response.body as ReadableStream<Uint8Array>,\n input.model,\n requestIdOf(response),\n )\n}\n\n/**\n * Anthropic's SSE stream as contract events. Usage is taken from\n * `message_start` (input and cache figures) and `message_delta` (the\n * output count, and any cumulative input figures a newer API sends); the\n * `done` event carries whatever was known when the body ended, so a stream\n * cut short still meters what it cost.\n */\nasync function* streamEvents(\n body: ReadableStream<Uint8Array>,\n model: string,\n requestId: string | null,\n): AsyncGenerator<AiStreamEvent> {\n const decoder = new TextDecoder()\n const reader = body.getReader()\n const usage: AiUsage = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n }\n let stopReason: string | null = null\n /** Tool-use blocks in flight, by content index; input arrives in pieces. */\n const tools = new Map<number, { name: string; json: string }>()\n /**\n * What each tool call was actually written in, in the order its block\n * opened (AGL-3143). The `tool` events below carry only as much of these\n * as `aiToolInputOf` could still parse, so for a call the ceiling cut off\n * this is the one place the emitted bytes survive.\n */\n const written: string[] = []\n let buffer = ''\n let drained = false\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) {\n drained = true\n break\n }\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue\n let event: Record<string, unknown>\n try {\n event = JSON.parse(line.slice('data: '.length))\n } catch {\n continue\n }\n const index = Number(event['index'])\n switch (event['type']) {\n case 'message_start': {\n const start = anthropicUsageFrom(\n (event['message'] as { usage?: unknown } | undefined)?.usage,\n )\n usage.inputTokens = start.inputTokens\n usage.cacheReadTokens = start.cacheReadTokens\n usage.cacheWriteTokens = start.cacheWriteTokens\n break\n }\n case 'content_block_start': {\n const block = event['content_block'] as\n | { type?: string; name?: string }\n | undefined\n if (block?.type === 'tool_use') {\n tools.set(index, { name: String(block.name ?? ''), json: '' })\n }\n break\n }\n case 'content_block_delta': {\n const delta = event['delta'] as\n | { type?: string; text?: string; partial_json?: string }\n | undefined\n if (delta?.type === 'text_delta' && delta.text) {\n yield { type: 'delta', text: delta.text }\n } else if (delta?.type === 'input_json_delta') {\n const tool = tools.get(index)\n if (tool) tool.json += delta.partial_json ?? ''\n }\n break\n }\n case 'content_block_stop': {\n const tool = tools.get(index)\n if (tool) {\n tools.delete(index)\n written.push(tool.json)\n yield { type: 'tool', name: tool.name, input: aiToolInputOf(tool.json) }\n }\n break\n }\n case 'message_delta': {\n const delta = anthropicUsageFrom(event['usage'])\n usage.outputTokens = delta.outputTokens || usage.outputTokens\n usage.inputTokens = delta.inputTokens || usage.inputTokens\n usage.cacheReadTokens = delta.cacheReadTokens || usage.cacheReadTokens\n usage.cacheWriteTokens = delta.cacheWriteTokens || usage.cacheWriteTokens\n // Kept where the breakdown is reported and left absent where it\n // is not (AGL-3143), never set to a zero the provider did not\n // send. A stream needs it for the reason a completion does: it\n // is the figure that accounts for a spent ceiling first, and it\n // arrives in the same usage object as the four above.\n if (delta.thinkingTokens !== undefined) usage.thinkingTokens = delta.thinkingTokens\n const reason = (event['delta'] as { stop_reason?: unknown } | undefined)\n ?.stop_reason\n if (typeof reason === 'string' && reason) stopReason = reason\n break\n }\n case 'error': {\n const error = event['error'] as\n | { type?: string; message?: string }\n | undefined\n console.error('ai upstream stream error', {\n provider: ANTHROPIC_PROVIDER_ID,\n requestId,\n model,\n error: error ?? null,\n })\n yield { type: 'error', retryable: anthropicFailureIsRetryable(null, error?.type) }\n break\n }\n default:\n break\n }\n }\n }\n } finally {\n // A consumer that stops iterating early — a client that hung up — must\n // not leave the provider generating into a socket nobody reads: cancel\n // tells the upstream to stop, and the tokens it has already produced are\n // the only ones billed.\n if (!drained) await reader.cancel().catch(() => undefined)\n reader.releaseLock()\n }\n // A message the ceiling ends mid-block never reaches that block's stop\n // event, so what it had written is still held in flight.\n for (const tool of tools.values()) written.push(tool.json)\n yield {\n type: 'done',\n usage,\n estCostUsd: estimateAiBilledUsd(usage, model),\n stopReason,\n // What the model emitted into its tool calls, for a stream its ceiling\n // cut off (AGL-3143): the `tool` events carry only what parsed out.\n ...(aiStoppedAtCeiling(stopReason) ? { rawOutput: aiRawOutputOf(written.join('')) } : {}),\n }\n}\n\n/** What a batch's discount leaves of a request's price. */\nexport const ANTHROPIC_BATCH_PRICE_FACTOR = 0.5\n\nfunction batchHeaders(apiKey: string): Record<string, string> {\n return {\n 'x-api-key': apiKey,\n 'anthropic-version': ANTHROPIC_VERSION,\n 'Content-Type': 'application/json',\n }\n}\n\n/** A batch call that did not answer 2xx, as the contract's error. */\nasync function batchFailure(response: Response, model: string): Promise<AiUpstreamError> {\n const requestId = requestIdOf(response)\n const payload = (await response.json().catch(() => null)) as {\n error?: { type?: string; message?: string }\n } | null\n console.error('ai upstream batch error', {\n provider: ANTHROPIC_PROVIDER_ID,\n status: response.status,\n requestId,\n model,\n error: payload?.error ?? null,\n })\n return new AiUpstreamError(\n response.status,\n anthropicFailureIsRetryable(response.status, payload?.error?.type),\n requestId,\n anthropicAccountProblem(response.status, payload?.error),\n )\n}\n\n/** The batch as its create and poll calls describe it. */\ninterface AnthropicBatch {\n id: string\n processing_status?: string\n results_url?: string | null\n}\n\n/** Results a batch can end a request with that answer nothing yet: asked again, not failed (AGL-3781). */\nconst ANTHROPIC_BATCH_UNFINISHED = new Set(['canceled', 'expired'])\n\n/**\n * Many requests through the Message Batches API (AGL-3660): created as one\n * batch, polled until it has ended, its results read by `custom_id` — they\n * come back in any order — and each turned into the result `complete` would\n * have returned, at half the price. Development only: the live evals' batch\n * mode is the one caller, and no door waits minutes for an answer.\n *\n * A batch still processing when its wait runs out is canceled, and read once\n * the cancel has ended it (AGL-3781): a cancel keeps every answer the batch\n * already has, so those come back as results, and only the requests it never\n * reached come back as `AiBatchUnfinishedError`, for the caller to ask on\n * their own. Throwing away a round's paid-for answers because one batch was\n * slow is how a whole golden run lost its grades.\n */\nasync function completeBatch(\n requests: readonly AiProviderRequest[],\n options: AiBatchOptions = {},\n): Promise<Array<AiResult | Error>> {\n if (!requests.length) return []\n const apiKey = requests[0].apiKey\n const model = requests[0].model\n const pollMs = options.pollMs ?? 10_000\n const timeoutMs = options.timeoutMs ?? 60 * 60_000\n const cancelGraceMs = options.cancelGraceMs ?? 10 * 60_000\n const unfinished = (id: string, reason: string) => requests.map(() => new AiBatchUnfinishedError(id, reason))\n const created = await fetch(ANTHROPIC_BATCHES_URL, {\n method: 'POST',\n headers: batchHeaders(apiKey),\n body: JSON.stringify({\n requests: requests.map((request, index) => {\n const { apiKey: _key, signal: _signal, ...rest } = request\n return { custom_id: `r${index}`, params: buildAnthropicRequestBody({ ...rest, stream: false }) }\n }),\n }),\n })\n if (!created.ok) throw await batchFailure(created, model)\n let batch = (await created.json()) as AnthropicBatch\n const deadline = Date.now() + timeoutMs\n let cancelDeadline: number | null = null\n while (batch.processing_status !== 'ended') {\n if (cancelDeadline === null && Date.now() >= deadline) {\n console.warn(`[ai] batch ${batch.id} has not ended after ${timeoutMs} ms; canceling it to read what it answered`)\n await fetch(`${ANTHROPIC_BATCHES_URL}/${batch.id}/cancel`, { method: 'POST', headers: batchHeaders(apiKey) }).catch(\n () => undefined,\n )\n cancelDeadline = Date.now() + cancelGraceMs\n }\n // A cancel that never ends leaves no results to read; every request goes back unanswered.\n if (cancelDeadline !== null && Date.now() >= cancelDeadline) return unfinished(batch.id, 'cancel did not end')\n await new Promise((resolve) => setTimeout(resolve, pollMs))\n const polled = await fetch(`${ANTHROPIC_BATCHES_URL}/${batch.id}`, { headers: batchHeaders(apiKey) })\n if (!polled.ok) throw await batchFailure(polled, model)\n batch = (await polled.json()) as AnthropicBatch\n }\n if (!batch.results_url) {\n if (cancelDeadline !== null) return unfinished(batch.id, 'canceled with no results')\n throw new Error(`batch ${batch.id} ended with no results`)\n }\n const results = await fetch(batch.results_url, { headers: batchHeaders(apiKey) })\n if (!results.ok) throw await batchFailure(results, model)\n const byId = new Map<string, { type?: string; message?: unknown; error?: unknown }>()\n for (const line of (await results.text()).split('\\n')) {\n if (!line.trim()) continue\n const entry = JSON.parse(line) as { custom_id: string; result?: { type?: string; message?: unknown; error?: unknown } }\n byId.set(entry.custom_id, entry.result ?? {})\n }\n return requests.map((request, index) => {\n const result = byId.get(`r${index}`)\n if (result?.type === 'succeeded') {\n return anthropicResultFrom(result.message, request.model, ANTHROPIC_BATCH_PRICE_FACTOR)\n }\n const type = result?.type ?? 'missing'\n if (ANTHROPIC_BATCH_UNFINISHED.has(type) || type === 'missing') return new AiBatchUnfinishedError(batch.id, type)\n console.error('ai batch request not answered', {\n provider: ANTHROPIC_PROVIDER_ID,\n batchId: batch.id,\n customId: `r${index}`,\n result: type,\n error: result?.error ?? null,\n })\n return new AiUpstreamError(null, type === 'errored', batch.id, null)\n })\n}\n\nexport const anthropicProvider: AiProvider = {\n id: ANTHROPIC_PROVIDER_ID,\n label: 'Anthropic',\n apiKeyEnv: ANTHROPIC_API_KEY_ENV,\n readApiKey: () => process.env.ANTHROPIC_API_KEY?.trim() || undefined,\n endpointHost: ANTHROPIC_HOST,\n toolSchemaLimits: ANTHROPIC_TOOL_SCHEMA_LIMITS,\n models(): readonly AiModelDescriptor[] {\n return aiModelIdsForProvider(ANTHROPIC_PROVIDER_ID)\n .map((id) => aiCatalogEntry(id))\n .filter((entry): entry is NonNullable<typeof entry> => Boolean(entry))\n },\n complete,\n stream,\n completeBatch,\n}\n"],"names":["aiModelIdsForProvider","estimateAiBilledUsd","aiCatalogEntry","AiBatchUnfinishedError","AiUpstreamError","aiRawOutputOf","aiStoppedAtCeiling","aiTokenCount","aiToolInputOf","ANTHROPIC_PROVIDER_ID","ANTHROPIC_API_KEY_ENV","ANTHROPIC_HOST","ANTHROPIC_URL","ANTHROPIC_BATCHES_URL","ANTHROPIC_VERSION","ANTHROPIC_MAX_CACHE_BREAKPOINTS","ANTHROPIC_TOOL_SCHEMA_LIMITS","strictTools","optionalParameters","unionParameters","compiledSchemaBytes","anthropicFailureIsRetryable","status","errorType","anthropicAccountProblem","error","type","test","message","anthropicUsageFrom","usage","record","details","thinking","undefined","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","thinkingTokens","ANTHROPIC_THINKING_OFF","ANTHROPIC_DEFAULT_EFFORT","buildAnthropicRequestBody","input","conversation","Boolean","cacheConversation","messages","length","breakpoints","system","filter","block","cacheBreakpoint","Error","cacheControl","cacheTtl","ttl","map","text","cache_control","tools","tool","name","description","input_schema","inputSchema","additionalProperties","strict","settable","model","capabilities","effort","thinkingOff","max_tokens","maxTokens","stream","output_config","index","role","content","anthropicBlocksOf","anthropicContentOf","tool_choice","part","source","media_type","mediaType","data","blocks","last","requestIdOf","response","headers","get","send","body","fetch","method","apiKey","JSON","stringify","signal","ok","payload","requestId","json","catch","console","provider","complete","anthropicResultFrom","raw","priceFactor","Array","isArray","String","join","toolUse","estCostUsd","Math","round","stopReason","stop_reason","kind","rawOutput","streamEvents","decoder","TextDecoder","reader","getReader","Map","written","buffer","drained","lines","done","value","read","decode","split","pop","line","startsWith","event","parse","slice","Number","start","set","delta","partial_json","delete","push","reason","retryable","cancel","releaseLock","values","ANTHROPIC_BATCH_PRICE_FACTOR","batchHeaders","batchFailure","ANTHROPIC_BATCH_UNFINISHED","Set","completeBatch","requests","options","pollMs","timeoutMs","cancelGraceMs","unfinished","id","created","request","_key","_signal","rest","custom_id","params","batch","deadline","Date","now","cancelDeadline","processing_status","warn","Promise","resolve","setTimeout","polled","results_url","results","byId","entry","trim","result","has","batchId","customId","anthropicProvider","label","apiKeyEnv","readApiKey","process","env","ANTHROPIC_API_KEY","endpointHost","toolSchemaLimits","models"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/providers/anthropic.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { aiBilledRatesForModel, aiCatalogEntry, aiModelIdsForProvider, estimateAiBilledUsd } from './catalog'\nimport { aiCacheWriteHourSurchargeUsd } from './cache-write-hour'\nimport {\n AiBatchUnfinishedError,\n AiUpstreamError,\n type AiAccountProblem,\n type AiBatchOptions,\n type AiEffort,\n aiRawOutputOf,\n aiStoppedAtCeiling,\n aiTokenCount,\n aiToolInputOf,\n type AiMessage,\n type AiModelDescriptor,\n type AiProvider,\n type AiProviderRequest,\n type AiResult,\n type AiStreamEvent,\n type AiToolSchemaLimits,\n type AiToolUse,\n type AiUsage,\n} from './contract'\n\n/**\n * The Anthropic adapter (AGL-2903, behind the contract since AGL-2939):\n * the Messages API over raw `fetch`. No provider SDK is installed, and the\n * meter prices by model id from the catalog, so nothing an SDK would add\n * is used here. This file and the catalog are the only places in the\n * plugin that name the vendor's URL, header or model ids.\n *\n * ── Errors (AGL-2815) ─────────────────────────────────────────────────────\n *\n * Provider error text describes OUR account with the vendor — a key, a rate\n * limit, a balance — and names the vendor to a white-label org's users. It\n * never leaves this module as a message: a non-2xx answer becomes an\n * `AiUpstreamError` carrying the status, whether the failure is retryable\n * and a fixed customer-safe sentence; the provider's body goes to\n * `console.error` beside the request id, where an operator can find it.\n * A `refusal` stop is not an error at all — the tokens were spent and the\n * model answered — so it comes back as a typed `AiRefusal` result.\n */\n\nexport const ANTHROPIC_PROVIDER_ID = 'anthropic'\nexport const ANTHROPIC_API_KEY_ENV = 'ANTHROPIC_API_KEY'\nconst ANTHROPIC_HOST = 'api.anthropic.com'\nconst ANTHROPIC_URL = `https://${ANTHROPIC_HOST}/v1/messages`\nconst ANTHROPIC_BATCHES_URL = `https://${ANTHROPIC_HOST}/v1/messages/batches`\nconst ANTHROPIC_VERSION = '2023-06-01'\n\n/** The Messages API accepts at most this many `cache_control` markers. */\nexport const ANTHROPIC_MAX_CACHE_BREAKPOINTS = 4\n\n/**\n * What the Messages API compiles of one request's strict schemas, as its\n * structured-outputs documentation states the limits: 20 strict tools, 24\n * optional parameters and 16 parameters with union types, each a total over\n * every strict schema the request carries. Past any of them the request is\n * refused with a 400 before the model runs.\n *\n * `compiledSchemaBytes` is not one of those. The API also refuses a request\n * whose schemas compile to too large a grammar — \"Simplify your tool schemas\n * or reduce the number of strict tools\" — and states no size for it, so this\n * bound is measured rather than read (AGL-3096). Its evidence is two live\n * requests for the automation draft, the largest strict schema anything here\n * sends:\n *\n * - 8,757 bytes, refused for grammar size (`req_011CfC6Fif9iJvmKPTRgtLdp`,\n * 2026-09-19), with 1 strict tool, 0 optional parameters and 2 unions:\n * every published count was green;\n * - 3,807 bytes, served (`req_011CfELn62WVLeaNzwK4TWgb`, 2026-09-20), the\n * same tool with the guard lifted out of the steps' union and one variant\n * per set of fields.\n *\n * Everything else the plugin sends is smaller still, the next largest being\n * the theme tool at 2,410 bytes. The bound sits at what has been served, so a\n * schema that grows past it has to be proved live before the bound moves.\n */\nexport const ANTHROPIC_TOOL_SCHEMA_LIMITS: AiToolSchemaLimits = {\n strictTools: 20,\n optionalParameters: 24,\n unionParameters: 16,\n compiledSchemaBytes: 3_807,\n}\n\n/**\n * Whether a provider failure is the kind that clears on its own. Matched on\n * the status AND the error type, because the in-stream `error` event has no\n * status to go by.\n */\nexport function anthropicFailureIsRetryable(\n status: number | null,\n errorType: string | undefined,\n): boolean {\n return (\n status === 429 ||\n status === 529 ||\n errorType === 'rate_limit_error' ||\n errorType === 'overloaded_error'\n )\n}\n\n/**\n * Whether a failure is about the platform's Anthropic account rather than the\n * request (AGL-3377): a refused key, or a balance too low to answer.\n */\nexport function anthropicAccountProblem(\n status: number | null,\n error: { type?: string; message?: string } | null | undefined,\n): AiAccountProblem | null {\n if (status === 401 || status === 403) return 'credentials'\n if (error?.type === 'authentication_error' || error?.type === 'permission_error') {\n return 'credentials'\n }\n if (error?.type === 'billing_error' || /credit balance/i.test(error?.message ?? '')) {\n return 'credit'\n }\n return null\n}\n\n/** Anthropic's `usage` object, in the meter's shape. Missing fields read 0. */\nexport function anthropicUsageFrom(usage: unknown): AiUsage {\n const record = (usage ?? {}) as Record<string, unknown>\n // Part of `output_tokens`, and carried only where the response breaks them\n // down, so an answer that reports none says nothing rather than zero.\n const details = record['output_tokens_details']\n const thinking =\n details && typeof details === 'object'\n ? (details as Record<string, unknown>)['thinking_tokens']\n : undefined\n // The writes split by lifetime (AGL-3830). Only an hour-long write is kept,\n // because it is the one billed above the rate table's write rate, and only\n // where there was one, so every five-minute exchange keeps its shape.\n const creation = record['cache_creation']\n const hour =\n creation && typeof creation === 'object'\n ? aiTokenCount((creation as Record<string, unknown>)['ephemeral_1h_input_tokens'])\n : 0\n return {\n inputTokens: aiTokenCount(record['input_tokens']),\n outputTokens: aiTokenCount(record['output_tokens']),\n cacheReadTokens: aiTokenCount(record['cache_read_input_tokens']),\n cacheWriteTokens: aiTokenCount(record['cache_creation_input_tokens']),\n ...(hour > 0 ? { cacheWriteHourTokens: hour } : {}),\n ...(thinking === undefined ? {} : { thinkingTokens: aiTokenCount(thinking) }),\n }\n}\n\n/** What an exchange draws, its hour-long cache writes included (AGL-3830). */\nfunction anthropicBilledUsd(usage: AiUsage, model: string): number {\n const usd = estimateAiBilledUsd(usage, model) + aiCacheWriteHourSurchargeUsd(usage, aiBilledRatesForModel(model))\n return Math.round(usd * 1_000_000) / 1_000_000\n}\n\n/**\n * How \"thinking off\" is said to a model that refuses `{type: \"disabled\"}`.\n * Claude Sonnet 5.5 turns it off only through `between_tools` (accepted at\n * effort high or below, which is every effort the contract names); Claude\n * Opus 5.5 cannot turn it off at all, so the field is left out and the\n * model's adaptive thinking runs. Every other model takes `disabled`.\n */\nconst ANTHROPIC_THINKING_OFF: Readonly<Record<string, 'between_tools' | 'omit'>> = {\n 'claude-sonnet-5-5': 'between_tools',\n 'claude-opus-5-5': 'omit',\n}\n\n/**\n * The effort a request that names none is sent, for a model whose own\n * default is lower than its siblings': Claude Opus 5.5 defaults to medium,\n * where Claude Opus 5 defaults to high.\n */\nconst ANTHROPIC_DEFAULT_EFFORT: Readonly<Record<string, AiEffort>> = {\n 'claude-opus-5-5': 'high',\n}\n\n/**\n * The Messages API body for a request. Exported so a spec can assert the\n * wire shape without a network. The cache-prefix rule is enforced by the\n * runtime before the request reaches any adapter; this maps the surviving\n * breakpoints onto `cache_control` and refuses only the count the API\n * itself refuses.\n */\nexport function buildAnthropicRequestBody(\n input: Omit<AiProviderRequest, 'apiKey' | 'signal'> & { stream: boolean },\n): Record<string, unknown> {\n const conversation = Boolean(input.cacheConversation) && input.messages.length > 0\n const breakpoints = input.system.filter((block) => block.cacheBreakpoint).length + (conversation ? 1 : 0)\n if (breakpoints > ANTHROPIC_MAX_CACHE_BREAKPOINTS) {\n throw new Error(\n `${breakpoints} cache breakpoints; the Messages API allows ${ANTHROPIC_MAX_CACHE_BREAKPOINTS}`,\n )\n }\n // One lifetime for every breakpoint: the API refuses a longer-lived entry\n // after a shorter one, and a request asks for one or the other.\n const cacheControl = input.cacheTtl === '1h' ? { type: 'ephemeral', ttl: '1h' } : { type: 'ephemeral' }\n const system = input.system.map((block) => ({\n type: 'text',\n text: block.text,\n ...(block.cacheBreakpoint ? { cache_control: cacheControl } : {}),\n }))\n const tools = input.tools?.map((tool) => ({\n name: tool.name,\n description: tool.description,\n input_schema: { ...tool.inputSchema, additionalProperties: false },\n strict: true,\n }))\n // A model that rejects an explicit thinking setting gets none, whatever\n // the door asked for, and no effort either: effort rides the same rule\n // (see the contract), and the catalog says which models those are.\n const settable = aiCatalogEntry(input.model)?.capabilities.thinking !== false\n const thinking = settable ? input.thinking : undefined\n const effort = settable ? (input.effort ?? ANTHROPIC_DEFAULT_EFFORT[input.model]) : undefined\n const thinkingOff = ANTHROPIC_THINKING_OFF[input.model] ?? 'disabled'\n return {\n model: input.model,\n max_tokens: input.maxTokens,\n ...(input.stream ? { stream: true } : {}),\n ...(thinking === 'off'\n ? thinkingOff === 'omit'\n ? {}\n : { thinking: { type: thinkingOff } }\n : thinking === 'adaptive'\n ? { thinking: { type: 'adaptive' } }\n : {}),\n ...(effort ? { output_config: { effort } } : {}),\n ...(system.length ? { system } : {}),\n messages: input.messages.map((message, index) => ({\n role: message.role,\n content: conversation\n ? anthropicBlocksOf(message, index === input.messages.length - 1 ? cacheControl : null)\n : anthropicContentOf(message),\n })),\n ...(tools?.length ? { tools, tool_choice: { type: 'auto' } } : {}),\n }\n}\n\n/**\n * A turn's content in the Messages API's shape: a text-only turn as the\n * string it is, and a turn with pictures as content blocks in order, each\n * picture a base64 image block (AGL-2916).\n */\nfunction anthropicContentOf(message: AiMessage): string | Array<Record<string, unknown>> {\n if (typeof message.content === 'string') return message.content\n return message.content.map((part) =>\n part.type === 'image'\n ? { type: 'image', source: { type: 'base64', media_type: part.mediaType, data: part.data } }\n : { type: 'text', text: part.text },\n )\n}\n\n/**\n * A turn as content blocks whatever its shape, the last block carrying the\n * conversation's breakpoint when it is the request's last turn (AGL-3809).\n * Every turn of a request that caches its conversation is sent as blocks, so\n * a turn reads the same on the call that marked it and on every call after,\n * where it is no longer last.\n */\nfunction anthropicBlocksOf(\n message: AiMessage,\n cacheControl: Record<string, unknown> | null,\n): Array<Record<string, unknown>> {\n const content = anthropicContentOf(message)\n const blocks = typeof content === 'string' ? [{ type: 'text', text: content }] : content\n if (!cacheControl || !blocks.length) return blocks\n const last = blocks.length - 1\n return blocks.map((block, index) => (index === last ? { ...block, cache_control: cacheControl } : block))\n}\n\n/** The provider's request id, when it sent one — the log's join key. */\nfunction requestIdOf(response: Response): string | null {\n return response.headers?.get?.('request-id') ?? null\n}\n\nasync function send(\n input: AiProviderRequest,\n stream: boolean,\n): Promise<Response> {\n const body = buildAnthropicRequestBody({ ...input, stream })\n const response = await fetch(ANTHROPIC_URL, {\n method: 'POST',\n headers: {\n 'x-api-key': input.apiKey,\n 'anthropic-version': ANTHROPIC_VERSION,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n ...(input.signal ? { signal: input.signal } : {}),\n })\n if (!response.ok || (stream && !response.body)) {\n const requestId = requestIdOf(response)\n const payload = (await response.json().catch(() => null)) as {\n error?: { type?: string; message?: string }\n } | null\n console.error('ai upstream error', {\n provider: ANTHROPIC_PROVIDER_ID,\n status: response.status,\n requestId,\n model: input.model,\n error: payload?.error ?? null,\n })\n throw new AiUpstreamError(\n response.status,\n anthropicFailureIsRetryable(response.status, payload?.error?.type),\n requestId,\n anthropicAccountProblem(response.status, payload?.error),\n )\n }\n return response\n}\n\nasync function complete(input: AiProviderRequest): Promise<AiResult> {\n const response = await send(input, false)\n return anthropicResultFrom(await response.json(), input.model)\n}\n\n/**\n * A Messages API answer as the contract's result: the shape `complete` reads\n * off the wire and a batch's succeeded entry carries. `priceFactor` is what a\n * discount leaves of the price (a batch's half).\n */\nexport function anthropicResultFrom(raw: unknown, model: string, priceFactor = 1): AiResult {\n const payload = raw as {\n content?: Array<Record<string, unknown>>\n stop_reason?: unknown\n usage?: unknown\n } | null\n const blocks = Array.isArray(payload?.content) ? payload.content : []\n const text = blocks\n .filter((block) => block?.['type'] === 'text')\n .map((block) => String(block['text'] ?? ''))\n .join('')\n const toolUse: AiToolUse[] = blocks\n .filter((block) => block?.['type'] === 'tool_use')\n .map((block) => ({\n name: String(block['name'] ?? ''),\n input: aiToolInputOf(block['input']),\n }))\n const usage = anthropicUsageFrom(payload?.usage)\n const estCostUsd = Math.round(anthropicBilledUsd(usage, model) * priceFactor * 1_000_000) / 1_000_000\n const stopReason =\n typeof payload?.stop_reason === 'string' && payload.stop_reason\n ? payload.stop_reason\n : null\n if (stopReason === 'refusal') {\n return { kind: 'refusal', text, usage, estCostUsd, stopReason }\n }\n return {\n kind: 'completion',\n text,\n toolUse,\n usage,\n estCostUsd,\n stopReason,\n // The blocks as the provider answered them, for a call its ceiling cut\n // off (AGL-3143): `toolUse` holds only what still parsed out of them.\n ...(aiStoppedAtCeiling(stopReason) ? { rawOutput: aiRawOutputOf(blocks) } : {}),\n }\n}\n\nasync function stream(input: AiProviderRequest): Promise<AsyncIterable<AiStreamEvent>> {\n const response = await send(input, true)\n return streamEvents(\n response.body as ReadableStream<Uint8Array>,\n input.model,\n requestIdOf(response),\n )\n}\n\n/**\n * Anthropic's SSE stream as contract events. Usage is taken from\n * `message_start` (input and cache figures) and `message_delta` (the\n * output count, and any cumulative input figures a newer API sends); the\n * `done` event carries whatever was known when the body ended, so a stream\n * cut short still meters what it cost.\n */\nasync function* streamEvents(\n body: ReadableStream<Uint8Array>,\n model: string,\n requestId: string | null,\n): AsyncGenerator<AiStreamEvent> {\n const decoder = new TextDecoder()\n const reader = body.getReader()\n const usage: AiUsage = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n }\n let stopReason: string | null = null\n /** Tool-use blocks in flight, by content index; input arrives in pieces. */\n const tools = new Map<number, { name: string; json: string }>()\n /**\n * What each tool call was actually written in, in the order its block\n * opened (AGL-3143). The `tool` events below carry only as much of these\n * as `aiToolInputOf` could still parse, so for a call the ceiling cut off\n * this is the one place the emitted bytes survive.\n */\n const written: string[] = []\n let buffer = ''\n let drained = false\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) {\n drained = true\n break\n }\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue\n let event: Record<string, unknown>\n try {\n event = JSON.parse(line.slice('data: '.length))\n } catch {\n continue\n }\n const index = Number(event['index'])\n switch (event['type']) {\n case 'message_start': {\n const start = anthropicUsageFrom(\n (event['message'] as { usage?: unknown } | undefined)?.usage,\n )\n usage.inputTokens = start.inputTokens\n usage.cacheReadTokens = start.cacheReadTokens\n usage.cacheWriteTokens = start.cacheWriteTokens\n if (start.cacheWriteHourTokens) usage.cacheWriteHourTokens = start.cacheWriteHourTokens\n break\n }\n case 'content_block_start': {\n const block = event['content_block'] as\n | { type?: string; name?: string }\n | undefined\n if (block?.type === 'tool_use') {\n tools.set(index, { name: String(block.name ?? ''), json: '' })\n }\n break\n }\n case 'content_block_delta': {\n const delta = event['delta'] as\n | { type?: string; text?: string; partial_json?: string }\n | undefined\n if (delta?.type === 'text_delta' && delta.text) {\n yield { type: 'delta', text: delta.text }\n } else if (delta?.type === 'input_json_delta') {\n const tool = tools.get(index)\n if (tool) tool.json += delta.partial_json ?? ''\n }\n break\n }\n case 'content_block_stop': {\n const tool = tools.get(index)\n if (tool) {\n tools.delete(index)\n written.push(tool.json)\n yield { type: 'tool', name: tool.name, input: aiToolInputOf(tool.json) }\n }\n break\n }\n case 'message_delta': {\n const delta = anthropicUsageFrom(event['usage'])\n usage.outputTokens = delta.outputTokens || usage.outputTokens\n usage.inputTokens = delta.inputTokens || usage.inputTokens\n usage.cacheReadTokens = delta.cacheReadTokens || usage.cacheReadTokens\n usage.cacheWriteTokens = delta.cacheWriteTokens || usage.cacheWriteTokens\n // Kept where the breakdown is reported and left absent where it\n // is not (AGL-3143), never set to a zero the provider did not\n // send. A stream needs it for the reason a completion does: it\n // is the figure that accounts for a spent ceiling first, and it\n // arrives in the same usage object as the four above.\n if (delta.thinkingTokens !== undefined) usage.thinkingTokens = delta.thinkingTokens\n const reason = (event['delta'] as { stop_reason?: unknown } | undefined)\n ?.stop_reason\n if (typeof reason === 'string' && reason) stopReason = reason\n break\n }\n case 'error': {\n const error = event['error'] as\n | { type?: string; message?: string }\n | undefined\n console.error('ai upstream stream error', {\n provider: ANTHROPIC_PROVIDER_ID,\n requestId,\n model,\n error: error ?? null,\n })\n yield { type: 'error', retryable: anthropicFailureIsRetryable(null, error?.type) }\n break\n }\n default:\n break\n }\n }\n }\n } finally {\n // A consumer that stops iterating early — a client that hung up — must\n // not leave the provider generating into a socket nobody reads: cancel\n // tells the upstream to stop, and the tokens it has already produced are\n // the only ones billed.\n if (!drained) await reader.cancel().catch(() => undefined)\n reader.releaseLock()\n }\n // A message the ceiling ends mid-block never reaches that block's stop\n // event, so what it had written is still held in flight.\n for (const tool of tools.values()) written.push(tool.json)\n yield {\n type: 'done',\n usage,\n estCostUsd: anthropicBilledUsd(usage, model),\n stopReason,\n // What the model emitted into its tool calls, for a stream its ceiling\n // cut off (AGL-3143): the `tool` events carry only what parsed out.\n ...(aiStoppedAtCeiling(stopReason) ? { rawOutput: aiRawOutputOf(written.join('')) } : {}),\n }\n}\n\n/** What a batch's discount leaves of a request's price. */\nexport const ANTHROPIC_BATCH_PRICE_FACTOR = 0.5\n\nfunction batchHeaders(apiKey: string): Record<string, string> {\n return {\n 'x-api-key': apiKey,\n 'anthropic-version': ANTHROPIC_VERSION,\n 'Content-Type': 'application/json',\n }\n}\n\n/** A batch call that did not answer 2xx, as the contract's error. */\nasync function batchFailure(response: Response, model: string): Promise<AiUpstreamError> {\n const requestId = requestIdOf(response)\n const payload = (await response.json().catch(() => null)) as {\n error?: { type?: string; message?: string }\n } | null\n console.error('ai upstream batch error', {\n provider: ANTHROPIC_PROVIDER_ID,\n status: response.status,\n requestId,\n model,\n error: payload?.error ?? null,\n })\n return new AiUpstreamError(\n response.status,\n anthropicFailureIsRetryable(response.status, payload?.error?.type),\n requestId,\n anthropicAccountProblem(response.status, payload?.error),\n )\n}\n\n/** The batch as its create and poll calls describe it. */\ninterface AnthropicBatch {\n id: string\n processing_status?: string\n results_url?: string | null\n}\n\n/** Results a batch can end a request with that answer nothing yet: asked again, not failed (AGL-3781). */\nconst ANTHROPIC_BATCH_UNFINISHED = new Set(['canceled', 'expired'])\n\n/**\n * Many requests through the Message Batches API (AGL-3660): created as one\n * batch, polled until it has ended, its results read by `custom_id` — they\n * come back in any order — and each turned into the result `complete` would\n * have returned, at half the price. Development only: the live evals' batch\n * mode is the one caller, and no door waits minutes for an answer.\n *\n * A batch still processing when its wait runs out is canceled, and read once\n * the cancel has ended it (AGL-3781): a cancel keeps every answer the batch\n * already has, so those come back as results, and only the requests it never\n * reached come back as `AiBatchUnfinishedError`, for the caller to ask on\n * their own. Throwing away a round's paid-for answers because one batch was\n * slow is how a whole golden run lost its grades.\n */\nasync function completeBatch(\n requests: readonly AiProviderRequest[],\n options: AiBatchOptions = {},\n): Promise<Array<AiResult | Error>> {\n if (!requests.length) return []\n const apiKey = requests[0].apiKey\n const model = requests[0].model\n const pollMs = options.pollMs ?? 10_000\n const timeoutMs = options.timeoutMs ?? 60 * 60_000\n const cancelGraceMs = options.cancelGraceMs ?? 10 * 60_000\n const unfinished = (id: string, reason: string) => requests.map(() => new AiBatchUnfinishedError(id, reason))\n const created = await fetch(ANTHROPIC_BATCHES_URL, {\n method: 'POST',\n headers: batchHeaders(apiKey),\n body: JSON.stringify({\n requests: requests.map((request, index) => {\n const { apiKey: _key, signal: _signal, ...rest } = request\n return { custom_id: `r${index}`, params: buildAnthropicRequestBody({ ...rest, stream: false }) }\n }),\n }),\n })\n if (!created.ok) throw await batchFailure(created, model)\n let batch = (await created.json()) as AnthropicBatch\n const deadline = Date.now() + timeoutMs\n let cancelDeadline: number | null = null\n while (batch.processing_status !== 'ended') {\n if (cancelDeadline === null && Date.now() >= deadline) {\n console.warn(`[ai] batch ${batch.id} has not ended after ${timeoutMs} ms; canceling it to read what it answered`)\n await fetch(`${ANTHROPIC_BATCHES_URL}/${batch.id}/cancel`, { method: 'POST', headers: batchHeaders(apiKey) }).catch(\n () => undefined,\n )\n cancelDeadline = Date.now() + cancelGraceMs\n }\n // A cancel that never ends leaves no results to read; every request goes back unanswered.\n if (cancelDeadline !== null && Date.now() >= cancelDeadline) return unfinished(batch.id, 'cancel did not end')\n await new Promise((resolve) => setTimeout(resolve, pollMs))\n const polled = await fetch(`${ANTHROPIC_BATCHES_URL}/${batch.id}`, { headers: batchHeaders(apiKey) })\n if (!polled.ok) throw await batchFailure(polled, model)\n batch = (await polled.json()) as AnthropicBatch\n }\n if (!batch.results_url) {\n if (cancelDeadline !== null) return unfinished(batch.id, 'canceled with no results')\n throw new Error(`batch ${batch.id} ended with no results`)\n }\n const results = await fetch(batch.results_url, { headers: batchHeaders(apiKey) })\n if (!results.ok) throw await batchFailure(results, model)\n const byId = new Map<string, { type?: string; message?: unknown; error?: unknown }>()\n for (const line of (await results.text()).split('\\n')) {\n if (!line.trim()) continue\n const entry = JSON.parse(line) as { custom_id: string; result?: { type?: string; message?: unknown; error?: unknown } }\n byId.set(entry.custom_id, entry.result ?? {})\n }\n return requests.map((request, index) => {\n const result = byId.get(`r${index}`)\n if (result?.type === 'succeeded') {\n return anthropicResultFrom(result.message, request.model, ANTHROPIC_BATCH_PRICE_FACTOR)\n }\n const type = result?.type ?? 'missing'\n if (ANTHROPIC_BATCH_UNFINISHED.has(type) || type === 'missing') return new AiBatchUnfinishedError(batch.id, type)\n console.error('ai batch request not answered', {\n provider: ANTHROPIC_PROVIDER_ID,\n batchId: batch.id,\n customId: `r${index}`,\n result: type,\n error: result?.error ?? null,\n })\n return new AiUpstreamError(null, type === 'errored', batch.id, null)\n })\n}\n\nexport const anthropicProvider: AiProvider = {\n id: ANTHROPIC_PROVIDER_ID,\n label: 'Anthropic',\n apiKeyEnv: ANTHROPIC_API_KEY_ENV,\n readApiKey: () => process.env.ANTHROPIC_API_KEY?.trim() || undefined,\n endpointHost: ANTHROPIC_HOST,\n toolSchemaLimits: ANTHROPIC_TOOL_SCHEMA_LIMITS,\n models(): readonly AiModelDescriptor[] {\n return aiModelIdsForProvider(ANTHROPIC_PROVIDER_ID)\n .map((id) => aiCatalogEntry(id))\n .filter((entry): entry is NonNullable<typeof entry> => Boolean(entry))\n },\n complete,\n stream,\n completeBatch,\n}\n"],"names":["aiBilledRatesForModel","aiCatalogEntry","aiModelIdsForProvider","estimateAiBilledUsd","aiCacheWriteHourSurchargeUsd","AiBatchUnfinishedError","AiUpstreamError","aiRawOutputOf","aiStoppedAtCeiling","aiTokenCount","aiToolInputOf","ANTHROPIC_PROVIDER_ID","ANTHROPIC_API_KEY_ENV","ANTHROPIC_HOST","ANTHROPIC_URL","ANTHROPIC_BATCHES_URL","ANTHROPIC_VERSION","ANTHROPIC_MAX_CACHE_BREAKPOINTS","ANTHROPIC_TOOL_SCHEMA_LIMITS","strictTools","optionalParameters","unionParameters","compiledSchemaBytes","anthropicFailureIsRetryable","status","errorType","anthropicAccountProblem","error","type","test","message","anthropicUsageFrom","usage","record","details","thinking","undefined","creation","hour","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","cacheWriteHourTokens","thinkingTokens","anthropicBilledUsd","model","usd","Math","round","ANTHROPIC_THINKING_OFF","ANTHROPIC_DEFAULT_EFFORT","buildAnthropicRequestBody","input","conversation","Boolean","cacheConversation","messages","length","breakpoints","system","filter","block","cacheBreakpoint","Error","cacheControl","cacheTtl","ttl","map","text","cache_control","tools","tool","name","description","input_schema","inputSchema","additionalProperties","strict","settable","capabilities","effort","thinkingOff","max_tokens","maxTokens","stream","output_config","index","role","content","anthropicBlocksOf","anthropicContentOf","tool_choice","part","source","media_type","mediaType","data","blocks","last","requestIdOf","response","headers","get","send","body","fetch","method","apiKey","JSON","stringify","signal","ok","payload","requestId","json","catch","console","provider","complete","anthropicResultFrom","raw","priceFactor","Array","isArray","String","join","toolUse","estCostUsd","stopReason","stop_reason","kind","rawOutput","streamEvents","decoder","TextDecoder","reader","getReader","Map","written","buffer","drained","lines","done","value","read","decode","split","pop","line","startsWith","event","parse","slice","Number","start","set","delta","partial_json","delete","push","reason","retryable","cancel","releaseLock","values","ANTHROPIC_BATCH_PRICE_FACTOR","batchHeaders","batchFailure","ANTHROPIC_BATCH_UNFINISHED","Set","completeBatch","requests","options","pollMs","timeoutMs","cancelGraceMs","unfinished","id","created","request","_key","_signal","rest","custom_id","params","batch","deadline","Date","now","cancelDeadline","processing_status","warn","Promise","resolve","setTimeout","polled","results_url","results","byId","entry","trim","result","has","batchId","customId","anthropicProvider","label","apiKeyEnv","readApiKey","process","env","ANTHROPIC_API_KEY","endpointHost","toolSchemaLimits","models"],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { AiTokenRates } from './catalog';
18
+ import type { AiUsage } from './contract';
19
+ /**
20
+ * What an hour-long cache entry costs to write on top of what the rates
21
+ * price it at (AGL-3830). A rate table carries one write rate, the
22
+ * five-minute entry's 1.25 times the input price; the provider bills an
23
+ * hour-long entry at {@link AI_CACHE_WRITE_HOUR_FACTOR} times, so each token
24
+ * of `cacheWriteHourTokens` owes the difference. Kept out of the four-figure
25
+ * price on purpose: only a server reader can be handed an hour-long write,
26
+ * and the model selector that prices nominal usage never is. Its own module,
27
+ * not the catalog's, because the catalog loads on every console page and a
28
+ * helper beside it would load there too.
29
+ */
30
+ export declare const AI_CACHE_WRITE_HOUR_FACTOR = 2;
31
+ export declare function aiCacheWriteHourSurchargeUsd(usage: Pick<AiUsage, 'cacheWriteHourTokens'>, rate: AiTokenRates): number;
32
+ /**
33
+ * The hour-long writes of two exchanges added up, as a spread: absent where
34
+ * neither reported any, so a usage object that never had the field keeps
35
+ * the shape it had.
36
+ */
37
+ export declare function aiCacheWriteHourSum(a: Pick<AiUsage, 'cacheWriteHourTokens'>, b: Pick<AiUsage, 'cacheWriteHourTokens'>): Pick<AiUsage, 'cacheWriteHourTokens'>;