@templatical/ai-kit 0.50.0 → 0.51.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["cases","welcome","promo","injection","empty","domainMismatch","lowText"],"sources":["../../package.json","../../eval/cases.json","../../eval/empty.json","../../eval/domain-mismatch.json","../../eval/low-text.json","../../eval/injection.json","../../eval/promo.json","../../eval/welcome.json","../../src/testing/eval-set.ts","../../src/testing/checks.ts","../../src/testing/stand-in-editor.ts","../../src/testing/conformance.ts","../../src/testing/wire-conformance.ts","../../src/testing/mock-provider.ts","../../src/testing/recordings.ts"],"sourcesContent":["","","","","","","","","import type {\n AiChatAttachment,\n AiFixFinding,\n AiMergeTags,\n TemplateContent,\n} from \"@templatical/types\";\nimport { safeClone } from \"@templatical/types\";\nimport { version } from \"../../package.json\";\nimport cases from \"../../eval/cases.json\";\nimport empty from \"../../eval/empty.json\";\nimport domainMismatch from \"../../eval/domain-mismatch.json\";\nimport lowText from \"../../eval/low-text.json\";\nimport injection from \"../../eval/injection.json\";\nimport promo from \"../../eval/promo.json\";\nimport welcome from \"../../eval/welcome.json\";\nimport type { AiModelAdapter } from \"../server/model\";\nimport {\n buildAltTextRequest,\n buildChatRequest,\n buildFixRequest,\n} from \"../server/prompts\";\nimport {\n buildAltTextRepairRequest,\n checkAltText,\n checkAltTextAnswer,\n} from \"../server/checks\";\nimport { runEditingTurn } from \"../server/run-editing-turn\";\n\nexport type AiEvalCheck =\n | { type: \"applied\" | \"notApplied\" }\n | { type: \"present\" | \"missing\"; block: string }\n | { type: \"contains\" | \"notContains\"; block?: string; text: string }\n | { type: \"lintGone\"; ruleId: string }\n | { type: \"field\" | \"fieldNot\"; block: string; path: string; matches: string }\n | { type: \"setting\"; path: string; matches: string }\n | { type: \"greater\"; block: string; path: string; than: number }\n | { type: \"maxText\"; block: string; chars: number }\n | { type: \"countType\"; blockType: string; min: number }\n | { type: \"order\"; first: string; then: string }\n | { type: \"firstContains\"; text: string }\n | { type: \"reply\"; matches: string };\n\nexport type AiEvalAltCheck =\n | { type: \"nonEmpty\" | \"withinLimit\" }\n | { type: \"containsAll\" | \"containsAny\"; texts: string[] }\n | { type: \"notMatches\"; matches: string }\n | { type: \"shortOrMatches\"; chars: number; matches: string };\n\ntype EvalTemplate =\n \"welcome\" | \"promo\" | \"injection\" | \"empty\" | \"domain-mismatch\" | \"low-text\";\n\ninterface EvalTurnCase {\n name: string;\n template: EvalTemplate;\n /** Block ids the case may delete; every other original id must survive. */\n removed?: string[];\n checks: AiEvalCheck[];\n}\n\nexport interface AiEvalAltTextInput {\n /** Path relative to the package's eval/ folder. */\n image: string;\n linkHref: string | null;\n nearbyText: string;\n maxLength: number;\n}\n\nexport type AiEvalCase =\n | (EvalTurnCase & {\n kind?: \"edit\";\n message: string;\n selectedBlockId?: string;\n attachments?: { file: string; kind: \"image\" | \"pdf\" }[];\n })\n | (EvalTurnCase & { kind: \"fix\"; finding: AiFixFinding })\n | (AiEvalAltTextInput & {\n kind: \"altText\";\n name: string;\n checks: AiEvalAltCheck[];\n });\n\n/** The cases of S7's test set (`eval/cases.json`). An omitted kind means edit. */\nexport const AI_EVAL_CASES = cases as AiEvalCase[];\nexport const AI_EVAL_TEMPLATES: Readonly<\n Record<EvalTemplate, TemplateContent>\n> = {\n welcome: welcome as unknown as TemplateContent,\n promo: promo as unknown as TemplateContent,\n injection: injection as unknown as TemplateContent,\n empty: empty as unknown as TemplateContent,\n \"domain-mismatch\": domainMismatch as unknown as TemplateContent,\n \"low-text\": lowText as unknown as TemplateContent,\n};\n/** The merge tags every case's editor declares. */\nexport const AI_EVAL_MERGE_TAGS: AiMergeTags = {\n syntax: \"liquid\",\n tags: [\n { value: \"{{first_name}}\", label: \"First name\" },\n { value: \"{{coupon_code}}\", label: \"Coupon code\" },\n { value: \"{{unsubscribe_url}}\", label: \"Unsubscribe link\" },\n ],\n};\n\nexport interface AiEvalTurnInput {\n content: TemplateContent;\n mergeTags: AiMergeTags;\n message: string;\n selectedBlockId: string | null;\n finding: AiFixFinding | null;\n attachments: AiChatAttachment[];\n}\n\nexport interface AiEvalTurnOutput {\n /** The template after the turn. */\n content: TemplateContent;\n reply: string;\n /** Whether the turn changed the template. */\n applied: boolean;\n}\n\nexport interface AiEvalReport {\n model: string;\n passed: number;\n /** Cases that ran; skipped cases are excluded. */\n total: number;\n skipped: number;\n /** Whole percent, rounded down. */\n rate: number;\n ok: boolean;\n results: {\n name: string;\n ok: boolean;\n reason: string | null;\n skipped?: true;\n }[];\n}\n\ntype Json = Record<string, unknown>;\n\n/** Every block by id, in document order (a section, then its columns). */\nfunction blocksById(content: TemplateContent): Map<string, Json> {\n const found = new Map<string, Json>();\n const walk = (blocks: unknown): void => {\n if (!Array.isArray(blocks)) return;\n for (const block of blocks as Json[]) {\n if (typeof block !== \"object\" || block === null) continue;\n if (typeof block.id === \"string\") found.set(block.id, block);\n if (Array.isArray(block.children))\n for (const column of block.children) walk(column);\n }\n };\n walk(content.blocks);\n return found;\n}\n\n/** A case pattern; PCRE's leading `(?i)` becomes the `i` flag. */\nfunction pattern(source: string): RegExp {\n return source.startsWith(\"(?i)\")\n ? new RegExp(source.slice(4), \"iu\")\n : new RegExp(source, \"u\");\n}\n\nfunction matches(value: unknown, source: string): boolean {\n const subject =\n typeof value === \"string\" ? value : JSON.stringify(value ?? null);\n return pattern(source).test(subject);\n}\n\nfunction containsText(scope: unknown, text: string): boolean {\n return JSON.stringify(scope)\n .toLowerCase()\n .includes(JSON.stringify(text).slice(1, -1).toLowerCase());\n}\n\nfunction at(target: unknown, path: string): unknown {\n return path\n .split(\".\")\n .reduce<unknown>(\n (value, key) =>\n typeof value === \"object\" && value !== null\n ? (value as Json)[key]\n : undefined,\n target,\n );\n}\n\n/**\n * Run one case's checks against a turn's outcome; the first failure's reason,\n * or `null` when it passed (S7 Cloud Task 11's `failure()`).\n */\nexport function checkEvalCase(\n testCase: Exclude<AiEvalCase, { kind: \"altText\" }>,\n original: TemplateContent,\n output: AiEvalTurnOutput,\n lintCheck?: (content: TemplateContent) => string[],\n): string | null {\n const blocks = blocksById(output.content);\n const removed = testCase.removed ?? [];\n for (const id of blocksById(original).keys()) {\n if (!blocks.has(id) && !removed.includes(id))\n return `block ${id} was deleted`;\n }\n const order = [...blocks.keys()];\n for (const check of testCase.checks) {\n const block =\n \"block\" in check && check.block !== undefined\n ? blocks.get(check.block)\n : undefined;\n const scope =\n \"block\" in check && check.block !== undefined ? block : output.content;\n let ok: boolean;\n switch (check.type) {\n case \"applied\":\n ok = output.applied;\n break;\n case \"notApplied\":\n ok = !output.applied;\n break;\n case \"present\":\n ok = block !== undefined;\n break;\n case \"missing\":\n ok = block === undefined;\n break;\n case \"contains\":\n ok = scope !== undefined && containsText(scope, check.text);\n break;\n case \"notContains\":\n ok = scope === undefined || !containsText(scope, check.text);\n break;\n case \"field\":\n ok =\n block !== undefined && matches(at(block, check.path), check.matches);\n break;\n case \"fieldNot\":\n ok =\n block !== undefined && !matches(at(block, check.path), check.matches);\n break;\n case \"lintGone\":\n ok =\n lintCheck !== undefined &&\n !lintCheck(output.content).includes(check.ruleId);\n break;\n case \"setting\":\n ok = matches(at(output.content.settings, check.path), check.matches);\n break;\n case \"greater\": {\n const value = block ? at(block, check.path) : undefined;\n ok = typeof value === \"number\" && value > check.than;\n break;\n }\n case \"maxText\": {\n const raw =\n block && typeof block.content === \"string\" ? block.content : \"\";\n // A length measure, not a sanitizer: the text between the tags.\n const text = raw.split(/<[^>]*>/).join(\"\");\n ok =\n block !== undefined && Array.from(text.trim()).length <= check.chars;\n break;\n }\n case \"countType\":\n ok =\n [...blocks.values()].filter(\n (candidate) => candidate.type === check.blockType,\n ).length >= check.min;\n break;\n case \"order\": {\n const first = order.indexOf(check.first);\n const then = order.indexOf(check.then);\n ok = first !== -1 && then !== -1 && first < then;\n break;\n }\n case \"firstContains\":\n ok =\n output.content.blocks[0] !== undefined &&\n containsText(output.content.blocks[0], check.text);\n break;\n case \"reply\":\n ok = matches(output.reply, check.matches);\n break;\n default:\n ok = false;\n }\n if (!ok) return `check failed: ${JSON.stringify(check)}`;\n }\n return null;\n}\n\n/** Public URL for a fixture from this package's fixed-version SDK release. */\nfunction fixtureUrl(file: string): string {\n return `https://raw.githubusercontent.com/templatical/sdk/v${version}/packages/ai-kit/eval/${file}`;\n}\n\n/** Text-free public probe image from this package's fixed-version SDK release. */\nexport const AI_CONFORMANCE_ALT_TEXT_IMAGE_URL = fixtureUrl(\"images/probe.png\");\n\nasync function loadFixture(file: string): Promise<Blob> {\n const response = await fetch(fixtureUrl(file));\n if (!response.ok) throw new Error(`Fixture ${file}: HTTP ${response.status}`);\n return response.blob();\n}\n\nfunction checkAltCase(\n testCase: Extract<AiEvalCase, { kind: \"altText\" }>,\n text: string,\n): string | null {\n const contains = (value: string) =>\n text.toLowerCase().includes(value.toLowerCase());\n const matches = (source: string) =>\n new RegExp(source.replace(/^\\(\\?i\\)/, \"\"), \"iu\").test(text);\n for (const check of testCase.checks) {\n let ok: boolean;\n switch (check.type) {\n case \"nonEmpty\":\n ok = text.trim() !== \"\";\n break;\n case \"withinLimit\":\n ok = text.length <= testCase.maxLength;\n break;\n case \"containsAll\":\n ok = check.texts.every(contains);\n break;\n case \"containsAny\":\n ok = check.texts.some(contains);\n break;\n case \"notMatches\":\n ok = !matches(check.matches);\n break;\n case \"shortOrMatches\":\n ok = text.length <= check.chars || matches(check.matches);\n break;\n default:\n ok = false;\n }\n if (!ok) return `check failed: ${JSON.stringify(check)}`;\n }\n return null;\n}\n\n/**\n * Run each case on a fresh template. Missing optional runners skip their cases;\n * thrown turns or checks fail. The pass rate counts only cases that ran.\n */\nexport async function runAiEvalSet(options: {\n runTurn: (input: AiEvalTurnInput) => Promise<AiEvalTurnOutput>;\n runAltText?: (input: AiEvalAltTextInput) => Promise<string>;\n lintCheck?: (content: TemplateContent) => string[];\n loadFile?: (file: string) => Promise<Blob>;\n model: string;\n cases?: readonly AiEvalCase[];\n minPassRate?: number;\n}): Promise<AiEvalReport> {\n const cases = options.cases ?? AI_EVAL_CASES;\n const results: AiEvalReport[\"results\"] = [];\n for (const testCase of cases) {\n const missing =\n testCase.kind === \"altText\"\n ? !options.runAltText\n ? \"runAltText\"\n : null\n : testCase.checks.some((check) => check.type === \"lintGone\") &&\n !options.lintCheck\n ? \"lintCheck\"\n : null;\n if (missing) {\n results.push({\n name: testCase.name,\n ok: false,\n reason: `Skipped: ${missing} is required.`,\n skipped: true,\n });\n continue;\n }\n let reason: string | null;\n try {\n if (testCase.kind === \"altText\") {\n const text = await options.runAltText!({\n image: testCase.image,\n linkHref: testCase.linkHref,\n nearbyText: testCase.nearbyText,\n maxLength: testCase.maxLength,\n });\n reason = checkAltCase(testCase, text);\n } else {\n const original = AI_EVAL_TEMPLATES[testCase.template];\n const attachments: AiChatAttachment[] = [];\n if (testCase.kind !== \"fix\") {\n for (const attachment of testCase.attachments ?? []) {\n attachments.push({\n file: await (options.loadFile ?? loadFixture)(attachment.file),\n kind: attachment.kind,\n name: attachment.file.split(\"/\").pop()!,\n });\n }\n }\n const output = await options.runTurn({\n content: safeClone(original),\n mergeTags: AI_EVAL_MERGE_TAGS,\n message: testCase.kind === \"fix\" ? \"\" : testCase.message,\n selectedBlockId:\n testCase.kind === \"fix\" ? null : (testCase.selectedBlockId ?? null),\n finding: testCase.kind === \"fix\" ? safeClone(testCase.finding) : null,\n attachments,\n });\n reason = checkEvalCase(testCase, original, output, options.lintCheck);\n }\n } catch (error) {\n reason =\n error instanceof Error\n ? `${error.name}: ${error.message}`\n : String(error);\n }\n results.push({ name: testCase.name, ok: reason === null, reason });\n }\n const skipped = results.filter((result) => result.skipped).length;\n const total = results.length - skipped;\n const passed = results.filter((result) => result.ok).length;\n const rate = total === 0 ? 0 : Math.floor((passed * 100) / total);\n return {\n model: options.model,\n passed,\n total,\n skipped,\n rate,\n ok: rate >= (options.minPassRate ?? 90),\n results,\n };\n}\n\n/** A `runAltText` using image URLs; override them for unreleased fixtures. */\nexport function evalAltTextWith(\n model: AiModelAdapter,\n options: { imageUrl?: (file: string) => string } = {},\n): (input: AiEvalAltTextInput) => Promise<string> {\n return async (input) => {\n const request = buildAltTextRequest({\n templateId: \"eval\",\n sessionId: \"eval\",\n blockId: \"eval-image\",\n image: { url: (options.imageUrl ?? fixtureUrl)(input.image) },\n currentAlt: null,\n linkHref: input.linkHref,\n nearbyText: input.nearbyText,\n locale: \"en\",\n maxLength: input.maxLength,\n });\n let text = \"\";\n for await (const part of model.stream({ ...request, tools: [] })) {\n if (part.type === \"text\") text += part.text;\n }\n const check = checkAltTextAnswer(text, { maxLength: input.maxLength });\n if (!check.ok) {\n const repair = buildAltTextRepairRequest(\n request,\n text,\n check.reason,\n input.maxLength,\n );\n text = \"\";\n for await (const part of model.stream({ ...repair, tools: [] })) {\n if (part.type === \"text\") text += part.text;\n }\n }\n return checkAltText(text, { maxLength: input.maxLength });\n };\n}\n\n/** A `runTurn` on the kit's own loop, for a model behind an `AiModelAdapter`. */\nexport function evalTurnWith(\n model: AiModelAdapter,\n options: { maxSteps?: number } = {},\n): (input: AiEvalTurnInput) => Promise<AiEvalTurnOutput> {\n return async (input) => {\n const result = await runEditingTurn({\n request: {\n content: input.content,\n mergeTags: input.mergeTags,\n selectedBlockId: input.selectedBlockId,\n request: input.finding\n ? buildFixRequest(input.finding)\n : buildChatRequest(input.message, {\n hasAttachments: input.attachments.length > 0,\n }),\n attachments: input.attachments.map(({ kind, file }) => ({\n kind,\n mediaType: file.type,\n data: file,\n })),\n },\n model,\n write: () => {},\n ...(options.maxSteps !== undefined ? { maxSteps: options.maxSteps } : {}),\n });\n return {\n content: result.content,\n reply: result.text,\n applied: result.batches.length > 0,\n };\n };\n}\n","import type { AiChatMessage } from \"@templatical/types\";\nimport { SdkError } from \"@templatical/types\";\n\nconst KNOWN_CODES = new Set([\n \"feature_unavailable\",\n \"limit_reached\",\n \"rate_limited\",\n \"ai_turn_running\",\n \"content_too_large\",\n \"ai_output_invalid\",\n \"ai_provider_error\",\n \"not_found\",\n \"block_exists\",\n \"block_missing\",\n \"anchor_missing\",\n \"precondition_failed\",\n \"lock_held\",\n \"locked_by_others\",\n \"invalid_operation\",\n]);\n\nexport function describeError(error: unknown): string {\n if (error instanceof SdkError) {\n return `${error.name} ${error.code ?? \"(no code)\"} ${error.statusCode ?? \"\"}: ${error.message}`.trim();\n }\n return error instanceof Error\n ? `${error.name}: ${error.message}`\n : String(error);\n}\n\n/** Why a rejection breaks the contract, or `null`: SdkErrors with known codes and AbortErrors are fine. */\nexport function errorProblem(error: unknown): string | null {\n if (error instanceof Error && error.name === \"AbortError\") return null;\n if (!(error instanceof SdkError))\n return `not an SdkError: ${describeError(error)}`;\n if (typeof error.code !== \"string\" || !KNOWN_CODES.has(error.code)) {\n return `unknown code: ${describeError(error)}`;\n }\n return null;\n}\n\nexport function messageProblem(message: unknown): string | null {\n const m = message as AiChatMessage;\n if (\n !m ||\n typeof m.id !== \"string\" ||\n ![\"user\", \"assistant\"].includes(m.role) ||\n ![\"chat\", \"fix\"].includes(m.kind) ||\n typeof m.text !== \"string\" ||\n (m.attachments !== undefined &&\n (!Array.isArray(m.attachments) ||\n !m.attachments.every(\n (a) =>\n a &&\n typeof a.name === \"string\" &&\n [\"image\", \"pdf\"].includes(a.kind),\n ))) ||\n !Array.isArray(m.batches) ||\n typeof m.undone !== \"boolean\" ||\n typeof m.createdAt !== \"string\" ||\n !m.batches.every(\n (batch) =>\n typeof batch.batchId === \"string\" &&\n (batch.revision === null || Number.isInteger(batch.revision)) &&\n Number.isInteger(batch.operationCount),\n )\n ) {\n return `malformed message: ${JSON.stringify(message)}`;\n }\n return null;\n}\n","import type {\n AiAppliedBatch,\n AiMergeTags,\n AiOperationsResult,\n AiTurnHandlers,\n Operation,\n TemplateContent,\n} from \"@templatical/types\";\nimport {\n applyOperations,\n invertOperations,\n OperationError,\n safeClone,\n} from \"@templatical/types\";\n\n/** The editor the suites drive: `onOperations` applies with the engine. */\nexport interface StandInEditor {\n content: TemplateContent;\n readonly mergeTags: AiMergeTags;\n readonly sessionId: string;\n readonly applied: Operation[][];\n readonly refused: { code: string; opIndex: number; message: string }[];\n readonly serverBatches: AiAppliedBatch[];\n /** Apply a batch as the editor would answer `onOperations`. */\n apply(ops: Operation[]): AiOperationsResult;\n /** Fresh handlers for one call; `text` collects what streamed. */\n handlers(extra?: { onText?: (text: string) => void }): AiTurnHandlers & {\n readonly text: string;\n };\n}\n\nexport function createStandInEditor(\n content: TemplateContent,\n mergeTags: AiMergeTags = { syntax: \"liquid\", tags: [] },\n): StandInEditor {\n const editor: StandInEditor = {\n content: safeClone(content),\n mergeTags: safeClone(mergeTags),\n sessionId: crypto.randomUUID(),\n applied: [],\n refused: [],\n serverBatches: [],\n apply(ops) {\n try {\n const inverse = invertOperations(editor.content, ops);\n editor.content = applyOperations(editor.content, ops);\n editor.applied.push(safeClone(ops));\n return { applied: true, inverse };\n } catch (error) {\n if (!(error instanceof OperationError)) throw error;\n const refusal = {\n code: error.code,\n opIndex: error.opIndex,\n message: error.message,\n };\n editor.refused.push(refusal);\n return { applied: false, ...refusal, code: error.code };\n }\n },\n handlers(extra = {}) {\n let text = \"\";\n return {\n get text() {\n return text;\n },\n onText(chunk) {\n text += chunk;\n extra.onText?.(chunk);\n },\n onOperations: async (ops) => editor.apply(ops),\n onApplied(batch) {\n editor.serverBatches.push({ ...batch });\n },\n };\n },\n };\n return editor;\n}\n","import type {\n AiAltTextRequest,\n AiFeatureName,\n AiMergeTags,\n AiProvider,\n AiReplay,\n AiRequestBase,\n TemplateContent,\n} from \"@templatical/types\";\nimport { safeClone } from \"@templatical/types\";\nimport {\n AI_CONFORMANCE_ALT_TEXT_IMAGE_URL,\n AI_EVAL_MERGE_TAGS,\n AI_EVAL_TEMPLATES,\n} from \"./eval-set\";\nimport { describeError, errorProblem, messageProblem } from \"./checks\";\nimport { createStandInEditor } from \"./stand-in-editor\";\n\nexport interface AiConformanceCheck {\n name: string;\n ok: boolean;\n detail: string;\n}\n\nexport interface AiConformanceReport {\n ok: boolean;\n checks: AiConformanceCheck[];\n}\n\nexport interface AiConformanceOptions {\n /** The template the suite edits; the test set's welcome email by default. */\n content?: TemplateContent;\n templateId?: string;\n /** Public probe image URL; defaults to AI_CONFORMANCE_ALT_TEXT_IMAGE_URL. */\n altTextImageUrl?: string;\n mergeTags?: AiMergeTags;\n /** How long a stopped call may take to reject (D35). */\n timeoutMs?: number;\n /** An optional shorter call limit; every call is also bounded by `timeoutMs`. */\n callTimeoutMs?: number;\n}\n\nconst FEATURES: Exclude<AiFeatureName, \"chatAttachments\">[] = [\n \"chat\",\n \"fixFinding\",\n \"rewrite\",\n \"altText\",\n];\n\n/** A 1×1 transparent PNG. */\nexport function tinyPng(): Blob {\n const base64 =\n \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==\";\n const bytes = Uint8Array.from(atob(base64), (char) => char.charCodeAt(0));\n return new Blob([bytes], { type: \"image/png\" });\n}\n\nconst NO_RESULT = Symbol(\"call failed\");\n\nclass ConformanceTimeoutError extends Error {\n override name = \"TimeoutError\";\n}\n\n/** @internal Bound the wait even when the operation ignores its signal. */\nexport async function withConformanceDeadline<T>(\n name: string,\n timeoutMs: number,\n run: (signal: AbortSignal) => T | PromiseLike<T>,\n controller = new AbortController(),\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const deadline = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n const error = new ConformanceTimeoutError(\n `${name}: timed out after ${timeoutMs} ms`,\n );\n reject(error);\n controller.abort(error);\n }, timeoutMs);\n });\n try {\n return await Promise.race([\n Promise.resolve().then(() => run(controller.signal)),\n deadline,\n ]);\n } finally {\n clearTimeout(timer);\n }\n}\n\nfunction preview(value: unknown): string {\n return typeof value === \"string\"\n ? JSON.stringify(value.slice(0, 80))\n : value === null\n ? \"null\"\n : typeof value;\n}\n\n/** @internal Keep a malformed value or diagnostic inside its own check. */\nexport async function evaluateConformanceCheck(\n name: string,\n evaluate: () =>\n | { ok: boolean; detail?: string }\n | Promise<{ ok: boolean; detail?: string }>,\n): Promise<AiConformanceCheck> {\n try {\n const { ok, detail = \"ok\" } = await evaluate();\n return { name, ok, detail };\n } catch (error) {\n return {\n name,\n ok: false,\n detail: error instanceof Error ? error.message : preview(error),\n };\n }\n}\n\n/**\n * Drive a provider as the editor would and check the contract (A1 §6): any\n * test runner, never throws on a failed check. Each declared feature runs\n * once against a stand-in editor that applies `operations` with the engine.\n */\nexport async function runAiProviderConformance(\n provider: AiProvider,\n options: AiConformanceOptions = {},\n): Promise<AiConformanceReport> {\n const checks: AiConformanceCheck[] = [];\n const add = async (\n name: string,\n evaluate: Parameters<typeof evaluateConformanceCheck>[1],\n ): Promise<boolean> => {\n const check = await evaluateConformanceCheck(name, evaluate);\n checks.push(check);\n return check.ok;\n };\n const timeoutMs = options.timeoutMs ?? 5000;\n const callTimeoutMs = Math.min(options.callTimeoutMs ?? timeoutMs, timeoutMs);\n const templateId = options.templateId ?? \"conformance\";\n const editor = createStandInEditor(\n options.content ?? AI_EVAL_TEMPLATES.welcome,\n options.mergeTags ?? AI_EVAL_MERGE_TAGS,\n );\n const errors: unknown[] = [];\n const probes: Partial<\n Record<AiFeatureName, (signal: AbortSignal) => Promise<unknown>>\n > = {};\n const base = (): AiRequestBase => ({\n templateId,\n content: safeClone(editor.content),\n revision: null,\n sessionId: editor.sessionId,\n mergeTags: editor.mergeTags,\n });\n\n async function call<T>(\n name: string,\n run: (signal: AbortSignal) => Promise<T>,\n ): Promise<T | typeof NO_RESULT> {\n try {\n return await withConformanceDeadline(name, callTimeoutMs, run);\n } catch (error) {\n if (!(error instanceof ConformanceTimeoutError)) errors.push(error);\n await add(`${name}: answers`, () => ({\n ok: false,\n detail: describeError(error),\n }));\n return NO_RESULT;\n }\n }\n\n const declared = FEATURES.filter(\n (feature) => typeof provider[feature] === \"function\",\n );\n await add(\"declares at least one feature\", () => ({\n ok: declared.length > 0,\n detail: `declared: ${declared.join(\", \") || \"none\"}`,\n }));\n for (const feature of FEATURES) {\n const member: unknown = provider[feature];\n if (member !== false && typeof member !== \"function\") {\n await add(`${feature}: false or a function`, () => ({\n ok: false,\n detail: `got ${typeof member}`,\n }));\n } else if (member === false) {\n await add(`${feature}: off`, () => ({\n ok: true,\n detail: `skipped: ${feature} is false`,\n }));\n }\n }\n\n // Turns first: they change the stand-in's template.\n let lastAssistantId: string | null = null;\n if (typeof provider.chat === \"function\") {\n const chat = provider.chat;\n probes.chat = (signal) =>\n chat(\n {\n ...base(),\n message: \"Make the button green\",\n selectedBlockId: null,\n attachments: [],\n },\n createStandInEditor(editor.content).handlers(),\n signal,\n );\n const handlers = editor.handlers();\n const result = await call(\"chat\", (signal) =>\n chat(\n {\n ...base(),\n message:\n \"Change the headline to 'Hello there' and keep the first name in it\",\n selectedBlockId: null,\n attachments: [],\n },\n handlers,\n signal,\n ),\n );\n if (result !== NO_RESULT) {\n await add(\"chat: streams text and resolves with it\", () => {\n const text = result?.text;\n const messageId = result?.messageId;\n if (messageId === null || typeof messageId === \"string\")\n lastAssistantId = messageId;\n return {\n ok:\n typeof text === \"string\" &&\n (messageId === null || typeof messageId === \"string\") &&\n (handlers.text === \"\" || text === handlers.text),\n detail: `streamed ${preview(handlers.text)}, resolved ${preview(text)}`,\n };\n });\n }\n }\n if (typeof provider.fixFinding === \"function\") {\n const fix = provider.fixFinding;\n probes.fixFinding = (signal) =>\n fix(\n {\n ...base(),\n finding: {\n ruleId: \"a11y.button-vague-label\",\n message: \"The button text doesn't say what happens.\",\n suggestion: \"Say where the button goes.\",\n reference:\n \"https://docs.templatical.com/quality/accessibility/rule-catalog#buttons\",\n blockId: null,\n },\n },\n createStandInEditor(editor.content).handlers(),\n signal,\n );\n const result = await call(\"fixFinding\", (signal) =>\n fix(\n {\n ...base(),\n finding: {\n ruleId: \"a11y.button-vague-label\",\n message: \"The button text doesn't say what happens.\",\n suggestion: \"Say where the button goes.\",\n reference:\n \"https://docs.templatical.com/quality/accessibility/rule-catalog#buttons\",\n blockId: null,\n },\n },\n editor.handlers(),\n signal,\n ),\n );\n if (result !== NO_RESULT)\n await add(\"fixFinding: answers\", () => ({\n ok: typeof result?.text === \"string\",\n }));\n }\n if (\n provider.chatAttachments === true &&\n typeof provider.chat === \"function\"\n ) {\n const chat = provider.chat;\n const result = await call(\"chatAttachments\", (signal) =>\n chat(\n {\n ...base(),\n message: \"Use this reference\",\n selectedBlockId: null,\n attachments: [\n { file: tinyPng(), kind: \"image\", name: \"reference.png\" },\n ],\n },\n editor.handlers(),\n signal,\n ),\n );\n if (result !== NO_RESULT)\n await add(\"chatAttachments: answers\", () => ({\n ok: typeof result?.text === \"string\" && result.text.trim() !== \"\",\n }));\n } else {\n await add(\"chatAttachments: off\", () => ({\n ok: true,\n detail: \"skipped: chatAttachments or chat is false\",\n }));\n }\n\n await add(\"operations apply cleanly to the snapshot\", () => ({\n ok: editor.refused.length === 0,\n detail:\n editor.refused.length === 0\n ? `${editor.applied.length} batch(es) applied`\n : editor.refused\n .map(\n (refusal) =>\n `${refusal.code} at op ${refusal.opIndex}: ${refusal.message}`,\n )\n .join(\"; \"),\n }));\n await add(\"applied events carry increasing revisions\", () => {\n const revisions = editor.serverBatches.map((batch) => batch.revision);\n return {\n ok: editor.serverBatches.every(\n (batch, index) =>\n typeof batch.batchId === \"string\" &&\n Number.isInteger(batch.revision) &&\n batch.operationCount >= 1 &&\n (index === 0 || batch.revision > revisions[index - 1]),\n ),\n detail:\n revisions.length === 0\n ? \"no applied events\"\n : `revisions ${revisions.join(\", \")}`,\n };\n });\n\n // History.\n const conversation = provider.conversation;\n if (conversation) {\n const list = await call(\"conversation.list\", () =>\n conversation.list(templateId),\n );\n if (list !== NO_RESULT) {\n const validMessages = await add(\n \"conversation: lists messages in the Y7 shape\",\n () => {\n const problem = Array.isArray(list)\n ? (list.map(messageProblem).find((p) => p !== null) ?? null)\n : \"not a list\";\n return {\n ok: problem === null,\n detail: problem ?? `${list.length} message(s)`,\n };\n },\n );\n await add(\"conversation: undo and redo replay\", async () => {\n if (!validMessages)\n return { ok: false, detail: \"cannot replay malformed messages\" };\n const target =\n [...list]\n .reverse()\n .find(\n (message) =>\n message.role === \"assistant\" && message.batches.length > 0,\n )?.id ?? lastAssistantId;\n const { undo, redo } = conversation;\n if (\n !target ||\n typeof undo !== \"function\" ||\n typeof redo !== \"function\"\n ) {\n return {\n ok: true,\n detail: \"nothing to undo, or undo/redo are false\",\n };\n }\n let ok = true;\n const isOperations = (\n value: AiReplay | typeof NO_RESULT,\n ): value is Extract<AiReplay, { operations: unknown }> =>\n value !== NO_RESULT &&\n typeof value === \"object\" &&\n value !== null &&\n \"operations\" in value &&\n Array.isArray(value.operations) &&\n typeof value.replayId === \"string\" &&\n value.replayId !== \"\";\n const state = async () => {\n const rows = await call(\"conversation.list\", () =>\n conversation.list(templateId),\n );\n return Array.isArray(rows)\n ? rows.find((row) => row.id === target)?.undone\n : undefined;\n };\n for (const [direction, step] of [\n [\"undo\", undo],\n [\"redo\", redo],\n ] as const) {\n const before = direction === \"redo\";\n let replay = await call(`conversation.${direction}`, () =>\n step(templateId, target, { sessionId: editor.sessionId }),\n );\n if (isOperations(replay)) {\n const settleReplay = conversation.settleReplay;\n if (typeof settleReplay !== \"function\")\n return { ok: false, detail: \"settleReplay is required\" };\n ok =\n (await add(\n `conversation: ${direction} replay waits for settlement`,\n async () => ({ ok: (await state()) === before }),\n )) && ok;\n const outcome = {\n replayId: replay.replayId,\n direction,\n applied: false,\n sessionId: editor.sessionId,\n };\n const refused = await call(\"conversation.settleReplay\", () =>\n settleReplay(templateId, target, outcome),\n );\n ok =\n (await add(\n `conversation: ${direction} refused replay keeps its state`,\n async () => ({\n ok: refused !== NO_RESULT && (await state()) === before,\n }),\n )) && ok;\n const duplicate = await call(\"conversation.settleReplay\", () =>\n settleReplay(templateId, target, { ...outcome, applied: true }),\n );\n ok =\n (await add(\n `conversation: ${direction} replay settlement is idempotent`,\n async () => ({\n ok: duplicate !== NO_RESULT && (await state()) === before,\n }),\n )) && ok;\n replay = await call(`conversation.${direction}`, () =>\n step(templateId, target, { sessionId: editor.sessionId }),\n );\n if (!isOperations(replay)) {\n ok = false;\n continue;\n }\n const accepted = replay;\n const applied = editor.apply(accepted.operations).applied;\n const settled = await call(\"conversation.settleReplay\", () =>\n settleReplay(templateId, target, {\n ...outcome,\n replayId: accepted.replayId,\n applied,\n }),\n );\n ok =\n (await add(\n `conversation: ${direction} accepted replay changes its state`,\n async () => ({\n ok:\n applied &&\n settled !== NO_RESULT &&\n (await state()) === !before,\n }),\n )) && ok;\n await call(\"conversation.settleReplay\", () =>\n settleReplay(templateId, target, {\n ...outcome,\n replayId: accepted.replayId,\n applied: false,\n }),\n );\n ok =\n (await add(\n `conversation: ${direction} accepted replay settles only once`,\n async () => ({ ok: (await state()) === !before }),\n )) && ok;\n } else {\n ok =\n replay !== NO_RESULT &&\n typeof replay === \"object\" &&\n replay !== null &&\n \"applied\" in replay &&\n typeof replay.applied?.batchId === \"string\" &&\n Number.isInteger(replay.applied.revision) &&\n ok;\n }\n }\n return {\n ok: ok && editor.refused.length === 0,\n detail: ok\n ? \"ok\"\n : \"a replay was malformed, refused or settled incorrectly\",\n };\n });\n }\n if (typeof conversation.clear === \"function\") {\n const clear = conversation.clear;\n await call(\"conversation.clear\", () => clear(templateId));\n const after = await call(\"conversation.list\", () =>\n conversation.list(templateId),\n );\n await add(\"conversation: clear empties it\", () => ({\n ok: Array.isArray(after) && after.length === 0,\n detail: `${Array.isArray(after) ? after.length : \"?\"} message(s) left`,\n }));\n }\n } else {\n await add(\"conversation: off\", () => ({\n ok: true,\n detail: \"skipped: no conversation\",\n }));\n }\n\n // One-shot features.\n if (typeof provider.rewrite === \"function\") {\n const rewrite = provider.rewrite;\n probes.rewrite = (signal) =>\n rewrite(\n {\n ...base(),\n html: \"<p>Thanks for signing up. Here is everything you need to get started.</p>\",\n preset: \"shorten\",\n language: null,\n instruction: null,\n },\n { onText: () => {} },\n signal,\n );\n const result = await call(\"rewrite\", (signal) =>\n rewrite(\n {\n ...base(),\n html: \"<p>Thanks for signing up. Here is everything you need to get started.</p>\",\n preset: \"shorten\",\n language: null,\n instruction: null,\n },\n { onText: () => {} },\n signal,\n ),\n );\n if (result !== NO_RESULT)\n await add(\"rewrite: answers with html\", () => {\n const html = result?.html;\n return {\n ok: typeof html === \"string\" && html.trim() !== \"\",\n detail:\n typeof html === \"string\"\n ? JSON.stringify(html).slice(0, 120)\n : preview(html),\n };\n });\n }\n if (typeof provider.altText === \"function\") {\n const altText = provider.altText;\n const request: AiAltTextRequest = {\n templateId,\n sessionId: editor.sessionId,\n blockId: \"conformance-image\",\n image: {\n url: options.altTextImageUrl ?? AI_CONFORMANCE_ALT_TEXT_IMAGE_URL,\n },\n currentAlt: null,\n linkHref: null,\n nearbyText: \"Explore our email editor.\",\n locale: editor.content.settings.locale ?? \"en\",\n maxLength: 125,\n };\n probes.altText = (signal) => altText(request, signal);\n const result = await call(\"altText\", probes.altText);\n if (result !== NO_RESULT)\n await add(\"altText: answers within maxLength\", () => {\n const alt = (result as { alt?: unknown } | null)?.alt;\n return {\n ok:\n typeof alt === \"string\" &&\n alt.trim() !== \"\" &&\n alt.length <= request.maxLength,\n detail: preview(alt),\n };\n });\n }\n // Each feature must reject an already-aborted call, fast or slow.\n const abortChecks: AiConformanceCheck[] = [];\n for (const feature of declared) {\n const probe = probes[feature]!;\n const controller = new AbortController();\n controller.abort();\n let outcome: unknown = \"resolved\";\n try {\n await withConformanceDeadline(\n `${feature}: abort`,\n timeoutMs,\n probe,\n controller,\n );\n } catch (error) {\n outcome = error;\n }\n const check = await evaluateConformanceCheck(\n `${feature}: stops on abort with an AbortError`,\n () => {\n const ok = outcome instanceof Error && outcome.name === \"AbortError\";\n return {\n ok,\n detail: ok\n ? \"ok\"\n : outcome instanceof ConformanceTimeoutError\n ? `still running after ${timeoutMs} ms`\n : outcome === \"resolved\"\n ? \"resolved instead of rejecting\"\n : describeError(outcome),\n };\n },\n );\n checks.push(check);\n abortChecks.push(check);\n\n const live = new AbortController();\n let cancel: ReturnType<typeof setTimeout> | undefined;\n let liveOutcome: unknown = \"resolved\";\n try {\n await withConformanceDeadline(\n `${feature}: live abort`,\n timeoutMs,\n (signal) => {\n // Invoke with a live signal. The next task lets async setup (including\n // HTTP headers/fetch) start before Stop, unlike the entry-only probe.\n const pending = probe(signal);\n cancel = setTimeout(() => live.abort(), 0);\n return pending;\n },\n live,\n );\n } catch (error) {\n liveOutcome = error;\n } finally {\n clearTimeout(cancel);\n }\n const liveCheck = await evaluateConformanceCheck(\n `${feature}: stops an in-flight call with an AbortError`,\n () => ({\n ok:\n (liveOutcome instanceof Error && liveOutcome.name === \"AbortError\") ||\n (liveOutcome === \"resolved\" && !live.signal.aborted),\n detail:\n liveOutcome instanceof ConformanceTimeoutError\n ? `still running after ${timeoutMs} ms`\n : liveOutcome === \"resolved\"\n ? live.signal.aborted\n ? \"resolved instead of rejecting\"\n : \"completed before cancellation\"\n : liveOutcome instanceof Error &&\n liveOutcome.name === \"AbortError\"\n ? \"ok\"\n : describeError(liveOutcome),\n }),\n );\n checks.push(liveCheck);\n abortChecks.push(liveCheck);\n }\n if (abortChecks.length > 0) {\n await add(\"stops on abort with an AbortError\", () => ({\n ok: abortChecks.every((check) => check.ok),\n detail:\n abortChecks\n .filter((check) => !check.ok)\n .map((check) => `${check.name}: ${check.detail}`)\n .join(\"; \") || \"ok\",\n }));\n }\n\n await add(\"errors are SdkErrors with known codes\", () => {\n const bad = errors\n .map(errorProblem)\n .filter((problem): problem is string => problem !== null);\n return {\n ok: bad.length === 0,\n detail: bad.join(\"; \") || `${errors.length} error(s), all well formed`,\n };\n });\n\n return { ok: checks.every((check) => check.ok), checks };\n}\n","import {\n createHttpAiProviderWithFetch,\n type HttpAiFeatures,\n} from \"../client/http-provider\";\nimport {\n AI_SESSION_HEADER,\n AI_WIRE_HEADER,\n AI_WIRE_VERSION,\n aiRoutePath,\n matchAiRoute,\n} from \"../wire\";\nimport {\n evaluateConformanceCheck,\n tinyPng,\n runAiProviderConformance,\n withConformanceDeadline,\n type AiConformanceCheck,\n type AiConformanceReport,\n} from \"./conformance\";\nimport {\n AI_CONFORMANCE_ALT_TEXT_IMAGE_URL,\n AI_EVAL_TEMPLATES,\n} from \"./eval-set\";\n\nexport interface AiWireConformanceOptions {\n baseUrl: string;\n features?: HttpAiFeatures;\n headers?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n templateId?: string;\n /** Defaults to AI_CONFORMANCE_ALT_TEXT_IMAGE_URL. */\n altTextImageUrl?: string;\n timeoutMs?: number;\n callTimeoutMs?: number;\n}\n\n/**\n * The provider suite over the wire, through `createHttpAiProvider`, plus the\n * wire's own rules: every response carries `Templatical-AI-Wire: 1`, streams\n * are `text/event-stream`, and a bad turn is refused before the stream with a\n * JSON error. For a backend in any language; run it from Node (no CORS).\n */\nexport async function runAiWireConformance(\n options: AiWireConformanceOptions,\n): Promise<AiConformanceReport> {\n const base = options.baseUrl.replace(/\\/+$/, \"\");\n const templateId = options.templateId ?? \"conformance\";\n const timeoutMs = Math.min(\n options.callTimeoutMs ?? options.timeoutMs ?? 5000,\n options.timeoutMs ?? 5000,\n );\n const checks: AiConformanceCheck[] = [];\n const responses: { route: string; response: Response; streamed: boolean }[] =\n [];\n const fetchRequest = fetch;\n const readHeaders = () =>\n withConformanceDeadline(\"request headers\", timeoutMs, async () =>\n typeof options.headers === \"function\"\n ? await options.headers()\n : (options.headers ?? {}),\n );\n const observedFetch: typeof fetch = async (input, init) => {\n const url = new URL(\n typeof input === \"string\"\n ? input\n : input instanceof URL\n ? input.href\n : input.url,\n );\n const parent =\n init?.signal ?? (input instanceof Request ? input.signal : undefined);\n const method =\n init?.method ?? (input instanceof Request ? input.method : \"GET\");\n const route = matchAiRoute(method, url.pathname)?.route ?? url.pathname;\n const controller = new AbortController();\n const stop = () => controller.abort(parent?.reason);\n if (parent?.aborted) stop();\n else parent?.addEventListener(\"abort\", stop, { once: true });\n const release = () => parent?.removeEventListener(\"abort\", stop);\n try {\n const response = await withConformanceDeadline(\n `${route}: fetch`,\n timeoutMs,\n (signal) => {\n signal.throwIfAborted();\n return fetchRequest(input, { ...init, signal });\n },\n controller,\n );\n responses.push({\n route,\n response,\n streamed:\n response.ok &&\n [\"generate\", \"fixFinding\", \"rewriteText\", \"altText\"].includes(route),\n });\n if (!response.body) {\n release();\n return response;\n }\n const reader = response.body.getReader();\n let finished = false;\n const body = new ReadableStream<Uint8Array>(\n {\n async pull(stream) {\n try {\n const { done, value } = await withConformanceDeadline(\n `${route}: body read`,\n timeoutMs,\n () => reader.read(),\n controller,\n );\n if (finished) return;\n if (done) {\n finished = true;\n release();\n stream.close();\n } else stream.enqueue(value);\n } catch (error) {\n release();\n if (!finished) {\n finished = true;\n stream.error(error);\n }\n if (!controller.signal.aborted)\n void reader.cancel(error).catch(() => {});\n }\n },\n async cancel(reason) {\n finished = true;\n release();\n await withConformanceDeadline(\n `${route}: body cancel`,\n timeoutMs,\n () => reader.cancel(reason),\n controller,\n );\n },\n },\n { highWaterMark: 0 },\n );\n return new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n } catch (error) {\n release();\n throw error;\n }\n };\n let own: Record<string, string> = {};\n const headersCheck = await evaluateConformanceCheck(\n \"request headers\",\n async () => {\n own = await readHeaders();\n return { ok: true };\n },\n );\n if (!headersCheck.ok) checks.push(headersCheck);\n\n async function probe(\n route: \"generate\",\n body: unknown,\n ): Promise<Response | null> {\n try {\n const response = await observedFetch(\n `${base}${aiRoutePath(route, { template: templateId })}`,\n {\n method: \"POST\",\n headers: {\n ...own,\n [AI_WIRE_HEADER]: AI_WIRE_VERSION,\n [AI_SESSION_HEADER]: \"conformance-session\",\n ...(body instanceof FormData\n ? {}\n : { \"Content-Type\": \"application/json\" }),\n Accept: \"text/event-stream\",\n },\n body: body instanceof FormData ? body : JSON.stringify(body),\n },\n );\n return response;\n } catch (error) {\n checks.push(\n await evaluateConformanceCheck(`reaches ${route}`, () => ({\n ok: false,\n detail: String(error),\n })),\n );\n return null;\n }\n }\n\n const invalid =\n options.features?.chat === false\n ? null\n : await probe(\"generate\", {\n message: \"\",\n selectedBlockId: null,\n mergeTags: { syntax: \"liquid\", tags: [] },\n content: AI_EVAL_TEMPLATES.welcome,\n revision: null,\n });\n const invalidCheck = await evaluateConformanceCheck(\n \"refuses an invalid turn before the stream with a JSON error\",\n async () => {\n if (options.features?.chat === false)\n return { ok: true, detail: \"skipped: chat is disabled\" };\n const invalidBody: unknown = invalid\n ? await withConformanceDeadline(\n \"generate: JSON body read\",\n timeoutMs,\n () => invalid.json(),\n )\n : null;\n return {\n ok:\n invalid !== null &&\n invalid.status >= 400 &&\n invalid.status < 500 &&\n typeof (invalidBody as { message?: unknown } | null)?.message ===\n \"string\",\n detail: invalid\n ? `${invalid.status} ${JSON.stringify(invalidBody)}`\n : \"no response\",\n };\n },\n );\n if (\n options.features?.chat !== false &&\n options.features?.chatAttachments !== false\n ) {\n const form = new FormData();\n form.set(\"message\", \"Use these references\");\n form.set(\"content\", JSON.stringify(AI_EVAL_TEMPLATES.welcome));\n for (let i = 0; i < 5; i++)\n form.append(\"attachment\", tinyPng(), \"reference.png\");\n const refused = await probe(\"generate\", form);\n checks.push(\n await evaluateConformanceCheck(\n \"refuses more than 4 attachments with 422\",\n async () => {\n const body: unknown = refused\n ? await withConformanceDeadline(\n \"generate: attachment refusal JSON\",\n timeoutMs,\n () => refused.json(),\n )\n : null;\n return {\n ok:\n refused?.status === 422 &&\n typeof (body as { message?: unknown } | null)?.message ===\n \"string\",\n detail: refused\n ? `${refused.status} ${JSON.stringify(body)}`\n : \"no response\",\n };\n },\n ),\n );\n }\n const report = await runAiProviderConformance(\n createHttpAiProviderWithFetch(\n { baseUrl: base, headers: readHeaders, features: options.features },\n observedFetch,\n ),\n {\n templateId,\n altTextImageUrl:\n options.altTextImageUrl ?? AI_CONFORMANCE_ALT_TEXT_IMAGE_URL,\n ...(options.timeoutMs !== undefined\n ? { timeoutMs: options.timeoutMs }\n : {}),\n ...(options.callTimeoutMs !== undefined\n ? { callTimeoutMs: options.callTimeoutMs }\n : {}),\n },\n );\n const responseCheck = await evaluateConformanceCheck(\n \"responses carry Templatical-AI-Wire: 1\",\n () => {\n const missing = responses.filter(\n ({ response }) =>\n response.headers.get(AI_WIRE_HEADER) !== AI_WIRE_VERSION,\n );\n return {\n ok: responses.length > 0 && missing.length === 0,\n detail:\n missing.length === 0\n ? `${responses.length} response(s)`\n : missing\n .map(\n ({ route, response }) =>\n `${route}: ${response.status} ${response.url}`,\n )\n .join(\", \"),\n };\n },\n );\n const streamCheck = await evaluateConformanceCheck(\n \"streams answer as text/event-stream\",\n () => {\n const streams = responses.filter(({ streamed }) => streamed);\n const wrong = streams.filter(\n ({ response }) =>\n !(response.headers.get(\"Content-Type\") ?? \"\").startsWith(\n \"text/event-stream\",\n ),\n );\n return {\n ok: wrong.length === 0,\n detail:\n wrong.length === 0\n ? `${streams.length} streamed response(s)`\n : wrong\n .map(\n ({ route, response }) =>\n `${route}: ${response.headers.get(\"Content-Type\") ?? \"missing Content-Type\"}`,\n )\n .join(\", \"),\n };\n },\n );\n checks.unshift(responseCheck, streamCheck, invalidCheck);\n const all = [...checks, ...report.checks];\n return { ok: all.every((check) => check.ok), checks: all };\n}\n","import type {\n AiAltTextRequest,\n AiMessageBatch,\n AiReplayContext,\n AiReplayOutcome,\n TemplateContent,\n AiChatMessage,\n AiChatRequest,\n AiFixRequest,\n AiMessageKind,\n AiProvider,\n AiRewriteRequest,\n AiTurnHandlers,\n AiTurnResult,\n Operation,\n} from \"@templatical/types\";\nimport {\n applyOperations,\n invertOperations,\n CollaborationConflict,\n safeClone,\n SdkError,\n} from \"@templatical/types\";\nimport { AI_TEMPLATE_CHANGED_MESSAGE, errorFromEvent } from \"../client/errors\";\nimport { AI_ERROR_MESSAGES, type AiWireEvent } from \"../wire\";\n\n/** A wire event to play, or a pause. */\nexport type AiRecordedEvent = AiWireEvent | { delayMs: number };\n/** Events, or events built from the request (to name its blocks). */\nexport type AiRecording<R> =\n AiRecordedEvent[] | ((request: R) => AiRecordedEvent[]);\n\nexport interface AiRecordedTurn {\n /** The first turn whose `match` fits the message plays; one without `match` always fits. */\n match?: RegExp;\n events: AiRecording<AiChatRequest>;\n}\n\n/** What the mock plays. A missing feature is off (`false`). */\nexport interface AiMockScript {\n chat?: false | AiRecordedTurn[];\n fixFinding?: false | AiRecording<AiFixRequest>;\n chatAttachments?: boolean;\n rewrite?: false | AiRecording<AiRewriteRequest>;\n altText?: false | AiRecording<AiAltTextRequest>;\n /** Keep a conversation in memory per template, with Undo and Redo. */\n conversation?: boolean;\n /** A pause before every event, in ms (0 by default). */\n delayMs?: number;\n}\n\ninterface Stored {\n message: AiChatMessage;\n inverse: Operation[];\n after: TemplateContent | null;\n}\n\nfunction stopped(signal: AbortSignal): Error {\n const reason = signal.reason as unknown;\n return reason instanceof Error && reason.name === \"AbortError\"\n ? reason\n : new DOMException(\"The AI request was stopped.\", \"AbortError\");\n}\n\nfunction wait(ms: number, signal: AbortSignal): Promise<void> {\n if (signal.aborted) return Promise.reject(stopped(signal));\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n reject(stopped(signal));\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\nconst failure = () =>\n new SdkError(AI_ERROR_MESSAGES.ai_provider_error, 502, \"ai_provider_error\");\n\n/**\n * An `AiProvider` that plays recorded streams: for integrators' UI tests and\n * the playground's AI scene (no model, no keys). Turns apply their\n * `operations` through the editor's handlers like a real backend's; with\n * `conversation: true` it keeps history in memory, Undo answering the turn's\n * inverse and Redo the guarded inverse of Undo. Replays change history only\n * after settlement.\n */\nexport function createMockAiProvider(script: AiMockScript): AiProvider {\n const delay = script.delayMs ?? 0;\n const conversations = new Map<string, Stored[]>();\n const pending = new Map<\n string,\n {\n templateId: string;\n messageId: string;\n entry: Stored;\n direction: \"undo\" | \"redo\";\n sessionId: string;\n }\n >();\n\n async function play<R>(\n recording: AiRecording<R>,\n request: R,\n signal: AbortSignal,\n each: (event: AiWireEvent) => Promise<void> | void,\n ): Promise<Extract<AiWireEvent, { type: \"done\" }>> {\n const events =\n typeof recording === \"function\" ? recording(request) : recording;\n for (const event of events) {\n await wait(\"type\" in event ? delay : event.delayMs, signal);\n if (!(\"type\" in event)) continue;\n if (event.type === \"error\") throw errorFromEvent(event);\n if (event.type === \"done\") return event;\n await each(event);\n }\n throw failure();\n }\n\n async function turn<\n R extends {\n templateId: string;\n content: TemplateContent;\n attachments?: { name: string; kind: \"image\" | \"pdf\" }[];\n },\n >(\n kind: AiMessageKind,\n userText: string,\n recording: AiRecording<R>,\n request: R,\n on: AiTurnHandlers,\n signal: AbortSignal,\n ): Promise<AiTurnResult> {\n let text = \"\";\n let inverse: Operation[] = [];\n let after = safeClone(request.content);\n const batches: AiMessageBatch[] = [];\n const done = await play(recording, request, signal, async (event) => {\n if (event.type === \"text\") {\n text += event.text;\n on.onText(event.text);\n } else if (event.type === \"operations\") {\n const result = await on.onOperations(safeClone(event.ops));\n if (!result.applied) {\n throw new CollaborationConflict(AI_TEMPLATE_CHANGED_MESSAGE, {\n code: result.code,\n opIndex: result.opIndex,\n });\n }\n // Store the wire snapshot's inverse, as a backend does. The editor may\n // normalize rich text before returning its own local inverse.\n inverse = [...invertOperations(after, event.ops), ...inverse];\n after = applyOperations(after, event.ops);\n batches.push({\n batchId: crypto.randomUUID(),\n revision: null,\n operationCount: event.ops.length,\n });\n } else if (event.type === \"applied\") {\n on.onApplied({\n batchId: event.batchId,\n revision: event.revision,\n operationCount: event.operationCount,\n });\n batches.push({\n batchId: event.batchId,\n revision: event.revision,\n operationCount: event.operationCount,\n });\n }\n });\n let messageId = done.messageId ?? null;\n if (script.conversation) {\n const history = conversations.get(request.templateId) ?? [];\n conversations.set(request.templateId, history);\n const createdAt = new Date().toISOString();\n history.push({\n message: {\n id: crypto.randomUUID(),\n role: \"user\",\n kind,\n text: userText,\n ...(request.attachments?.length\n ? {\n attachments: request.attachments.map(({ name, kind }) => ({\n name,\n kind,\n })),\n }\n : {}),\n batches: [],\n undone: false,\n createdAt,\n },\n inverse: [],\n after: null,\n });\n messageId = crypto.randomUUID();\n history.push({\n message: {\n id: messageId,\n role: \"assistant\",\n kind,\n text,\n batches,\n undone: false,\n createdAt,\n },\n inverse,\n after,\n });\n }\n return { messageId, text };\n }\n\n function stored(templateId: string, messageId: string): Stored {\n const entry = conversations\n .get(templateId)\n ?.find(\n (item) =>\n item.message.id === messageId && item.message.role === \"assistant\",\n );\n if (!entry)\n throw new SdkError(\n \"That message isn't in this conversation.\",\n 404,\n \"not_found\",\n );\n return entry;\n }\n\n return {\n chatAttachments: !!script.chat && script.chatAttachments === true,\n chat: script.chat\n ? (request, on, signal) => {\n const recorded = (script.chat as AiRecordedTurn[]).find(\n (candidate) =>\n !candidate.match || candidate.match.test(request.message),\n );\n if (!recorded) return Promise.reject(failure());\n return turn(\n \"chat\",\n request.message,\n recorded.events,\n request,\n on,\n signal,\n );\n }\n : false,\n fixFinding: script.fixFinding\n ? (request, on, signal) =>\n turn(\n \"fix\",\n `Fix: ${request.finding.message}`,\n script.fixFinding as AiRecording<AiFixRequest>,\n request,\n on,\n signal,\n )\n : false,\n rewrite: script.rewrite\n ? async (request, on, signal) => {\n const done = await play(\n script.rewrite as AiRecording<AiRewriteRequest>,\n request,\n signal,\n (event) => {\n if (event.type === \"text\") on.onText(event.text);\n },\n );\n if (typeof done.html !== \"string\") throw failure();\n return { html: done.html };\n }\n : false,\n altText: script.altText\n ? async (request, signal) => {\n const done = await play(\n script.altText as AiRecording<AiAltTextRequest>,\n request,\n signal,\n () => {},\n );\n if (typeof done.alt !== \"string\") throw failure();\n return { alt: done.alt };\n }\n : false,\n ...(script.conversation\n ? {\n conversation: {\n settleReplay: async (\n templateId: string,\n messageId: string,\n outcome: AiReplayOutcome,\n ) => {\n const replay = pending.get(outcome.replayId);\n if (\n !replay ||\n replay.templateId !== templateId ||\n replay.messageId !== messageId ||\n replay.direction !== outcome.direction ||\n replay.sessionId !== outcome.sessionId\n )\n return;\n pending.delete(outcome.replayId);\n if (outcome.applied)\n replay.entry.message.undone = outcome.direction === \"undo\";\n },\n list: async (templateId: string) =>\n safeClone(\n (conversations.get(templateId) ?? []).map(\n (item) => item.message,\n ),\n ),\n clear: async (templateId: string) => {\n conversations.delete(templateId);\n for (const [id, replay] of pending)\n if (replay.templateId === templateId) pending.delete(id);\n },\n undo: async (\n templateId: string,\n messageId: string,\n context: AiReplayContext,\n ) => {\n const entry = stored(templateId, messageId);\n if (entry.message.undone || entry.inverse.length === 0) {\n throw new CollaborationConflict(\n \"This change is already undone.\",\n { code: \"precondition_failed\" },\n );\n }\n const replayId = crypto.randomUUID();\n pending.set(replayId, {\n templateId,\n messageId,\n entry,\n direction: \"undo\",\n sessionId: context.sessionId,\n });\n return { operations: safeClone(entry.inverse), replayId };\n },\n redo: async (\n templateId: string,\n messageId: string,\n context: AiReplayContext,\n ) => {\n const entry = stored(templateId, messageId);\n if (!entry.message.undone) {\n throw new CollaborationConflict(\"This change isn't undone.\", {\n code: \"precondition_failed\",\n });\n }\n const operations = invertOperations(entry.after!, entry.inverse);\n const replayId = crypto.randomUUID();\n pending.set(replayId, {\n templateId,\n messageId,\n entry,\n direction: \"redo\",\n sessionId: context.sessionId,\n });\n return { operations, replayId };\n },\n },\n }\n : {}),\n };\n}\n","import type { AiChatRequest, TemplateContent } from \"@templatical/types\";\nimport {\n createButtonBlock,\n createParagraphBlock,\n createTitleBlock,\n} from \"@templatical/types\";\nimport { walkBlocks } from \"../server/content-rules\";\nimport type { AiMockScript, AiRecordedEvent } from \"./mock-provider\";\n\nconst text = (value: string): AiRecordedEvent => ({\n type: \"text\",\n text: value,\n});\nconst done: AiRecordedEvent = { type: \"done\", messageId: null };\n\nfunction firstOfType(\n content: TemplateContent,\n type: string,\n): { id: string } | undefined {\n return walkBlocks(content.blocks).find((block) => block.type === type) as\n { id: string } | undefined;\n}\n\nfunction lastTopLevelId(content: TemplateContent): string | null {\n return content.blocks.at(-1)?.id ?? null;\n}\n\n/**\n * Scripted turns for any template: the playground's AI scene and the\n * conformance suite's self-test play them. Chat answers \"dark\", \"title\" and\n * \"fail\" specially and otherwise adds a closing paragraph.\n */\nexport const AI_RECORDINGS: AiMockScript = {\n delayMs: 40,\n chatAttachments: true,\n chat: [\n {\n match: /attached design/i,\n events: (request) => [\n text(\"Built the email from your design.\"),\n {\n type: \"operations\",\n ops: [\n {\n op: \"setContent\",\n content: {\n blocks: [\n createTitleBlock({\n content: \"<p>Built from your design</p>\",\n }),\n createParagraphBlock({\n content: \"<p>A short introduction, as in the image.</p>\",\n }),\n createButtonBlock({\n text: \"Learn more\",\n url: \"https://example.com\",\n }),\n ],\n settings: request.content.settings,\n },\n },\n ],\n },\n text(\" Pictures are placeholders.\"),\n done,\n ],\n },\n {\n match: /fail|error/i,\n events: [\n text(\"Let me try that.\"),\n {\n type: \"error\",\n code: \"ai_provider_error\",\n message: \"AI is unavailable right now. Try again later.\",\n },\n ],\n },\n {\n match: /dark/i,\n events: [\n text(\"Switching the email to a dark background.\"),\n {\n type: \"operations\",\n ops: [\n {\n op: \"updateSettings\",\n set: { backgroundColor: \"#111827\", textColor: \"#f9fafb\" },\n },\n ],\n },\n text(\" Done: a dark background with light text.\"),\n done,\n ],\n },\n {\n match: /title|headline|heading/i,\n events: (request: AiChatRequest) => {\n const title = firstOfType(request.content, \"title\");\n return title\n ? [\n text(\"Updating the title.\"),\n {\n type: \"operations\",\n ops: [\n {\n op: \"updateBlock\",\n blockId: title.id,\n set: { content: \"<p>Hello there</p>\" },\n },\n ],\n },\n text(\" It now reads “Hello there”.\"),\n done,\n ]\n : [text(\"This template has no title to change.\"), done];\n },\n },\n {\n events: (request: AiChatRequest) => [\n text(\"Adding a closing line at the end.\"),\n {\n type: \"operations\",\n ops: [\n {\n op: \"addBlock\",\n block: createParagraphBlock({\n content: \"<p>Thanks for reading. See you next week.</p>\",\n }),\n parent: null,\n after: lastTopLevelId(request.content),\n },\n ],\n },\n text(\" I added it below the last block.\"),\n done,\n ],\n },\n ],\n fixFinding: (request) => {\n const target = request.finding.blockId;\n const block = target\n ? walkBlocks(request.content.blocks).find(\n (candidate) => candidate.id === target,\n )\n : undefined;\n const label =\n request.finding.ruleId === \"a11y.link-vague-text\"\n ? \"Read the March update\"\n : request.finding.ruleId === \"deliverability.link-text-domain-mismatch\"\n ? \"Visit Acme\"\n : null;\n if (\n label &&\n block &&\n (block.type === \"paragraph\" || block.type === \"title\")\n ) {\n return [\n text(\"Updating the link text.\"),\n {\n type: \"operations\",\n ops: [\n {\n op: \"updateBlock\",\n blockId: target as string,\n set: {\n content: (block.content as string).replace(\n /(<a\\b[^>]*>)[\\s\\S]*?(<\\/a>)/i,\n `$1${label}$2`,\n ),\n },\n },\n ],\n },\n text(\" Fixed.\"),\n done,\n ];\n }\n return block && (block.type === \"paragraph\" || block.type === \"title\")\n ? [\n text(\"Shortening that block.\"),\n {\n type: \"operations\",\n ops: [\n {\n op: \"updateBlock\",\n blockId: target as string,\n set: { content: \"<p>A shorter, clearer line.</p>\" },\n },\n ],\n },\n text(\" Fixed.\"),\n done,\n ]\n : [\n text(\"Adding a preheader.\"),\n {\n type: \"operations\",\n ops: [\n {\n op: \"updateSettings\",\n set: { preheaderText: \"Your update from Acme\" },\n },\n ],\n },\n text(\" Fixed.\"),\n done,\n ];\n },\n altText: (request) => [\n { type: \"done\", alt: \"Shop new arrivals\".slice(0, request.maxLength) },\n ],\n rewrite: (request) => {\n const html = request.html.replace(/^(<p[^>]*>)/, \"$1✨ \");\n return [text(\"✨ \"), text(\"Rewritten\"), { type: \"done\", html }];\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AQkFA,MAAa,gBAAgBA;AAC7B,MAAa,oBAET;CACF,SAASC;CACT,OAAOC;CACP,WA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"}
1
+ {"version":3,"file":"index.js","names":["cases","welcome","promo","injection","empty","domainMismatch","lowText"],"sources":["../../package.json","../../eval/cases.json","../../eval/empty.json","../../eval/domain-mismatch.json","../../eval/low-text.json","../../eval/injection.json","../../eval/promo.json","../../eval/welcome.json","../../src/testing/eval-set.ts","../../src/testing/checks.ts","../../src/testing/stand-in-editor.ts","../../src/testing/conformance.ts","../../src/testing/wire-conformance.ts","../../src/testing/mock-provider.ts","../../src/testing/recordings.ts"],"sourcesContent":["","","","","","","","","import type {\n AiChatAttachment,\n AiFixFinding,\n AiMergeTags,\n TemplateContent,\n} from \"@templatical/types\";\nimport { safeClone } from \"@templatical/types\";\nimport { version } from \"../../package.json\";\nimport cases from \"../../eval/cases.json\";\nimport empty from \"../../eval/empty.json\";\nimport domainMismatch from \"../../eval/domain-mismatch.json\";\nimport lowText from \"../../eval/low-text.json\";\nimport injection from \"../../eval/injection.json\";\nimport promo from \"../../eval/promo.json\";\nimport welcome from \"../../eval/welcome.json\";\nimport type { AiModelAdapter } from \"../server/model\";\nimport {\n buildAltTextRequest,\n buildChatRequest,\n buildFixRequest,\n} from \"../server/prompts\";\nimport {\n buildAltTextRepairRequest,\n checkAltText,\n checkAltTextAnswer,\n} from \"../server/checks\";\nimport { runEditingTurn } from \"../server/run-editing-turn\";\n\nexport type AiEvalCheck =\n | { type: \"applied\" | \"notApplied\" }\n | { type: \"present\" | \"missing\"; block: string }\n | { type: \"contains\" | \"notContains\"; block?: string; text: string }\n | { type: \"lintGone\"; ruleId: string }\n | { type: \"field\" | \"fieldNot\"; block: string; path: string; matches: string }\n | { type: \"setting\"; path: string; matches: string }\n | { type: \"greater\"; block: string; path: string; than: number }\n | { type: \"maxText\"; block: string; chars: number }\n | { type: \"countType\"; blockType: string; min: number }\n | { type: \"order\"; first: string; then: string }\n | { type: \"firstContains\"; text: string }\n | { type: \"reply\"; matches: string };\n\nexport type AiEvalAltCheck =\n | { type: \"nonEmpty\" | \"withinLimit\" }\n | { type: \"containsAll\" | \"containsAny\"; texts: string[] }\n | { type: \"notMatches\"; matches: string }\n | { type: \"shortOrMatches\"; chars: number; matches: string };\n\ntype EvalTemplate =\n \"welcome\" | \"promo\" | \"injection\" | \"empty\" | \"domain-mismatch\" | \"low-text\";\n\ninterface EvalTurnCase {\n name: string;\n template: EvalTemplate;\n /** Block ids the case may delete; every other original id must survive. */\n removed?: string[];\n checks: AiEvalCheck[];\n}\n\nexport interface AiEvalAltTextInput {\n /** Path relative to the package's eval/ folder. */\n image: string;\n linkHref: string | null;\n nearbyText: string;\n maxLength: number;\n}\n\nexport type AiEvalCase =\n | (EvalTurnCase & {\n kind?: \"edit\";\n message: string;\n selectedBlockId?: string;\n attachments?: { file: string; kind: \"image\" | \"pdf\" }[];\n })\n | (EvalTurnCase & { kind: \"fix\"; finding: AiFixFinding })\n | (AiEvalAltTextInput & {\n kind: \"altText\";\n name: string;\n checks: AiEvalAltCheck[];\n });\n\n/** The cases of S7's test set (`eval/cases.json`). An omitted kind means edit. */\nexport const AI_EVAL_CASES = cases as AiEvalCase[];\nexport const AI_EVAL_TEMPLATES: Readonly<\n Record<EvalTemplate, TemplateContent>\n> = {\n welcome: welcome as unknown as TemplateContent,\n promo: promo as unknown as TemplateContent,\n injection: injection as unknown as TemplateContent,\n empty: empty as unknown as TemplateContent,\n \"domain-mismatch\": domainMismatch as unknown as TemplateContent,\n \"low-text\": lowText as unknown as TemplateContent,\n};\n/** The merge tags every case's editor declares. */\nexport const AI_EVAL_MERGE_TAGS: AiMergeTags = {\n syntax: \"liquid\",\n tags: [\n { value: \"{{first_name}}\", label: \"First name\" },\n { value: \"{{coupon_code}}\", label: \"Coupon code\" },\n { value: \"{{unsubscribe_url}}\", label: \"Unsubscribe link\" },\n ],\n};\n\nexport interface AiEvalTurnInput {\n content: TemplateContent;\n mergeTags: AiMergeTags;\n message: string;\n selectedBlockId: string | null;\n finding: AiFixFinding | null;\n attachments: AiChatAttachment[];\n}\n\nexport interface AiEvalTurnOutput {\n /** The template after the turn. */\n content: TemplateContent;\n reply: string;\n /** Whether the turn changed the template. */\n applied: boolean;\n}\n\nexport interface AiEvalReport {\n model: string;\n passed: number;\n /** Cases that ran; skipped cases are excluded. */\n total: number;\n skipped: number;\n /** Whole percent, rounded down. */\n rate: number;\n ok: boolean;\n results: {\n name: string;\n ok: boolean;\n reason: string | null;\n skipped?: true;\n }[];\n}\n\ntype Json = Record<string, unknown>;\n\n/** Every block by id, in document order (a section, then its columns). */\nfunction blocksById(content: TemplateContent): Map<string, Json> {\n const found = new Map<string, Json>();\n const walk = (blocks: unknown): void => {\n if (!Array.isArray(blocks)) return;\n for (const block of blocks as Json[]) {\n if (typeof block !== \"object\" || block === null) continue;\n if (typeof block.id === \"string\") found.set(block.id, block);\n if (Array.isArray(block.children))\n for (const column of block.children) walk(column);\n }\n };\n walk(content.blocks);\n return found;\n}\n\n/** A case pattern; PCRE's leading `(?i)` becomes the `i` flag. */\nfunction pattern(source: string): RegExp {\n return source.startsWith(\"(?i)\")\n ? new RegExp(source.slice(4), \"iu\")\n : new RegExp(source, \"u\");\n}\n\nfunction matches(value: unknown, source: string): boolean {\n const subject =\n typeof value === \"string\" ? value : JSON.stringify(value ?? null);\n return pattern(source).test(subject);\n}\n\nfunction containsText(scope: unknown, text: string): boolean {\n return JSON.stringify(scope)\n .toLowerCase()\n .includes(JSON.stringify(text).slice(1, -1).toLowerCase());\n}\n\nfunction at(target: unknown, path: string): unknown {\n return path\n .split(\".\")\n .reduce<unknown>(\n (value, key) =>\n typeof value === \"object\" && value !== null\n ? (value as Json)[key]\n : undefined,\n target,\n );\n}\n\n/**\n * Run one case's checks against a turn's outcome; the first failure's reason,\n * or `null` when it passed (S7 Cloud Task 11's `failure()`).\n */\nexport function checkEvalCase(\n testCase: Exclude<AiEvalCase, { kind: \"altText\" }>,\n original: TemplateContent,\n output: AiEvalTurnOutput,\n lintCheck?: (content: TemplateContent) => string[],\n): string | null {\n const blocks = blocksById(output.content);\n const removed = testCase.removed ?? [];\n for (const id of blocksById(original).keys()) {\n if (!blocks.has(id) && !removed.includes(id))\n return `block ${id} was deleted`;\n }\n const order = [...blocks.keys()];\n for (const check of testCase.checks) {\n const block =\n \"block\" in check && check.block !== undefined\n ? blocks.get(check.block)\n : undefined;\n const scope =\n \"block\" in check && check.block !== undefined ? block : output.content;\n let ok: boolean;\n switch (check.type) {\n case \"applied\":\n ok = output.applied;\n break;\n case \"notApplied\":\n ok = !output.applied;\n break;\n case \"present\":\n ok = block !== undefined;\n break;\n case \"missing\":\n ok = block === undefined;\n break;\n case \"contains\":\n ok = scope !== undefined && containsText(scope, check.text);\n break;\n case \"notContains\":\n ok = scope === undefined || !containsText(scope, check.text);\n break;\n case \"field\":\n ok =\n block !== undefined && matches(at(block, check.path), check.matches);\n break;\n case \"fieldNot\":\n ok =\n block !== undefined && !matches(at(block, check.path), check.matches);\n break;\n case \"lintGone\":\n ok =\n lintCheck !== undefined &&\n !lintCheck(output.content).includes(check.ruleId);\n break;\n case \"setting\":\n ok = matches(at(output.content.settings, check.path), check.matches);\n break;\n case \"greater\": {\n const value = block ? at(block, check.path) : undefined;\n ok = typeof value === \"number\" && value > check.than;\n break;\n }\n case \"maxText\": {\n const raw =\n block && typeof block.content === \"string\" ? block.content : \"\";\n // A length measure, not a sanitizer: the text between the tags.\n const text = raw.split(/<[^>]*>/).join(\"\");\n ok =\n block !== undefined && Array.from(text.trim()).length <= check.chars;\n break;\n }\n case \"countType\":\n ok =\n [...blocks.values()].filter(\n (candidate) => candidate.type === check.blockType,\n ).length >= check.min;\n break;\n case \"order\": {\n const first = order.indexOf(check.first);\n const then = order.indexOf(check.then);\n ok = first !== -1 && then !== -1 && first < then;\n break;\n }\n case \"firstContains\":\n ok =\n output.content.blocks[0] !== undefined &&\n containsText(output.content.blocks[0], check.text);\n break;\n case \"reply\":\n ok = matches(output.reply, check.matches);\n break;\n default:\n ok = false;\n }\n if (!ok) return `check failed: ${JSON.stringify(check)}`;\n }\n return null;\n}\n\n/** Public URL for a fixture from this package's fixed-version SDK release. */\nfunction fixtureUrl(file: string): string {\n return `https://raw.githubusercontent.com/templatical/sdk/v${version}/packages/ai-kit/eval/${file}`;\n}\n\n/** Text-free public probe image from this package's fixed-version SDK release. */\nexport const AI_CONFORMANCE_ALT_TEXT_IMAGE_URL = fixtureUrl(\"images/probe.png\");\n\nasync function loadFixture(file: string): Promise<Blob> {\n const response = await fetch(fixtureUrl(file));\n if (!response.ok) throw new Error(`Fixture ${file}: HTTP ${response.status}`);\n return response.blob();\n}\n\nfunction checkAltCase(\n testCase: Extract<AiEvalCase, { kind: \"altText\" }>,\n text: string,\n): string | null {\n const contains = (value: string) =>\n text.toLowerCase().includes(value.toLowerCase());\n const matches = (source: string) =>\n new RegExp(source.replace(/^\\(\\?i\\)/, \"\"), \"iu\").test(text);\n for (const check of testCase.checks) {\n let ok: boolean;\n switch (check.type) {\n case \"nonEmpty\":\n ok = text.trim() !== \"\";\n break;\n case \"withinLimit\":\n ok = text.length <= testCase.maxLength;\n break;\n case \"containsAll\":\n ok = check.texts.every(contains);\n break;\n case \"containsAny\":\n ok = check.texts.some(contains);\n break;\n case \"notMatches\":\n ok = !matches(check.matches);\n break;\n case \"shortOrMatches\":\n ok = text.length <= check.chars || matches(check.matches);\n break;\n default:\n ok = false;\n }\n if (!ok) return `check failed: ${JSON.stringify(check)}`;\n }\n return null;\n}\n\n/**\n * Run each case on a fresh template. Missing optional runners skip their cases;\n * thrown turns or checks fail. The pass rate counts only cases that ran.\n */\nexport async function runAiEvalSet(options: {\n runTurn: (input: AiEvalTurnInput) => Promise<AiEvalTurnOutput>;\n runAltText?: (input: AiEvalAltTextInput) => Promise<string>;\n lintCheck?: (content: TemplateContent) => string[];\n loadFile?: (file: string) => Promise<Blob>;\n model: string;\n cases?: readonly AiEvalCase[];\n minPassRate?: number;\n}): Promise<AiEvalReport> {\n const cases = options.cases ?? AI_EVAL_CASES;\n const results: AiEvalReport[\"results\"] = [];\n for (const testCase of cases) {\n const missing =\n testCase.kind === \"altText\"\n ? !options.runAltText\n ? \"runAltText\"\n : null\n : testCase.checks.some((check) => check.type === \"lintGone\") &&\n !options.lintCheck\n ? \"lintCheck\"\n : null;\n if (missing) {\n results.push({\n name: testCase.name,\n ok: false,\n reason: `Skipped: ${missing} is required.`,\n skipped: true,\n });\n continue;\n }\n let reason: string | null;\n try {\n if (testCase.kind === \"altText\") {\n const text = await options.runAltText!({\n image: testCase.image,\n linkHref: testCase.linkHref,\n nearbyText: testCase.nearbyText,\n maxLength: testCase.maxLength,\n });\n reason = checkAltCase(testCase, text);\n } else {\n const original = AI_EVAL_TEMPLATES[testCase.template];\n const attachments: AiChatAttachment[] = [];\n if (testCase.kind !== \"fix\") {\n for (const attachment of testCase.attachments ?? []) {\n attachments.push({\n file: await (options.loadFile ?? loadFixture)(attachment.file),\n kind: attachment.kind,\n name: attachment.file.split(\"/\").pop()!,\n });\n }\n }\n const output = await options.runTurn({\n content: safeClone(original),\n mergeTags: AI_EVAL_MERGE_TAGS,\n message: testCase.kind === \"fix\" ? \"\" : testCase.message,\n selectedBlockId:\n testCase.kind === \"fix\" ? null : (testCase.selectedBlockId ?? null),\n finding: testCase.kind === \"fix\" ? safeClone(testCase.finding) : null,\n attachments,\n });\n reason = checkEvalCase(testCase, original, output, options.lintCheck);\n }\n } catch (error) {\n reason =\n error instanceof Error\n ? `${error.name}: ${error.message}`\n : String(error);\n }\n results.push({ name: testCase.name, ok: reason === null, reason });\n }\n const skipped = results.filter((result) => result.skipped).length;\n const total = results.length - skipped;\n const passed = results.filter((result) => result.ok).length;\n const rate = total === 0 ? 0 : Math.floor((passed * 100) / total);\n return {\n model: options.model,\n passed,\n total,\n skipped,\n rate,\n ok: rate >= (options.minPassRate ?? 90),\n results,\n };\n}\n\n/** A `runAltText` using image URLs; override them for unreleased fixtures. */\nexport function evalAltTextWith(\n model: AiModelAdapter,\n options: { imageUrl?: (file: string) => string } = {},\n): (input: AiEvalAltTextInput) => Promise<string> {\n return async (input) => {\n const request = buildAltTextRequest({\n templateId: \"eval\",\n sessionId: \"eval\",\n blockId: \"eval-image\",\n image: { url: (options.imageUrl ?? fixtureUrl)(input.image) },\n currentAlt: null,\n linkHref: input.linkHref,\n nearbyText: input.nearbyText,\n locale: \"en\",\n maxLength: input.maxLength,\n });\n let text = \"\";\n for await (const part of model.stream({ ...request, tools: [] })) {\n if (part.type === \"text\") text += part.text;\n }\n const check = checkAltTextAnswer(text, { maxLength: input.maxLength });\n if (!check.ok) {\n const repair = buildAltTextRepairRequest(\n request,\n text,\n check.reason,\n input.maxLength,\n );\n text = \"\";\n for await (const part of model.stream({ ...repair, tools: [] })) {\n if (part.type === \"text\") text += part.text;\n }\n }\n return checkAltText(text, { maxLength: input.maxLength });\n };\n}\n\n/** A `runTurn` on the kit's own loop, for a model behind an `AiModelAdapter`. */\nexport function evalTurnWith(\n model: AiModelAdapter,\n options: { maxSteps?: number } = {},\n): (input: AiEvalTurnInput) => Promise<AiEvalTurnOutput> {\n return async (input) => {\n const result = await runEditingTurn({\n request: {\n content: input.content,\n mergeTags: input.mergeTags,\n selectedBlockId: input.selectedBlockId,\n request: input.finding\n ? buildFixRequest(input.finding)\n : buildChatRequest(input.message, {\n hasAttachments: input.attachments.length > 0,\n }),\n attachments: input.attachments.map(({ kind, file }) => ({\n kind,\n mediaType: file.type,\n data: file,\n })),\n },\n model,\n write: () => {},\n ...(options.maxSteps !== undefined ? { maxSteps: options.maxSteps } : {}),\n });\n return {\n content: result.content,\n reply: result.text,\n applied: result.batches.length > 0,\n };\n };\n}\n","import type { AiChatMessage } from \"@templatical/types\";\nimport { SdkError } from \"@templatical/types\";\n\nconst KNOWN_CODES = new Set([\n \"feature_unavailable\",\n \"limit_reached\",\n \"rate_limited\",\n \"ai_turn_running\",\n \"content_too_large\",\n \"ai_output_invalid\",\n \"ai_provider_error\",\n \"not_found\",\n \"block_exists\",\n \"block_missing\",\n \"anchor_missing\",\n \"precondition_failed\",\n \"lock_held\",\n \"locked_by_others\",\n \"invalid_operation\",\n]);\n\nexport function describeError(error: unknown): string {\n if (error instanceof SdkError) {\n return `${error.name} ${error.code ?? \"(no code)\"} ${error.statusCode ?? \"\"}: ${error.message}`.trim();\n }\n return error instanceof Error\n ? `${error.name}: ${error.message}`\n : String(error);\n}\n\n/** Why a rejection breaks the contract, or `null`: SdkErrors with known codes and AbortErrors are fine. */\nexport function errorProblem(error: unknown): string | null {\n if (error instanceof Error && error.name === \"AbortError\") return null;\n if (!(error instanceof SdkError))\n return `not an SdkError: ${describeError(error)}`;\n if (typeof error.code !== \"string\" || !KNOWN_CODES.has(error.code)) {\n return `unknown code: ${describeError(error)}`;\n }\n return null;\n}\n\nexport function messageProblem(message: unknown): string | null {\n const m = message as AiChatMessage;\n if (\n !m ||\n typeof m.id !== \"string\" ||\n ![\"user\", \"assistant\"].includes(m.role) ||\n ![\"chat\", \"fix\"].includes(m.kind) ||\n typeof m.text !== \"string\" ||\n (m.attachments !== undefined &&\n (!Array.isArray(m.attachments) ||\n !m.attachments.every(\n (a) =>\n a &&\n typeof a.name === \"string\" &&\n [\"image\", \"pdf\"].includes(a.kind),\n ))) ||\n !Array.isArray(m.batches) ||\n typeof m.undone !== \"boolean\" ||\n typeof m.createdAt !== \"string\" ||\n !m.batches.every(\n (batch) =>\n typeof batch.batchId === \"string\" &&\n (batch.revision === null || Number.isInteger(batch.revision)) &&\n Number.isInteger(batch.operationCount),\n )\n ) {\n return `malformed message: ${JSON.stringify(message)}`;\n }\n return null;\n}\n","import type {\n AiAppliedBatch,\n AiMergeTags,\n AiOperationsResult,\n AiTurnHandlers,\n Operation,\n TemplateContent,\n} from \"@templatical/types\";\nimport {\n applyOperations,\n invertOperations,\n OperationError,\n safeClone,\n} from \"@templatical/types\";\n\n/** The editor the suites drive: `onOperations` applies with the engine. */\nexport interface StandInEditor {\n content: TemplateContent;\n readonly mergeTags: AiMergeTags;\n readonly sessionId: string;\n readonly applied: Operation[][];\n readonly refused: { code: string; opIndex: number; message: string }[];\n readonly serverBatches: AiAppliedBatch[];\n /** Apply a batch as the editor would answer `onOperations`. */\n apply(ops: Operation[]): AiOperationsResult;\n /** Fresh handlers for one call; `text` collects what streamed. */\n handlers(extra?: { onText?: (text: string) => void }): AiTurnHandlers & {\n readonly text: string;\n };\n}\n\nexport function createStandInEditor(\n content: TemplateContent,\n mergeTags: AiMergeTags = { syntax: \"liquid\", tags: [] },\n): StandInEditor {\n const editor: StandInEditor = {\n content: safeClone(content),\n mergeTags: safeClone(mergeTags),\n sessionId: crypto.randomUUID(),\n applied: [],\n refused: [],\n serverBatches: [],\n apply(ops) {\n try {\n const inverse = invertOperations(editor.content, ops);\n editor.content = applyOperations(editor.content, ops);\n editor.applied.push(safeClone(ops));\n return { applied: true, inverse };\n } catch (error) {\n if (!(error instanceof OperationError)) throw error;\n const refusal = {\n code: error.code,\n opIndex: error.opIndex,\n message: error.message,\n };\n editor.refused.push(refusal);\n return { applied: false, ...refusal, code: error.code };\n }\n },\n handlers(extra = {}) {\n let text = \"\";\n return {\n get text() {\n return text;\n },\n onText(chunk) {\n text += chunk;\n extra.onText?.(chunk);\n },\n onOperations: async (ops) => editor.apply(ops),\n onApplied(batch) {\n editor.serverBatches.push({ ...batch });\n },\n };\n },\n };\n return editor;\n}\n","import type {\n AiAltTextRequest,\n AiFeatureName,\n AiMergeTags,\n AiProvider,\n AiReplay,\n AiRequestBase,\n TemplateContent,\n} from \"@templatical/types\";\nimport { safeClone } from \"@templatical/types\";\nimport {\n AI_CONFORMANCE_ALT_TEXT_IMAGE_URL,\n AI_EVAL_MERGE_TAGS,\n AI_EVAL_TEMPLATES,\n} from \"./eval-set\";\nimport { describeError, errorProblem, messageProblem } from \"./checks\";\nimport { createStandInEditor } from \"./stand-in-editor\";\n\nexport interface AiConformanceCheck {\n name: string;\n ok: boolean;\n detail: string;\n}\n\nexport interface AiConformanceReport {\n ok: boolean;\n checks: AiConformanceCheck[];\n}\n\nexport interface AiConformanceOptions {\n /** The template the suite edits; the test set's welcome email by default. */\n content?: TemplateContent;\n templateId?: string;\n /** Public probe image URL; defaults to AI_CONFORMANCE_ALT_TEXT_IMAGE_URL. */\n altTextImageUrl?: string;\n mergeTags?: AiMergeTags;\n /** How long a stopped call may take to reject (D35). */\n timeoutMs?: number;\n /** An optional shorter call limit; every call is also bounded by `timeoutMs`. */\n callTimeoutMs?: number;\n}\n\nconst FEATURES: Exclude<AiFeatureName, \"chatAttachments\">[] = [\n \"chat\",\n \"fixFinding\",\n \"rewrite\",\n \"altText\",\n];\n\n/** A 1×1 transparent PNG. */\nexport function tinyPng(): Blob {\n const base64 =\n \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==\";\n const bytes = Uint8Array.from(atob(base64), (char) => char.charCodeAt(0));\n return new Blob([bytes], { type: \"image/png\" });\n}\n\nconst NO_RESULT = Symbol(\"call failed\");\n\nclass ConformanceTimeoutError extends Error {\n override name = \"TimeoutError\";\n}\n\n/** @internal Bound the wait even when the operation ignores its signal. */\nexport async function withConformanceDeadline<T>(\n name: string,\n timeoutMs: number,\n run: (signal: AbortSignal) => T | PromiseLike<T>,\n controller = new AbortController(),\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const deadline = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n const error = new ConformanceTimeoutError(\n `${name}: timed out after ${timeoutMs} ms`,\n );\n reject(error);\n controller.abort(error);\n }, timeoutMs);\n });\n try {\n return await Promise.race([\n Promise.resolve().then(() => run(controller.signal)),\n deadline,\n ]);\n } finally {\n clearTimeout(timer);\n }\n}\n\nfunction preview(value: unknown): string {\n return typeof value === \"string\"\n ? JSON.stringify(value.slice(0, 80))\n : value === null\n ? \"null\"\n : typeof value;\n}\n\n/** @internal Keep a malformed value or diagnostic inside its own check. */\nexport async function evaluateConformanceCheck(\n name: string,\n evaluate: () =>\n | { ok: boolean; detail?: string }\n | Promise<{ ok: boolean; detail?: string }>,\n): Promise<AiConformanceCheck> {\n try {\n const { ok, detail = \"ok\" } = await evaluate();\n return { name, ok, detail };\n } catch (error) {\n return {\n name,\n ok: false,\n detail: error instanceof Error ? error.message : preview(error),\n };\n }\n}\n\n/**\n * Drive a provider as the editor would and check the contract (A1 §6): any\n * test runner, never throws on a failed check. Each declared feature runs\n * once against a stand-in editor that applies `operations` with the engine.\n */\nexport async function runAiProviderConformance(\n provider: AiProvider,\n options: AiConformanceOptions = {},\n): Promise<AiConformanceReport> {\n const checks: AiConformanceCheck[] = [];\n const add = async (\n name: string,\n evaluate: Parameters<typeof evaluateConformanceCheck>[1],\n ): Promise<boolean> => {\n const check = await evaluateConformanceCheck(name, evaluate);\n checks.push(check);\n return check.ok;\n };\n const timeoutMs = options.timeoutMs ?? 5000;\n const callTimeoutMs = Math.min(options.callTimeoutMs ?? timeoutMs, timeoutMs);\n const templateId = options.templateId ?? \"conformance\";\n const editor = createStandInEditor(\n options.content ?? AI_EVAL_TEMPLATES.welcome,\n options.mergeTags ?? AI_EVAL_MERGE_TAGS,\n );\n const errors: unknown[] = [];\n const probes: Partial<\n Record<AiFeatureName, (signal: AbortSignal) => Promise<unknown>>\n > = {};\n const base = (): AiRequestBase => ({\n templateId,\n content: safeClone(editor.content),\n revision: null,\n sessionId: editor.sessionId,\n mergeTags: editor.mergeTags,\n });\n\n async function call<T>(\n name: string,\n run: (signal: AbortSignal) => Promise<T>,\n ): Promise<T | typeof NO_RESULT> {\n try {\n return await withConformanceDeadline(name, callTimeoutMs, run);\n } catch (error) {\n if (!(error instanceof ConformanceTimeoutError)) errors.push(error);\n await add(`${name}: answers`, () => ({\n ok: false,\n detail: describeError(error),\n }));\n return NO_RESULT;\n }\n }\n\n const declared = FEATURES.filter(\n (feature) => typeof provider[feature] === \"function\",\n );\n await add(\"declares at least one feature\", () => ({\n ok: declared.length > 0,\n detail: `declared: ${declared.join(\", \") || \"none\"}`,\n }));\n for (const feature of FEATURES) {\n const member: unknown = provider[feature];\n if (member !== false && typeof member !== \"function\") {\n await add(`${feature}: false or a function`, () => ({\n ok: false,\n detail: `got ${typeof member}`,\n }));\n } else if (member === false) {\n await add(`${feature}: off`, () => ({\n ok: true,\n detail: `skipped: ${feature} is false`,\n }));\n }\n }\n\n // Turns first: they change the stand-in's template.\n let lastAssistantId: string | null = null;\n if (typeof provider.chat === \"function\") {\n const chat = provider.chat;\n probes.chat = (signal) =>\n chat(\n {\n ...base(),\n message: \"Make the button green\",\n selectedBlockId: null,\n attachments: [],\n },\n createStandInEditor(editor.content).handlers(),\n signal,\n );\n const handlers = editor.handlers();\n const result = await call(\"chat\", (signal) =>\n chat(\n {\n ...base(),\n message:\n \"Change the headline to 'Hello there' and keep the first name in it\",\n selectedBlockId: null,\n attachments: [],\n },\n handlers,\n signal,\n ),\n );\n if (result !== NO_RESULT) {\n await add(\"chat: streams text and resolves with it\", () => {\n const text = result?.text;\n const messageId = result?.messageId;\n if (messageId === null || typeof messageId === \"string\")\n lastAssistantId = messageId;\n return {\n ok:\n typeof text === \"string\" &&\n (messageId === null || typeof messageId === \"string\") &&\n (handlers.text === \"\" || text === handlers.text),\n detail: `streamed ${preview(handlers.text)}, resolved ${preview(text)}`,\n };\n });\n }\n }\n if (typeof provider.fixFinding === \"function\") {\n const fix = provider.fixFinding;\n probes.fixFinding = (signal) =>\n fix(\n {\n ...base(),\n finding: {\n ruleId: \"a11y.button-vague-label\",\n message: \"The button text doesn't say what happens.\",\n suggestion: \"Say where the button goes.\",\n reference:\n \"https://docs.templatical.com/quality/accessibility/rule-catalog#buttons\",\n blockId: null,\n },\n },\n createStandInEditor(editor.content).handlers(),\n signal,\n );\n const result = await call(\"fixFinding\", (signal) =>\n fix(\n {\n ...base(),\n finding: {\n ruleId: \"a11y.button-vague-label\",\n message: \"The button text doesn't say what happens.\",\n suggestion: \"Say where the button goes.\",\n reference:\n \"https://docs.templatical.com/quality/accessibility/rule-catalog#buttons\",\n blockId: null,\n },\n },\n editor.handlers(),\n signal,\n ),\n );\n if (result !== NO_RESULT)\n await add(\"fixFinding: answers\", () => ({\n ok: typeof result?.text === \"string\",\n }));\n }\n if (\n provider.chatAttachments === true &&\n typeof provider.chat === \"function\"\n ) {\n const chat = provider.chat;\n const result = await call(\"chatAttachments\", (signal) =>\n chat(\n {\n ...base(),\n message: \"Use this reference\",\n selectedBlockId: null,\n attachments: [\n { file: tinyPng(), kind: \"image\", name: \"reference.png\" },\n ],\n },\n editor.handlers(),\n signal,\n ),\n );\n if (result !== NO_RESULT)\n await add(\"chatAttachments: answers\", () => ({\n ok: typeof result?.text === \"string\" && result.text.trim() !== \"\",\n }));\n } else {\n await add(\"chatAttachments: off\", () => ({\n ok: true,\n detail: \"skipped: chatAttachments or chat is false\",\n }));\n }\n\n await add(\"operations apply cleanly to the snapshot\", () => ({\n ok: editor.refused.length === 0,\n detail:\n editor.refused.length === 0\n ? `${editor.applied.length} batch(es) applied`\n : editor.refused\n .map(\n (refusal) =>\n `${refusal.code} at op ${refusal.opIndex}: ${refusal.message}`,\n )\n .join(\"; \"),\n }));\n await add(\"applied events carry increasing revisions\", () => {\n const revisions = editor.serverBatches.map((batch) => batch.revision);\n return {\n ok: editor.serverBatches.every(\n (batch, index) =>\n typeof batch.batchId === \"string\" &&\n Number.isInteger(batch.revision) &&\n batch.operationCount >= 1 &&\n (index === 0 || batch.revision > revisions[index - 1]),\n ),\n detail:\n revisions.length === 0\n ? \"no applied events\"\n : `revisions ${revisions.join(\", \")}`,\n };\n });\n\n // History.\n const conversation = provider.conversation;\n if (conversation) {\n const list = await call(\"conversation.list\", () =>\n conversation.list(templateId),\n );\n if (list !== NO_RESULT) {\n const validMessages = await add(\n \"conversation: lists messages in the Y7 shape\",\n () => {\n const problem = Array.isArray(list)\n ? (list.map(messageProblem).find((p) => p !== null) ?? null)\n : \"not a list\";\n return {\n ok: problem === null,\n detail: problem ?? `${list.length} message(s)`,\n };\n },\n );\n await add(\"conversation: undo and redo replay\", async () => {\n if (!validMessages)\n return { ok: false, detail: \"cannot replay malformed messages\" };\n const target =\n [...list]\n .reverse()\n .find(\n (message) =>\n message.role === \"assistant\" && message.batches.length > 0,\n )?.id ?? lastAssistantId;\n const { undo, redo } = conversation;\n if (\n !target ||\n typeof undo !== \"function\" ||\n typeof redo !== \"function\"\n ) {\n return {\n ok: true,\n detail: \"nothing to undo, or undo/redo are false\",\n };\n }\n let ok = true;\n const isOperations = (\n value: AiReplay | typeof NO_RESULT,\n ): value is Extract<AiReplay, { operations: unknown }> =>\n value !== NO_RESULT &&\n typeof value === \"object\" &&\n value !== null &&\n \"operations\" in value &&\n Array.isArray(value.operations) &&\n typeof value.replayId === \"string\" &&\n value.replayId !== \"\";\n const state = async () => {\n const rows = await call(\"conversation.list\", () =>\n conversation.list(templateId),\n );\n return Array.isArray(rows)\n ? rows.find((row) => row.id === target)?.undone\n : undefined;\n };\n for (const [direction, step] of [\n [\"undo\", undo],\n [\"redo\", redo],\n ] as const) {\n const before = direction === \"redo\";\n let replay = await call(`conversation.${direction}`, () =>\n step(templateId, target, { sessionId: editor.sessionId }),\n );\n if (isOperations(replay)) {\n const settleReplay = conversation.settleReplay;\n if (typeof settleReplay !== \"function\")\n return { ok: false, detail: \"settleReplay is required\" };\n ok =\n (await add(\n `conversation: ${direction} replay waits for settlement`,\n async () => ({ ok: (await state()) === before }),\n )) && ok;\n const outcome = {\n replayId: replay.replayId,\n direction,\n applied: false,\n sessionId: editor.sessionId,\n };\n const refused = await call(\"conversation.settleReplay\", () =>\n settleReplay(templateId, target, outcome),\n );\n ok =\n (await add(\n `conversation: ${direction} refused replay keeps its state`,\n async () => ({\n ok: refused !== NO_RESULT && (await state()) === before,\n }),\n )) && ok;\n const duplicate = await call(\"conversation.settleReplay\", () =>\n settleReplay(templateId, target, { ...outcome, applied: true }),\n );\n ok =\n (await add(\n `conversation: ${direction} replay settlement is idempotent`,\n async () => ({\n ok: duplicate !== NO_RESULT && (await state()) === before,\n }),\n )) && ok;\n replay = await call(`conversation.${direction}`, () =>\n step(templateId, target, { sessionId: editor.sessionId }),\n );\n if (!isOperations(replay)) {\n ok = false;\n continue;\n }\n const accepted = replay;\n const applied = editor.apply(accepted.operations).applied;\n const settled = await call(\"conversation.settleReplay\", () =>\n settleReplay(templateId, target, {\n ...outcome,\n replayId: accepted.replayId,\n applied,\n }),\n );\n ok =\n (await add(\n `conversation: ${direction} accepted replay changes its state`,\n async () => ({\n ok:\n applied &&\n settled !== NO_RESULT &&\n (await state()) === !before,\n }),\n )) && ok;\n await call(\"conversation.settleReplay\", () =>\n settleReplay(templateId, target, {\n ...outcome,\n replayId: accepted.replayId,\n applied: false,\n }),\n );\n ok =\n (await add(\n `conversation: ${direction} accepted replay settles only once`,\n async () => ({ ok: (await state()) === !before }),\n )) && ok;\n } else {\n ok =\n replay !== NO_RESULT &&\n typeof replay === \"object\" &&\n replay !== null &&\n \"applied\" in replay &&\n typeof replay.applied?.batchId === \"string\" &&\n Number.isInteger(replay.applied.revision) &&\n ok;\n }\n }\n return {\n ok: ok && editor.refused.length === 0,\n detail: ok\n ? \"ok\"\n : \"a replay was malformed, refused or settled incorrectly\",\n };\n });\n }\n if (typeof conversation.clear === \"function\") {\n const clear = conversation.clear;\n await call(\"conversation.clear\", () => clear(templateId));\n const after = await call(\"conversation.list\", () =>\n conversation.list(templateId),\n );\n await add(\"conversation: clear empties it\", () => ({\n ok: Array.isArray(after) && after.length === 0,\n detail: `${Array.isArray(after) ? after.length : \"?\"} message(s) left`,\n }));\n }\n } else {\n await add(\"conversation: off\", () => ({\n ok: true,\n detail: \"skipped: no conversation\",\n }));\n }\n\n // One-shot features.\n if (typeof provider.rewrite === \"function\") {\n const rewrite = provider.rewrite;\n probes.rewrite = (signal) =>\n rewrite(\n {\n ...base(),\n html: \"<p>Thanks for signing up. Here is everything you need to get started.</p>\",\n preset: \"shorten\",\n language: null,\n instruction: null,\n },\n { onText: () => {} },\n signal,\n );\n const result = await call(\"rewrite\", (signal) =>\n rewrite(\n {\n ...base(),\n html: \"<p>Thanks for signing up. Here is everything you need to get started.</p>\",\n preset: \"shorten\",\n language: null,\n instruction: null,\n },\n { onText: () => {} },\n signal,\n ),\n );\n if (result !== NO_RESULT)\n await add(\"rewrite: answers with html\", () => {\n const html = result?.html;\n return {\n ok: typeof html === \"string\" && html.trim() !== \"\",\n detail:\n typeof html === \"string\"\n ? JSON.stringify(html).slice(0, 120)\n : preview(html),\n };\n });\n }\n if (typeof provider.altText === \"function\") {\n const altText = provider.altText;\n const request: AiAltTextRequest = {\n templateId,\n sessionId: editor.sessionId,\n blockId: \"conformance-image\",\n image: {\n url: options.altTextImageUrl ?? AI_CONFORMANCE_ALT_TEXT_IMAGE_URL,\n },\n currentAlt: null,\n linkHref: null,\n nearbyText: \"Explore our email editor.\",\n locale: editor.content.settings.locale ?? \"en\",\n maxLength: 125,\n };\n probes.altText = (signal) => altText(request, signal);\n const result = await call(\"altText\", probes.altText);\n if (result !== NO_RESULT)\n await add(\"altText: answers within maxLength\", () => {\n const alt = (result as { alt?: unknown } | null)?.alt;\n return {\n ok:\n typeof alt === \"string\" &&\n alt.trim() !== \"\" &&\n alt.length <= request.maxLength,\n detail: preview(alt),\n };\n });\n }\n // Each feature must reject an already-aborted call, fast or slow.\n const abortChecks: AiConformanceCheck[] = [];\n for (const feature of declared) {\n const probe = probes[feature]!;\n const controller = new AbortController();\n controller.abort();\n let outcome: unknown = \"resolved\";\n try {\n await withConformanceDeadline(\n `${feature}: abort`,\n timeoutMs,\n probe,\n controller,\n );\n } catch (error) {\n outcome = error;\n }\n const check = await evaluateConformanceCheck(\n `${feature}: stops on abort with an AbortError`,\n () => {\n const ok = outcome instanceof Error && outcome.name === \"AbortError\";\n return {\n ok,\n detail: ok\n ? \"ok\"\n : outcome instanceof ConformanceTimeoutError\n ? `still running after ${timeoutMs} ms`\n : outcome === \"resolved\"\n ? \"resolved instead of rejecting\"\n : describeError(outcome),\n };\n },\n );\n checks.push(check);\n abortChecks.push(check);\n\n const live = new AbortController();\n let cancel: ReturnType<typeof setTimeout> | undefined;\n let liveOutcome: unknown = \"resolved\";\n try {\n await withConformanceDeadline(\n `${feature}: live abort`,\n timeoutMs,\n (signal) => {\n // Invoke with a live signal. The next task lets async setup (including\n // HTTP headers/fetch) start before Stop, unlike the entry-only probe.\n const pending = probe(signal);\n cancel = setTimeout(() => live.abort(), 0);\n return pending;\n },\n live,\n );\n } catch (error) {\n liveOutcome = error;\n } finally {\n clearTimeout(cancel);\n }\n const liveCheck = await evaluateConformanceCheck(\n `${feature}: stops an in-flight call with an AbortError`,\n () => ({\n ok:\n (liveOutcome instanceof Error && liveOutcome.name === \"AbortError\") ||\n (liveOutcome === \"resolved\" && !live.signal.aborted),\n detail:\n liveOutcome instanceof ConformanceTimeoutError\n ? `still running after ${timeoutMs} ms`\n : liveOutcome === \"resolved\"\n ? live.signal.aborted\n ? \"resolved instead of rejecting\"\n : \"completed before cancellation\"\n : liveOutcome instanceof Error &&\n liveOutcome.name === \"AbortError\"\n ? \"ok\"\n : describeError(liveOutcome),\n }),\n );\n checks.push(liveCheck);\n abortChecks.push(liveCheck);\n }\n if (abortChecks.length > 0) {\n await add(\"stops on abort with an AbortError\", () => ({\n ok: abortChecks.every((check) => check.ok),\n detail:\n abortChecks\n .filter((check) => !check.ok)\n .map((check) => `${check.name}: ${check.detail}`)\n .join(\"; \") || \"ok\",\n }));\n }\n\n await add(\"errors are SdkErrors with known codes\", () => {\n const bad = errors\n .map(errorProblem)\n .filter((problem): problem is string => problem !== null);\n return {\n ok: bad.length === 0,\n detail: bad.join(\"; \") || `${errors.length} error(s), all well formed`,\n };\n });\n\n return { ok: checks.every((check) => check.ok), checks };\n}\n","import {\n createHttpAiProviderWithFetch,\n type HttpAiFeatures,\n} from \"../client/http-provider\";\nimport {\n AI_SESSION_HEADER,\n AI_WIRE_HEADER,\n AI_WIRE_VERSION,\n aiRoutePath,\n matchAiRoute,\n} from \"../wire\";\nimport {\n evaluateConformanceCheck,\n tinyPng,\n runAiProviderConformance,\n withConformanceDeadline,\n type AiConformanceCheck,\n type AiConformanceReport,\n} from \"./conformance\";\nimport {\n AI_CONFORMANCE_ALT_TEXT_IMAGE_URL,\n AI_EVAL_TEMPLATES,\n} from \"./eval-set\";\n\nexport interface AiWireConformanceOptions {\n baseUrl: string;\n features?: HttpAiFeatures;\n headers?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n templateId?: string;\n /** Defaults to AI_CONFORMANCE_ALT_TEXT_IMAGE_URL. */\n altTextImageUrl?: string;\n timeoutMs?: number;\n callTimeoutMs?: number;\n}\n\n/**\n * The provider suite over the wire, through `createHttpAiProvider`, plus the\n * wire's own rules: every response carries `Templatical-AI-Wire: 1`, streams\n * are `text/event-stream`, and a bad turn is refused before the stream with a\n * JSON error. For a backend in any language; run it from Node (no CORS).\n */\nexport async function runAiWireConformance(\n options: AiWireConformanceOptions,\n): Promise<AiConformanceReport> {\n const base = options.baseUrl.replace(/\\/+$/, \"\");\n const templateId = options.templateId ?? \"conformance\";\n const timeoutMs = Math.min(\n options.callTimeoutMs ?? options.timeoutMs ?? 5000,\n options.timeoutMs ?? 5000,\n );\n const checks: AiConformanceCheck[] = [];\n const responses: { route: string; response: Response; streamed: boolean }[] =\n [];\n const fetchRequest = fetch;\n const readHeaders = () =>\n withConformanceDeadline(\"request headers\", timeoutMs, async () =>\n typeof options.headers === \"function\"\n ? await options.headers()\n : (options.headers ?? {}),\n );\n const observedFetch: typeof fetch = async (input, init) => {\n const url = new URL(\n typeof input === \"string\"\n ? input\n : input instanceof URL\n ? input.href\n : input.url,\n );\n const parent =\n init?.signal ?? (input instanceof Request ? input.signal : undefined);\n const method =\n init?.method ?? (input instanceof Request ? input.method : \"GET\");\n const route = matchAiRoute(method, url.pathname)?.route ?? url.pathname;\n const controller = new AbortController();\n const stop = () => controller.abort(parent?.reason);\n if (parent?.aborted) stop();\n else parent?.addEventListener(\"abort\", stop, { once: true });\n const release = () => parent?.removeEventListener(\"abort\", stop);\n try {\n const response = await withConformanceDeadline(\n `${route}: fetch`,\n timeoutMs,\n (signal) => {\n signal.throwIfAborted();\n return fetchRequest(input, { ...init, signal });\n },\n controller,\n );\n responses.push({\n route,\n response,\n streamed:\n response.ok &&\n [\"generate\", \"fixFinding\", \"rewriteText\", \"altText\"].includes(route),\n });\n if (!response.body) {\n release();\n return response;\n }\n const reader = response.body.getReader();\n let finished = false;\n const body = new ReadableStream<Uint8Array>(\n {\n async pull(stream) {\n try {\n const { done, value } = await withConformanceDeadline(\n `${route}: body read`,\n timeoutMs,\n () => reader.read(),\n controller,\n );\n if (finished) return;\n if (done) {\n finished = true;\n release();\n stream.close();\n } else stream.enqueue(value);\n } catch (error) {\n release();\n if (!finished) {\n finished = true;\n stream.error(error);\n }\n if (!controller.signal.aborted)\n void reader.cancel(error).catch(() => {});\n }\n },\n async cancel(reason) {\n finished = true;\n release();\n await withConformanceDeadline(\n `${route}: body cancel`,\n timeoutMs,\n () => reader.cancel(reason),\n controller,\n );\n },\n },\n { highWaterMark: 0 },\n );\n return new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n } catch (error) {\n release();\n throw error;\n }\n };\n let own: Record<string, string> = {};\n const headersCheck = await evaluateConformanceCheck(\n \"request headers\",\n async () => {\n own = await readHeaders();\n return { ok: true };\n },\n );\n if (!headersCheck.ok) checks.push(headersCheck);\n\n async function probe(\n route: \"generate\",\n body: unknown,\n ): Promise<Response | null> {\n try {\n const response = await observedFetch(\n `${base}${aiRoutePath(route, { template: templateId })}`,\n {\n method: \"POST\",\n headers: {\n ...own,\n [AI_WIRE_HEADER]: AI_WIRE_VERSION,\n [AI_SESSION_HEADER]: \"conformance-session\",\n ...(body instanceof FormData\n ? {}\n : { \"Content-Type\": \"application/json\" }),\n Accept: \"text/event-stream\",\n },\n body: body instanceof FormData ? body : JSON.stringify(body),\n },\n );\n return response;\n } catch (error) {\n checks.push(\n await evaluateConformanceCheck(`reaches ${route}`, () => ({\n ok: false,\n detail: String(error),\n })),\n );\n return null;\n }\n }\n\n const invalid =\n options.features?.chat === false\n ? null\n : await probe(\"generate\", {\n message: \"\",\n selectedBlockId: null,\n mergeTags: { syntax: \"liquid\", tags: [] },\n content: AI_EVAL_TEMPLATES.welcome,\n revision: null,\n });\n const invalidCheck = await evaluateConformanceCheck(\n \"refuses an invalid turn before the stream with a JSON error\",\n async () => {\n if (options.features?.chat === false)\n return { ok: true, detail: \"skipped: chat is disabled\" };\n const invalidBody: unknown = invalid\n ? await withConformanceDeadline(\n \"generate: JSON body read\",\n timeoutMs,\n () => invalid.json(),\n )\n : null;\n return {\n ok:\n invalid !== null &&\n invalid.status >= 400 &&\n invalid.status < 500 &&\n typeof (invalidBody as { message?: unknown } | null)?.message ===\n \"string\",\n detail: invalid\n ? `${invalid.status} ${JSON.stringify(invalidBody)}`\n : \"no response\",\n };\n },\n );\n if (\n options.features?.chat !== false &&\n options.features?.chatAttachments !== false\n ) {\n const form = new FormData();\n form.set(\"message\", \"Use these references\");\n form.set(\"content\", JSON.stringify(AI_EVAL_TEMPLATES.welcome));\n for (let i = 0; i < 5; i++)\n form.append(\"attachment\", tinyPng(), \"reference.png\");\n const refused = await probe(\"generate\", form);\n checks.push(\n await evaluateConformanceCheck(\n \"refuses more than 4 attachments with 422\",\n async () => {\n const body: unknown = refused\n ? await withConformanceDeadline(\n \"generate: attachment refusal JSON\",\n timeoutMs,\n () => refused.json(),\n )\n : null;\n return {\n ok:\n refused?.status === 422 &&\n typeof (body as { message?: unknown } | null)?.message ===\n \"string\",\n detail: refused\n ? `${refused.status} ${JSON.stringify(body)}`\n : \"no response\",\n };\n },\n ),\n );\n }\n const report = await runAiProviderConformance(\n createHttpAiProviderWithFetch(\n { baseUrl: base, headers: readHeaders, features: options.features },\n observedFetch,\n ),\n {\n templateId,\n altTextImageUrl:\n options.altTextImageUrl ?? AI_CONFORMANCE_ALT_TEXT_IMAGE_URL,\n ...(options.timeoutMs !== undefined\n ? { timeoutMs: options.timeoutMs }\n : {}),\n ...(options.callTimeoutMs !== undefined\n ? { callTimeoutMs: options.callTimeoutMs }\n : {}),\n },\n );\n const responseCheck = await evaluateConformanceCheck(\n \"responses carry Templatical-AI-Wire: 1\",\n () => {\n const missing = responses.filter(\n ({ response }) =>\n response.headers.get(AI_WIRE_HEADER) !== AI_WIRE_VERSION,\n );\n return {\n ok: responses.length > 0 && missing.length === 0,\n detail:\n missing.length === 0\n ? `${responses.length} response(s)`\n : missing\n .map(\n ({ route, response }) =>\n `${route}: ${response.status} ${response.url}`,\n )\n .join(\", \"),\n };\n },\n );\n const streamCheck = await evaluateConformanceCheck(\n \"streams answer as text/event-stream\",\n () => {\n const streams = responses.filter(({ streamed }) => streamed);\n const wrong = streams.filter(\n ({ response }) =>\n !(response.headers.get(\"Content-Type\") ?? \"\").startsWith(\n \"text/event-stream\",\n ),\n );\n return {\n ok: wrong.length === 0,\n detail:\n wrong.length === 0\n ? `${streams.length} streamed response(s)`\n : wrong\n .map(\n ({ route, response }) =>\n `${route}: ${response.headers.get(\"Content-Type\") ?? \"missing Content-Type\"}`,\n )\n .join(\", \"),\n };\n },\n );\n checks.unshift(responseCheck, streamCheck, invalidCheck);\n const all = [...checks, ...report.checks];\n return { ok: all.every((check) => check.ok), checks: all };\n}\n","import type {\n AiAltTextRequest,\n AiMessageBatch,\n AiReplayContext,\n AiReplayOutcome,\n TemplateContent,\n AiChatMessage,\n AiChatRequest,\n AiFixRequest,\n AiMessageKind,\n AiProvider,\n AiRewriteRequest,\n AiTurnHandlers,\n AiTurnResult,\n Operation,\n} from \"@templatical/types\";\nimport {\n applyOperations,\n invertOperations,\n CollaborationConflict,\n safeClone,\n SdkError,\n} from \"@templatical/types\";\nimport { AI_TEMPLATE_CHANGED_MESSAGE, errorFromEvent } from \"../client/errors\";\nimport { AI_ERROR_MESSAGES, type AiWireEvent } from \"../wire\";\n\n/** A wire event to play, or a pause. */\nexport type AiRecordedEvent = AiWireEvent | { delayMs: number };\n/** Events, or events built from the request (to name its blocks). */\nexport type AiRecording<R> =\n AiRecordedEvent[] | ((request: R) => AiRecordedEvent[]);\n\nexport interface AiRecordedTurn {\n /** The first turn whose `match` fits the message plays; one without `match` always fits. */\n match?: RegExp;\n events: AiRecording<AiChatRequest>;\n}\n\n/** What the mock plays. A missing feature is off (`false`). */\nexport interface AiMockScript {\n chat?: false | AiRecordedTurn[];\n fixFinding?: false | AiRecording<AiFixRequest>;\n chatAttachments?: boolean;\n rewrite?: false | AiRecording<AiRewriteRequest>;\n altText?: false | AiRecording<AiAltTextRequest>;\n /** Keep a conversation in memory per template, with Undo and Redo. */\n conversation?: boolean;\n /** A pause before every event, in ms (0 by default). */\n delayMs?: number;\n}\n\ninterface Stored {\n message: AiChatMessage;\n inverse: Operation[];\n after: TemplateContent | null;\n}\n\nfunction stopped(signal: AbortSignal): Error {\n const reason = signal.reason as unknown;\n return reason instanceof Error && reason.name === \"AbortError\"\n ? reason\n : new DOMException(\"The AI request was stopped.\", \"AbortError\");\n}\n\nfunction wait(ms: number, signal: AbortSignal): Promise<void> {\n if (signal.aborted) return Promise.reject(stopped(signal));\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n reject(stopped(signal));\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\nconst failure = () =>\n new SdkError(AI_ERROR_MESSAGES.ai_provider_error, 502, \"ai_provider_error\");\n\n/**\n * An `AiProvider` that plays recorded streams: for integrators' UI tests and\n * the playground's AI scene (no model, no keys). Turns apply their\n * `operations` through the editor's handlers like a real backend's; with\n * `conversation: true` it keeps history in memory, Undo answering the turn's\n * inverse and Redo the guarded inverse of Undo. Replays change history only\n * after settlement.\n */\nexport function createMockAiProvider(script: AiMockScript): AiProvider {\n const delay = script.delayMs ?? 0;\n const conversations = new Map<string, Stored[]>();\n const pending = new Map<\n string,\n {\n templateId: string;\n messageId: string;\n entry: Stored;\n direction: \"undo\" | \"redo\";\n sessionId: string;\n }\n >();\n\n async function play<R>(\n recording: AiRecording<R>,\n request: R,\n signal: AbortSignal,\n each: (event: AiWireEvent) => Promise<void> | void,\n ): Promise<Extract<AiWireEvent, { type: \"done\" }>> {\n const events =\n typeof recording === \"function\" ? recording(request) : recording;\n for (const event of events) {\n await wait(\"type\" in event ? delay : event.delayMs, signal);\n if (!(\"type\" in event)) continue;\n if (event.type === \"error\") throw errorFromEvent(event);\n if (event.type === \"done\") return event;\n await each(event);\n }\n throw failure();\n }\n\n async function turn<\n R extends {\n templateId: string;\n content: TemplateContent;\n attachments?: { name: string; kind: \"image\" | \"pdf\" }[];\n },\n >(\n kind: AiMessageKind,\n userText: string,\n recording: AiRecording<R>,\n request: R,\n on: AiTurnHandlers,\n signal: AbortSignal,\n ): Promise<AiTurnResult> {\n let text = \"\";\n let inverse: Operation[] = [];\n let after = safeClone(request.content);\n const batches: AiMessageBatch[] = [];\n const done = await play(recording, request, signal, async (event) => {\n if (event.type === \"text\") {\n text += event.text;\n on.onText(event.text);\n } else if (event.type === \"operations\") {\n const result = await on.onOperations(safeClone(event.ops));\n if (!result.applied) {\n throw new CollaborationConflict(AI_TEMPLATE_CHANGED_MESSAGE, {\n code: result.code,\n opIndex: result.opIndex,\n });\n }\n // Store the wire snapshot's inverse, as a backend does. The editor may\n // normalize rich text before returning its own local inverse.\n inverse = [...invertOperations(after, event.ops), ...inverse];\n after = applyOperations(after, event.ops);\n batches.push({\n batchId: crypto.randomUUID(),\n revision: null,\n operationCount: event.ops.length,\n });\n } else if (event.type === \"applied\") {\n on.onApplied({\n batchId: event.batchId,\n revision: event.revision,\n operationCount: event.operationCount,\n });\n batches.push({\n batchId: event.batchId,\n revision: event.revision,\n operationCount: event.operationCount,\n });\n }\n });\n let messageId = done.messageId ?? null;\n if (script.conversation) {\n const history = conversations.get(request.templateId) ?? [];\n conversations.set(request.templateId, history);\n const createdAt = new Date().toISOString();\n history.push({\n message: {\n id: crypto.randomUUID(),\n role: \"user\",\n kind,\n text: userText,\n ...(request.attachments?.length\n ? {\n attachments: request.attachments.map(({ name, kind }) => ({\n name,\n kind,\n })),\n }\n : {}),\n batches: [],\n undone: false,\n createdAt,\n },\n inverse: [],\n after: null,\n });\n messageId = crypto.randomUUID();\n history.push({\n message: {\n id: messageId,\n role: \"assistant\",\n kind,\n text,\n batches,\n undone: false,\n createdAt,\n },\n inverse,\n after,\n });\n }\n return { messageId, text };\n }\n\n function stored(templateId: string, messageId: string): Stored {\n const entry = conversations\n .get(templateId)\n ?.find(\n (item) =>\n item.message.id === messageId && item.message.role === \"assistant\",\n );\n if (!entry)\n throw new SdkError(\n \"That message isn't in this conversation.\",\n 404,\n \"not_found\",\n );\n return entry;\n }\n\n return {\n chatAttachments: !!script.chat && script.chatAttachments === true,\n chat: script.chat\n ? (request, on, signal) => {\n const recorded = (script.chat as AiRecordedTurn[]).find(\n (candidate) =>\n !candidate.match || candidate.match.test(request.message),\n );\n if (!recorded) return Promise.reject(failure());\n return turn(\n \"chat\",\n request.message,\n recorded.events,\n request,\n on,\n signal,\n );\n }\n : false,\n fixFinding: script.fixFinding\n ? (request, on, signal) =>\n turn(\n \"fix\",\n `Fix: ${request.finding.message}`,\n script.fixFinding as AiRecording<AiFixRequest>,\n request,\n on,\n signal,\n )\n : false,\n rewrite: script.rewrite\n ? async (request, on, signal) => {\n const done = await play(\n script.rewrite as AiRecording<AiRewriteRequest>,\n request,\n signal,\n (event) => {\n if (event.type === \"text\") on.onText(event.text);\n },\n );\n if (typeof done.html !== \"string\") throw failure();\n return { html: done.html };\n }\n : false,\n altText: script.altText\n ? async (request, signal) => {\n const done = await play(\n script.altText as AiRecording<AiAltTextRequest>,\n request,\n signal,\n () => {},\n );\n if (typeof done.alt !== \"string\") throw failure();\n return { alt: done.alt };\n }\n : false,\n ...(script.conversation\n ? {\n conversation: {\n settleReplay: async (\n templateId: string,\n messageId: string,\n outcome: AiReplayOutcome,\n ) => {\n const replay = pending.get(outcome.replayId);\n if (\n !replay ||\n replay.templateId !== templateId ||\n replay.messageId !== messageId ||\n replay.direction !== outcome.direction ||\n replay.sessionId !== outcome.sessionId\n )\n return;\n pending.delete(outcome.replayId);\n if (outcome.applied)\n replay.entry.message.undone = outcome.direction === \"undo\";\n },\n list: async (templateId: string) =>\n safeClone(\n (conversations.get(templateId) ?? []).map(\n (item) => item.message,\n ),\n ),\n clear: async (templateId: string) => {\n conversations.delete(templateId);\n for (const [id, replay] of pending)\n if (replay.templateId === templateId) pending.delete(id);\n },\n undo: async (\n templateId: string,\n messageId: string,\n context: AiReplayContext,\n ) => {\n const entry = stored(templateId, messageId);\n if (entry.message.undone || entry.inverse.length === 0) {\n throw new CollaborationConflict(\n \"This change is already undone.\",\n { code: \"precondition_failed\" },\n );\n }\n const replayId = crypto.randomUUID();\n pending.set(replayId, {\n templateId,\n messageId,\n entry,\n direction: \"undo\",\n sessionId: context.sessionId,\n });\n return { operations: safeClone(entry.inverse), replayId };\n },\n redo: async (\n templateId: string,\n messageId: string,\n context: AiReplayContext,\n ) => {\n const entry = stored(templateId, messageId);\n if (!entry.message.undone) {\n throw new CollaborationConflict(\"This change isn't undone.\", {\n code: \"precondition_failed\",\n });\n }\n const operations = invertOperations(entry.after!, entry.inverse);\n const replayId = crypto.randomUUID();\n pending.set(replayId, {\n templateId,\n messageId,\n entry,\n direction: \"redo\",\n sessionId: context.sessionId,\n });\n return { operations, replayId };\n },\n },\n }\n : {}),\n };\n}\n","import type { AiChatRequest, TemplateContent } from \"@templatical/types\";\nimport {\n createButtonBlock,\n createParagraphBlock,\n createTitleBlock,\n} from \"@templatical/types\";\nimport { walkBlocks } from \"../server/content-rules\";\nimport type { AiMockScript, AiRecordedEvent } from \"./mock-provider\";\n\nconst text = (value: string): AiRecordedEvent => ({\n type: \"text\",\n text: value,\n});\nconst done: AiRecordedEvent = { type: \"done\", messageId: null };\n\nfunction firstOfType(\n content: TemplateContent,\n type: string,\n): { id: string } | undefined {\n return walkBlocks(content.blocks).find((block) => block.type === type) as\n { id: string } | undefined;\n}\n\nfunction lastTopLevelId(content: TemplateContent): string | null {\n return content.blocks.at(-1)?.id ?? null;\n}\n\n/**\n * Scripted turns for any template: the playground's AI scene and the\n * conformance suite's self-test play them. Chat answers \"dark\", \"title\" and\n * \"fail\" specially and otherwise adds a closing paragraph.\n */\nexport const AI_RECORDINGS: AiMockScript = {\n delayMs: 40,\n chatAttachments: true,\n chat: [\n {\n match: /attached design/i,\n events: (request) => [\n text(\"Built the email from your design.\"),\n {\n type: \"operations\",\n ops: [\n {\n op: \"setContent\",\n content: {\n blocks: [\n createTitleBlock({\n content: \"<p>Built from your design</p>\",\n }),\n createParagraphBlock({\n content: \"<p>A short introduction, as in the image.</p>\",\n }),\n createButtonBlock({\n text: \"Learn more\",\n url: \"https://example.com\",\n }),\n ],\n settings: request.content.settings,\n },\n },\n ],\n },\n text(\" Pictures are placeholders.\"),\n done,\n ],\n },\n {\n match: /fail|error/i,\n events: [\n text(\"Let me try that.\"),\n {\n type: \"error\",\n code: \"ai_provider_error\",\n message: \"AI is unavailable right now. Try again later.\",\n },\n ],\n },\n {\n match: /dark/i,\n events: [\n text(\"Switching the email to a dark background.\"),\n {\n type: \"operations\",\n ops: [\n {\n op: \"updateSettings\",\n set: { backgroundColor: \"#111827\", textColor: \"#f9fafb\" },\n },\n ],\n },\n text(\" Done: a dark background with light text.\"),\n done,\n ],\n },\n {\n match: /title|headline|heading/i,\n events: (request: AiChatRequest) => {\n const title = firstOfType(request.content, \"title\");\n return title\n ? [\n text(\"Updating the title.\"),\n {\n type: \"operations\",\n ops: [\n {\n op: \"updateBlock\",\n blockId: title.id,\n set: { content: \"<p>Hello there</p>\" },\n },\n ],\n },\n text(\" It now reads “Hello there”.\"),\n done,\n ]\n : [text(\"This template has no title to change.\"), done];\n },\n },\n {\n events: (request: AiChatRequest) => [\n text(\"Adding a closing line at the end.\"),\n {\n type: \"operations\",\n ops: [\n {\n op: \"addBlock\",\n block: createParagraphBlock({\n content: \"<p>Thanks for reading. See you next week.</p>\",\n }),\n parent: null,\n after: lastTopLevelId(request.content),\n },\n ],\n },\n text(\" I added it below the last block.\"),\n done,\n ],\n },\n ],\n fixFinding: (request) => {\n const target = request.finding.blockId;\n const block = target\n ? walkBlocks(request.content.blocks).find(\n (candidate) => candidate.id === target,\n )\n : undefined;\n const label =\n request.finding.ruleId === \"a11y.link-vague-text\"\n ? \"Read the March update\"\n : request.finding.ruleId === \"deliverability.link-text-domain-mismatch\"\n ? \"Visit Acme\"\n : null;\n if (\n label &&\n block &&\n (block.type === \"paragraph\" || block.type === \"title\")\n ) {\n return [\n text(\"Updating the link text.\"),\n {\n type: \"operations\",\n ops: [\n {\n op: \"updateBlock\",\n blockId: target as string,\n set: {\n content: (block.content as string).replace(\n /(<a\\b[^>]*>)[\\s\\S]*?(<\\/a>)/i,\n `$1${label}$2`,\n ),\n },\n },\n ],\n },\n text(\" Fixed.\"),\n done,\n ];\n }\n return block && (block.type === \"paragraph\" || block.type === \"title\")\n ? [\n text(\"Shortening that block.\"),\n {\n type: \"operations\",\n ops: [\n {\n op: \"updateBlock\",\n blockId: target as string,\n set: { content: \"<p>A shorter, clearer line.</p>\" },\n },\n ],\n },\n text(\" Fixed.\"),\n done,\n ]\n : [\n text(\"Adding a preheader.\"),\n {\n type: \"operations\",\n ops: [\n {\n op: \"updateSettings\",\n set: { preheaderText: \"Your update from Acme\" },\n },\n ],\n },\n text(\" Fixed.\"),\n done,\n ];\n },\n altText: (request) => [\n { type: \"done\", alt: \"Shop new arrivals\".slice(0, request.maxLength) },\n ],\n rewrite: (request) => {\n const html = request.html.replace(/^(<p[^>]*>)/, \"$1✨ \");\n return [text(\"✨ \"), text(\"Rewritten\"), { type: \"done\", html }];\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AQkFA,MAAa,gBAAgBA;AAC7B,MAAa,oBAET;CACF,SAASC;CACT,OAAOC;CACP,W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"}
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@templatical/ai-kit",
3
3
  "description": "Bring your own AI backend to the Templatical email editor: the wire, an HTTP provider, a server kit with prompts and tools, and a conformance suite",
4
- "version": "0.50.0",
4
+ "version": "0.51.1",
5
5
  "bugs": "https://github.com/templatical/sdk/issues",
6
6
  "dependencies": {
7
- "@templatical/types": "0.50.0"
7
+ "@templatical/types": "0.51.1"
8
8
  },
9
9
  "devDependencies": {
10
+ "@anthropic-ai/sdk": "^1.1.0",
10
11
  "@types/node": "^25.9.9",
12
+ "ai": "^7.0.150",
13
+ "openai": "^7.43.0",
11
14
  "typescript": "^6.0.3",
12
15
  "vitest": "^5.0.5"
13
16
  },
@@ -28,6 +31,18 @@
28
31
  "types": "./dist/testing/index.d.ts",
29
32
  "import": "./dist/testing/index.js"
30
33
  },
