@aglyn/plugins-ai 1.0.0-beta.254 → 1.0.0-beta.256
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/components/assist-signals-page.component.js +5 -6
- package/src/lib/components/assist-signals-page.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-card.component.d.ts +0 -10
- package/src/lib/components/staff-org-ai-card.component.js +10 -79
- package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-conversations.component.js +8 -41
- package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -1
- package/src/lib/components/staff-user-ai-usage-card.component.js +2 -1
- package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +14 -1
- package/src/lib/jobs/ai-job-page-language.js +18 -3
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +17 -3
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.js +4 -0
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +30 -0
- package/src/lib/jobs/ai-layout-stock-photos.js +76 -8
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-dental-site-plan-recording.d.ts +95 -0
- package/src/lib/jobs/fixtures/ai-dental-site-plan-recording.js +137 -0
- package/src/lib/jobs/fixtures/ai-dental-site-plan-recording.js.map +1 -0
- package/src/lib/layout-language/ai-layout-labels.d.ts +2 -0
- package/src/lib/layout-language/ai-layout-labels.js +12 -6
- package/src/lib/layout-language/ai-layout-labels.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +12 -3
- package/src/lib/layout-language/ai-layout-pictures.js +6 -3
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-products-bulk.d.ts +48 -0
- package/src/lib/model/ai-products-bulk.js +56 -0
- package/src/lib/model/ai-products-bulk.js.map +1 -0
- package/src/lib/model/ai-products.d.ts +2 -4
- package/src/lib/model/ai-products.js +4 -9
- package/src/lib/model/ai-products.js.map +1 -1
- package/src/lib/providers/anthropic.js +26 -5
- package/src/lib/providers/anthropic.js.map +1 -1
- package/src/lib/providers/contract.d.ts +10 -0
- package/src/lib/providers/contract.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +20 -0
- package/src/lib/runtime/ai-doctrine-validators.js +50 -0
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.d.ts +11 -0
- package/src/lib/runtime/ai-doctrine.js +9 -4
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-runtime.d.ts +6 -0
- package/src/lib/runtime/ai-runtime.js +10 -0
- package/src/lib/runtime/ai-runtime.js.map +1 -1
- package/src/lib/server/ai-admin-conversations.d.ts +2 -0
- package/src/lib/server/ai-admin-conversations.js +40 -7
- package/src/lib/server/ai-admin-conversations.js.map +1 -1
- package/src/lib/server/ai-admin-org.js +28 -2
- package/src/lib/server/ai-admin-org.js.map +1 -1
- package/src/lib/server/ai-admin-orgs-spend.js +19 -1
- package/src/lib/server/ai-admin-orgs-spend.js.map +1 -1
- package/src/lib/server/ai-admin-signals.js +11 -3
- package/src/lib/server/ai-admin-signals.js.map +1 -1
- package/src/lib/server/ai-admin-user.js +11 -1
- package/src/lib/server/ai-admin-user.js.map +1 -1
- package/src/lib/usage/ai-usage-wire.d.ts +5 -2
- package/src/lib/usage/ai-usage-wire.js +5 -2
- package/src/lib/usage/ai-usage-wire.js.map +1 -1
- package/src/lib/usage/assist-signals-view.d.ts +123 -0
- package/src/lib/usage/assist-signals-view.js +151 -0
- package/src/lib/usage/assist-signals-view.js.map +1 -0
- package/src/lib/usage/staff-org-ai-conversations-view.d.ts +51 -0
- package/src/lib/usage/staff-org-ai-conversations-view.js +127 -0
- package/src/lib/usage/staff-org-ai-conversations-view.js.map +1 -0
- package/src/lib/usage/staff-org-ai-view.d.ts +181 -0
- package/src/lib/usage/staff-org-ai-view.js +263 -0
- package/src/lib/usage/staff-org-ai-view.js.map +1 -0
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
* THE BULK COPY JOB AN IMPORT ASKS FOR (AGL-2916, AGL-3671) — the pure half of
|
|
18
|
+
* "write descriptions with AI as they land", with no import at all, so the
|
|
19
|
+
* native apps replay it (`aiProductsImportJob`) beside the console's products
|
|
20
|
+
* hub card, which starts the very same job.
|
|
21
|
+
*
|
|
22
|
+
* A product import that asked for its copy leaves one `products` job for the
|
|
23
|
+
* products it CREATED: the first `AI_PRODUCTS_BULK_MAX` of them, their ids
|
|
24
|
+
* read from the import job's results (`api/transfer/status`), one brief that
|
|
25
|
+
* names how many, and `inputs` that carry the ids.
|
|
26
|
+
*/ /** The most products one bulk job writes the copy of. */ export const AI_PRODUCTS_BULK_MAX = 50;
|
|
27
|
+
const PRODUCT_ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
28
|
+
/** The brief a bulk copy job carries, which the jobs list shows and a card recognizes it by. */ export const aiProductsBulkBrief = (count)=>`Write storefront copy for ${count} ${count === 1 ? 'product' : 'products'}`;
|
|
29
|
+
/** A bulk job's `inputs`: the product ids that can be read back, once each, at most `AI_PRODUCTS_BULK_MAX`. */ export function aiProductsBulkInputs(productIds) {
|
|
30
|
+
return {
|
|
31
|
+
target: 'bulk',
|
|
32
|
+
productIds: [
|
|
33
|
+
...new Set(productIds.filter((id)=>PRODUCT_ID.test(id)))
|
|
34
|
+
].slice(0, AI_PRODUCTS_BULK_MAX).join(',')
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** The products an applied import created, once each and in the order the results list them. */ export function createdProductIdsOf(rows) {
|
|
38
|
+
const ids = [];
|
|
39
|
+
for (const row of rows != null ? rows : []){
|
|
40
|
+
if ((row == null ? void 0 : row.outcome) === 'created' && row.recordId && !ids.includes(row.recordId)) ids.push(row.recordId);
|
|
41
|
+
}
|
|
42
|
+
return ids;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The copy job a landed import starts — the brief and the `inputs` the jobs route takes — for the first
|
|
46
|
+
* `AI_PRODUCTS_BULK_MAX` products it created; `null` when it created none.
|
|
47
|
+
*/ export function aiProductsImportJob(rows) {
|
|
48
|
+
const ids = createdProductIdsOf(rows).slice(0, AI_PRODUCTS_BULK_MAX);
|
|
49
|
+
if (!ids.length) return null;
|
|
50
|
+
return {
|
|
51
|
+
brief: aiProductsBulkBrief(ids.length),
|
|
52
|
+
inputs: aiProductsBulkInputs(ids)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
//# sourceMappingURL=ai-products-bulk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-products-bulk.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\n/**\n * THE BULK COPY JOB AN IMPORT ASKS FOR (AGL-2916, AGL-3671) — the pure half of\n * \"write descriptions with AI as they land\", with no import at all, so the\n * native apps replay it (`aiProductsImportJob`) beside the console's products\n * hub card, which starts the very same job.\n *\n * A product import that asked for its copy leaves one `products` job for the\n * products it CREATED: the first `AI_PRODUCTS_BULK_MAX` of them, their ids\n * read from the import job's results (`api/transfer/status`), one brief that\n * names how many, and `inputs` that carry the ids.\n */\n\n/** The most products one bulk job writes the copy of. */\nexport const AI_PRODUCTS_BULK_MAX = 50\n\nconst PRODUCT_ID = /^[A-Za-z0-9_-]{1,64}$/\n\n/** The brief a bulk copy job carries, which the jobs list shows and a card recognizes it by. */\nexport const aiProductsBulkBrief = (count: number): string =>\n `Write storefront copy for ${count} ${count === 1 ? 'product' : 'products'}`\n\n/** A bulk job's `inputs`: the product ids that can be read back, once each, at most `AI_PRODUCTS_BULK_MAX`. */\nexport function aiProductsBulkInputs(productIds: readonly string[]): Record<string, string> {\n return {\n target: 'bulk',\n productIds: [...new Set(productIds.filter((id) => PRODUCT_ID.test(id)))].slice(0, AI_PRODUCTS_BULK_MAX).join(','),\n }\n}\n\n/** One row of an import job's results. */\nexport interface ImportResultRow {\n outcome?: string\n recordId?: string\n}\n\n/** The products an applied import created, once each and in the order the results list them. */\nexport function createdProductIdsOf(rows: readonly ImportResultRow[] | null | undefined): string[] {\n const ids: string[] = []\n for (const row of rows ?? []) {\n if (row?.outcome === 'created' && row.recordId && !ids.includes(row.recordId)) ids.push(row.recordId)\n }\n return ids\n}\n\n/**\n * The copy job a landed import starts — the brief and the `inputs` the jobs route takes — for the first\n * `AI_PRODUCTS_BULK_MAX` products it created; `null` when it created none.\n */\nexport function aiProductsImportJob(\n rows: readonly ImportResultRow[] | null | undefined,\n): { brief: string; inputs: Record<string, string> } | null {\n const ids = createdProductIdsOf(rows).slice(0, AI_PRODUCTS_BULK_MAX)\n if (!ids.length) return null\n return { brief: aiProductsBulkBrief(ids.length), inputs: aiProductsBulkInputs(ids) }\n}\n"],"names":["AI_PRODUCTS_BULK_MAX","PRODUCT_ID","aiProductsBulkBrief","count","aiProductsBulkInputs","productIds","target","Set","filter","id","test","slice","join","createdProductIdsOf","rows","ids","row","outcome","recordId","includes","push","aiProductsImportJob","length","brief","inputs"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;CAUC,GAED,uDAAuD,GACvD,OAAO,MAAMA,uBAAuB,GAAE;AAEtC,MAAMC,aAAa;AAEnB,8FAA8F,GAC9F,OAAO,MAAMC,sBAAsB,CAACC,QAClC,CAAC,0BAA0B,EAAEA,MAAM,CAAC,EAAEA,UAAU,IAAI,YAAY,YAAY,CAAA;AAE9E,6GAA6G,GAC7G,OAAO,SAASC,qBAAqBC,UAA6B;IAChE,OAAO;QACLC,QAAQ;QACRD,YAAY;eAAI,IAAIE,IAAIF,WAAWG,MAAM,CAAC,CAACC,KAAOR,WAAWS,IAAI,CAACD;SAAM,CAACE,KAAK,CAAC,GAAGX,sBAAsBY,IAAI,CAAC;IAC/G;AACF;AAQA,8FAA8F,GAC9F,OAAO,SAASC,oBAAoBC,IAAmD;IACrF,MAAMC,MAAgB,EAAE;IACxB,KAAK,MAAMC,OAAOF,eAAAA,OAAQ,EAAE,CAAE;QAC5B,IAAIE,CAAAA,uBAAAA,IAAKC,OAAO,MAAK,aAAaD,IAAIE,QAAQ,IAAI,CAACH,IAAII,QAAQ,CAACH,IAAIE,QAAQ,GAAGH,IAAIK,IAAI,CAACJ,IAAIE,QAAQ;IACtG;IACA,OAAOH;AACT;AAEA;;;CAGC,GACD,OAAO,SAASM,oBACdP,IAAmD;IAEnD,MAAMC,MAAMF,oBAAoBC,MAAMH,KAAK,CAAC,GAAGX;IAC/C,IAAI,CAACe,IAAIO,MAAM,EAAE,OAAO;IACxB,OAAO;QAAEC,OAAOrB,oBAAoBa,IAAIO,MAAM;QAAGE,QAAQpB,qBAAqBW;IAAK;AACrF"}
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
import type { AiJobOutput, AiJobSummary } from './ai-jobs.types';
|
|
18
|
+
import { AI_PRODUCTS_BULK_MAX, aiProductsBulkBrief } from './ai-products-bulk';
|
|
19
|
+
export { AI_PRODUCTS_BULK_MAX, aiProductsBulkBrief };
|
|
18
20
|
/**
|
|
19
21
|
* COMMERCE BY AI (AGL-2916): what a `products` job is asked, and what it
|
|
20
22
|
* proposes. Pure, so the step that asks, the console cards that start a job
|
|
@@ -57,8 +59,6 @@ export declare const AI_PRODUCT_CATEGORY_LIST_MAX = 100;
|
|
|
57
59
|
export declare const AI_PRODUCT_OPTIONS_MAX = 3;
|
|
58
60
|
export declare const AI_PRODUCT_OPTION_VALUES_MAX = 25;
|
|
59
61
|
export declare const AI_PRODUCT_OPTION_NAME_MAX_CHARS = 30;
|
|
60
|
-
/** The most products one bulk job writes the copy of. */
|
|
61
|
-
export declare const AI_PRODUCTS_BULK_MAX = 50;
|
|
62
62
|
/** How many products a catalog proposal holds. */
|
|
63
63
|
export declare const AI_CATALOG_PRODUCTS: {
|
|
64
64
|
readonly min: 1;
|
|
@@ -275,7 +275,5 @@ export declare function aiProductsProposalOfKind<K extends AiProductsProposal['k
|
|
|
275
275
|
export declare function aiProductGaps(...texts: readonly string[]): string[];
|
|
276
276
|
/** The option the import zone sets to write the imported products' copy. */
|
|
277
277
|
export declare const AI_PRODUCTS_IMPORT_WRITE_COPY = "ai.writeCopy";
|
|
278
|
-
/** The brief a bulk copy job carries, which the jobs list shows and a card recognizes it by. */
|
|
279
|
-
export declare const aiProductsBulkBrief: (count: number) => string;
|
|
280
278
|
/** How many products a bulk copy job was asked for, read from its brief; `null` for any other job. */
|
|
281
279
|
export declare function aiProductsBulkCount(job: Pick<AiJobSummary, 'brief'> | null | undefined): number | null;
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
|
-
*/
|
|
16
|
+
*/ import { AI_PRODUCTS_BULK_MAX, aiProductsBulkBrief, aiProductsBulkInputs } from "./ai-products-bulk.js";
|
|
17
|
+
export { AI_PRODUCTS_BULK_MAX, aiProductsBulkBrief };
|
|
18
|
+
/**
|
|
17
19
|
* COMMERCE BY AI (AGL-2916): what a `products` job is asked, and what it
|
|
18
20
|
* proposes. Pure, so the step that asks, the console cards that start a job
|
|
19
21
|
* and show its proposals, and the eval harness all read one shape.
|
|
@@ -55,7 +57,6 @@ export const AI_PRODUCT_TAG_MAX_CHARS = 30;
|
|
|
55
57
|
/** A product's option axes, as the commerce editor bounds them. */ export const AI_PRODUCT_OPTIONS_MAX = 3;
|
|
56
58
|
export const AI_PRODUCT_OPTION_VALUES_MAX = 25;
|
|
57
59
|
export const AI_PRODUCT_OPTION_NAME_MAX_CHARS = 30;
|
|
58
|
-
/** The most products one bulk job writes the copy of. */ export const AI_PRODUCTS_BULK_MAX = 50;
|
|
59
60
|
/** How many products a catalog proposal holds. */ export const AI_CATALOG_PRODUCTS = {
|
|
60
61
|
min: 1,
|
|
61
62
|
max: 12
|
|
@@ -181,12 +182,7 @@ function jsonOptions(value) {
|
|
|
181
182
|
return inputs;
|
|
182
183
|
}
|
|
183
184
|
case 'bulk':
|
|
184
|
-
return
|
|
185
|
-
target: 'bulk',
|
|
186
|
-
productIds: [
|
|
187
|
-
...new Set(input.productIds.filter((id)=>ID.test(id)))
|
|
188
|
-
].slice(0, AI_PRODUCTS_BULK_MAX).join(',')
|
|
189
|
-
};
|
|
185
|
+
return aiProductsBulkInputs(input.productIds);
|
|
190
186
|
default:
|
|
191
187
|
return {
|
|
192
188
|
target: input.target
|
|
@@ -275,7 +271,6 @@ const isRecord = (value)=>typeof value === 'object' && value !== null && !Array.
|
|
|
275
271
|
/* ------------------------------------------------------------------------ *
|
|
276
272
|
* The console's cards
|
|
277
273
|
* ------------------------------------------------------------------------ */ /** The option the import zone sets to write the imported products' copy. */ export const AI_PRODUCTS_IMPORT_WRITE_COPY = 'ai.writeCopy';
|
|
278
|
-
/** The brief a bulk copy job carries, which the jobs list shows and a card recognizes it by. */ export const aiProductsBulkBrief = (count)=>`Write storefront copy for ${count} ${count === 1 ? 'product' : 'products'}`;
|
|
279
274
|
const BULK_BRIEF = /^Write storefront copy for (\d+) products?$/;
|
|
280
275
|
/** How many products a bulk copy job was asked for, read from its brief; `null` for any other job. */ export function aiProductsBulkCount(job) {
|
|
281
276
|
var _ref;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-products.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 type { AiJobOutput, AiJobSummary } from './ai-jobs.types'\n\n/**\n * COMMERCE BY AI (AGL-2916): what a `products` job is asked, and what it\n * proposes. Pure, so the step that asks, the console cards that start a job\n * and show its proposals, and the eval harness all read one shape.\n *\n * `inputs.target` names the work:\n *\n * - `product` — one product's copy, from what its editor holds right now:\n * the name, the text, the tags, the categories and options it has, and the\n * first photo. The editor may hold a product nobody has saved yet, so the\n * job never reads a product document for this target.\n * - `bulk` — the copy of up to `AI_PRODUCTS_BULK_MAX` saved products, one\n * product a pass, each read from the catalog as it is reached.\n * - `catalog` — a store's first products from the brief: names, copy, tags\n * and options, what each photo should show, and a suggested price by what\n * the product is (`aiCatalogPrice`, Zach 2026-10-12). No picture.\n * - `categories` — store categories and a first set of discounts from the brief.\n *\n * Every output is a PROPOSAL (`resource: 'product'`, with `proposal`), and the\n * job writes nothing. The commerce plugin's own surfaces write what a person\n * accepts, through the doors those surfaces already use.\n */\n\nexport const AI_PRODUCTS_TARGETS = ['product', 'bulk', 'catalog', 'categories'] as const\nexport type AiProductsTarget = (typeof AI_PRODUCTS_TARGETS)[number]\n\nexport const AI_PRODUCT_TYPES = ['physical', 'digital', 'service'] as const\nexport type AiProductType = (typeof AI_PRODUCT_TYPES)[number]\n\n/** The longest product name the editor saves. */\nexport const AI_PRODUCT_NAME_MAX_CHARS = 120\n\n/** How much of a product's existing description a request carries. */\nexport const AI_PRODUCT_TEXT_MAX_CHARS = 2_000\n\n/** The longest description the job proposes. */\nexport const AI_PRODUCT_DESCRIPTION_MAX_CHARS = 1_200\n\nexport const AI_PRODUCT_TAGS_MAX = 8\nexport const AI_PRODUCT_TAG_MAX_CHARS = 30\n\n/** The most categories one product's proposal names. */\nexport const AI_PRODUCT_CATEGORIES_MAX = 3\n\n/** The most of a site's categories a request lists to choose from. */\nexport const AI_PRODUCT_CATEGORY_LIST_MAX = 100\n\n/** A product's option axes, as the commerce editor bounds them. */\nexport const AI_PRODUCT_OPTIONS_MAX = 3\nexport const AI_PRODUCT_OPTION_VALUES_MAX = 25\nexport const AI_PRODUCT_OPTION_NAME_MAX_CHARS = 30\n\n/** The most products one bulk job writes the copy of. */\nexport const AI_PRODUCTS_BULK_MAX = 50\n\n/** How many products a catalog proposal holds. */\nexport const AI_CATALOG_PRODUCTS = { min: 1, max: 12 } as const\n\n/**\n * A proposed product is shorter than a written one: its description and tags\n * are a start the owner completes, and twelve of them at every bound still fit\n * one answer (`ai-job-products-step.spec.ts` measures it).\n */\nexport const AI_CATALOG_DESCRIPTION_MAX_CHARS = 600\nexport const AI_CATALOG_TAGS_MAX = 6\n\n/** A proposed product's options: few axes and few short values, so its variants stay a short list. */\nexport const AI_CATALOG_OPTIONS_MAX = 2\nexport const AI_CATALOG_OPTION_VALUES_MAX = 6\nexport const AI_CATALOG_OPTION_VALUE_MAX_CHARS = 30\n\n/** What a proposed product's photo should show, in one sentence. */\nexport const AI_CATALOG_PHOTO_MAX_CHARS = 160\n\n/**\n * A proposed product's suggested price, in US dollars — the one currency a\n * catalog prices in (`priceUsd`) — and the range code keeps it to (Zach,\n * 2026-10-12): a store's first products priced by what each one is (a bar\n * of handmade soap about $8–12, a candle $18–32, a gift set $30–45) instead\n * of every one at the commerce default. Never $0 or less, nothing past what\n * a small shop's first product plausibly lists at; a price outside the range,\n * or none, is no price, and the writer gives the product its default price.\n * Below the commerce ceiling (`COMMERCE_MAX_PRICE_USD`, $10,000) by design.\n */\nexport const AI_CATALOG_PRICE_USD = { min: 1, max: 5_000 } as const\n\n/**\n * A proposed price as code keeps it: a finite number of dollars (or a plain\n * \"$12.50\" a model wrote as text) inside `AI_CATALOG_PRICE_USD`, rounded to\n * whole cents; anything else — missing, zero, negative, absurd, not a number\n * — is `null`, so the product takes the store's default price for the owner\n * to change. The model is never re-asked for a price: a bad one costs the\n * product only its suggestion.\n */\nexport function aiCatalogPrice(raw: unknown): number | null {\n let value: number | null = null\n if (typeof raw === 'number') value = raw\n else if (typeof raw === 'string') {\n const match = /^\\s*(?:US\\s*)?\\$?\\s*(\\d{1,6}(?:\\.\\d{1,2})?)\\s*(?:USD)?\\s*$/i.exec(raw)\n value = match ? Number(match[1]) : null\n }\n if (value === null || !Number.isFinite(value)) return null\n const cents = Math.round(value * 100)\n if (cents < AI_CATALOG_PRICE_USD.min * 100 || cents > AI_CATALOG_PRICE_USD.max * 100) return null\n return cents / 100\n}\n\nexport const AI_CATEGORIES_MAX = 12\nexport const AI_DISCOUNTS_MAX = 5\nexport const AI_CATEGORY_NAME_MAX_CHARS = 60\nexport const AI_PROPOSAL_WHY_MAX_CHARS = 200\n\n/** The highest percentage a proposed discount takes off, and the most a fixed one takes, in dollars. */\nexport const AI_DISCOUNT_MAX_PERCENT = 90\nexport const AI_DISCOUNT_MAX_FIXED_USD = 1_000\n\n/** A discount code: letters, digits, dashes and underscores, stored upper case. */\nexport const AI_DISCOUNT_CODE = /^[A-Z0-9_-]{3,20}$/\n\n/** An option axis. */\nexport interface AiProductOption {\n name: string\n values: string[]\n}\n\n/** One product as its editor hands it to a `product` job. */\nexport interface AiProductFacts {\n /** `null` for a product nobody has saved yet. */\n id: string | null\n name: string\n type: AiProductType\n /** The product's current description, cut to `AI_PRODUCT_TEXT_MAX_CHARS`. */\n text: string\n tags: string[]\n categoryIds: string[]\n options: AiProductOption[]\n /** The first photo's stored media value, or `null`. */\n imageUrl: string | null\n seoTitle: string\n seoDescription: string\n}\n\n/** A site's product category, as a request lists it to choose from. */\nexport interface AiProductCategory {\n id: string\n name: string\n}\n\n/** The merchant's own words about a product, which a claim in its copy may repeat. */\nexport function aiProductMerchantWords(product: Pick<AiProductFacts, 'name' | 'text' | 'tags'>): string {\n return [product.name, product.text, ...product.tags].join('\\n')\n}\n\nexport type AiProductsJobInputs =\n | { target: 'product'; product: AiProductFacts }\n | { target: 'bulk'; productIds: string[] }\n | { target: 'catalog' }\n | { target: 'categories' }\n\nconst ID = /^[A-Za-z0-9_-]{1,64}$/\n\nconst text = (value: unknown, max: number): string =>\n typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim().slice(0, max) : ''\n\n/** A list stored as JSON, read defensively: strings only, trimmed, bounded. */\nfunction jsonStrings(value: unknown, max: number, maxChars: number): string[] {\n if (typeof value !== 'string' || !value.trim()) return []\n try {\n const parsed: unknown = JSON.parse(value)\n if (!Array.isArray(parsed)) return []\n return parsed\n .map((entry) => text(entry, maxChars))\n .filter(Boolean)\n .slice(0, max)\n } catch {\n return []\n }\n}\n\nfunction jsonOptions(value: unknown): AiProductOption[] {\n if (typeof value !== 'string' || !value.trim()) return []\n try {\n const parsed: unknown = JSON.parse(value)\n if (!Array.isArray(parsed)) return []\n return parsed\n .map((entry) => {\n const record = (entry ?? {}) as Record<string, unknown>\n return {\n name: text(record['name'], AI_PRODUCT_OPTION_NAME_MAX_CHARS),\n values: Array.isArray(record['values'])\n ? record['values'].map((value) => text(value, 40)).filter(Boolean).slice(0, AI_PRODUCT_OPTION_VALUES_MAX)\n : [],\n }\n })\n .slice(0, AI_PRODUCT_OPTIONS_MAX)\n } catch {\n return []\n }\n}\n\n/**\n * How large a job's inputs may be once written as JSON: under the 8,000\n * characters the create door admits, with room for the door's own keys.\n */\nexport const AI_PRODUCTS_INPUTS_MAX_JSON_CHARS = 7_500\n\n/**\n * A job's inputs for a target, as the console sends them: every value a\n * scalar, lists as JSON or as comma-separated ids, each bounded, and the\n * product's text shortened last until the whole fits what the create door\n * admits.\n */\nexport function aiProductsJobInputs(input: AiProductsJobInputs): Record<string, string> {\n switch (input.target) {\n case 'product': {\n const product = input.product\n const inputs: Record<string, string> = {\n target: 'product',\n productId: product.id ?? '',\n name: text(product.name, AI_PRODUCT_NAME_MAX_CHARS),\n type: product.type,\n text: product.text.slice(0, AI_PRODUCT_TEXT_MAX_CHARS),\n tags: JSON.stringify(product.tags.slice(0, 10).map((tag) => tag.slice(0, 40))),\n categoryIds: product.categoryIds.filter((id) => ID.test(id)).slice(0, 10).join(','),\n options: JSON.stringify(\n product.options.slice(0, AI_PRODUCT_OPTIONS_MAX).map((option) => ({\n name: option.name.slice(0, AI_PRODUCT_OPTION_NAME_MAX_CHARS),\n values: option.values.slice(0, AI_PRODUCT_OPTION_VALUES_MAX).map((value) => value.slice(0, 40)),\n })),\n ),\n imageUrl: (product.imageUrl ?? '').slice(0, 300),\n seoTitle: product.seoTitle.slice(0, 100),\n seoDescription: product.seoDescription.slice(0, 200),\n }\n while (JSON.stringify(inputs).length > AI_PRODUCTS_INPUTS_MAX_JSON_CHARS && inputs['text']) {\n inputs['text'] = inputs['text'].slice(0, Math.max(0, inputs['text'].length - 250))\n }\n return inputs\n }\n case 'bulk':\n return {\n target: 'bulk',\n productIds: [...new Set(input.productIds.filter((id) => ID.test(id)))]\n .slice(0, AI_PRODUCTS_BULK_MAX)\n .join(','),\n }\n default:\n return { target: input.target }\n }\n}\n\n/** Why a `products` job's inputs cannot run, in words a person can act on. */\nexport const AI_PRODUCTS_NO_TARGET_COPY = 'This AI job does not say what to write for your products.'\nexport const AI_PRODUCTS_NO_NAME_COPY = 'Give the product a name before writing its copy.'\nexport const AI_PRODUCTS_NO_SELECTION_COPY = 'Pick the products to write copy for.'\nexport const AI_PRODUCTS_TOO_MANY_COPY = `Pick at most ${AI_PRODUCTS_BULK_MAX} products at a time.`\n\n/** A job's inputs read back, or the sentence that says why they cannot run. */\nexport function parseAiProductsJobInputs(raw: Readonly<Record<string, unknown>> | undefined): AiProductsJobInputs | string {\n const inputs = raw ?? {}\n const target = inputs['target']\n if (!(AI_PRODUCTS_TARGETS as readonly unknown[]).includes(target)) return AI_PRODUCTS_NO_TARGET_COPY\n if (target === 'product') {\n const name = text(inputs['name'], AI_PRODUCT_NAME_MAX_CHARS)\n if (!name) return AI_PRODUCTS_NO_NAME_COPY\n const type = inputs['type']\n const id = typeof inputs['productId'] === 'string' && ID.test(inputs['productId']) ? inputs['productId'] : null\n return {\n target,\n product: {\n id,\n name,\n type: (AI_PRODUCT_TYPES as readonly unknown[]).includes(type) ? (type as AiProductType) : 'physical',\n text: typeof inputs['text'] === 'string' ? inputs['text'].trim().slice(0, AI_PRODUCT_TEXT_MAX_CHARS) : '',\n tags: jsonStrings(inputs['tags'], 10, 40),\n categoryIds: String(inputs['categoryIds'] ?? '')\n .split(',')\n .map((part) => part.trim())\n .filter((part) => ID.test(part))\n .slice(0, 10),\n options: jsonOptions(inputs['options']),\n imageUrl: text(inputs['imageUrl'], 300) || null,\n seoTitle: text(inputs['seoTitle'], 100),\n seoDescription: text(inputs['seoDescription'], 200),\n },\n }\n }\n if (target === 'bulk') {\n const ids = String(inputs['productIds'] ?? '')\n .split(',')\n .map((part) => part.trim())\n .filter((part) => ID.test(part))\n const unique = [...new Set(ids)]\n if (!unique.length) return AI_PRODUCTS_NO_SELECTION_COPY\n if (unique.length > AI_PRODUCTS_BULK_MAX) return AI_PRODUCTS_TOO_MANY_COPY\n return { target, productIds: unique }\n }\n return { target: target as 'catalog' | 'categories' }\n}\n\n/* ------------------------------------------------------------------------ *\n * Proposals\n * ------------------------------------------------------------------------ */\n\n/** A product's copy, as proposed. */\nexport interface AiProductCopy {\n description: string\n seoTitle: string\n seoDescription: string\n tags: string[]\n categoryIds: string[]\n /** One name per option the product had, in order. */\n optionNames: string[]\n}\n\n/** What became of the product's photo. */\nexport type AiProductPhotoRead =\n /** The photo was read. */\n | 'read'\n /** The product has no photo. */\n | 'none'\n /** The photo is not an asset of this site's media library, or its org's. */\n | 'not-in-library'\n /** The photo could not be read. */\n | 'unreadable'\n /** The model this job ran on does not read pictures. */\n | 'model'\n\nexport interface AiProductCopyProposal {\n kind: 'copy'\n product: { id: string | null; name: string }\n /** `null` when there was nothing to propose: see `skipped`. */\n values: AiProductCopy | null\n /** The categories `values` names, with the names they had when the job read them. */\n categories: Array<{ id: string; name: string }>\n /** The option names the product had, beside the ones proposed. */\n optionNamesBefore: string[]\n /** The facts the copy marks as missing, in square brackets, for a person to fill in. */\n gaps: string[]\n photo: AiProductPhotoRead\n /** Why a bulk pass proposed nothing for this product, in customer-safe words. */\n skipped: string | null\n notes: string[]\n}\n\nexport interface AiCatalogProduct {\n name: string\n type: AiProductType\n description: string\n tags: string[]\n options: AiProductOption[]\n seoTitle: string\n seoDescription: string\n /** What the product's photo should show; nothing is uploaded or linked. */\n photo: string\n /**\n * The suggested price for every variant, in US dollars, as `aiCatalogPrice`\n * kept it; `null` (or absent, on a proposal made before 2026-10-12) when\n * the model gave none in range, and the store's default price stands in.\n */\n price?: number | null\n}\n\nexport interface AiCatalogProposal {\n kind: 'catalog'\n products: AiCatalogProduct[]\n notes: string[]\n}\n\nexport type AiDiscountKind = 'percent' | 'fixed' | 'free_shipping'\n\nexport interface AiProposedDiscount {\n name: string\n /** The code a shopper types, upper case; `null` for an automatic discount. */\n code: string | null\n kind: AiDiscountKind\n /** A `percent` discount's percentage. */\n valuePct: number | null\n /** A `fixed` discount's amount off, in cents. */\n valueCents: number | null\n /** The smallest order it applies to, in cents; `null` for any order. */\n minSubtotalCents: number | null\n why: string\n}\n\nexport interface AiCategoriesProposal {\n kind: 'categories'\n categories: Array<{ name: string; why: string }>\n discounts: AiProposedDiscount[]\n notes: string[]\n}\n\nexport type AiProductsProposal = AiProductCopyProposal | AiCatalogProposal | AiCategoriesProposal\n\n/** Output ids, so a pass that runs again finds what it already proposed. */\nexport const AI_PRODUCTS_OUTPUT_IDS = {\n copy: (productId: string | null) => `copy:${productId ?? 'new'}`,\n catalog: 'catalog',\n categories: 'categories',\n} as const\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/** An output's proposal, or `null` for an output that is not a products proposal. */\nexport function aiProductsProposalOf(output: Pick<AiJobOutput, 'resource' | 'proposal'> | null | undefined): AiProductsProposal | null {\n if (!output || output.resource !== 'product' || !isRecord(output.proposal)) return null\n const kind = output.proposal['kind']\n return kind === 'copy' || kind === 'catalog' || kind === 'categories'\n ? (output.proposal as unknown as AiProductsProposal)\n : null\n}\n\n/** A job's copy proposals, in the order the job made them. */\nexport function aiProductCopyProposals(job: Pick<AiJobSummary, 'outputs'> | null | undefined): AiProductCopyProposal[] {\n return (job?.outputs ?? [])\n .map((output) => aiProductsProposalOf(output))\n .filter((proposal): proposal is AiProductCopyProposal => proposal?.kind === 'copy')\n}\n\n/** A job's proposal of one kind, or `null`. */\nexport function aiProductsProposalOfKind<K extends AiProductsProposal['kind']>(\n job: Pick<AiJobSummary, 'outputs'> | null | undefined,\n kind: K,\n): Extract<AiProductsProposal, { kind: K }> | null {\n for (const output of job?.outputs ?? []) {\n const proposal = aiProductsProposalOf(output)\n if (proposal?.kind === kind) return proposal as Extract<AiProductsProposal, { kind: K }>\n }\n return null\n}\n\n/** The gaps a text marks for a person in square brackets, as `[material]`, each once. */\nexport function aiProductGaps(...texts: readonly string[]): string[] {\n const found = new Set<string>()\n for (const value of texts) {\n for (const match of value.matchAll(/\\[([^\\]\\n]{1,60})\\]/g)) found.add(match[1].trim())\n }\n return [...found].filter(Boolean)\n}\n\n/* ------------------------------------------------------------------------ *\n * The console's cards\n * ------------------------------------------------------------------------ */\n\n/** The option the import zone sets to write the imported products' copy. */\nexport const AI_PRODUCTS_IMPORT_WRITE_COPY = 'ai.writeCopy'\n\n/** The brief a bulk copy job carries, which the jobs list shows and a card recognizes it by. */\nexport const aiProductsBulkBrief = (count: number): string =>\n `Write storefront copy for ${count} ${count === 1 ? 'product' : 'products'}`\n\nconst BULK_BRIEF = /^Write storefront copy for (\\d+) products?$/\n\n/** How many products a bulk copy job was asked for, read from its brief; `null` for any other job. */\nexport function aiProductsBulkCount(job: Pick<AiJobSummary, 'brief'> | null | undefined): number | null {\n const match = BULK_BRIEF.exec(job?.brief ?? '')\n return match ? Number(match[1]) : null\n}\n"],"names":["AI_PRODUCTS_TARGETS","AI_PRODUCT_TYPES","AI_PRODUCT_NAME_MAX_CHARS","AI_PRODUCT_TEXT_MAX_CHARS","AI_PRODUCT_DESCRIPTION_MAX_CHARS","AI_PRODUCT_TAGS_MAX","AI_PRODUCT_TAG_MAX_CHARS","AI_PRODUCT_CATEGORIES_MAX","AI_PRODUCT_CATEGORY_LIST_MAX","AI_PRODUCT_OPTIONS_MAX","AI_PRODUCT_OPTION_VALUES_MAX","AI_PRODUCT_OPTION_NAME_MAX_CHARS","AI_PRODUCTS_BULK_MAX","AI_CATALOG_PRODUCTS","min","max","AI_CATALOG_DESCRIPTION_MAX_CHARS","AI_CATALOG_TAGS_MAX","AI_CATALOG_OPTIONS_MAX","AI_CATALOG_OPTION_VALUES_MAX","AI_CATALOG_OPTION_VALUE_MAX_CHARS","AI_CATALOG_PHOTO_MAX_CHARS","AI_CATALOG_PRICE_USD","aiCatalogPrice","raw","value","match","exec","Number","isFinite","cents","Math","round","AI_CATEGORIES_MAX","AI_DISCOUNTS_MAX","AI_CATEGORY_NAME_MAX_CHARS","AI_PROPOSAL_WHY_MAX_CHARS","AI_DISCOUNT_MAX_PERCENT","AI_DISCOUNT_MAX_FIXED_USD","AI_DISCOUNT_CODE","aiProductMerchantWords","product","name","text","tags","join","ID","replace","trim","slice","jsonStrings","maxChars","parsed","JSON","parse","Array","isArray","map","entry","filter","Boolean","jsonOptions","record","values","AI_PRODUCTS_INPUTS_MAX_JSON_CHARS","aiProductsJobInputs","input","target","inputs","productId","id","type","stringify","tag","categoryIds","test","options","option","imageUrl","seoTitle","seoDescription","length","productIds","Set","AI_PRODUCTS_NO_TARGET_COPY","AI_PRODUCTS_NO_NAME_COPY","AI_PRODUCTS_NO_SELECTION_COPY","AI_PRODUCTS_TOO_MANY_COPY","parseAiProductsJobInputs","includes","String","split","part","ids","unique","AI_PRODUCTS_OUTPUT_IDS","copy","catalog","categories","isRecord","aiProductsProposalOf","output","resource","proposal","kind","aiProductCopyProposals","job","outputs","aiProductsProposalOfKind","aiProductGaps","texts","found","matchAll","add","AI_PRODUCTS_IMPORT_WRITE_COPY","aiProductsBulkBrief","count","BULK_BRIEF","aiProductsBulkCount","brief"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-products.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 type { AiJobOutput, AiJobSummary } from './ai-jobs.types'\nimport { AI_PRODUCTS_BULK_MAX, aiProductsBulkBrief, aiProductsBulkInputs } from './ai-products-bulk'\n\nexport { AI_PRODUCTS_BULK_MAX, aiProductsBulkBrief }\n\n/**\n * COMMERCE BY AI (AGL-2916): what a `products` job is asked, and what it\n * proposes. Pure, so the step that asks, the console cards that start a job\n * and show its proposals, and the eval harness all read one shape.\n *\n * `inputs.target` names the work:\n *\n * - `product` — one product's copy, from what its editor holds right now:\n * the name, the text, the tags, the categories and options it has, and the\n * first photo. The editor may hold a product nobody has saved yet, so the\n * job never reads a product document for this target.\n * - `bulk` — the copy of up to `AI_PRODUCTS_BULK_MAX` saved products, one\n * product a pass, each read from the catalog as it is reached.\n * - `catalog` — a store's first products from the brief: names, copy, tags\n * and options, what each photo should show, and a suggested price by what\n * the product is (`aiCatalogPrice`, Zach 2026-10-12). No picture.\n * - `categories` — store categories and a first set of discounts from the brief.\n *\n * Every output is a PROPOSAL (`resource: 'product'`, with `proposal`), and the\n * job writes nothing. The commerce plugin's own surfaces write what a person\n * accepts, through the doors those surfaces already use.\n */\n\nexport const AI_PRODUCTS_TARGETS = ['product', 'bulk', 'catalog', 'categories'] as const\nexport type AiProductsTarget = (typeof AI_PRODUCTS_TARGETS)[number]\n\nexport const AI_PRODUCT_TYPES = ['physical', 'digital', 'service'] as const\nexport type AiProductType = (typeof AI_PRODUCT_TYPES)[number]\n\n/** The longest product name the editor saves. */\nexport const AI_PRODUCT_NAME_MAX_CHARS = 120\n\n/** How much of a product's existing description a request carries. */\nexport const AI_PRODUCT_TEXT_MAX_CHARS = 2_000\n\n/** The longest description the job proposes. */\nexport const AI_PRODUCT_DESCRIPTION_MAX_CHARS = 1_200\n\nexport const AI_PRODUCT_TAGS_MAX = 8\nexport const AI_PRODUCT_TAG_MAX_CHARS = 30\n\n/** The most categories one product's proposal names. */\nexport const AI_PRODUCT_CATEGORIES_MAX = 3\n\n/** The most of a site's categories a request lists to choose from. */\nexport const AI_PRODUCT_CATEGORY_LIST_MAX = 100\n\n/** A product's option axes, as the commerce editor bounds them. */\nexport const AI_PRODUCT_OPTIONS_MAX = 3\nexport const AI_PRODUCT_OPTION_VALUES_MAX = 25\nexport const AI_PRODUCT_OPTION_NAME_MAX_CHARS = 30\n\n/** How many products a catalog proposal holds. */\nexport const AI_CATALOG_PRODUCTS = { min: 1, max: 12 } as const\n\n/**\n * A proposed product is shorter than a written one: its description and tags\n * are a start the owner completes, and twelve of them at every bound still fit\n * one answer (`ai-job-products-step.spec.ts` measures it).\n */\nexport const AI_CATALOG_DESCRIPTION_MAX_CHARS = 600\nexport const AI_CATALOG_TAGS_MAX = 6\n\n/** A proposed product's options: few axes and few short values, so its variants stay a short list. */\nexport const AI_CATALOG_OPTIONS_MAX = 2\nexport const AI_CATALOG_OPTION_VALUES_MAX = 6\nexport const AI_CATALOG_OPTION_VALUE_MAX_CHARS = 30\n\n/** What a proposed product's photo should show, in one sentence. */\nexport const AI_CATALOG_PHOTO_MAX_CHARS = 160\n\n/**\n * A proposed product's suggested price, in US dollars — the one currency a\n * catalog prices in (`priceUsd`) — and the range code keeps it to (Zach,\n * 2026-10-12): a store's first products priced by what each one is (a bar\n * of handmade soap about $8–12, a candle $18–32, a gift set $30–45) instead\n * of every one at the commerce default. Never $0 or less, nothing past what\n * a small shop's first product plausibly lists at; a price outside the range,\n * or none, is no price, and the writer gives the product its default price.\n * Below the commerce ceiling (`COMMERCE_MAX_PRICE_USD`, $10,000) by design.\n */\nexport const AI_CATALOG_PRICE_USD = { min: 1, max: 5_000 } as const\n\n/**\n * A proposed price as code keeps it: a finite number of dollars (or a plain\n * \"$12.50\" a model wrote as text) inside `AI_CATALOG_PRICE_USD`, rounded to\n * whole cents; anything else — missing, zero, negative, absurd, not a number\n * — is `null`, so the product takes the store's default price for the owner\n * to change. The model is never re-asked for a price: a bad one costs the\n * product only its suggestion.\n */\nexport function aiCatalogPrice(raw: unknown): number | null {\n let value: number | null = null\n if (typeof raw === 'number') value = raw\n else if (typeof raw === 'string') {\n const match = /^\\s*(?:US\\s*)?\\$?\\s*(\\d{1,6}(?:\\.\\d{1,2})?)\\s*(?:USD)?\\s*$/i.exec(raw)\n value = match ? Number(match[1]) : null\n }\n if (value === null || !Number.isFinite(value)) return null\n const cents = Math.round(value * 100)\n if (cents < AI_CATALOG_PRICE_USD.min * 100 || cents > AI_CATALOG_PRICE_USD.max * 100) return null\n return cents / 100\n}\n\nexport const AI_CATEGORIES_MAX = 12\nexport const AI_DISCOUNTS_MAX = 5\nexport const AI_CATEGORY_NAME_MAX_CHARS = 60\nexport const AI_PROPOSAL_WHY_MAX_CHARS = 200\n\n/** The highest percentage a proposed discount takes off, and the most a fixed one takes, in dollars. */\nexport const AI_DISCOUNT_MAX_PERCENT = 90\nexport const AI_DISCOUNT_MAX_FIXED_USD = 1_000\n\n/** A discount code: letters, digits, dashes and underscores, stored upper case. */\nexport const AI_DISCOUNT_CODE = /^[A-Z0-9_-]{3,20}$/\n\n/** An option axis. */\nexport interface AiProductOption {\n name: string\n values: string[]\n}\n\n/** One product as its editor hands it to a `product` job. */\nexport interface AiProductFacts {\n /** `null` for a product nobody has saved yet. */\n id: string | null\n name: string\n type: AiProductType\n /** The product's current description, cut to `AI_PRODUCT_TEXT_MAX_CHARS`. */\n text: string\n tags: string[]\n categoryIds: string[]\n options: AiProductOption[]\n /** The first photo's stored media value, or `null`. */\n imageUrl: string | null\n seoTitle: string\n seoDescription: string\n}\n\n/** A site's product category, as a request lists it to choose from. */\nexport interface AiProductCategory {\n id: string\n name: string\n}\n\n/** The merchant's own words about a product, which a claim in its copy may repeat. */\nexport function aiProductMerchantWords(product: Pick<AiProductFacts, 'name' | 'text' | 'tags'>): string {\n return [product.name, product.text, ...product.tags].join('\\n')\n}\n\nexport type AiProductsJobInputs =\n | { target: 'product'; product: AiProductFacts }\n | { target: 'bulk'; productIds: string[] }\n | { target: 'catalog' }\n | { target: 'categories' }\n\nconst ID = /^[A-Za-z0-9_-]{1,64}$/\n\nconst text = (value: unknown, max: number): string =>\n typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim().slice(0, max) : ''\n\n/** A list stored as JSON, read defensively: strings only, trimmed, bounded. */\nfunction jsonStrings(value: unknown, max: number, maxChars: number): string[] {\n if (typeof value !== 'string' || !value.trim()) return []\n try {\n const parsed: unknown = JSON.parse(value)\n if (!Array.isArray(parsed)) return []\n return parsed\n .map((entry) => text(entry, maxChars))\n .filter(Boolean)\n .slice(0, max)\n } catch {\n return []\n }\n}\n\nfunction jsonOptions(value: unknown): AiProductOption[] {\n if (typeof value !== 'string' || !value.trim()) return []\n try {\n const parsed: unknown = JSON.parse(value)\n if (!Array.isArray(parsed)) return []\n return parsed\n .map((entry) => {\n const record = (entry ?? {}) as Record<string, unknown>\n return {\n name: text(record['name'], AI_PRODUCT_OPTION_NAME_MAX_CHARS),\n values: Array.isArray(record['values'])\n ? record['values'].map((value) => text(value, 40)).filter(Boolean).slice(0, AI_PRODUCT_OPTION_VALUES_MAX)\n : [],\n }\n })\n .slice(0, AI_PRODUCT_OPTIONS_MAX)\n } catch {\n return []\n }\n}\n\n/**\n * How large a job's inputs may be once written as JSON: under the 8,000\n * characters the create door admits, with room for the door's own keys.\n */\nexport const AI_PRODUCTS_INPUTS_MAX_JSON_CHARS = 7_500\n\n/**\n * A job's inputs for a target, as the console sends them: every value a\n * scalar, lists as JSON or as comma-separated ids, each bounded, and the\n * product's text shortened last until the whole fits what the create door\n * admits.\n */\nexport function aiProductsJobInputs(input: AiProductsJobInputs): Record<string, string> {\n switch (input.target) {\n case 'product': {\n const product = input.product\n const inputs: Record<string, string> = {\n target: 'product',\n productId: product.id ?? '',\n name: text(product.name, AI_PRODUCT_NAME_MAX_CHARS),\n type: product.type,\n text: product.text.slice(0, AI_PRODUCT_TEXT_MAX_CHARS),\n tags: JSON.stringify(product.tags.slice(0, 10).map((tag) => tag.slice(0, 40))),\n categoryIds: product.categoryIds.filter((id) => ID.test(id)).slice(0, 10).join(','),\n options: JSON.stringify(\n product.options.slice(0, AI_PRODUCT_OPTIONS_MAX).map((option) => ({\n name: option.name.slice(0, AI_PRODUCT_OPTION_NAME_MAX_CHARS),\n values: option.values.slice(0, AI_PRODUCT_OPTION_VALUES_MAX).map((value) => value.slice(0, 40)),\n })),\n ),\n imageUrl: (product.imageUrl ?? '').slice(0, 300),\n seoTitle: product.seoTitle.slice(0, 100),\n seoDescription: product.seoDescription.slice(0, 200),\n }\n while (JSON.stringify(inputs).length > AI_PRODUCTS_INPUTS_MAX_JSON_CHARS && inputs['text']) {\n inputs['text'] = inputs['text'].slice(0, Math.max(0, inputs['text'].length - 250))\n }\n return inputs\n }\n case 'bulk':\n return aiProductsBulkInputs(input.productIds)\n default:\n return { target: input.target }\n }\n}\n\n/** Why a `products` job's inputs cannot run, in words a person can act on. */\nexport const AI_PRODUCTS_NO_TARGET_COPY = 'This AI job does not say what to write for your products.'\nexport const AI_PRODUCTS_NO_NAME_COPY = 'Give the product a name before writing its copy.'\nexport const AI_PRODUCTS_NO_SELECTION_COPY = 'Pick the products to write copy for.'\nexport const AI_PRODUCTS_TOO_MANY_COPY = `Pick at most ${AI_PRODUCTS_BULK_MAX} products at a time.`\n\n/** A job's inputs read back, or the sentence that says why they cannot run. */\nexport function parseAiProductsJobInputs(raw: Readonly<Record<string, unknown>> | undefined): AiProductsJobInputs | string {\n const inputs = raw ?? {}\n const target = inputs['target']\n if (!(AI_PRODUCTS_TARGETS as readonly unknown[]).includes(target)) return AI_PRODUCTS_NO_TARGET_COPY\n if (target === 'product') {\n const name = text(inputs['name'], AI_PRODUCT_NAME_MAX_CHARS)\n if (!name) return AI_PRODUCTS_NO_NAME_COPY\n const type = inputs['type']\n const id = typeof inputs['productId'] === 'string' && ID.test(inputs['productId']) ? inputs['productId'] : null\n return {\n target,\n product: {\n id,\n name,\n type: (AI_PRODUCT_TYPES as readonly unknown[]).includes(type) ? (type as AiProductType) : 'physical',\n text: typeof inputs['text'] === 'string' ? inputs['text'].trim().slice(0, AI_PRODUCT_TEXT_MAX_CHARS) : '',\n tags: jsonStrings(inputs['tags'], 10, 40),\n categoryIds: String(inputs['categoryIds'] ?? '')\n .split(',')\n .map((part) => part.trim())\n .filter((part) => ID.test(part))\n .slice(0, 10),\n options: jsonOptions(inputs['options']),\n imageUrl: text(inputs['imageUrl'], 300) || null,\n seoTitle: text(inputs['seoTitle'], 100),\n seoDescription: text(inputs['seoDescription'], 200),\n },\n }\n }\n if (target === 'bulk') {\n const ids = String(inputs['productIds'] ?? '')\n .split(',')\n .map((part) => part.trim())\n .filter((part) => ID.test(part))\n const unique = [...new Set(ids)]\n if (!unique.length) return AI_PRODUCTS_NO_SELECTION_COPY\n if (unique.length > AI_PRODUCTS_BULK_MAX) return AI_PRODUCTS_TOO_MANY_COPY\n return { target, productIds: unique }\n }\n return { target: target as 'catalog' | 'categories' }\n}\n\n/* ------------------------------------------------------------------------ *\n * Proposals\n * ------------------------------------------------------------------------ */\n\n/** A product's copy, as proposed. */\nexport interface AiProductCopy {\n description: string\n seoTitle: string\n seoDescription: string\n tags: string[]\n categoryIds: string[]\n /** One name per option the product had, in order. */\n optionNames: string[]\n}\n\n/** What became of the product's photo. */\nexport type AiProductPhotoRead =\n /** The photo was read. */\n | 'read'\n /** The product has no photo. */\n | 'none'\n /** The photo is not an asset of this site's media library, or its org's. */\n | 'not-in-library'\n /** The photo could not be read. */\n | 'unreadable'\n /** The model this job ran on does not read pictures. */\n | 'model'\n\nexport interface AiProductCopyProposal {\n kind: 'copy'\n product: { id: string | null; name: string }\n /** `null` when there was nothing to propose: see `skipped`. */\n values: AiProductCopy | null\n /** The categories `values` names, with the names they had when the job read them. */\n categories: Array<{ id: string; name: string }>\n /** The option names the product had, beside the ones proposed. */\n optionNamesBefore: string[]\n /** The facts the copy marks as missing, in square brackets, for a person to fill in. */\n gaps: string[]\n photo: AiProductPhotoRead\n /** Why a bulk pass proposed nothing for this product, in customer-safe words. */\n skipped: string | null\n notes: string[]\n}\n\nexport interface AiCatalogProduct {\n name: string\n type: AiProductType\n description: string\n tags: string[]\n options: AiProductOption[]\n seoTitle: string\n seoDescription: string\n /** What the product's photo should show; nothing is uploaded or linked. */\n photo: string\n /**\n * The suggested price for every variant, in US dollars, as `aiCatalogPrice`\n * kept it; `null` (or absent, on a proposal made before 2026-10-12) when\n * the model gave none in range, and the store's default price stands in.\n */\n price?: number | null\n}\n\nexport interface AiCatalogProposal {\n kind: 'catalog'\n products: AiCatalogProduct[]\n notes: string[]\n}\n\nexport type AiDiscountKind = 'percent' | 'fixed' | 'free_shipping'\n\nexport interface AiProposedDiscount {\n name: string\n /** The code a shopper types, upper case; `null` for an automatic discount. */\n code: string | null\n kind: AiDiscountKind\n /** A `percent` discount's percentage. */\n valuePct: number | null\n /** A `fixed` discount's amount off, in cents. */\n valueCents: number | null\n /** The smallest order it applies to, in cents; `null` for any order. */\n minSubtotalCents: number | null\n why: string\n}\n\nexport interface AiCategoriesProposal {\n kind: 'categories'\n categories: Array<{ name: string; why: string }>\n discounts: AiProposedDiscount[]\n notes: string[]\n}\n\nexport type AiProductsProposal = AiProductCopyProposal | AiCatalogProposal | AiCategoriesProposal\n\n/** Output ids, so a pass that runs again finds what it already proposed. */\nexport const AI_PRODUCTS_OUTPUT_IDS = {\n copy: (productId: string | null) => `copy:${productId ?? 'new'}`,\n catalog: 'catalog',\n categories: 'categories',\n} as const\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/** An output's proposal, or `null` for an output that is not a products proposal. */\nexport function aiProductsProposalOf(output: Pick<AiJobOutput, 'resource' | 'proposal'> | null | undefined): AiProductsProposal | null {\n if (!output || output.resource !== 'product' || !isRecord(output.proposal)) return null\n const kind = output.proposal['kind']\n return kind === 'copy' || kind === 'catalog' || kind === 'categories'\n ? (output.proposal as unknown as AiProductsProposal)\n : null\n}\n\n/** A job's copy proposals, in the order the job made them. */\nexport function aiProductCopyProposals(job: Pick<AiJobSummary, 'outputs'> | null | undefined): AiProductCopyProposal[] {\n return (job?.outputs ?? [])\n .map((output) => aiProductsProposalOf(output))\n .filter((proposal): proposal is AiProductCopyProposal => proposal?.kind === 'copy')\n}\n\n/** A job's proposal of one kind, or `null`. */\nexport function aiProductsProposalOfKind<K extends AiProductsProposal['kind']>(\n job: Pick<AiJobSummary, 'outputs'> | null | undefined,\n kind: K,\n): Extract<AiProductsProposal, { kind: K }> | null {\n for (const output of job?.outputs ?? []) {\n const proposal = aiProductsProposalOf(output)\n if (proposal?.kind === kind) return proposal as Extract<AiProductsProposal, { kind: K }>\n }\n return null\n}\n\n/** The gaps a text marks for a person in square brackets, as `[material]`, each once. */\nexport function aiProductGaps(...texts: readonly string[]): string[] {\n const found = new Set<string>()\n for (const value of texts) {\n for (const match of value.matchAll(/\\[([^\\]\\n]{1,60})\\]/g)) found.add(match[1].trim())\n }\n return [...found].filter(Boolean)\n}\n\n/* ------------------------------------------------------------------------ *\n * The console's cards\n * ------------------------------------------------------------------------ */\n\n/** The option the import zone sets to write the imported products' copy. */\nexport const AI_PRODUCTS_IMPORT_WRITE_COPY = 'ai.writeCopy'\n\nconst BULK_BRIEF = /^Write storefront copy for (\\d+) products?$/\n\n/** How many products a bulk copy job was asked for, read from its brief; `null` for any other job. */\nexport function aiProductsBulkCount(job: Pick<AiJobSummary, 'brief'> | null | undefined): number | null {\n const match = BULK_BRIEF.exec(job?.brief ?? '')\n return match ? Number(match[1]) : null\n}\n"],"names":["AI_PRODUCTS_BULK_MAX","aiProductsBulkBrief","aiProductsBulkInputs","AI_PRODUCTS_TARGETS","AI_PRODUCT_TYPES","AI_PRODUCT_NAME_MAX_CHARS","AI_PRODUCT_TEXT_MAX_CHARS","AI_PRODUCT_DESCRIPTION_MAX_CHARS","AI_PRODUCT_TAGS_MAX","AI_PRODUCT_TAG_MAX_CHARS","AI_PRODUCT_CATEGORIES_MAX","AI_PRODUCT_CATEGORY_LIST_MAX","AI_PRODUCT_OPTIONS_MAX","AI_PRODUCT_OPTION_VALUES_MAX","AI_PRODUCT_OPTION_NAME_MAX_CHARS","AI_CATALOG_PRODUCTS","min","max","AI_CATALOG_DESCRIPTION_MAX_CHARS","AI_CATALOG_TAGS_MAX","AI_CATALOG_OPTIONS_MAX","AI_CATALOG_OPTION_VALUES_MAX","AI_CATALOG_OPTION_VALUE_MAX_CHARS","AI_CATALOG_PHOTO_MAX_CHARS","AI_CATALOG_PRICE_USD","aiCatalogPrice","raw","value","match","exec","Number","isFinite","cents","Math","round","AI_CATEGORIES_MAX","AI_DISCOUNTS_MAX","AI_CATEGORY_NAME_MAX_CHARS","AI_PROPOSAL_WHY_MAX_CHARS","AI_DISCOUNT_MAX_PERCENT","AI_DISCOUNT_MAX_FIXED_USD","AI_DISCOUNT_CODE","aiProductMerchantWords","product","name","text","tags","join","ID","replace","trim","slice","jsonStrings","maxChars","parsed","JSON","parse","Array","isArray","map","entry","filter","Boolean","jsonOptions","record","values","AI_PRODUCTS_INPUTS_MAX_JSON_CHARS","aiProductsJobInputs","input","target","inputs","productId","id","type","stringify","tag","categoryIds","test","options","option","imageUrl","seoTitle","seoDescription","length","productIds","AI_PRODUCTS_NO_TARGET_COPY","AI_PRODUCTS_NO_NAME_COPY","AI_PRODUCTS_NO_SELECTION_COPY","AI_PRODUCTS_TOO_MANY_COPY","parseAiProductsJobInputs","includes","String","split","part","ids","unique","Set","AI_PRODUCTS_OUTPUT_IDS","copy","catalog","categories","isRecord","aiProductsProposalOf","output","resource","proposal","kind","aiProductCopyProposals","job","outputs","aiProductsProposalOfKind","aiProductGaps","texts","found","matchAll","add","AI_PRODUCTS_IMPORT_WRITE_COPY","BULK_BRIEF","aiProductsBulkCount","brief"],"mappings":"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"}
|
|
@@ -135,9 +135,10 @@ const ANTHROPIC_VERSION = '2023-06-01';
|
|
|
135
135
|
*/ export function buildAnthropicRequestBody(input) {
|
|
136
136
|
var _input_effort, _ANTHROPIC_THINKING_OFF_input_model;
|
|
137
137
|
var _input_tools, _aiCatalogEntry;
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
const conversation = Boolean(input.cacheConversation) && input.messages.length > 0;
|
|
139
|
+
const breakpoints = input.system.filter((block)=>block.cacheBreakpoint).length + (conversation ? 1 : 0);
|
|
140
|
+
if (breakpoints > ANTHROPIC_MAX_CACHE_BREAKPOINTS) {
|
|
141
|
+
throw new Error(`${breakpoints} cache breakpoints; the Messages API allows ${ANTHROPIC_MAX_CACHE_BREAKPOINTS}`);
|
|
141
142
|
}
|
|
142
143
|
// One lifetime for every breakpoint: the API refuses a longer-lived entry
|
|
143
144
|
// after a shorter one, and a request asks for one or the other.
|
|
@@ -188,9 +189,9 @@ const ANTHROPIC_VERSION = '2023-06-01';
|
|
|
188
189
|
} : {}, system.length ? {
|
|
189
190
|
system
|
|
190
191
|
} : {}, {
|
|
191
|
-
messages: input.messages.map((message)=>({
|
|
192
|
+
messages: input.messages.map((message, index)=>({
|
|
192
193
|
role: message.role,
|
|
193
|
-
content: anthropicContentOf(message)
|
|
194
|
+
content: conversation ? anthropicBlocksOf(message, index === input.messages.length - 1 ? cacheControl : null) : anthropicContentOf(message)
|
|
194
195
|
}))
|
|
195
196
|
}, (tools == null ? void 0 : tools.length) ? {
|
|
196
197
|
tools,
|
|
@@ -217,6 +218,26 @@ const ANTHROPIC_VERSION = '2023-06-01';
|
|
|
217
218
|
text: part.text
|
|
218
219
|
});
|
|
219
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* A turn as content blocks whatever its shape, the last block carrying the
|
|
223
|
+
* conversation's breakpoint when it is the request's last turn (AGL-3809).
|
|
224
|
+
* Every turn of a request that caches its conversation is sent as blocks, so
|
|
225
|
+
* a turn reads the same on the call that marked it and on every call after,
|
|
226
|
+
* where it is no longer last.
|
|
227
|
+
*/ function anthropicBlocksOf(message, cacheControl) {
|
|
228
|
+
const content = anthropicContentOf(message);
|
|
229
|
+
const blocks = typeof content === 'string' ? [
|
|
230
|
+
{
|
|
231
|
+
type: 'text',
|
|
232
|
+
text: content
|
|
233
|
+
}
|
|
234
|
+
] : content;
|
|
235
|
+
if (!cacheControl || !blocks.length) return blocks;
|
|
236
|
+
const last = blocks.length - 1;
|
|
237
|
+
return blocks.map((block, index)=>index === last ? _extends({}, block, {
|
|
238
|
+
cache_control: cacheControl
|
|
239
|
+
}) : block);
|
|
240
|
+
}
|
|
220
241
|
/** The provider's request id, when it sent one — the log's join key. */ function requestIdOf(response) {
|
|
221
242
|
var _ref;
|
|
222
243
|
var _response_headers_get, _response_headers;
|
|
@@ -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 breakpoints = input.system.filter((block) => block.cacheBreakpoint)\n if (breakpoints.length > ANTHROPIC_MAX_CACHE_BREAKPOINTS) {\n throw new Error(\n `${breakpoints.length} 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) => ({\n role: message.role,\n content: 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/** 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","breakpoints","system","filter","block","cacheBreakpoint","length","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","messages","role","content","anthropicContentOf","tool_choice","part","source","media_type","mediaType","data","requestIdOf","response","headers","get","send","body","fetch","method","apiKey","JSON","stringify","signal","ok","payload","requestId","json","catch","console","provider","complete","anthropicResultFrom","raw","priceFactor","blocks","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","index","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","Boolean"],"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 { 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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"}
|
|
@@ -220,6 +220,16 @@ export interface AiRequestBase {
|
|
|
220
220
|
* request within the hour pays for the prefix.
|
|
221
221
|
*/
|
|
222
222
|
cacheTtl?: '1h';
|
|
223
|
+
/**
|
|
224
|
+
* Cache the conversation through its last message as well as the system
|
|
225
|
+
* blocks (AGL-3809), for a request the door expects to continue: a
|
|
226
|
+
* generation that looks the site up or is asked again re-sends its whole
|
|
227
|
+
* first turn, which without this is billed at the full input rate on every
|
|
228
|
+
* continuation. One more breakpoint, on the last message, counted against
|
|
229
|
+
* the same limit as the system's; a provider without a cache ignores it.
|
|
230
|
+
* It changes no answer, so a replay key does not read it.
|
|
231
|
+
*/
|
|
232
|
+
cacheConversation?: boolean;
|
|
223
233
|
}
|
|
224
234
|
/** The request an adapter receives: the runtime has already validated it. */
|
|
225
235
|
export interface AiProviderRequest extends AiRequestBase {
|