@templatical/ai-kit 0.0.0-stage → 0.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +63 -2
- package/dist/adapters/anthropic.d.ts +31 -0
- package/dist/adapters/anthropic.js +139 -0
- package/dist/adapters/anthropic.js.map +1 -0
- package/dist/adapters/openai.d.ts +29 -0
- package/dist/adapters/openai.js +131 -0
- package/dist/adapters/openai.js.map +1 -0
- package/dist/adapters/vercel.d.ts +17 -0
- package/dist/adapters/vercel.js +137 -0
- package/dist/adapters/vercel.js.map +1 -0
- package/dist/checks-DvLD3892.js +656 -0
- package/dist/checks-DvLD3892.js.map +1 -0
- package/dist/client/index.d.ts +24 -0
- package/dist/client/index.js +3 -0
- package/dist/errors-DWshWx18.js +55 -0
- package/dist/errors-DWshWx18.js.map +1 -0
- package/dist/http-provider-Cb41NXd5.d.ts +22 -0
- package/dist/http-provider-Cx2ZcLRB.js +278 -0
- package/dist/http-provider-Cx2ZcLRB.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/model-xJvGTfLB.d.ts +84 -0
- package/dist/server/index.d.ts +313 -0
- package/dist/server/index.js +355 -0
- package/dist/server/index.js.map +1 -0
- package/dist/shared-BuhccY6c.js +61 -0
- package/dist/shared-BuhccY6c.js.map +1 -0
- package/dist/testing/index.d.ts +277 -0
- package/dist/testing/index.js +2445 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/wire-B6AF_UGT.js +132 -0
- package/dist/wire-B6AF_UGT.js.map +1 -0
- package/dist/wire-Cv7Fk-7n.d.ts +136 -0
- package/eval/MANIFEST.sha256 +23 -0
- package/eval/cases.json +287 -0
- package/eval/designs/newsletter.png +0 -0
- package/eval/designs/one-page.pdf +0 -0
- package/eval/designs/promo.png +0 -0
- package/eval/domain-mismatch.json +25 -0
- package/eval/empty.json +11 -0
- package/eval/images/banner.png +0 -0
- package/eval/images/chart.png +0 -0
- package/eval/images/divider.png +0 -0
- package/eval/images/probe.png +0 -0
- package/eval/images/product.png +0 -0
- package/eval/injection.json +13 -0
- package/eval/low-text.json +41 -0
- package/eval/promo.json +12 -0
- package/eval/sources/banner.html +16 -0
- package/eval/sources/chart.html +16 -0
- package/eval/sources/divider.html +16 -0
- package/eval/sources/newsletter.html +16 -0
- package/eval/sources/one-page.html +16 -0
- package/eval/sources/probe.html +16 -0
- package/eval/sources/product.html +16 -0
- package/eval/sources/promo.html +16 -0
- package/eval/welcome.json +12 -0
- package/package.json +99 -3
- package/prompts/alt-text.md +7 -0
- package/prompts/chat-attachments.md +1 -0
- package/prompts/chat.md +2 -0
- package/prompts/editing.md +10 -0
- package/prompts/fix-rules.json +9 -0
- package/prompts/fix.md +8 -0
- package/prompts/operations.md +127 -0
- package/prompts/rewrite-presets.json +8 -0
- package/prompts/rewrite.md +4 -0
- package/schema/template-content.schema.json +1884 -0
|
@@ -0,0 +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,W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"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
//#region src/wire.ts
|
|
2
|
+
/** Sent as {@link AI_WIRE_HEADER} on every request and response. */
|
|
3
|
+
const AI_WIRE_VERSION = "1";
|
|
4
|
+
const AI_WIRE_HEADER = "Templatical-AI-Wire";
|
|
5
|
+
/** The editor session, on turns, Undo, Redo and replay settlement. */
|
|
6
|
+
const AI_SESSION_HEADER = "X-Templatical-Session";
|
|
7
|
+
/** The wire's routes (A1 §4). */
|
|
8
|
+
const AI_ROUTES = {
|
|
9
|
+
generate: {
|
|
10
|
+
method: "POST",
|
|
11
|
+
path: "/templates/{template}/ai/generate"
|
|
12
|
+
},
|
|
13
|
+
fixFinding: {
|
|
14
|
+
method: "POST",
|
|
15
|
+
path: "/templates/{template}/ai/fix-finding"
|
|
16
|
+
},
|
|
17
|
+
rewriteText: {
|
|
18
|
+
method: "POST",
|
|
19
|
+
path: "/templates/{template}/ai/rewrite-text"
|
|
20
|
+
},
|
|
21
|
+
altText: {
|
|
22
|
+
method: "POST",
|
|
23
|
+
path: "/templates/{template}/ai/alt-text"
|
|
24
|
+
},
|
|
25
|
+
conversationMessages: {
|
|
26
|
+
method: "GET",
|
|
27
|
+
path: "/templates/{template}/ai/conversation-messages"
|
|
28
|
+
},
|
|
29
|
+
undoMessage: {
|
|
30
|
+
method: "POST",
|
|
31
|
+
path: "/templates/{template}/ai/messages/{message}/undo"
|
|
32
|
+
},
|
|
33
|
+
redoMessage: {
|
|
34
|
+
method: "POST",
|
|
35
|
+
path: "/templates/{template}/ai/messages/{message}/redo"
|
|
36
|
+
},
|
|
37
|
+
settleReplay: {
|
|
38
|
+
method: "POST",
|
|
39
|
+
path: "/templates/{template}/ai/messages/{message}/replays/{replayId}"
|
|
40
|
+
},
|
|
41
|
+
clearConversation: {
|
|
42
|
+
method: "DELETE",
|
|
43
|
+
path: "/templates/{template}/ai/conversation"
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
/** A route's path with its ids filled in, each URI-encoded. */
|
|
47
|
+
function aiRoutePath(route, params) {
|
|
48
|
+
return AI_ROUTES[route].path.replace(/\{(template|message|replayId)\}/g, (_, key) => encodeURIComponent(params[key] ?? ""));
|
|
49
|
+
}
|
|
50
|
+
const MATCHERS = Object.keys(AI_ROUTES).map((route) => ({
|
|
51
|
+
route,
|
|
52
|
+
method: AI_ROUTES[route].method,
|
|
53
|
+
pattern: new RegExp(`${AI_ROUTES[route].path.replace("{template}", "([^/]+)").replace("{message}", "([^/]+)").replace("{replayId}", "([^/]+)")}$`),
|
|
54
|
+
hasMessage: AI_ROUTES[route].path.includes("{message}")
|
|
55
|
+
}));
|
|
56
|
+
/**
|
|
57
|
+
* Which route a request is, whatever path the backend mounts the wire under:
|
|
58
|
+
* the route must end the pathname. `null` for anything else.
|
|
59
|
+
*/
|
|
60
|
+
function matchAiRoute(method, pathname) {
|
|
61
|
+
const upper = method.toUpperCase();
|
|
62
|
+
for (const matcher of MATCHERS) {
|
|
63
|
+
if (matcher.method !== upper) continue;
|
|
64
|
+
const found = matcher.pattern.exec(pathname);
|
|
65
|
+
if (!found) continue;
|
|
66
|
+
try {
|
|
67
|
+
return {
|
|
68
|
+
route: matcher.route,
|
|
69
|
+
template: decodeURIComponent(found[1]),
|
|
70
|
+
message: matcher.hasMessage ? decodeURIComponent(found[2]) : null,
|
|
71
|
+
...matcher.route === "settleReplay" ? { replayId: decodeURIComponent(found[3]) } : {}
|
|
72
|
+
};
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (error instanceof URIError) return null;
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
/** The multipart fields of generate; mergeTags and content are JSON strings. */
|
|
81
|
+
const AI_GENERATE_FIELDS = {
|
|
82
|
+
message: "message",
|
|
83
|
+
selectedBlockId: "selectedBlockId",
|
|
84
|
+
mergeTags: "mergeTags",
|
|
85
|
+
content: "content",
|
|
86
|
+
revision: "revision",
|
|
87
|
+
attachment: "attachment"
|
|
88
|
+
};
|
|
89
|
+
/** The HTTP status each code answers with before a stream starts. */
|
|
90
|
+
const AI_ERROR_STATUS = {
|
|
91
|
+
feature_unavailable: 403,
|
|
92
|
+
limit_reached: 403,
|
|
93
|
+
rate_limited: 429,
|
|
94
|
+
ai_turn_running: 409,
|
|
95
|
+
content_too_large: 413,
|
|
96
|
+
ai_output_invalid: 422,
|
|
97
|
+
ai_provider_error: 502,
|
|
98
|
+
not_found: 404,
|
|
99
|
+
block_exists: 409,
|
|
100
|
+
block_missing: 409,
|
|
101
|
+
anchor_missing: 409,
|
|
102
|
+
precondition_failed: 409,
|
|
103
|
+
lock_held: 409,
|
|
104
|
+
locked_by_others: 409,
|
|
105
|
+
invalid_operation: 422
|
|
106
|
+
};
|
|
107
|
+
/** Default messages, user-presentable, for the AI codes. */
|
|
108
|
+
const AI_ERROR_MESSAGES = {
|
|
109
|
+
feature_unavailable: "This AI feature isn't available.",
|
|
110
|
+
limit_reached: "You've used up your AI allowance for now.",
|
|
111
|
+
rate_limited: "Too many AI requests. Try again in a moment.",
|
|
112
|
+
ai_turn_running: "An AI request is already running on this template.",
|
|
113
|
+
content_too_large: "This template is too large for AI.",
|
|
114
|
+
ai_output_invalid: "The AI couldn't produce a valid result. Try again.",
|
|
115
|
+
ai_provider_error: "AI is unavailable right now. Try again later."
|
|
116
|
+
};
|
|
117
|
+
/** The wire's request limits (S7 Y3), shared by the client and the kit. */
|
|
118
|
+
const AI_LIMITS = {
|
|
119
|
+
contentMaxBytes: 2e5,
|
|
120
|
+
messageMaxChars: 4e3,
|
|
121
|
+
attachmentsMax: 4,
|
|
122
|
+
attachmentMaxBytes: 5242880,
|
|
123
|
+
attachmentsTotalMaxBytes: 10485760,
|
|
124
|
+
rewriteHtmlMaxChars: 2e4,
|
|
125
|
+
instructionMaxChars: 500,
|
|
126
|
+
mergeTagsMax: 500,
|
|
127
|
+
opsPerBatchMax: 100
|
|
128
|
+
};
|
|
129
|
+
//#endregion
|
|
130
|
+
export { AI_ROUTES as a, AI_WIRE_VERSION as c, AI_LIMITS as i, aiRoutePath as l, AI_ERROR_STATUS as n, AI_SESSION_HEADER as o, AI_GENERATE_FIELDS as r, AI_WIRE_HEADER as s, AI_ERROR_MESSAGES as t, matchAiRoute as u };
|
|
131
|
+
|
|
132
|
+
//# sourceMappingURL=wire-B6AF_UGT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire-B6AF_UGT.js","names":[],"sources":["../src/wire.ts"],"sourcesContent":["import type {\n AiAppliedBatch,\n AiAltTextRequest,\n AiChatMessage,\n AiChatAttachment,\n AiErrorCode,\n AiFixFinding,\n AiMergeTags,\n AiRewritePreset,\n Operation,\n TemplateContent,\n} from \"@templatical/types\";\n\n/** Sent as {@link AI_WIRE_HEADER} on every request and response. */\nexport const AI_WIRE_VERSION = \"1\";\nexport const AI_WIRE_HEADER = \"Templatical-AI-Wire\";\n/** The editor session, on turns, Undo, Redo and replay settlement. */\nexport const AI_SESSION_HEADER = \"X-Templatical-Session\";\n\nexport type AiRouteName =\n | \"generate\"\n | \"fixFinding\"\n | \"rewriteText\"\n | \"altText\"\n | \"conversationMessages\"\n | \"undoMessage\"\n | \"redoMessage\"\n | \"clearConversation\"\n | \"settleReplay\";\n\nexport interface AiRoute {\n method: \"GET\" | \"POST\" | \"DELETE\";\n /** Relative to the base URL the integrator chooses. */\n path: string;\n}\n\n/** The wire's routes (A1 §4). */\nexport const AI_ROUTES: Readonly<Record<AiRouteName, AiRoute>> = {\n generate: { method: \"POST\", path: \"/templates/{template}/ai/generate\" },\n fixFinding: { method: \"POST\", path: \"/templates/{template}/ai/fix-finding\" },\n rewriteText: {\n method: \"POST\",\n path: \"/templates/{template}/ai/rewrite-text\",\n },\n altText: { method: \"POST\", path: \"/templates/{template}/ai/alt-text\" },\n conversationMessages: {\n method: \"GET\",\n path: \"/templates/{template}/ai/conversation-messages\",\n },\n undoMessage: {\n method: \"POST\",\n path: \"/templates/{template}/ai/messages/{message}/undo\",\n },\n redoMessage: {\n method: \"POST\",\n path: \"/templates/{template}/ai/messages/{message}/redo\",\n },\n settleReplay: {\n method: \"POST\",\n path: \"/templates/{template}/ai/messages/{message}/replays/{replayId}\",\n },\n clearConversation: {\n method: \"DELETE\",\n path: \"/templates/{template}/ai/conversation\",\n },\n};\n\n/** A route's path with its ids filled in, each URI-encoded. */\nexport function aiRoutePath(\n route: AiRouteName,\n params: { template: string; message?: string; replayId?: string },\n): string {\n return AI_ROUTES[route].path.replace(\n /\\{(template|message|replayId)\\}/g,\n (_, key: \"template\" | \"message\" | \"replayId\") =>\n encodeURIComponent(params[key] ?? \"\"),\n );\n}\n\nexport interface AiRouteMatch {\n route: AiRouteName;\n template: string;\n message: string | null;\n replayId?: string;\n}\n\nconst MATCHERS = (Object.keys(AI_ROUTES) as AiRouteName[]).map((route) => ({\n route,\n method: AI_ROUTES[route].method,\n pattern: new RegExp(\n `${AI_ROUTES[route].path\n .replace(\"{template}\", \"([^/]+)\")\n .replace(\"{message}\", \"([^/]+)\")\n .replace(\"{replayId}\", \"([^/]+)\")}$`,\n ),\n hasMessage: AI_ROUTES[route].path.includes(\"{message}\"),\n}));\n\n/**\n * Which route a request is, whatever path the backend mounts the wire under:\n * the route must end the pathname. `null` for anything else.\n */\nexport function matchAiRoute(\n method: string,\n pathname: string,\n): AiRouteMatch | null {\n const upper = method.toUpperCase();\n for (const matcher of MATCHERS) {\n if (matcher.method !== upper) continue;\n const found = matcher.pattern.exec(pathname);\n if (!found) continue;\n try {\n return {\n route: matcher.route,\n template: decodeURIComponent(found[1]),\n message: matcher.hasMessage ? decodeURIComponent(found[2]) : null,\n ...(matcher.route === \"settleReplay\"\n ? { replayId: decodeURIComponent(found[3]) }\n : {}),\n };\n } catch (error) {\n if (error instanceof URIError) return null;\n throw error;\n }\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Server-sent events: `data: {json}` lines, `: ping` comments\n// ---------------------------------------------------------------------------\n\n/** A chunk of the reply (chat, fix) or of the rewritten text. */\nexport interface AiTextEvent {\n type: \"text\";\n text: string;\n}\n\n/** Operations for the editor to apply (new in A1). */\nexport interface AiOperationsEvent {\n type: \"operations\";\n ops: Operation[];\n}\n\n/** A batch the backend applied to its copy of the template. */\nexport interface AiAppliedEvent extends AiAppliedBatch {\n type: \"applied\";\n}\n\n/** The last event of a successful stream: turns require `messageId`, or `null`\n * without history; rewrite requires `html`.\n */\nexport type AiDoneEvent = { type: \"done\" } & (\n | { messageId: string | null; html?: never; alt?: never }\n | { html: string; messageId?: never; alt?: never }\n | { alt: string; messageId?: never; html?: never }\n);\n\n/** The last event of a failed stream; other fields are the error's details. */\nexport interface AiErrorEvent {\n type: \"error\";\n code: string;\n message: string;\n [detail: string]: unknown;\n}\n\nexport type AiWireEvent =\n AiTextEvent | AiOperationsEvent | AiAppliedEvent | AiDoneEvent | AiErrorEvent;\n\n// ---------------------------------------------------------------------------\n// Request bodies\n// ---------------------------------------------------------------------------\n\nexport type AiAltTextBody = Omit<AiAltTextRequest, \"templateId\" | \"sessionId\">;\n\nexport interface AiGenerateBody {\n attachments: AiChatAttachment[];\n message: string;\n selectedBlockId: string | null;\n mergeTags: AiMergeTags;\n content: TemplateContent;\n revision: number | null;\n}\n\nexport interface AiFixFindingBody {\n finding: AiFixFinding;\n mergeTags: AiMergeTags;\n content: TemplateContent;\n revision: number | null;\n}\n\nexport interface AiRewriteBody {\n html: string;\n preset: AiRewritePreset | null;\n language: string | null;\n instruction: string | null;\n mergeTags: AiMergeTags;\n}\n\n/** The multipart fields of generate; mergeTags and content are JSON strings. */\nexport const AI_GENERATE_FIELDS = {\n message: \"message\",\n selectedBlockId: \"selectedBlockId\",\n mergeTags: \"mergeTags\",\n content: \"content\",\n revision: \"revision\",\n attachment: \"attachment\",\n} as const;\n\n/** `GET …/conversation-messages`. */\nexport interface AiConversationResponse {\n data: AiChatMessage[];\n}\n\n/** `POST …/messages/{m}/undo` and `…/redo`. */\nexport interface AiReplayResponse {\n data:\n | { operations: Operation[]; replayId: string }\n | { batchId: string; revision: number };\n}\n\n// ---------------------------------------------------------------------------\n// Errors and limits\n// ---------------------------------------------------------------------------\n\n/** The HTTP status each code answers with before a stream starts. */\nexport const AI_ERROR_STATUS: Readonly<Record<string, number>> = {\n feature_unavailable: 403,\n limit_reached: 403,\n rate_limited: 429,\n ai_turn_running: 409,\n content_too_large: 413,\n ai_output_invalid: 422,\n ai_provider_error: 502,\n not_found: 404,\n block_exists: 409,\n block_missing: 409,\n anchor_missing: 409,\n precondition_failed: 409,\n lock_held: 409,\n locked_by_others: 409,\n invalid_operation: 422,\n};\n\n/** Default messages, user-presentable, for the AI codes. */\nexport const AI_ERROR_MESSAGES: Readonly<Record<AiErrorCode, string>> = {\n feature_unavailable: \"This AI feature isn't available.\",\n limit_reached: \"You've used up your AI allowance for now.\",\n rate_limited: \"Too many AI requests. Try again in a moment.\",\n ai_turn_running: \"An AI request is already running on this template.\",\n content_too_large: \"This template is too large for AI.\",\n ai_output_invalid: \"The AI couldn't produce a valid result. Try again.\",\n ai_provider_error: \"AI is unavailable right now. Try again later.\",\n};\n\n/** The wire's request limits (S7 Y3), shared by the client and the kit. */\nexport const AI_LIMITS = {\n contentMaxBytes: 200_000,\n messageMaxChars: 4000,\n attachmentsMax: 4,\n attachmentMaxBytes: 5 * 1024 * 1024,\n attachmentsTotalMaxBytes: 10 * 1024 * 1024,\n rewriteHtmlMaxChars: 20_000,\n instructionMaxChars: 500,\n mergeTagsMax: 500,\n opsPerBatchMax: 100,\n} as const;\n"],"mappings":";;AAcA,MAAa,kBAAkB;AAC/B,MAAa,iBAAiB;;AAE9B,MAAa,oBAAoB;;AAoBjC,MAAa,YAAoD;CAC/D,UAAU;EAAE,QAAQ;EAAQ,MAAM;CAAoC;CACtE,YAAY;EAAE,QAAQ;EAAQ,MAAM;CAAuC;CAC3E,aAAa;EACX,QAAQ;EACR,MAAM;CACR;CACA,SAAS;EAAE,QAAQ;EAAQ,MAAM;CAAoC;CACrE,sBAAsB;EACpB,QAAQ;EACR,MAAM;CACR;CACA,aAAa;EACX,QAAQ;EACR,MAAM;CACR;CACA,aAAa;EACX,QAAQ;EACR,MAAM;CACR;CACA,cAAc;EACZ,QAAQ;EACR,MAAM;CACR;CACA,mBAAmB;EACjB,QAAQ;EACR,MAAM;CACR;AACF;;AAGA,SAAgB,YACd,OACA,QACQ;CACR,OAAO,UAAU,MAAM,CAAC,KAAK,QAC3B,qCACC,GAAG,QACF,mBAAmB,OAAO,QAAQ,EAAE,CACxC;AACF;AASA,MAAM,WAAY,OAAO,KAAK,SAAS,CAAC,CAAmB,KAAK,WAAW;CACzE;CACA,QAAQ,UAAU,MAAM,CAAC;CACzB,SAAS,IAAI,OACX,GAAG,UAAU,MAAM,CAAC,KACjB,QAAQ,cAAc,SAAS,CAAC,CAChC,QAAQ,aAAa,SAAS,CAAC,CAC/B,QAAQ,cAAc,SAAS,EAAE,EACtC;CACA,YAAY,UAAU,MAAM,CAAC,KAAK,SAAS,WAAW;AACxD,EAAE;;;;;AAMF,SAAgB,aACd,QACA,UACqB;CACrB,MAAM,QAAQ,OAAO,YAAY;CACjC,KAAK,MAAM,WAAW,UAAU;EAC9B,IAAI,QAAQ,WAAW,OAAO;EAC9B,MAAM,QAAQ,QAAQ,QAAQ,KAAK,QAAQ;EAC3C,IAAI,CAAC,OAAO;EACZ,IAAI;GACF,OAAO;IACL,OAAO,QAAQ;IACf,UAAU,mBAAmB,MAAM,EAAE;IACrC,SAAS,QAAQ,aAAa,mBAAmB,MAAM,EAAE,IAAI;IAC7D,GAAI,QAAQ,UAAU,iBAClB,EAAE,UAAU,mBAAmB,MAAM,EAAE,EAAE,IACzC,CAAC;GACP;EACF,SAAS,OAAO;GACd,IAAI,iBAAiB,UAAU,OAAO;GACtC,MAAM;EACR;CACF;CACA,OAAO;AACT;;AA0EA,MAAa,qBAAqB;CAChC,SAAS;CACT,iBAAiB;CACjB,WAAW;CACX,SAAS;CACT,UAAU;CACV,YAAY;AACd;;AAmBA,MAAa,kBAAoD;CAC/D,qBAAqB;CACrB,eAAe;CACf,cAAc;CACd,iBAAiB;CACjB,mBAAmB;CACnB,mBAAmB;CACnB,mBAAmB;CACnB,WAAW;CACX,cAAc;CACd,eAAe;CACf,gBAAgB;CAChB,qBAAqB;CACrB,WAAW;CACX,kBAAkB;CAClB,mBAAmB;AACrB;;AAGA,MAAa,oBAA2D;CACtE,qBAAqB;CACrB,eAAe;CACf,cAAc;CACd,iBAAiB;CACjB,mBAAmB;CACnB,mBAAmB;CACnB,mBAAmB;AACrB;;AAGA,MAAa,YAAY;CACvB,iBAAiB;CACjB,iBAAiB;CACjB,gBAAgB;CAChB,oBAAoB;CACpB,0BAA0B;CAC1B,qBAAqB;CACrB,qBAAqB;CACrB,cAAc;CACd,gBAAgB;AAClB"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { AiAltTextRequest, AiAppliedBatch, AiChatAttachment, AiChatMessage, AiErrorCode, AiFixFinding, AiMergeTags, AiRewritePreset, Operation, TemplateContent } from "@templatical/types";
|
|
2
|
+
//#region src/wire.d.ts
|
|
3
|
+
/** Sent as {@link AI_WIRE_HEADER} on every request and response. */
|
|
4
|
+
declare const AI_WIRE_VERSION = "1";
|
|
5
|
+
declare const AI_WIRE_HEADER = "Templatical-AI-Wire";
|
|
6
|
+
/** The editor session, on turns, Undo, Redo and replay settlement. */
|
|
7
|
+
declare const AI_SESSION_HEADER = "X-Templatical-Session";
|
|
8
|
+
type AiRouteName = "generate" | "fixFinding" | "rewriteText" | "altText" | "conversationMessages" | "undoMessage" | "redoMessage" | "clearConversation" | "settleReplay";
|
|
9
|
+
interface AiRoute {
|
|
10
|
+
method: "GET" | "POST" | "DELETE";
|
|
11
|
+
/** Relative to the base URL the integrator chooses. */
|
|
12
|
+
path: string;
|
|
13
|
+
}
|
|
14
|
+
/** The wire's routes (A1 §4). */
|
|
15
|
+
declare const AI_ROUTES: Readonly<Record<AiRouteName, AiRoute>>;
|
|
16
|
+
/** A route's path with its ids filled in, each URI-encoded. */
|
|
17
|
+
declare function aiRoutePath(route: AiRouteName, params: {
|
|
18
|
+
template: string;
|
|
19
|
+
message?: string;
|
|
20
|
+
replayId?: string;
|
|
21
|
+
}): string;
|
|
22
|
+
interface AiRouteMatch {
|
|
23
|
+
route: AiRouteName;
|
|
24
|
+
template: string;
|
|
25
|
+
message: string | null;
|
|
26
|
+
replayId?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Which route a request is, whatever path the backend mounts the wire under:
|
|
30
|
+
* the route must end the pathname. `null` for anything else.
|
|
31
|
+
*/
|
|
32
|
+
declare function matchAiRoute(method: string, pathname: string): AiRouteMatch | null;
|
|
33
|
+
/** A chunk of the reply (chat, fix) or of the rewritten text. */
|
|
34
|
+
interface AiTextEvent {
|
|
35
|
+
type: "text";
|
|
36
|
+
text: string;
|
|
37
|
+
}
|
|
38
|
+
/** Operations for the editor to apply (new in A1). */
|
|
39
|
+
interface AiOperationsEvent {
|
|
40
|
+
type: "operations";
|
|
41
|
+
ops: Operation[];
|
|
42
|
+
}
|
|
43
|
+
/** A batch the backend applied to its copy of the template. */
|
|
44
|
+
interface AiAppliedEvent extends AiAppliedBatch {
|
|
45
|
+
type: "applied";
|
|
46
|
+
}
|
|
47
|
+
/** The last event of a successful stream: turns require `messageId`, or `null`
|
|
48
|
+
* without history; rewrite requires `html`.
|
|
49
|
+
*/
|
|
50
|
+
type AiDoneEvent = {
|
|
51
|
+
type: "done";
|
|
52
|
+
} & ({
|
|
53
|
+
messageId: string | null;
|
|
54
|
+
html?: never;
|
|
55
|
+
alt?: never;
|
|
56
|
+
} | {
|
|
57
|
+
html: string;
|
|
58
|
+
messageId?: never;
|
|
59
|
+
alt?: never;
|
|
60
|
+
} | {
|
|
61
|
+
alt: string;
|
|
62
|
+
messageId?: never;
|
|
63
|
+
html?: never;
|
|
64
|
+
});
|
|
65
|
+
/** The last event of a failed stream; other fields are the error's details. */
|
|
66
|
+
interface AiErrorEvent {
|
|
67
|
+
type: "error";
|
|
68
|
+
code: string;
|
|
69
|
+
message: string;
|
|
70
|
+
[detail: string]: unknown;
|
|
71
|
+
}
|
|
72
|
+
type AiWireEvent = AiTextEvent | AiOperationsEvent | AiAppliedEvent | AiDoneEvent | AiErrorEvent;
|
|
73
|
+
type AiAltTextBody = Omit<AiAltTextRequest, "templateId" | "sessionId">;
|
|
74
|
+
interface AiGenerateBody {
|
|
75
|
+
attachments: AiChatAttachment[];
|
|
76
|
+
message: string;
|
|
77
|
+
selectedBlockId: string | null;
|
|
78
|
+
mergeTags: AiMergeTags;
|
|
79
|
+
content: TemplateContent;
|
|
80
|
+
revision: number | null;
|
|
81
|
+
}
|
|
82
|
+
interface AiFixFindingBody {
|
|
83
|
+
finding: AiFixFinding;
|
|
84
|
+
mergeTags: AiMergeTags;
|
|
85
|
+
content: TemplateContent;
|
|
86
|
+
revision: number | null;
|
|
87
|
+
}
|
|
88
|
+
interface AiRewriteBody {
|
|
89
|
+
html: string;
|
|
90
|
+
preset: AiRewritePreset | null;
|
|
91
|
+
language: string | null;
|
|
92
|
+
instruction: string | null;
|
|
93
|
+
mergeTags: AiMergeTags;
|
|
94
|
+
}
|
|
95
|
+
/** The multipart fields of generate; mergeTags and content are JSON strings. */
|
|
96
|
+
declare const AI_GENERATE_FIELDS: {
|
|
97
|
+
readonly message: "message";
|
|
98
|
+
readonly selectedBlockId: "selectedBlockId";
|
|
99
|
+
readonly mergeTags: "mergeTags";
|
|
100
|
+
readonly content: "content";
|
|
101
|
+
readonly revision: "revision";
|
|
102
|
+
readonly attachment: "attachment";
|
|
103
|
+
};
|
|
104
|
+
/** `GET …/conversation-messages`. */
|
|
105
|
+
interface AiConversationResponse {
|
|
106
|
+
data: AiChatMessage[];
|
|
107
|
+
}
|
|
108
|
+
/** `POST …/messages/{m}/undo` and `…/redo`. */
|
|
109
|
+
interface AiReplayResponse {
|
|
110
|
+
data: {
|
|
111
|
+
operations: Operation[];
|
|
112
|
+
replayId: string;
|
|
113
|
+
} | {
|
|
114
|
+
batchId: string;
|
|
115
|
+
revision: number;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/** The HTTP status each code answers with before a stream starts. */
|
|
119
|
+
declare const AI_ERROR_STATUS: Readonly<Record<string, number>>;
|
|
120
|
+
/** Default messages, user-presentable, for the AI codes. */
|
|
121
|
+
declare const AI_ERROR_MESSAGES: Readonly<Record<AiErrorCode, string>>;
|
|
122
|
+
/** The wire's request limits (S7 Y3), shared by the client and the kit. */
|
|
123
|
+
declare const AI_LIMITS: {
|
|
124
|
+
readonly contentMaxBytes: 200000;
|
|
125
|
+
readonly messageMaxChars: 4000;
|
|
126
|
+
readonly attachmentsMax: 4;
|
|
127
|
+
readonly attachmentMaxBytes: number;
|
|
128
|
+
readonly attachmentsTotalMaxBytes: number;
|
|
129
|
+
readonly rewriteHtmlMaxChars: 20000;
|
|
130
|
+
readonly instructionMaxChars: 500;
|
|
131
|
+
readonly mergeTagsMax: 500;
|
|
132
|
+
readonly opsPerBatchMax: 100;
|
|
133
|
+
};
|
|
134
|
+
//#endregion
|
|
135
|
+
export { AiWireEvent as C, AiTextEvent as S, matchAiRoute as T, AiReplayResponse as _, AI_ROUTES as a, AiRouteMatch as b, AI_WIRE_VERSION as c, AiConversationResponse as d, AiDoneEvent as f, AiOperationsEvent as g, AiGenerateBody as h, AI_LIMITS as i, AiAltTextBody as l, AiFixFindingBody as m, AI_ERROR_STATUS as n, AI_SESSION_HEADER as o, AiErrorEvent as p, AI_GENERATE_FIELDS as r, AI_WIRE_HEADER as s, AI_ERROR_MESSAGES as t, AiAppliedEvent as u, AiRewriteBody as v, aiRoutePath as w, AiRouteName as x, AiRoute as y };
|
|
136
|
+
//# sourceMappingURL=wire-Cv7Fk-7n.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
d9fa5fa34ea101bcd4278a4a1712160ea6e4203022180805a2223484ca3a452b cases.json
|
|
2
|
+
a3e737c598648b59ff371ed6592469e3a2c73b993827774896958a7dbf022683 designs/newsletter.png
|
|
3
|
+
1e8dbd1dba68734a90178e6c0f93ba9fad858227962e0bc97a77b73ec11a6899 designs/one-page.pdf
|
|
4
|
+
254a0886cd72d32ea24befff79d9a33d2b537338b2134f0433635dbfa8b20910 designs/promo.png
|
|
5
|
+
1c87fc3499ca75e5aeda9d14d8a15f30be6dd8a00f8d6c6eecfa0193914ddbe0 domain-mismatch.json
|
|
6
|
+
7824655ea8ad291eb69879485950fe4ea2b6ea4da475dd214a0298c467336b68 empty.json
|
|
7
|
+
16d13c32227038bf0a0f8574018fb8b83069442ce82b00225d77ff1083e85b90 images/banner.png
|
|
8
|
+
e4bce1af23bc6d78de1ae8c250e1c861df64ea66927e1122abfe93fd4ebea5ef images/chart.png
|
|
9
|
+
7d0cd002d852692ed164e4840a35543bd6eec2454ec94677629ad18c4e366d3b images/divider.png
|
|
10
|
+
86d3f74dc4aa955f9dcb9d391287cccbed2cd9bf7de5eae536a42af5c2233942 images/probe.png
|
|
11
|
+
d2f0f34238782fbb2029d819c03274e57f3398ae2f70d5520eee7c4ee956a91b images/product.png
|
|
12
|
+
643a8ffff6e93d3aa21e332912304e8c019d66a4061e12b5a8008f7a0d123d77 injection.json
|
|
13
|
+
46201c860b767ca95d1b87704f0645f60e0b098e2c97434ec8ea74843fa2f1a0 low-text.json
|
|
14
|
+
1082008244d406a6f4dd2b4a5dc4cd585aecca7329cabe75973bce8979c11f66 promo.json
|
|
15
|
+
45307b22d4a52d88803a2cd4d4417b8f25d0fd4ce601f005c4abe3234acad51b sources/banner.html
|
|
16
|
+
178fe47f640eb661b451ebc6494630056c9ab0bac56b870f6a6daa948b84d366 sources/chart.html
|
|
17
|
+
28eda14202671f234ee8af2d2cc82b27607d903c0e663edbd57d909c1f56c796 sources/divider.html
|
|
18
|
+
f4bdfcbde3278cefddee5f6e6b502a9889ca07dab84b70117ab2b9a11e10ab60 sources/newsletter.html
|
|
19
|
+
f28c579c5be2b82ab0051d686fdea93b6dcd0656a63a290ba93cb4897a46f1ae sources/one-page.html
|
|
20
|
+
e1b518fecc4223908fbc3a4d322ad6b6ff55cd492a0a64eb3e25d51bb8a1796e sources/probe.html
|
|
21
|
+
6513a14ec3451b08fea9153795a6c54292ff061c0ff26917ee26ff1079e079c8 sources/product.html
|
|
22
|
+
1a9f0ec32af3753abcf64d981a233489c3a67473ca86cfa1801bfb58603fb8f3 sources/promo.html
|
|
23
|
+
66da66c451cccf5b561c8f4142cab69e39e668c6b3ba0eb0d32f039691d20eee welcome.json
|