@modainteract/moda-interact-shared 5.11.1 → 5.12.1

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/README.md CHANGED
@@ -298,6 +298,12 @@ details schema. `finalResponseToolSchema(definition)` produces the host-local
298
298
  finalization Tool's input schema. `verifyResponseContract` checks the definition
299
299
  against its hash using the caller's digest implementation.
300
300
 
301
+ When a product-bearing answer omits its structured selections, the runner allows
302
+ one bounded correction using the existing observations and model-step/deadline
303
+ budget. Only `finalResponse` is available during correction: no provider reads
304
+ are repeated. An unsuccessful correction returns `INVALID_FINAL` with
305
+ `FINAL_PRODUCT_SELECTION_MISSING` to both Studio and Background.
306
+
301
307
  An ANSWER requires null referral reason; a REFER_TO_STORE response requires the
302
308
  appropriate reason, empty evidence IDs and empty details. Language detection
303
309
  fields are either both null or a valid pair. Only reply text is customer-facing;
@@ -1,100 +1,7 @@
1
1
  import {
2
- CommerceAnswerPresentationSelectionSchema,
3
- CommerceConversationGrantSchema,
4
- CommerceManifestSchema,
5
2
  CommerceProductSchema,
6
- CommerceProductSelectedOptionsSchema,
7
- GrantedToolsSchema,
8
- PublishedToolDescriptorSchema,
9
- isBuiltInTool,
10
- isTrustedCommerceBuiltInDescriptor,
11
- isTrustedCommerceBuiltInGrant
3
+ CommerceProductSelectedOptionsSchema
12
4
  } from "./chunk-W5Y5ZLGN.js";
13
- import {
14
- IdSchema
15
- } from "./chunk-GCCT2BDE.js";
16
- import {
17
- canonicalJson
18
- } from "./chunk-AUS3WQIW.js";
19
-
20
- // src/commerce/selection.ts
21
- function selectCapabilities(candidates, facts) {
22
- for (const candidate of candidates) {
23
- IdSchema.parse(candidate.key);
24
- IdSchema.parse(candidate.featureId);
25
- if (!Number.isSafeInteger(candidate.position) || candidate.position < 0)
26
- throw new TypeError("Invalid capability position");
27
- }
28
- const selected = candidates.filter((c) => {
29
- if (!c.enabled) return false;
30
- const f = facts.features.find((f2) => f2.id === c.featureId);
31
- return !!f && f.active && f.planMappingEnabled && (f.mode === "ALWAYS_ENABLED" || f.preferenceEnabled === true);
32
- }).sort((a, b) => a.position - b.position);
33
- if (new Set(selected.map((c) => c.key)).size !== selected.length || new Set(selected.map((c) => c.position)).size !== selected.length)
34
- throw new TypeError("Duplicate release membership");
35
- return selected.map((c) => c.key);
36
- }
37
- function deduplicateTools(capabilities, builtInTools = []) {
38
- const byId = /* @__PURE__ */ new Map();
39
- const names = /* @__PURE__ */ new Map();
40
- for (const cap of capabilities) {
41
- const d = PublishedToolDescriptorSchema.parse(cap.toolDescriptor);
42
- const previous = byId.get(d.toolId);
43
- const descriptor = canonicalJson(d);
44
- if (previous && previous.descriptor !== descriptor || names.has(d.name) && names.get(d.name) !== d.toolId)
45
- throw new TypeError("Conflicting tool association");
46
- names.set(d.name, d.toolId);
47
- if (previous)
48
- previous.tool.capabilityKeys = [
49
- .../* @__PURE__ */ new Set([...previous.tool.capabilityKeys, cap.key])
50
- ].sort();
51
- else
52
- byId.set(d.toolId, {
53
- descriptor,
54
- tool: {
55
- toolId: d.toolId,
56
- toolRevisionId: d.toolRevisionId,
57
- toolName: d.name,
58
- definitionVersion: d.definitionVersion,
59
- capabilityKeys: [cap.key]
60
- }
61
- });
62
- }
63
- if (builtInTools.some((tool) => !isTrustedCommerceBuiltInDescriptor(tool)))
64
- throw new TypeError("Untrusted built-in descriptor");
65
- return GrantedToolsSchema.parse([
66
- ...[...byId.values()].map((value) => value.tool),
67
- ...builtInTools.map((tool) => ({
68
- kind: "BUILT_IN",
69
- toolName: tool.name,
70
- definitionVersion: tool.definitionVersion
71
- }))
72
- ]).sort((a, b) => a.toolName.localeCompare(b.toolName, "en"));
73
- }
74
- function manifestMatchesGrant(manifest, grant) {
75
- if (!CommerceManifestSchema.safeParse(manifest).success || !CommerceConversationGrantSchema.safeParse(grant).success) return false;
76
- return manifest.releaseId === grant.releaseId && canonicalJson(manifest.purposeProfile ?? null) === canonicalJson(grant.purposeProfile ?? null) && canonicalJson(manifest.selectedCapabilityKeys) === canonicalJson(grant.selectedCapabilityKeys) && canonicalJson(
77
- [...manifest.grantedTools].sort(
78
- (a, b) => a.toolName.localeCompare(b.toolName)
79
- )
80
- ) === canonicalJson(
81
- [...grant.grantedTools].sort(
82
- (a, b) => a.toolName.localeCompare(b.toolName)
83
- )
84
- );
85
- }
86
- var parseCommerceManifest = (raw) => CommerceManifestSchema.parse(raw);
87
- function currentlyGrantedTools(grant, eligibleCapabilityKeys, enabledToolIds, allowedBuiltInTools = []) {
88
- return grant.grantedTools.filter(
89
- (t) => isBuiltInTool(t) ? isTrustedCommerceBuiltInGrant(t) && allowedBuiltInTools.some((descriptor) => isTrustedCommerceBuiltInDescriptor(descriptor) && descriptor.name === t.toolName && descriptor.definitionVersion === t.definitionVersion) : enabledToolIds.has(t.toolId) && t.capabilityKeys.some((k) => eligibleCapabilityKeys.has(k))
90
- );
91
- }
92
- function verifyCommerceAnswerPresentationSelection(raw) {
93
- const parsed = CommerceAnswerPresentationSelectionSchema.safeParse(raw);
94
- if (!parsed.success || parsed.data.productOptionSelections?.length && parsed.data.productDetailSelections?.length)
95
- return { kind: "limitation", reason: "INVALID_SELECTION" };
96
- return { kind: "verified", selection: parsed.data };
97
- }
98
5
 
99
6
  // src/commerce/product-context.ts
100
7
  import { z } from "zod";
@@ -161,15 +68,9 @@ function recordCommerceProductPresentation(context, pairs, identity) {
161
68
  }
162
69
 