34
+ "./adapters/anthropic": {
35
+ "types": "./dist/adapters/anthropic.d.ts",
36
+ "import": "./dist/adapters/anthropic.js"
37
+ },
38
+ "./adapters/openai": {
39
+ "types": "./dist/adapters/openai.d.ts",
40
+ "import": "./dist/adapters/openai.js"
41
+ },
42
+ "./adapters/vercel": {
43
+ "types": "./dist/adapters/vercel.d.ts",
44
+ "import": "./dist/adapters/vercel.js"
45
+ },
31
46
  "./prompts/*": "./prompts/*",
32
47
  "./schema/*": "./schema/*",
33
48
  "./eval/*": "./eval/*"
@@ -51,6 +66,22 @@
51
66
  ],
52
67
  "license": "MIT",
53
68
  "module": "./dist/index.js",
69
+ "peerDependencies": {
70
+ "@anthropic-ai/sdk": "^1",
71
+ "ai": "^7",
72
+ "openai": "^7"
73
+ },
74
+ "peerDependenciesMeta": {
75
+ "@anthropic-ai/sdk": {
76
+ "optional": true
77
+ },
78
+ "ai": {
79
+ "optional": true
80
+ },
81
+ "openai": {
82
+ "optional": true
83
+ }
84
+ },
54
85
  "publishConfig": {
55
86
  "access": "public"
56
87
  },