163
70
  export {
164
- selectCapabilities,
165
- deduplicateTools,
166
- manifestMatchesGrant,
167
- parseCommerceManifest,
168
- currentlyGrantedTools,
169
- verifyCommerceAnswerPresentationSelection,
170
71
  CommerceConversationProductContextSchema,
171
72
  mergeConversationProductContext,
172
73
  resolveConversationProductReference,
173
74
  recordCommerceProductPresentation
174
75
  };
175
- //# sourceMappingURL=chunk-5BIYNMMT.js.map
76
+ //# sourceMappingURL=chunk-JWX4ZVKK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commerce/product-context.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { CommerceProductSchema, CommerceProductSelectedOptionsSchema } from \"./schemas.js\";\nconst id = z.string().min(1).max(128);\nconst date = z.iso.datetime({offset:false});\nexport const CommerceConversationProductContextSchema = z.strictObject({schemaVersion:z.literal(1),shopId:id,conversationId:id,platform:z.enum([\"SHOPIFY\",\"WOOCOMMERCE\"]),references:z.array(z.strictObject({productId:id,variantId:id.nullable(),title:z.string().min(1).max(512),selectedOptions:CommerceProductSelectedOptionsSchema.optional(),observedAt:date})).max(20),presentation:z.strictObject({references:z.array(z.strictObject({productId:id,variantId:id.nullable()})).max(3),presentedAt:date}).optional(),updatedAt:date}).superRefine((v,c)=>{\n const pairs=v.presentation?.references??[];\n if(new Set(pairs.map(r=>JSON.stringify([r.productId,r.variantId]))).size!==pairs.length||pairs.some(p=>!v.references.some(r=>r.productId===p.productId&&r.variantId===p.variantId)))c.addIssue({code:'custom',message:'Invalid product presentation'});\n const valid=(value:string,kind:string)=>v.platform === \"SHOPIFY\" ? new RegExp(`^gid://shopify/${kind}/[1-9][0-9]*$`).test(value) : /^[1-9][0-9]*$/.test(value) && Number.isSafeInteger(Number(value));\n if(v.references.some(r=>!valid(r.productId,\"Product\")||(r.variantId!==null&&!valid(r.variantId,\"ProductVariant\")))||new TextEncoder().encode(JSON.stringify(v)).length>32768)c.addIssue({code:\"custom\",message:\"Invalid product context\"});\n});\nexport type CommerceConversationProductContext = z.infer<typeof CommerceConversationProductContextSchema>;\nexport type CommerceProductObservation = {toolName:string;products:z.infer<typeof CommerceProductSchema>[];observedAt:string};\nexport type CommerceProductContextIdentity = {shopId:string;conversationId:string;platform:\"SHOPIFY\"|\"WOOCOMMERCE\";now:number};\nexport function mergeConversationProductContext(previous:CommerceConversationProductContext|null,observations:readonly CommerceProductObservation[],identity:CommerceProductContextIdentity):CommerceConversationProductContext {\n id.parse(identity.shopId);id.parse(identity.conversationId);z.enum([\"SHOPIFY\",\"WOOCOMMERCE\"]).parse(identity.platform);if(!Number.isFinite(identity.now))throw new Error(\"PRODUCT_CONTEXT_TIME_INVALID\");\n let admitted=false;\n if(previous){previous=CommerceConversationProductContextSchema.parse(previous);if(previous.shopId!==identity.shopId||previous.conversationId!==identity.conversationId||previous.platform!==identity.platform)throw new Error(\"PRODUCT_CONTEXT_IDENTITY_MISMATCH\");}\n const fresh=(time:string)=>Number.isFinite(Date.parse(time))&&Date.parse(time)<=identity.now&&Date.parse(time)>=identity.now-86400000;\n const entries=new Map<string,CommerceConversationProductContext[\"references\"][number]>();\n const add=(r:CommerceConversationProductContext[\"references\"][number])=>{if(!fresh(r.observedAt))return;const key=JSON.stringify([r.productId,r.variantId]);const old=entries.get(key);if(!old||Date.parse(r.observedAt)>=Date.parse(old.observedAt))entries.set(key,r);};\n if(previous&&fresh(previous.updatedAt))previous.references.forEach(add);\n for(const o of observations){if(![\"commerce_search_products\",\"commerce_get_product\"].includes(o.toolName)||!fresh(o.observedAt))continue;for(const raw of o.products){const p=CommerceProductSchema.parse(raw);admitted=true;add({productId:p.productId,variantId:p.variantId,title:p.title,...(p.selectedOptions!==undefined?{selectedOptions:p.selectedOptions}:{}),observedAt:o.observedAt});}}\n const references=[...entries.values()].sort((a,b)=>Date.parse(b.observedAt)-Date.parse(a.observedAt)||(a.productId<b.productId?-1:a.productId>b.productId?1:0)||((a.variantId??\"\")<(b.variantId??\"\")?-1:(a.variantId??\"\")>(b.variantId??\"\")?1:0)).slice(0,20);\n const result:CommerceConversationProductContext={schemaVersion:1 as const,shopId:identity.shopId,conversationId:identity.conversationId,platform:identity.platform,references,updatedAt:admitted?new Date(identity.now).toISOString():previous&&fresh(previous.updatedAt)?previous.updatedAt:new Date(identity.now).toISOString()};\n if(previous?.presentation&&fresh(previous.presentation.presentedAt)&&previous.presentation.references.every(p=>references.some(r=>r.productId===p.productId&&r.variantId===p.variantId)))result.presentation=previous.presentation;\n while(references.length&&new TextEncoder().encode(JSON.stringify(result)).length>32768){references.pop();if(result.presentation?.references.some(p=>!references.some(r=>r.productId===p.productId&&r.variantId===p.variantId)))delete result.presentation;}\n return CommerceConversationProductContextSchema.parse(result);\n}\nexport function resolveConversationProductReference(context:CommerceConversationProductContext,reference:{productId?:string;variantId?:string|null;title?:string}):{kind:\"MATCH\";productId:string;variantId:string|null}|{kind:\"AMBIGUOUS\"}|{kind:\"MISSING\"}{\n const parsed=CommerceConversationProductContextSchema.parse(context);\n const matches=parsed.references.filter(r=>(reference.productId!==undefined?r.productId===reference.productId:reference.title!==undefined&&r.title.trim().toLowerCase()===reference.title.trim().toLowerCase())&&(reference.variantId===undefined||r.variantId===reference.variantId));\n const unique=[...new Map(matches.map(r=>[JSON.stringify([r.productId,r.variantId]),r])).values()];return unique.length===1?{kind:\"MATCH\",productId:unique[0]!.productId,variantId:unique[0]!.variantId}:unique.length>1?{kind:\"AMBIGUOUS\"}:{kind:\"MISSING\"};\n}\n\n/** Record only exact pairs actually composed by the host; references remain stale labels. */\nexport function recordCommerceProductPresentation(context:CommerceConversationProductContext,pairs:readonly {productId:string;variantId:string|null}[],identity:CommerceProductContextIdentity):CommerceConversationProductContext {\n const fresh=mergeConversationProductContext(context,[],identity);\n const parsed=z.array(z.strictObject({productId:id,variantId:id.nullable()})).max(3).safeParse(pairs);\n if(!parsed.success||new Set(pairs.map(p=>JSON.stringify([p.productId,p.variantId]))).size!==pairs.length||pairs.some(p=>!fresh.references.some(r=>r.productId===p.productId&&r.variantId===p.variantId)))throw new Error('PRODUCT_PRESENTATION_INVALID');\n const result:CommerceConversationProductContext={...fresh,presentation:{references:parsed.data,presentedAt:new Date(identity.now).toISOString()}};\n while(result.references.length&&new TextEncoder().encode(JSON.stringify(result)).length>32768){result.references.pop();if(result.presentation?.references.some(p=>!result.references.some(r=>r.productId===p.productId&&r.variantId===p.variantId)))delete result.presentation;}\n return CommerceConversationProductContextSchema.parse(result);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,184 @@
1
+ import {
2
+ CommerceProductSchema
3
+ } from "./chunk-W5Y5ZLGN.js";
4
+
5
+ // src/commerce/presentation/product-image.ts
6
+ import { z } from "zod";
7
+
8
+ // src/commerce/presentation/wrapper-projection.ts
9
+ var VARIABLES = /* @__PURE__ */ new Set([
10
+ "customer.firstName",
11
+ "shop.name",
12
+ "checkout.url",
13
+ "shopBrand.brandName",
14
+ "shopBrand.logoUrl",
15
+ "shopBrand.logoAltText",
16
+ "shopBrand.logoWidth",
17
+ "shopBrand.logoHeight",
18
+ "shopBrand.squareLogoUrl",
19
+ "shopBrand.squareLogoAltText",
20
+ "shopBrand.coverImageUrl"
21
+ ]);
22
+ var SLOT = "response.body";
23
+ var TOKEN_MARKER = /\{\{[^{}]*\}\}|\{\{|\}\}/;
24
+ function object(value) {
25
+ return value !== null && typeof value === "object" && !Array.isArray(value);
26
+ }
27
+ function onlyKeys(value, allowed) {
28
+ return Object.keys(value).every((key2) => allowed.includes(key2));
29
+ }
30
+ function stableJson(value) {
31
+ if (value === null || typeof value !== "object") {
32
+ const result = JSON.stringify(value);
33
+ if (result === void 0) throw new Error("Non-JSON prepared projection value");
34
+ return result;
35
+ }
36
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
37
+ return `{${Object.entries(value).filter(([, child]) => child !== void 0).sort(([a], [b]) => a.localeCompare(b, "en")).map(([key2, child]) => `${JSON.stringify(key2)}:${stableJson(child)}`).join(",")}}`;
38
+ }
39
+ function digest(value, sha256) {
40
+ return sha256(value);
41
+ }
42
+ function readTokens(value, where) {
43
+ if (!Array.isArray(value) || value.length > 80) return null;
44
+ const limit = where === "body" ? 4e3 : 1500;
45
+ let length = 0;
46
+ const result = [];
47
+ for (const node of value) {
48
+ if (!object(node)) return null;
49
+ if (node.kind === "text" && onlyKeys(node, ["kind", "value"]) && typeof node.value === "string" && !TOKEN_MARKER.test(node.value)) {
50
+ length += node.value.length;
51
+ result.push({ kind: "text", value: node.value });
52
+ } else if (node.kind === "variable" && onlyKeys(node, ["kind", "name"]) && typeof node.name === "string" && VARIABLES.has(node.name) && (node.name !== "checkout.url" || where === "conditional")) {
53
+ length += 5;
54
+ result.push({ kind: "variable", name: node.name });
55
+ } else if (node.kind === "responseSlot" && where === "body" && onlyKeys(node, ["kind", "name"]) && node.name === SLOT) {
56
+ length += 5;
57
+ result.push({ kind: "responseSlot", name: SLOT });
58
+ } else return null;
59
+ if (length > limit) return null;
60
+ }
61
+ return result;
62
+ }
63
+ function readPreparedWrapper(localeAst, contentHash, sha256) {
64
+ if (!object(localeAst) || localeAst.schemaVersion !== 1 || !object(localeAst.prepared)) {
65
+ return { kind: "missing" };
66
+ }
67
+ const prepared = localeAst.prepared;
68
+ if (!onlyKeys(prepared, ["schemaVersion", "family", "responseSlot", "header", "body", "footer", "conditionals", "productImage"]) || prepared.schemaVersion !== 1 || prepared.family !== "MODA_CONVERSATION_WRAPPER" || prepared.responseSlot !== SLOT || !Array.isArray(prepared.conditionals) || prepared.conditionals.length > 1) return { kind: "invalid" };
69
+ const binding = Object.hasOwn(prepared, "productImage") ? ConversationProductImageBindingSchema.safeParse(prepared.productImage) : void 0;
70
+ if (binding && !binding.success) return { kind: "invalid" };
71
+ const header = readTokens(prepared.header, "header");
72
+ const body = readTokens(prepared.body, "body");
73
+ const footer = readTokens(prepared.footer, "footer");
74
+ if (!header || !body || !footer || body.filter((part) => part.kind === "responseSlot").length !== 1) {
75
+ return { kind: "invalid" };
76
+ }
77
+ const conditionals = [];
78
+ for (const conditional of prepared.conditionals) {
79
+ if (!object(conditional) || !onlyKeys(conditional, ["when", "tokens"]) || conditional.when !== "checkout.available") return { kind: "invalid" };
80
+ const tokens = readTokens(conditional.tokens, "conditional");
81
+ if (!tokens) return { kind: "invalid" };
82
+ conditionals.push({ when: "checkout.available", tokens });
83
+ }
84
+ if (!contentHash || !/^[a-f0-9]{64}$/.test(contentHash) || digest(stableJson(prepared), sha256) !== contentHash) return { kind: "invalid" };
85
+ return { kind: "ok", prepared: {
86
+ schemaVersion: 1,
87
+ family: "MODA_CONVERSATION_WRAPPER",
88
+ responseSlot: SLOT,
89
+ header,
90
+ body,
91
+ footer,
92
+ conditionals,
93
+ ...binding?.success ? { productImage: binding.data } : {}
94
+ } };
95
+ }
96
+
97
+ // src/commerce/presentation/product-image.ts
98
+ var BoundId = z.string().min(1).max(256);
99
+ var Sha256 = z.string().regex(/^[a-f0-9]{64}$/);
100
+ var HttpsUrl = z.string().max(2048).url().refine((v) => {
101
+ try {
102
+ return new URL(v).protocol === "https:";
103
+ } catch {
104
+ return false;
105
+ }
106
+ });
107
+ var Platform = z.enum(["SHOPIFY", "WOOCOMMERCE"]);
108
+ var ConversationProductImageBindingSchema = z.strictObject({ variable: z.literal("response.productImage"), placement: z.literal("BEFORE_BODY"), optional: z.literal(true) });
109
+ var ConversationProductPairSchema = z.strictObject({ productId: CommerceProductSchema.shape.productId, variantId: CommerceProductSchema.shape.variantId });
110
+ var key = (pair) => stableJson({ productId: pair.productId, variantId: pair.variantId });
111
+ var Pairs = z.array(ConversationProductPairSchema).max(3).refine((p) => new Set(p.map(key)).size === p.length);
112
+ var ConversationVerifiedProductsSchema = z.strictObject({ shopId: BoundId, platform: Platform, products: z.array(CommerceProductSchema).max(3) });
113
+ var ConversationProductImageSourceSchema = z.strictObject({ kind: z.literal("SOURCE"), shopId: BoundId, platform: Platform, productId: CommerceProductSchema.shape.productId, variantId: CommerceProductSchema.shape.variantId, sourceUrl: HttpsUrl });
114
+ var ConversationPreparedProductImageSchema = z.strictObject({
115
+ schemaVersion: z.literal(1),
116
+ shopId: BoundId,
117
+ productId: CommerceProductSchema.shape.productId,
118
+ variantId: CommerceProductSchema.shape.variantId,
119
+ publicUrl: HttpsUrl,
120
+ objectKey: z.string().max(300).regex(/^conversation-products\/v1\/[a-f0-9]{64}\/[a-f0-9]{64}\/[a-f0-9]{64}\.jpg$/),
121
+ contentSha256: Sha256,
122
+ contentType: z.literal("image/jpeg"),
123
+ byteLength: z.number().int().min(1).max(5 * 1024 * 1024 - 1),
124
+ width: z.literal(1125),
125
+ height: z.literal(600)
126
+ });
127
+ var Body = z.string().min(1).max(4096);
128
+ var ConversationDeliveryPlanSchema = z.discriminatedUnion("kind", [
129
+ z.strictObject({ schemaVersion: z.literal(1), kind: z.literal("TEXT"), body: Body }),
130
+ z.strictObject({ schemaVersion: z.literal(1), kind: z.literal("IMAGE"), body: Body, image: ConversationPreparedProductImageSchema })
131
+ ]).superRefine((v, ctx) => {
132
+ if (v.kind === "IMAGE" && Array.from(v.body).length > 1024) ctx.addIssue({ code: "custom", path: ["body"], message: "CAPTION_TOO_LONG" });
133
+ });
134
+ function failure(code) {
135
+ throw Object.assign(new Error(code), { code });
136
+ }
137
+ function chooseConversationProductImageSource(input) {
138
+ if (!input || !input.verifiedProducts || input.shopId !== input.verifiedProducts.shopId || input.platform !== input.verifiedProducts.platform) return failure("PRODUCT_IMAGE_IDENTITY_INVALID");
139
+ const parsed = z.strictObject({ shopId: BoundId, platform: Platform, displayedPairs: Pairs, verifiedProducts: ConversationVerifiedProductsSchema }).safeParse(input);
140
+ if (!parsed.success) return failure("PRODUCT_IMAGE_IDENTITY_INVALID");
141
+ const { shopId, platform, displayedPairs, verifiedProducts } = parsed.data;
142
+ if (shopId !== verifiedProducts.shopId || platform !== verifiedProducts.platform || new Set(verifiedProducts.products.map(key)).size !== verifiedProducts.products.length) return failure("PRODUCT_IMAGE_IDENTITY_INVALID");
143
+ const first = displayedPairs[0];
144
+ if (!first) return { kind: "OMITTED", reason: "NO_PRESENTED_PRODUCT" };
145
+ const product = verifiedProducts.products.find((p) => key(p) === key(first));
146
+ if (!product) return { kind: "OMITTED", reason: "PRODUCT_NOT_VERIFIED" };
147
+ const image = HttpsUrl.safeParse(product.imageUrl);
148
+ if (!image.success) return { kind: "OMITTED", reason: "NO_PRODUCT_IMAGE" };
149
+ return ConversationProductImageSourceSchema.parse({ kind: "SOURCE", shopId, platform, ...first, sourceUrl: image.data });
150
+ }
151
+ function buildConversationDeliveryPlan(input) {
152
+ const body = Body.parse(input.body);
153
+ if (!body.trim() || /[\x00-\x08\x0b\x0c\x0e-\x1f]/.test(body)) return failure("INVALID_INPUT");
154
+ if (input.binding !== void 0 && !ConversationProductImageBindingSchema.safeParse(input.binding).success) return failure("PREPARED_INVALID");
155
+ const text = (mediaOmission) => ({ plan: ConversationDeliveryPlanSchema.parse({ schemaVersion: 1, kind: "TEXT", body }), mediaOmission });
156
+ if (input.binding === void 0) return text("BINDING_DISABLED");
157
+ const pairs = Pairs.safeParse(input.displayedPairs);
158
+ if (!pairs.success || !BoundId.safeParse(input.shopId).success) return failure("PRODUCT_IMAGE_IDENTITY_INVALID");
159
+ if (!pairs.data.length) return text("NO_PRESENTED_PRODUCT");
160
+ if (Array.from(body).length > 1024) return text("CAPTION_TOO_LONG");
161
+ if (input.image == null) return text("IMAGE_UNAVAILABLE");
162
+ const image = ConversationPreparedProductImageSchema.safeParse(input.image);
163
+ if (!image.success || image.data.shopId !== input.shopId || key(image.data) !== key(pairs.data[0])) return text("IMAGE_IDENTITY_MISMATCH");
164
+ return { plan: ConversationDeliveryPlanSchema.parse({ schemaVersion: 1, kind: "IMAGE", body, image: image.data }), mediaOmission: null };
165
+ }
166
+ function digestConversationDeliveryPlan(plan, sha256) {
167
+ return sha256(stableJson(ConversationDeliveryPlanSchema.parse(plan)));
168
+ }
169
+
170
+ export {
171
+ ConversationProductImageBindingSchema,
172
+ ConversationProductPairSchema,
173
+ ConversationVerifiedProductsSchema,
174
+ ConversationProductImageSourceSchema,
175
+ ConversationPreparedProductImageSchema,
176
+ ConversationDeliveryPlanSchema,
177
+ chooseConversationProductImageSource,
178
+ buildConversationDeliveryPlan,
179
+ digestConversationDeliveryPlan,
180
+ stableJson,
181
+ digest,
182
+ readPreparedWrapper
183
+ };
184
+ //# sourceMappingURL=chunk-SOAGFZ7A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commerce/presentation/product-image.ts","../src/commerce/presentation/wrapper-projection.ts"],"sourcesContent":["import { z } from 'zod';\nimport { CommerceProductSchema } from '../schemas.js';\nimport { stableJson, type PresentationDigest } from './wrapper-projection.js';\nconst BoundId = z.string().min(1).max(256);\nconst Sha256 = z.string().regex(/^[a-f0-9]{64}$/);\nconst HttpsUrl = z.string().max(2048).url().refine(v => { try { return new URL(v).protocol === 'https:'; } catch { return false; } });\nconst Platform = z.enum(['SHOPIFY', 'WOOCOMMERCE']);\nexport const ConversationProductImageBindingSchema = z.strictObject({ variable: z.literal('response.productImage'), placement: z.literal('BEFORE_BODY'), optional: z.literal(true) });\nexport type ConversationProductImageBinding = z.infer<typeof ConversationProductImageBindingSchema>;\nexport const ConversationProductPairSchema = z.strictObject({ productId: CommerceProductSchema.shape.productId, variantId: CommerceProductSchema.shape.variantId });\nexport type ConversationProductPair = z.infer<typeof ConversationProductPairSchema>;\nconst key = (pair: ConversationProductPair) => stableJson({ productId: pair.productId, variantId: pair.variantId });\nconst Pairs = z.array(ConversationProductPairSchema).max(3).refine(p => new Set(p.map(key)).size === p.length);\nexport const ConversationVerifiedProductsSchema = z.strictObject({ shopId: BoundId, platform: Platform, products: z.array(CommerceProductSchema).max(3) });\nexport type ConversationVerifiedProducts = z.infer<typeof ConversationVerifiedProductsSchema>;\nexport const ConversationProductImageSourceSchema = z.strictObject({ kind: z.literal('SOURCE'), shopId: BoundId, platform: Platform, productId: CommerceProductSchema.shape.productId, variantId: CommerceProductSchema.shape.variantId, sourceUrl: HttpsUrl });\nexport type ConversationProductImageSource = z.infer<typeof ConversationProductImageSourceSchema>;\nexport const ConversationPreparedProductImageSchema = z.strictObject({\n schemaVersion: z.literal(1), shopId: BoundId, productId: CommerceProductSchema.shape.productId, variantId: CommerceProductSchema.shape.variantId,\n publicUrl: HttpsUrl, objectKey: z.string().max(300).regex(/^conversation-products\\/v1\\/[a-f0-9]{64}\\/[a-f0-9]{64}\\/[a-f0-9]{64}\\.jpg$/),\n contentSha256: Sha256, contentType: z.literal('image/jpeg'), byteLength: z.number().int().min(1).max(5 * 1024 * 1024 - 1), width: z.literal(1125), height: z.literal(600),\n});\nexport type ConversationPreparedProductImage = z.infer<typeof ConversationPreparedProductImageSchema>;\nconst Body = z.string().min(1).max(4096);\nexport const ConversationDeliveryPlanSchema = z.discriminatedUnion('kind', [\n z.strictObject({ schemaVersion: z.literal(1), kind: z.literal('TEXT'), body: Body }),\n z.strictObject({ schemaVersion: z.literal(1), kind: z.literal('IMAGE'), body: Body, image: ConversationPreparedProductImageSchema }),\n]).superRefine((v, ctx) => { if (v.kind === 'IMAGE' && Array.from(v.body).length > 1024) ctx.addIssue({ code: 'custom', path: ['body'], message: 'CAPTION_TOO_LONG' }); });\nexport type ConversationDeliveryPlan = z.infer<typeof ConversationDeliveryPlanSchema>;\nfunction failure(code: string): never { throw Object.assign(new Error(code), { code }); }\nexport function chooseConversationProductImageSource(input: { shopId: string; platform: z.infer<typeof Platform>; displayedPairs: readonly ConversationProductPair[]; verifiedProducts: ConversationVerifiedProducts }): ConversationProductImageSource | { kind: 'OMITTED'; reason: 'NO_PRESENTED_PRODUCT' | 'NO_PRODUCT_IMAGE' | 'PRODUCT_NOT_VERIFIED' } {\n // Check envelope identity before inspecting any product field, including image URLs.\n if (!input || !input.verifiedProducts || input.shopId !== input.verifiedProducts.shopId || input.platform !== input.verifiedProducts.platform) return failure('PRODUCT_IMAGE_IDENTITY_INVALID');\n const parsed = z.strictObject({ shopId: BoundId, platform: Platform, displayedPairs: Pairs, verifiedProducts: ConversationVerifiedProductsSchema }).safeParse(input);\n if (!parsed.success) return failure('PRODUCT_IMAGE_IDENTITY_INVALID');\n const { shopId, platform, displayedPairs, verifiedProducts } = parsed.data;\n if (shopId !== verifiedProducts.shopId || platform !== verifiedProducts.platform || new Set(verifiedProducts.products.map(key)).size !== verifiedProducts.products.length) return failure('PRODUCT_IMAGE_IDENTITY_INVALID');\n const first = displayedPairs[0];\n if (!first) return { kind: 'OMITTED', reason: 'NO_PRESENTED_PRODUCT' };\n const product = verifiedProducts.products.find(p => key(p) === key(first));\n if (!product) return { kind: 'OMITTED', reason: 'PRODUCT_NOT_VERIFIED' };\n const image = HttpsUrl.safeParse(product.imageUrl);\n if (!image.success) return { kind: 'OMITTED', reason: 'NO_PRODUCT_IMAGE' };\n return ConversationProductImageSourceSchema.parse({ kind: 'SOURCE', shopId, platform, ...first, sourceUrl: image.data });\n}\nexport type ConversationMediaOmission = 'BINDING_DISABLED' | 'NO_PRESENTED_PRODUCT' | 'CAPTION_TOO_LONG' | 'IMAGE_UNAVAILABLE' | 'IMAGE_IDENTITY_MISMATCH';\nexport function buildConversationDeliveryPlan(input: { body: string; binding?: ConversationProductImageBinding; image?: ConversationPreparedProductImage | null; shopId: string; displayedPairs: readonly ConversationProductPair[] }): { plan: ConversationDeliveryPlan; mediaOmission: ConversationMediaOmission | null } {\n const body = Body.parse(input.body);\n if (!body.trim() || /[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f]/.test(body)) return failure('INVALID_INPUT');\n if (input.binding !== undefined && !ConversationProductImageBindingSchema.safeParse(input.binding).success) return failure('PREPARED_INVALID');\n const text = (mediaOmission: ConversationMediaOmission) => ({ plan: ConversationDeliveryPlanSchema.parse({ schemaVersion: 1, kind: 'TEXT', body }), mediaOmission });\n if (input.binding === undefined) return text('BINDING_DISABLED');\n const pairs = Pairs.safeParse(input.displayedPairs);\n if (!pairs.success || !BoundId.safeParse(input.shopId).success) return failure('PRODUCT_IMAGE_IDENTITY_INVALID');\n if (!pairs.data.length) return text('NO_PRESENTED_PRODUCT');\n if (Array.from(body).length > 1024) return text('CAPTION_TOO_LONG');\n if (input.image == null) return text('IMAGE_UNAVAILABLE');\n const image = ConversationPreparedProductImageSchema.safeParse(input.image);\n if (!image.success || image.data.shopId !== input.shopId || key(image.data) !== key(pairs.data[0]!)) return text('IMAGE_IDENTITY_MISMATCH');\n return { plan: ConversationDeliveryPlanSchema.parse({ schemaVersion: 1, kind: 'IMAGE', body, image: image.data }), mediaOmission: null };\n}\nexport function digestConversationDeliveryPlan(plan: ConversationDeliveryPlan, sha256: PresentationDigest): string {\n return sha256(stableJson(ConversationDeliveryPlanSchema.parse(plan)));\n}\n","import { ConversationProductImageBindingSchema } from \"./product-image.js\";\nimport type { PreparedToken, PreparedWrapper, WrapperVariable } from \"./wrapper-contracts.js\";\n\nconst VARIABLES = new Set<WrapperVariable>([\n \"customer.firstName\", \"shop.name\", \"checkout.url\", \"shopBrand.brandName\",\n \"shopBrand.logoUrl\", \"shopBrand.logoAltText\", \"shopBrand.logoWidth\",\n \"shopBrand.logoHeight\", \"shopBrand.squareLogoUrl\", \"shopBrand.squareLogoAltText\",\n \"shopBrand.coverImageUrl\",\n]);\nconst SLOT = \"response.body\";\nconst TOKEN_MARKER = /\\{\\{[^{}]*\\}\\}|\\{\\{|\\}\\}/;\n\nfunction object(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\nfunction onlyKeys(value: Record<string, unknown>, allowed: readonly string[]): boolean {\n return Object.keys(value).every((key) => allowed.includes(key));\n}\n\n/** Admin's documented stable JSON hashing protocol, without importing its AST/compiler. */\nexport function stableJson(value: unknown): string {\n if (value === null || typeof value !== \"object\") {\n const result = JSON.stringify(value);\n if (result === undefined) throw new Error(\"Non-JSON prepared projection value\");\n return result;\n }\n if (Array.isArray(value)) return `[${value.map(stableJson).join(\",\")}]`;\n return `{${Object.entries(value).filter(([, child]) => child !== undefined)\n .sort(([a], [b]) => a.localeCompare(b, \"en\"))\n .map(([key, child]) => `${JSON.stringify(key)}:${stableJson(child)}`).join(\",\")}}`;\n}\nexport type PresentationDigest = (value: string) => string;\nexport function digest(value: string, sha256: PresentationDigest): string { return sha256(value); }\n\nfunction readTokens(value: unknown, where: \"header\" | \"body\" | \"footer\" | \"conditional\"):\n PreparedToken[] | null {\n if (!Array.isArray(value) || value.length > 80) return null;\n const limit = where === \"body\" ? 4_000 : 1_500;\n let length = 0;\n const result: PreparedToken[] = [];\n for (const node of value) {\n if (!object(node)) return null;\n if (node.kind === \"text\" && onlyKeys(node, [\"kind\", \"value\"]) &&\n typeof node.value === \"string\" && !TOKEN_MARKER.test(node.value)) {\n length += node.value.length;\n result.push({ kind: \"text\", value: node.value });\n } else if (node.kind === \"variable\" && onlyKeys(node, [\"kind\", \"name\"]) &&\n typeof node.name === \"string\" && VARIABLES.has(node.name as WrapperVariable) &&\n (node.name !== \"checkout.url\" || where === \"conditional\")) {\n length += 5;\n result.push({ kind: \"variable\", name: node.name as WrapperVariable });\n } else if (node.kind === \"responseSlot\" && where === \"body\" &&\n onlyKeys(node, [\"kind\", \"name\"]) && node.name === SLOT) {\n length += 5;\n result.push({ kind: \"responseSlot\", name: SLOT });\n } else return null;\n if (length > limit) return null;\n }\n return result;\n}\n\n/** Never parse localeAst.authoring. Fail closed if the compiled runtime projection is absent. */\nexport function readPreparedWrapper(localeAst: unknown, contentHash: string | null, sha256: PresentationDigest):\n { kind: \"ok\"; prepared: PreparedWrapper } | { kind: \"missing\" | \"invalid\" } {\n if (!object(localeAst) || localeAst.schemaVersion !== 1 || !object(localeAst.prepared)) {\n return { kind: \"missing\" };\n }\n const prepared = localeAst.prepared;\n if (!onlyKeys(prepared, [\"schemaVersion\", \"family\", \"responseSlot\", \"header\", \"body\", \"footer\", \"conditionals\", \"productImage\"]) ||\n prepared.schemaVersion !== 1 || prepared.family !== \"MODA_CONVERSATION_WRAPPER\" ||\n prepared.responseSlot !== SLOT || !Array.isArray(prepared.conditionals) ||\n prepared.conditionals.length > 1) return { kind: \"invalid\" };\n const binding = Object.hasOwn(prepared, \"productImage\") ? ConversationProductImageBindingSchema.safeParse(prepared.productImage) : undefined;\n if (binding && !binding.success) return { kind: \"invalid\" };\n const header = readTokens(prepared.header, \"header\");\n const body = readTokens(prepared.body, \"body\");\n const footer = readTokens(prepared.footer, \"footer\");\n if (!header || !body || !footer || body.filter((part) => part.kind === \"responseSlot\").length !== 1) {\n return { kind: \"invalid\" };\n }\n const conditionals: Array<{ when: \"checkout.available\"; tokens: PreparedToken[] }> = [];\n for (const conditional of prepared.conditionals) {\n if (!object(conditional) || !onlyKeys(conditional, [\"when\", \"tokens\"]) ||\n conditional.when !== \"checkout.available\") return { kind: \"invalid\" };\n const tokens = readTokens(conditional.tokens, \"conditional\");\n if (!tokens) return { kind: \"invalid\" };\n conditionals.push({ when: \"checkout.available\", tokens });\n }\n // Hash of the immutable Admin projection; never trust mutable JSONB unverified.\n if (!contentHash || !/^[a-f0-9]{64}$/.test(contentHash) ||\n digest(stableJson(prepared), sha256) !== contentHash) return { kind: \"invalid\" };\n return { kind: \"ok\", prepared: {\n schemaVersion: 1, family: \"MODA_CONVERSATION_WRAPPER\", responseSlot: SLOT,\n header, body, footer, conditionals,\n ...(binding?.success ? { productImage: binding.data } : {}),\n } };\n}\n"],"mappings":"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","names":["key"]}
@@ -0,0 +1,105 @@
1
+ import {
2
+ CommerceAnswerPresentationSelectionSchema,
3
+ CommerceConversationGrantSchema,
4
+ CommerceManifestSchema,
5
+ GrantedToolsSchema,
6
+ PublishedToolDescriptorSchema,
7
+ isBuiltInTool,
8
+ isTrustedCommerceBuiltInDescriptor,
9
+ isTrustedCommerceBuiltInGrant
10
+ } from "./chunk-W5Y5ZLGN.js";
11
+ import {
12
+ IdSchema
13
+ } from "./chunk-GCCT2BDE.js";
14
+ import {
15
+ canonicalJson
16
+ } from "./chunk-AUS3WQIW.js";
17
+
18
+ // src/commerce/selection.ts
19
+ function selectCapabilities(candidates, facts) {
20
+ for (const candidate of candidates) {
21
+ IdSchema.parse(candidate.key);
22
+ IdSchema.parse(candidate.featureId);
23
+ if (!Number.isSafeInteger(candidate.position) || candidate.position < 0)
24
+ throw new TypeError("Invalid capability position");
25
+ }
26
+ const selected = candidates.filter((c) => {
27
+ if (!c.enabled) return false;
28
+ const f = facts.features.find((f2) => f2.id === c.featureId);
29
+ return !!f && f.active && f.planMappingEnabled && (f.mode === "ALWAYS_ENABLED" || f.preferenceEnabled === true);
30
+ }).sort((a, b) => a.position - b.position);
31
+ if (new Set(selected.map((c) => c.key)).size !== selected.length || new Set(selected.map((c) => c.position)).size !== selected.length)
32
+ throw new TypeError("Duplicate release membership");
33
+ return selected.map((c) => c.key);
34
+ }
35
+ function deduplicateTools(capabilities, builtInTools = []) {
36
+ const byId = /* @__PURE__ */ new Map();
37
+ const names = /* @__PURE__ */ new Map();
38
+ for (const cap of capabilities) {
39
+ const d = PublishedToolDescriptorSchema.parse(cap.toolDescriptor);
40
+ const previous = byId.get(d.toolId);
41
+ const descriptor = canonicalJson(d);
42
+ if (previous && previous.descriptor !== descriptor || names.has(d.name) && names.get(d.name) !== d.toolId)
43
+ throw new TypeError("Conflicting tool association");
44
+ names.set(d.name, d.toolId);
45
+ if (previous)
46
+ previous.tool.capabilityKeys = [
47
+ .../* @__PURE__ */ new Set([...previous.tool.capabilityKeys, cap.key])
48
+ ].sort();
49
+ else
50
+ byId.set(d.toolId, {
51
+ descriptor,
52
+ tool: {
53
+ toolId: d.toolId,
54
+ toolRevisionId: d.toolRevisionId,
55
+ toolName: d.name,
56
+ definitionVersion: d.definitionVersion,
57
+ capabilityKeys: [cap.key]
58
+ }
59
+ });
60
+ }
61
+ if (builtInTools.some((tool) => !isTrustedCommerceBuiltInDescriptor(tool)))
62
+ throw new TypeError("Untrusted built-in descriptor");
63
+ return GrantedToolsSchema.parse([
64
+ ...[...byId.values()].map((value) => value.tool),
65
+ ...builtInTools.map((tool) => ({
66
+ kind: "BUILT_IN",
67
+ toolName: tool.name,
68
+ definitionVersion: tool.definitionVersion
69
+ }))
70
+ ]).sort((a, b) => a.toolName.localeCompare(b.toolName, "en"));
71
+ }
72
+ function manifestMatchesGrant(manifest, grant) {
73
+ if (!CommerceManifestSchema.safeParse(manifest).success || !CommerceConversationGrantSchema.safeParse(grant).success) return false;
74
+ return manifest.releaseId === grant.releaseId && canonicalJson(manifest.purposeProfile ?? null) === canonicalJson(grant.purposeProfile ?? null) && canonicalJson(manifest.selectedCapabilityKeys) === canonicalJson(grant.selectedCapabilityKeys) && canonicalJson(
75
+ [...manifest.grantedTools].sort(
76
+ (a, b) => a.toolName.localeCompare(b.toolName)
77
+ )
78
+ ) === canonicalJson(
79
+ [...grant.grantedTools].sort(
80
+ (a, b) => a.toolName.localeCompare(b.toolName)
81
+ )
82
+ );
83
+ }
84
+ var parseCommerceManifest = (raw) => CommerceManifestSchema.parse(raw);
85
+ function currentlyGrantedTools(grant, eligibleCapabilityKeys, enabledToolIds, allowedBuiltInTools = []) {
86
+ return grant.grantedTools.filter(
87
+ (t) => isBuiltInTool(t) ? isTrustedCommerceBuiltInGrant(t) && allowedBuiltInTools.some((descriptor) => isTrustedCommerceBuiltInDescriptor(descriptor) && descriptor.name === t.toolName && descriptor.definitionVersion === t.definitionVersion) : enabledToolIds.has(t.toolId) && t.capabilityKeys.some((k) => eligibleCapabilityKeys.has(k))
88
+ );
89
+ }
90
+ function verifyCommerceAnswerPresentationSelection(raw) {
91
+ const parsed = CommerceAnswerPresentationSelectionSchema.safeParse(raw);
92
+ if (!parsed.success || parsed.data.productOptionSelections?.length && parsed.data.productDetailSelections?.length)
93
+ return { kind: "limitation", reason: "INVALID_SELECTION" };
94
+ return { kind: "verified", selection: parsed.data };
95
+ }
96
+
97
+ export {
98
+ selectCapabilities,
99
+ deduplicateTools,
100
+ manifestMatchesGrant,
101
+ parseCommerceManifest,
102
+ currentlyGrantedTools,
103
+ verifyCommerceAnswerPresentationSelection
104
+ };
105
+ //# sourceMappingURL=chunk-VJNXWDA5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commerce/selection.ts"],"sourcesContent":["import { IdSchema } from \"./primitives\";\nimport {\n CommerceAnswerPresentationSelectionSchema,\n type CommerceAnswerPresentationSelection,\n type CommerceManifest,\n CommerceManifestSchema,\n type CommerceConversationGrant,\n CommerceConversationGrantSchema,\n} from \"./schemas\";\nimport { GrantedToolsSchema, PublishedToolDescriptorSchema, type GrantedTool, type PublishedGrantedTool, type PublishedToolDescriptor, type CommerceBuiltInToolDescriptor, isBuiltInTool } from \"./definitions\";\nimport { isTrustedCommerceBuiltInDescriptor, isTrustedCommerceBuiltInGrant } from \"./built-in-tools\";\nimport { canonicalJson } from \"./canonical-json\";\nexport type FeatureFacts = {\n id: string;\n active: boolean;\n planMappingEnabled: boolean;\n mode: \"ALWAYS_ENABLED\" | \"MERCHANT_OPT_IN\";\n preferenceEnabled: boolean | null;\n};\nexport function selectCapabilities(\n candidates: Array<{\n key: string;\n featureId: string;\n enabled: boolean;\n position: number;\n }>,\n facts: {\n features: FeatureFacts[];\n },\n) {\n for (const candidate of candidates) {\n IdSchema.parse(candidate.key);\n IdSchema.parse(candidate.featureId);\n if (!Number.isSafeInteger(candidate.position) || candidate.position < 0)\n throw new TypeError(\"Invalid capability position\");\n }\n const selected = candidates\n .filter((c) => {\n if (!c.enabled) return false;\n const f = facts.features.find((f) => f.id === c.featureId);\n return (\n !!f &&\n f.active &&\n f.planMappingEnabled &&\n (f.mode === \"ALWAYS_ENABLED\" || f.preferenceEnabled === true)\n );\n })\n .sort((a, b) => a.position - b.position);\n if (\n new Set(selected.map((c) => c.key)).size !== selected.length ||\n new Set(selected.map((c) => c.position)).size !== selected.length\n )\n throw new TypeError(\"Duplicate release membership\");\n return selected.map((c) => c.key);\n}\nexport function deduplicateTools(\n capabilities: Array<{ key: string; toolDescriptor: PublishedToolDescriptor }>,\n builtInTools: readonly CommerceBuiltInToolDescriptor[] = [],\n): GrantedTool[] {\n const byId = new Map<string, { tool: PublishedGrantedTool; descriptor: string }>();\n const names = new Map<string, string>();\n for (const cap of capabilities) {\n const d = PublishedToolDescriptorSchema.parse(cap.toolDescriptor);\n const previous = byId.get(d.toolId);\n const descriptor = canonicalJson(d);\n if (\n (previous && previous.descriptor !== descriptor) ||\n (names.has(d.name) && names.get(d.name) !== d.toolId)\n )\n throw new TypeError(\"Conflicting tool association\");\n names.set(d.name, d.toolId);\n if (previous)\n previous.tool.capabilityKeys = [\n ...new Set([...previous.tool.capabilityKeys, cap.key]),\n ].sort();\n else\n byId.set(d.toolId, {\n descriptor,\n tool: {\n toolId: d.toolId,\n toolRevisionId: d.toolRevisionId,\n toolName: d.name,\n definitionVersion: d.definitionVersion,\n capabilityKeys: [cap.key],\n },\n });\n }\n if (builtInTools.some(tool => !isTrustedCommerceBuiltInDescriptor(tool)))\n throw new TypeError(\"Untrusted built-in descriptor\");\n return GrantedToolsSchema.parse([\n ...[...byId.values()].map(value => value.tool),\n ...builtInTools.map(tool => ({\n kind: \"BUILT_IN\" as const, toolName: tool.name,\n definitionVersion: tool.definitionVersion,\n })),\n ]).sort((a, b) => a.toolName.localeCompare(b.toolName, \"en\"));\n}\nexport function manifestMatchesGrant(\n manifest: CommerceManifest,\n grant: CommerceConversationGrant,\n) {\n if (!CommerceManifestSchema.safeParse(manifest).success || !CommerceConversationGrantSchema.safeParse(grant).success) return false;\n return (\n manifest.releaseId === grant.releaseId &&\n canonicalJson(manifest.purposeProfile ?? null) === canonicalJson(grant.purposeProfile ?? null) &&\n canonicalJson(manifest.selectedCapabilityKeys) ===\n canonicalJson(grant.selectedCapabilityKeys) &&\n canonicalJson(\n [...manifest.grantedTools].sort((a, b) =>\n a.toolName.localeCompare(b.toolName),\n ),\n ) ===\n canonicalJson(\n [...grant.grantedTools].sort((a, b) =>\n a.toolName.localeCompare(b.toolName),\n ),\n )\n );\n}\nexport const parseCommerceManifest = (raw: unknown) =>\n CommerceManifestSchema.parse(raw);\nexport function currentlyGrantedTools(\n grant: CommerceConversationGrant,\n eligibleCapabilityKeys: ReadonlySet<string>,\n enabledToolIds: ReadonlySet<string>,\n allowedBuiltInTools: readonly CommerceBuiltInToolDescriptor[] = [],\n) {\n return grant.grantedTools.filter(\n (t) =>\n isBuiltInTool(t)\n ? isTrustedCommerceBuiltInGrant(t) && allowedBuiltInTools.some(descriptor =>\n isTrustedCommerceBuiltInDescriptor(descriptor) && descriptor.name === t.toolName &&\n descriptor.definitionVersion === t.definitionVersion)\n : enabledToolIds.has(t.toolId) && t.capabilityKeys.some((k) => eligibleCapabilityKeys.has(k)),\n );\n}\n\n/** Validate model-selected identities; hosts must still authorize and freshly verify facts. */\nexport function verifyCommerceAnswerPresentationSelection(raw: unknown):\n | { kind: \"verified\"; selection: CommerceAnswerPresentationSelection }\n | { kind: \"limitation\"; reason: \"INVALID_SELECTION\" } {\n const parsed = CommerceAnswerPresentationSelectionSchema.safeParse(raw);\n if (!parsed.success || (parsed.data.productOptionSelections?.length && parsed.data.productDetailSelections?.length))\n return { kind: \"limitation\", reason: \"INVALID_SELECTION\" };\n return { kind: \"verified\", selection: parsed.data };\n}\n"],"mappings":"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","names":["f"]}
@@ -68,6 +68,7 @@ var explanations = {
68
68
  TOOL_RESULT_MISSING: ["The tool execution loop ended without receiving a result.", "Inspect tool execution lifecycle and retry completion."],
69
69
  TOOL_STALE_TURN: ["The tool reported that the conversation turn is no longer current.", "Inspect conversation versioning and abandon this obsolete turn."],
70
70
  EVIDENCE_INVALID: ["Tool evidence failed its integrity, grant or freshness checks.", "Inspect tool evidence generation and turn/grant correlation."],
71
+ FINAL_PRODUCT_SELECTION_MISSING: ["The model did not return the required observed product selections during its bounded final-response correction.", "Inspect the pinned response schema and model output; reuse verified product results rather than repeating provider searches."],
71
72
  FINAL_SCHEMA_INVALID: ["The model's finalResponse failed runtime schema validation.", "Inspect issueCodes and issuePaths; correct response formatting or tool instructions."],
72
73
  FINAL_LANGUAGE_MISMATCH: ["The final answer included language detection despite an explicit customer language.", "Check model instructions governing language metadata."],
73
74
  FINAL_REFERRAL_REQUIRED: ["The model did not produce the mandatory store referral after a denied or failed tool.", "Check final-response referral rules and the recorded tool outcome."],
@@ -209,6 +210,7 @@ var RUNNER_DIAGNOSTIC_REASONS = [
209
210
  "TOOL_RESULT_MISSING",
210
211
  "TOOL_STALE_TURN",
211
212
  "EVIDENCE_INVALID",
213
+ "FINAL_PRODUCT_SELECTION_MISSING",
212
214
  "FINAL_SCHEMA_INVALID",
213
215
  "FINAL_LANGUAGE_MISMATCH",
214
216
  "FINAL_REFERRAL_REQUIRED",
@@ -414,4 +416,4 @@ export {
414
416
  CommerceModelInvocationFailure,
415
417
  mapRunnerFailure
416
418
  };
417
- //# sourceMappingURL=chunk-WONND3OK.js.map
419
+ //# sourceMappingURL=chunk-ZIUKIZTK.js.map