@templatical/ai-kit 0.0.0-stage → 0.50.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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -2
  3. package/dist/checks-Blbakkgc.js +649 -0
  4. package/dist/checks-Blbakkgc.js.map +1 -0
  5. package/dist/client/index.d.ts +24 -0
  6. package/dist/client/index.js +1 -0
  7. package/dist/http-provider-BoRkCd7H.js +327 -0
  8. package/dist/http-provider-BoRkCd7H.js.map +1 -0
  9. package/dist/http-provider-Cb41NXd5.d.ts +22 -0
  10. package/dist/index.d.ts +3 -0
  11. package/dist/index.js +3 -0
  12. package/dist/model-BuHDCTlM.d.ts +74 -0
  13. package/dist/server/index.d.ts +313 -0
  14. package/dist/server/index.js +355 -0
  15. package/dist/server/index.js.map +1 -0
  16. package/dist/testing/index.d.ts +277 -0
  17. package/dist/testing/index.js +2444 -0
  18. package/dist/testing/index.js.map +1 -0
  19. package/dist/wire-B6AF_UGT.js +132 -0
  20. package/dist/wire-B6AF_UGT.js.map +1 -0
  21. package/dist/wire-Cv7Fk-7n.d.ts +136 -0
  22. package/eval/MANIFEST.sha256 +23 -0
  23. package/eval/cases.json +287 -0
  24. package/eval/designs/newsletter.png +0 -0
  25. package/eval/designs/one-page.pdf +0 -0
  26. package/eval/designs/promo.png +0 -0
  27. package/eval/domain-mismatch.json +25 -0
  28. package/eval/empty.json +11 -0
  29. package/eval/images/banner.png +0 -0
  30. package/eval/images/chart.png +0 -0
  31. package/eval/images/divider.png +0 -0
  32. package/eval/images/probe.png +0 -0
  33. package/eval/images/product.png +0 -0
  34. package/eval/injection.json +13 -0
  35. package/eval/low-text.json +41 -0
  36. package/eval/promo.json +12 -0
  37. package/eval/sources/banner.html +16 -0
  38. package/eval/sources/chart.html +16 -0
  39. package/eval/sources/divider.html +16 -0
  40. package/eval/sources/newsletter.html +16 -0
  41. package/eval/sources/one-page.html +16 -0
  42. package/eval/sources/probe.html +16 -0
  43. package/eval/sources/product.html +16 -0
  44. package/eval/sources/promo.html +16 -0
  45. package/eval/welcome.json +12 -0
  46. package/package.json +68 -3
  47. package/prompts/alt-text.md +7 -0
  48. package/prompts/chat-attachments.md +1 -0
  49. package/prompts/chat.md +2 -0
  50. package/prompts/editing.md +10 -0
  51. package/prompts/fix-rules.json +9 -0
  52. package/prompts/fix.md +8 -0
  53. package/prompts/operations.md +127 -0
  54. package/prompts/rewrite-presets.json +8 -0
  55. package/prompts/rewrite.md +4 -0
  56. package/schema/template-content.schema.json +1884 -0
@@ -0,0 +1,355 @@
1
+ import { i as AI_LIMITS, n as AI_ERROR_STATUS, o as AI_SESSION_HEADER, r as AI_GENERATE_FIELDS, s as AI_WIRE_HEADER, t as AI_ERROR_MESSAGES, u as matchAiRoute } from "../wire-B6AF_UGT.js";
2
+ import { C as mergeTagTokens, E as FIX_RULE_GUIDANCE, S as flattenMergeTags, T as unknownMergeTags, _ as buildEditingPrompt, a as checkRewrite, b as editingInstructions, c as createEditingSession, d as opRefusal, f as contentRuleRefusal, g as buildChatRequest, h as buildAltTextRequest, i as checkAltTextAnswer, l as applyOperationsTool, m as REWRITE_PRESET_INSTRUCTIONS, n as buildRewriteRepairPrompt, o as runEditingTurn, p as walkBlocks, r as checkAltText, s as EDITING_SESSION_LIMITS, t as buildAltTextRepairRequest, u as describeOp, v as buildFixRequest, w as mergeTagTokensIn, x as rewriteInstructions, y as buildRewritePrompt } from "../checks-Blbakkgc.js";
3
+ import { CollaborationConflict, SdkError, SdkError as SdkError$1, ThrottledError, ThrottledError as ThrottledError$1 } from "@templatical/types";
4
+ //#region src/server/errors.ts
5
+ const PROVIDER_ERROR = {
6
+ code: "ai_provider_error",
7
+ message: AI_ERROR_MESSAGES.ai_provider_error
8
+ };
9
+ function known(error) {
10
+ return error instanceof SdkError$1 && typeof error.code === "string";
11
+ }
12
+ function conflictDetails(error) {
13
+ if (!(error instanceof CollaborationConflict)) return {};
14
+ return {
15
+ ...error.opIndex !== void 0 ? { opIndex: error.opIndex } : {},
16
+ ...error.holder !== void 0 ? { holder: error.holder } : {},
17
+ ...error.holders !== void 0 ? { holders: error.holders } : {}
18
+ };
19
+ }
20
+ /** An error as the stream's last event. Anything but a coded `SdkError` hides behind the provider error. */
21
+ function aiErrorEvent(error) {
22
+ if (!known(error)) return {
23
+ type: "error",
24
+ ...PROVIDER_ERROR
25
+ };
26
+ return {
27
+ ...error.details,
28
+ ...conflictDetails(error),
29
+ type: "error",
30
+ code: error.code,
31
+ message: error.message
32
+ };
33
+ }
34
+ /** A JSON response with the wire's header; `204` has no body. */
35
+ function aiJsonResponse(body, status = 200) {
36
+ const headers = new Headers({ [AI_WIRE_HEADER]: "1" });
37
+ if (status === 204) return new Response(null, {
38
+ status,
39
+ headers
40
+ });
41
+ headers.set("Content-Type", "application/json");
42
+ return new Response(JSON.stringify(body), {
43
+ status,
44
+ headers
45
+ });
46
+ }
47
+ /**
48
+ * An error before the stream starts: `{ message, code, …details }` with the
49
+ * code's status, `Retry-After` (seconds) for a `ThrottledError`.
50
+ */
51
+ function aiErrorResponse(error) {
52
+ if (!(error instanceof SdkError$1)) return aiJsonResponse({ ...PROVIDER_ERROR }, 502);
53
+ const code = typeof error.code === "string" ? error.code : void 0;
54
+ const status = error.statusCode ?? (code !== void 0 ? AI_ERROR_STATUS[code] : void 0) ?? 400;
55
+ const response = aiJsonResponse({
56
+ ...error.details,
57
+ ...conflictDetails(error),
58
+ message: error.message,
59
+ ...code !== void 0 ? { code } : {}
60
+ }, status);
61
+ if (error instanceof ThrottledError$1) response.headers.set("Retry-After", String(Math.ceil(error.retryAfterMs / 1e3)));
62
+ return response;
63
+ }
64
+ //#endregion
65
+ //#region src/server/event-stream.ts
66
+ /**
67
+ * The SSE response for any Fetch-API server (Next, Hono, Workers, Deno, Bun;
68
+ * Express through an adapter). The producer writes events; a producer that
69
+ * throws ends the stream with an `error` event, and one that returns without
70
+ * `done` or `error` ends it with the provider error.
71
+ */
72
+ function aiEventStream(producer, options = {}) {
73
+ const pingMs = options.pingMs ?? 15e3;
74
+ const encoder = new TextEncoder();
75
+ const abort = new AbortController();
76
+ function forwardAbort() {
77
+ abort.abort(options.signal?.reason);
78
+ }
79
+ if (options.signal) {
80
+ if (options.signal.aborted) abort.abort(options.signal.reason);
81
+ else options.signal.addEventListener("abort", forwardAbort, { once: true });
82
+ }
83
+ let timer = null;
84
+ let closed = false;
85
+ function cleanup() {
86
+ if (timer) clearInterval(timer);
87
+ options.signal?.removeEventListener("abort", forwardAbort);
88
+ }
89
+ const body = new ReadableStream({
90
+ start(controller) {
91
+ let finished = false;
92
+ let lastWrite = Date.now();
93
+ const write = (chunk) => {
94
+ if (closed) return;
95
+ controller.enqueue(encoder.encode(chunk));
96
+ lastWrite = Date.now();
97
+ };
98
+ const send = (event) => {
99
+ if (finished) return;
100
+ write(`data: ${JSON.stringify(event)}\n\n`);
101
+ if (event.type === "done" || event.type === "error") finished = true;
102
+ };
103
+ timer = setInterval(() => {
104
+ if (Date.now() - lastWrite >= pingMs) write(": ping\n\n");
105
+ }, Math.max(1, Math.floor(pingMs / 3)));
106
+ (async () => {
107
+ try {
108
+ await producer({
109
+ send,
110
+ signal: abort.signal
111
+ });
112
+ if (!abort.signal.aborted) send({
113
+ type: "error",
114
+ ...PROVIDER_ERROR
115
+ });
116
+ } catch (error) {
117
+ if (!abort.signal.aborted) send(aiErrorEvent(error));
118
+ } finally {
119
+ cleanup();
120
+ if (!closed) {
121
+ closed = true;
122
+ controller.close();
123
+ }
124
+ }
125
+ })();
126
+ },
127
+ cancel(reason) {
128
+ closed = true;
129
+ cleanup();
130
+ abort.abort(reason);
131
+ }
132
+ });
133
+ const headers = new Headers(options.headers);
134
+ headers.set("Content-Type", "text/event-stream");
135
+ headers.set("Cache-Control", "no-cache");
136
+ headers.set("X-Accel-Buffering", "no");
137
+ headers.set(AI_WIRE_HEADER, "1");
138
+ return new Response(body, { headers });
139
+ }
140
+ //#endregion
141
+ //#region src/server/read-request.ts
142
+ const PRESETS = [
143
+ "shorten",
144
+ "expand",
145
+ "friendlier",
146
+ "formal",
147
+ "fixGrammar",
148
+ "translate"
149
+ ];
150
+ const IMAGE_TYPES = [
151
+ "image/png",
152
+ "image/jpeg",
153
+ "image/webp",
154
+ "image/gif"
155
+ ];
156
+ const MERGE_TAG_SYNTAXES = [
157
+ "liquid",
158
+ "handlebars",
159
+ "mailchimp",
160
+ "ampscript",
161
+ "custom"
162
+ ];
163
+ function invalid(message) {
164
+ throw new SdkError$1(message, 422);
165
+ }
166
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
167
+ const isBoundedText = (value, max) => typeof value === "string" && Array.from(value).length <= max;
168
+ function content(value) {
169
+ if (!isRecord(value) || !Array.isArray(value.blocks) || !isRecord(value.settings)) invalid("content must be the template's content.");
170
+ if (new TextEncoder().encode(JSON.stringify(value)).length > AI_LIMITS.contentMaxBytes) throw new SdkError$1(AI_ERROR_MESSAGES.content_too_large, 413, "content_too_large");
171
+ return value;
172
+ }
173
+ function mergeTags(value) {
174
+ if (!isRecord(value) || typeof value.syntax !== "string" || !MERGE_TAG_SYNTAXES.includes(value.syntax) || !Array.isArray(value.tags) || value.tags.length > AI_LIMITS.mergeTagsMax) invalid("mergeTags must be { syntax, tags } with at most 500 tags.");
175
+ for (const tag of value.tags) if (!isRecord(tag) || !isBoundedText(tag.value, 200) || !isBoundedText(tag.label, 100) || tag.group !== void 0 && !isBoundedText(tag.group, 100) || tag.description !== void 0 && !isBoundedText(tag.description, 200)) invalid("Each merge tag must have string value (at most 200 characters) and label (at most 100), with optional string group (at most 100) and description (at most 200).");
176
+ return value;
177
+ }
178
+ function revision(value) {
179
+ if (value === null || value === void 0 || value === "") return null;
180
+ const number = typeof value === "string" ? Number(value) : value;
181
+ if (typeof number !== "number" || !Number.isInteger(number) || number < 0) invalid("revision must be a whole number or null.");
182
+ return number;
183
+ }
184
+ function optionalText(value, max, message) {
185
+ if (value === null || value === void 0 || value === "") return null;
186
+ if (typeof value !== "string" || Array.from(value).length > max) invalid(message);
187
+ return value;
188
+ }
189
+ function finding(value) {
190
+ if (!isRecord(value)) invalid("finding must be { ruleId, message, suggestion, reference, blockId, excerpt? }.");
191
+ if (typeof value.ruleId !== "string" || value.ruleId.length === 0 || value.ruleId.length > 200) invalid("finding.ruleId must be 1 to 200 characters.");
192
+ if (!isBoundedText(value.message, 1e3)) invalid("finding.message must be at most 1,000 code points.");
193
+ if (!isBoundedText(value.suggestion, 1e3)) invalid("finding.suggestion must be at most 1,000 code points.");
194
+ let reference = null;
195
+ if (typeof value.reference === "string" && value.reference.length <= 2048) try {
196
+ reference = new URL(value.reference);
197
+ } catch {}
198
+ if (!reference || reference.protocol !== "http:" && reference.protocol !== "https:") invalid("finding.reference must be an http: or https: URL of at most 2,048 characters.");
199
+ if (value.blockId !== null && (typeof value.blockId !== "string" || value.blockId.length > 200)) invalid("finding.blockId must be a string of at most 200 characters or null.");
200
+ if (value.excerpt !== void 0 && !isBoundedText(value.excerpt, 1e3)) invalid("finding.excerpt must be at most 1,000 code points when present.");
201
+ return value;
202
+ }
203
+ /**
204
+ * The wire's request, parsed and checked against `AI_LIMITS`; `null` when the
205
+ * request is no route of the wire. Field validation throws `SdkError(message, 422)`;
206
+ * oversized content throws `content_too_large` (413).
207
+ */
208
+ async function readAiRequest(request) {
209
+ const match = matchAiRoute(request.method, new URL(request.url).pathname);
210
+ if (!match) return null;
211
+ const base = {
212
+ template: match.template,
213
+ sessionId: request.headers.get(AI_SESSION_HEADER)
214
+ };
215
+ if (base.sessionId !== null && !isBoundedText(base.sessionId, 200)) invalid("The session id must be at most 200 characters.");
216
+ switch (match.route) {
217
+ case "conversationMessages":
218
+ case "clearConversation": return {
219
+ ...base,
220
+ route: match.route
221
+ };
222
+ case "undoMessage":
223
+ case "redoMessage": return {
224
+ ...base,
225
+ route: match.route,
226
+ message: match.message ?? ""
227
+ };
228
+ }
229
+ let attachments = [];
230
+ let body;
231
+ if (match.route === "generate" && request.headers.get("Content-Type")?.startsWith("multipart/form-data")) {
232
+ const form = await request.formData().catch(() => invalid("Send the message as multipart form data."));
233
+ const files = form.getAll(AI_GENERATE_FIELDS.attachment);
234
+ if (files.length > AI_LIMITS.attachmentsMax) invalid("Attach at most 4 files.");
235
+ if (files.some((file) => !(file instanceof Blob) || !IMAGE_TYPES.includes(file.type) && file.type !== "application/pdf")) invalid("Attach PNG, JPEG, WebP or GIF images, or PDFs.");
236
+ const blobs = files;
237
+ if (blobs.some((file) => file.size > AI_LIMITS.attachmentMaxBytes)) invalid("Each attachment must be 5 MB or smaller.");
238
+ if (blobs.reduce((total, file) => total + file.size, 0) > AI_LIMITS.attachmentsTotalMaxBytes) invalid("Attachments must be 10 MB or smaller together.");
239
+ if (blobs.some((file) => file.name.length > 255)) invalid("An attachment's file name must be at most 255 characters.");
240
+ attachments = blobs.map((file) => ({
241
+ file,
242
+ kind: file.type === "application/pdf" ? "pdf" : "image",
243
+ name: file.name
244
+ }));
245
+ const json = (field) => {
246
+ const raw = form.get(field);
247
+ if (raw === null) return null;
248
+ try {
249
+ return JSON.parse(String(raw));
250
+ } catch {
251
+ return invalid(`${field} must be JSON.`);
252
+ }
253
+ };
254
+ body = {
255
+ message: form.get(AI_GENERATE_FIELDS.message),
256
+ selectedBlockId: form.get(AI_GENERATE_FIELDS.selectedBlockId),
257
+ revision: form.get(AI_GENERATE_FIELDS.revision),
258
+ mergeTags: json(AI_GENERATE_FIELDS.mergeTags),
259
+ content: json(AI_GENERATE_FIELDS.content)
260
+ };
261
+ } else body = await request.json().catch(() => invalid("Send a JSON body."));
262
+ if (!isRecord(body)) invalid("Send a JSON object.");
263
+ switch (match.route) {
264
+ case "altText": {
265
+ if (typeof body.blockId !== "string" || body.blockId.length === 0 || body.blockId.length > 200) invalid("blockId must be 1 to 200 characters.");
266
+ let url = null;
267
+ if (isRecord(body.image) && typeof body.image.url === "string" && body.image.url.length <= 2048) try {
268
+ url = new URL(body.image.url);
269
+ } catch {}
270
+ if (!url || url.protocol !== "http:" && url.protocol !== "https:") invalid("image.url must be an http: or https: URL of at most 2,048 characters.");
271
+ for (const key of ["currentAlt", "linkHref"]) if (body[key] !== null && (typeof body[key] !== "string" || body[key].length > 2048)) invalid(`${key} must be a string of at most 2,048 characters or null.`);
272
+ if (!isBoundedText(body.nearbyText, 2e3)) invalid("nearbyText must be at most 2,000 code points.");
273
+ if (typeof body.locale !== "string" || body.locale.length > 35 || !/^[A-Za-z]{2,3}(-[A-Za-z0-9]{1,8})*$/.test(body.locale)) invalid("locale must be a BCP 47 tag of at most 35 characters.");
274
+ if (typeof body.maxLength !== "number" || !Number.isInteger(body.maxLength) || body.maxLength < 1 || body.maxLength > 1e3) invalid("maxLength must be an integer from 1 to 1,000.");
275
+ return {
276
+ ...base,
277
+ route: "altText",
278
+ body: {
279
+ blockId: body.blockId,
280
+ image: { url: body.image.url },
281
+ currentAlt: body.currentAlt,
282
+ linkHref: body.linkHref,
283
+ nearbyText: body.nearbyText,
284
+ locale: body.locale,
285
+ maxLength: body.maxLength
286
+ }
287
+ };
288
+ }
289
+ case "settleReplay":
290
+ if (body.direction !== "undo" && body.direction !== "redo" || typeof body.applied !== "boolean") invalid("A replay outcome must have direction undo or redo and boolean applied.");
291
+ return {
292
+ ...base,
293
+ route: "settleReplay",
294
+ message: match.message,
295
+ replayId: match.replayId,
296
+ body: {
297
+ direction: body.direction,
298
+ applied: body.applied
299
+ }
300
+ };
301
+ case "generate": {
302
+ const message = body.message;
303
+ if (typeof message !== "string" || message.trim() === "" || Array.from(message).length > AI_LIMITS.messageMaxChars) invalid("The message must be 1 to 4,000 characters.");
304
+ if (typeof body.selectedBlockId === "string" && !isBoundedText(body.selectedBlockId, 200)) invalid("selectedBlockId must be at most 200 characters.");
305
+ return {
306
+ ...base,
307
+ route: "generate",
308
+ body: {
309
+ message,
310
+ attachments,
311
+ selectedBlockId: typeof body.selectedBlockId === "string" ? body.selectedBlockId : null,
312
+ mergeTags: mergeTags(body.mergeTags),
313
+ content: content(body.content),
314
+ revision: revision(body.revision)
315
+ }
316
+ };
317
+ }
318
+ case "fixFinding": return {
319
+ ...base,
320
+ route: "fixFinding",
321
+ body: {
322
+ finding: finding(body.finding),
323
+ mergeTags: mergeTags(body.mergeTags),
324
+ content: content(body.content),
325
+ revision: revision(body.revision)
326
+ }
327
+ };
328
+ case "rewriteText": {
329
+ const html = body.html;
330
+ if (typeof html !== "string" || html === "" || Array.from(html).length > AI_LIMITS.rewriteHtmlMaxChars) invalid("html must be 1 to 20,000 characters.");
331
+ const preset = body.preset ?? null;
332
+ if (preset !== null && !PRESETS.includes(preset)) invalid("Unknown preset.");
333
+ const instruction = optionalText(body.instruction, AI_LIMITS.instructionMaxChars, "The instruction must be at most 500 characters.");
334
+ if (preset === null && instruction === null) invalid("Choose a preset or write an instruction.");
335
+ const language = body.language === "" ? null : body.language ?? null;
336
+ if (language !== null && (typeof language !== "string" || !/^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(language))) invalid("language must be a BCP 47 tag.");
337
+ if (preset === "translate" && language === null) invalid("Choose a language to translate into.");
338
+ return {
339
+ ...base,
340
+ route: "rewriteText",
341
+ body: {
342
+ html,
343
+ preset,
344
+ language,
345
+ instruction,
346
+ mergeTags: mergeTags(body.mergeTags)
347
+ }
348
+ };
349
+ }
350
+ }
351
+ }
352
+ //#endregion
353
+ export { EDITING_SESSION_LIMITS, FIX_RULE_GUIDANCE, REWRITE_PRESET_INSTRUCTIONS, SdkError, ThrottledError, aiErrorEvent, aiErrorResponse, aiEventStream, aiJsonResponse, applyOperationsTool, buildAltTextRepairRequest, buildAltTextRequest, buildChatRequest, buildEditingPrompt, buildFixRequest, buildRewritePrompt, buildRewriteRepairPrompt, checkAltText, checkAltTextAnswer, checkRewrite, contentRuleRefusal, createEditingSession, describeOp, editingInstructions, flattenMergeTags, mergeTagTokens, mergeTagTokensIn, opRefusal, readAiRequest, rewriteInstructions, runEditingTurn, unknownMergeTags, walkBlocks };
354
+
355
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["SdkError","ThrottledError","SdkError"],"sources":["../../src/server/errors.ts","../../src/server/event-stream.ts","../../src/server/read-request.ts"],"sourcesContent":["import {\n CollaborationConflict,\n SdkError,\n ThrottledError,\n} from \"@templatical/types\";\nimport {\n AI_ERROR_MESSAGES,\n AI_ERROR_STATUS,\n AI_WIRE_HEADER,\n AI_WIRE_VERSION,\n type AiErrorEvent,\n} from \"../wire\";\n\nconst PROVIDER_ERROR = {\n code: \"ai_provider_error\",\n message: AI_ERROR_MESSAGES.ai_provider_error,\n} as const;\n\nfunction known(error: unknown): error is SdkError & { code: string } {\n return error instanceof SdkError && typeof error.code === \"string\";\n}\n\nfunction conflictDetails(error: unknown): Record<string, unknown> {\n if (!(error instanceof CollaborationConflict)) return {};\n return {\n ...(error.opIndex !== undefined ? { opIndex: error.opIndex } : {}),\n ...(error.holder !== undefined ? { holder: error.holder } : {}),\n ...(error.holders !== undefined ? { holders: error.holders } : {}),\n };\n}\n\n/** An error as the stream's last event. Anything but a coded `SdkError` hides behind the provider error. */\nexport function aiErrorEvent(error: unknown): AiErrorEvent {\n if (!known(error)) return { type: \"error\", ...PROVIDER_ERROR };\n return {\n ...error.details,\n ...conflictDetails(error),\n type: \"error\",\n code: error.code,\n message: error.message,\n };\n}\n\n/** A JSON response with the wire's header; `204` has no body. */\nexport function aiJsonResponse(body: unknown, status = 200): Response {\n const headers = new Headers({ [AI_WIRE_HEADER]: AI_WIRE_VERSION });\n if (status === 204) return new Response(null, { status, headers });\n headers.set(\"Content-Type\", \"application/json\");\n return new Response(JSON.stringify(body), { status, headers });\n}\n\n/**\n * An error before the stream starts: `{ message, code, …details }` with the\n * code's status, `Retry-After` (seconds) for a `ThrottledError`.\n */\nexport function aiErrorResponse(error: unknown): Response {\n if (!(error instanceof SdkError))\n return aiJsonResponse({ ...PROVIDER_ERROR }, 502);\n // A validation failure (`readAiRequest`'s 422) has no code, as Laravel's has none.\n const code = typeof error.code === \"string\" ? error.code : undefined;\n const status =\n error.statusCode ??\n (code !== undefined ? AI_ERROR_STATUS[code] : undefined) ??\n 400;\n const response = aiJsonResponse(\n {\n ...error.details,\n ...conflictDetails(error),\n message: error.message,\n ...(code !== undefined ? { code } : {}),\n },\n status,\n );\n if (error instanceof ThrottledError) {\n response.headers.set(\n \"Retry-After\",\n String(Math.ceil(error.retryAfterMs / 1000)),\n );\n }\n return response;\n}\n\nexport { PROVIDER_ERROR };\n","import { AI_WIRE_HEADER, AI_WIRE_VERSION, type AiWireEvent } from \"../wire\";\nimport { aiErrorEvent, PROVIDER_ERROR } from \"./errors\";\n\nexport interface AiEventStreamController {\n /** Write an event. Ignored once `done` or `error` was written. */\n send(event: AiWireEvent): void;\n /** Aborted when the client goes away or `options.signal` aborts. */\n readonly signal: AbortSignal;\n}\n\nexport interface AiEventStreamOptions {\n /** Write `: ping` after this long without a write (15 s). */\n pingMs?: number;\n signal?: AbortSignal;\n headers?: HeadersInit;\n}\n\n/**\n * The SSE response for any Fetch-API server (Next, Hono, Workers, Deno, Bun;\n * Express through an adapter). The producer writes events; a producer that\n * throws ends the stream with an `error` event, and one that returns without\n * `done` or `error` ends it with the provider error.\n */\nexport function aiEventStream(\n producer: (stream: AiEventStreamController) => Promise<void>,\n options: AiEventStreamOptions = {},\n): Response {\n const pingMs = options.pingMs ?? 15_000;\n const encoder = new TextEncoder();\n const abort = new AbortController();\n function forwardAbort(): void {\n abort.abort(options.signal?.reason);\n }\n if (options.signal) {\n if (options.signal.aborted) abort.abort(options.signal.reason);\n else options.signal.addEventListener(\"abort\", forwardAbort, { once: true });\n }\n let timer: ReturnType<typeof setInterval> | null = null;\n let closed = false;\n\n function cleanup(): void {\n if (timer) clearInterval(timer);\n options.signal?.removeEventListener(\"abort\", forwardAbort);\n }\n\n const body = new ReadableStream<Uint8Array>({\n start(controller) {\n let finished = false;\n let lastWrite = Date.now();\n const write = (chunk: string): void => {\n if (closed) return;\n controller.enqueue(encoder.encode(chunk));\n lastWrite = Date.now();\n };\n const send = (event: AiWireEvent): void => {\n if (finished) return;\n write(`data: ${JSON.stringify(event)}\\n\\n`);\n if (event.type === \"done\" || event.type === \"error\") finished = true;\n };\n timer = setInterval(\n () => {\n if (Date.now() - lastWrite >= pingMs) write(\": ping\\n\\n\");\n },\n Math.max(1, Math.floor(pingMs / 3)),\n );\n void (async () => {\n try {\n await producer({ send, signal: abort.signal });\n if (!abort.signal.aborted) send({ type: \"error\", ...PROVIDER_ERROR });\n } catch (error) {\n if (!abort.signal.aborted) send(aiErrorEvent(error));\n } finally {\n cleanup();\n if (!closed) {\n closed = true;\n controller.close();\n }\n }\n })();\n },\n cancel(reason) {\n closed = true;\n cleanup();\n abort.abort(reason);\n },\n });\n\n const headers = new Headers(options.headers);\n headers.set(\"Content-Type\", \"text/event-stream\");\n headers.set(\"Cache-Control\", \"no-cache\");\n headers.set(\"X-Accel-Buffering\", \"no\");\n headers.set(AI_WIRE_HEADER, AI_WIRE_VERSION);\n return new Response(body, { headers });\n}\n","import type {\n AiFixFinding,\n AiChatAttachment,\n AiMergeTags,\n AiRewritePreset,\n TemplateContent,\n} from \"@templatical/types\";\nimport { SdkError } from \"@templatical/types\";\nimport {\n AI_GENERATE_FIELDS,\n AI_ERROR_MESSAGES,\n AI_LIMITS,\n AI_SESSION_HEADER,\n matchAiRoute,\n type AiFixFindingBody,\n type AiAltTextBody,\n type AiGenerateBody,\n type AiRewriteBody,\n} from \"../wire\";\n\ninterface Base {\n template: string;\n /** `X-Templatical-Session`, when sent. */\n sessionId: string | null;\n}\n\nexport type AiWireRequest = Base &\n (\n | { route: \"generate\"; body: AiGenerateBody }\n | { route: \"fixFinding\"; body: AiFixFindingBody }\n | { route: \"rewriteText\"; body: AiRewriteBody }\n | { route: \"altText\"; body: AiAltTextBody }\n | { route: \"conversationMessages\" | \"clearConversation\" }\n | { route: \"undoMessage\" | \"redoMessage\"; message: string }\n | {\n route: \"settleReplay\";\n message: string;\n replayId: string;\n body: { direction: \"undo\" | \"redo\"; applied: boolean };\n }\n );\n\nconst PRESETS: readonly AiRewritePreset[] = [\n \"shorten\",\n \"expand\",\n \"friendlier\",\n \"formal\",\n \"fixGrammar\",\n \"translate\",\n];\nconst IMAGE_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\"];\nconst MERGE_TAG_SYNTAXES = [\n \"liquid\",\n \"handlebars\",\n \"mailchimp\",\n \"ampscript\",\n \"custom\",\n];\n\nfunction invalid(message: string): never {\n throw new SdkError(message, 422);\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst isBoundedText = (value: unknown, max: number): value is string =>\n typeof value === \"string\" && Array.from(value).length <= max;\n\nfunction content(value: unknown): TemplateContent {\n if (\n !isRecord(value) ||\n !Array.isArray(value.blocks) ||\n !isRecord(value.settings)\n ) {\n invalid(\"content must be the template's content.\");\n }\n if (\n new TextEncoder().encode(JSON.stringify(value)).length >\n AI_LIMITS.contentMaxBytes\n )\n throw new SdkError(\n AI_ERROR_MESSAGES.content_too_large,\n 413,\n \"content_too_large\",\n );\n return value as unknown as TemplateContent;\n}\n\nfunction mergeTags(value: unknown): AiMergeTags {\n if (\n !isRecord(value) ||\n typeof value.syntax !== \"string\" ||\n !MERGE_TAG_SYNTAXES.includes(value.syntax) ||\n !Array.isArray(value.tags) ||\n value.tags.length > AI_LIMITS.mergeTagsMax\n ) {\n invalid(\"mergeTags must be { syntax, tags } with at most 500 tags.\");\n }\n for (const tag of value.tags) {\n if (\n !isRecord(tag) ||\n !isBoundedText(tag.value, 200) ||\n !isBoundedText(tag.label, 100) ||\n (tag.group !== undefined && !isBoundedText(tag.group, 100)) ||\n (tag.description !== undefined && !isBoundedText(tag.description, 200))\n ) {\n invalid(\n \"Each merge tag must have string value (at most 200 characters) and label (at most 100), with optional string group (at most 100) and description (at most 200).\",\n );\n }\n }\n return value as unknown as AiMergeTags;\n}\n\nfunction revision(value: unknown): number | null {\n if (value === null || value === undefined || value === \"\") return null;\n const number = typeof value === \"string\" ? Number(value) : value;\n if (typeof number !== \"number\" || !Number.isInteger(number) || number < 0) {\n invalid(\"revision must be a whole number or null.\");\n }\n return number;\n}\n\nfunction optionalText(\n value: unknown,\n max: number,\n message: string,\n): string | null {\n if (value === null || value === undefined || value === \"\") return null;\n if (typeof value !== \"string\" || Array.from(value).length > max)\n invalid(message);\n return value;\n}\n\nfunction finding(value: unknown): AiFixFinding {\n if (!isRecord(value))\n invalid(\n \"finding must be { ruleId, message, suggestion, reference, blockId, excerpt? }.\",\n );\n if (\n typeof value.ruleId !== \"string\" ||\n value.ruleId.length === 0 ||\n value.ruleId.length > 200\n )\n invalid(\"finding.ruleId must be 1 to 200 characters.\");\n if (!isBoundedText(value.message, 1000))\n invalid(\"finding.message must be at most 1,000 code points.\");\n if (!isBoundedText(value.suggestion, 1000))\n invalid(\"finding.suggestion must be at most 1,000 code points.\");\n let reference: URL | null = null;\n if (typeof value.reference === \"string\" && value.reference.length <= 2048) {\n try {\n reference = new URL(value.reference);\n } catch {\n /* Invalid URL. */\n }\n }\n if (\n !reference ||\n (reference.protocol !== \"http:\" && reference.protocol !== \"https:\")\n )\n invalid(\n \"finding.reference must be an http: or https: URL of at most 2,048 characters.\",\n );\n if (\n value.blockId !== null &&\n (typeof value.blockId !== \"string\" || value.blockId.length > 200)\n )\n invalid(\n \"finding.blockId must be a string of at most 200 characters or null.\",\n );\n if (value.excerpt !== undefined && !isBoundedText(value.excerpt, 1000))\n invalid(\"finding.excerpt must be at most 1,000 code points when present.\");\n return value as unknown as AiFixFinding;\n}\n\n/**\n * The wire's request, parsed and checked against `AI_LIMITS`; `null` when the\n * request is no route of the wire. Field validation throws `SdkError(message, 422)`;\n * oversized content throws `content_too_large` (413).\n */\nexport async function readAiRequest(\n request: Request,\n): Promise<AiWireRequest | null> {\n const match = matchAiRoute(request.method, new URL(request.url).pathname);\n if (!match) return null;\n const base: Base = {\n template: match.template,\n sessionId: request.headers.get(AI_SESSION_HEADER),\n };\n\n if (base.sessionId !== null && !isBoundedText(base.sessionId, 200))\n invalid(\"The session id must be at most 200 characters.\");\n\n switch (match.route) {\n case \"conversationMessages\":\n case \"clearConversation\":\n return { ...base, route: match.route };\n case \"undoMessage\":\n case \"redoMessage\":\n return { ...base, route: match.route, message: match.message ?? \"\" };\n default:\n break;\n }\n\n let attachments: AiChatAttachment[] = [];\n let body: unknown;\n if (\n match.route === \"generate\" &&\n request.headers.get(\"Content-Type\")?.startsWith(\"multipart/form-data\")\n ) {\n const form = await request\n .formData()\n .catch(() => invalid(\"Send the message as multipart form data.\"));\n const files = form.getAll(AI_GENERATE_FIELDS.attachment);\n if (files.length > AI_LIMITS.attachmentsMax)\n invalid(\"Attach at most 4 files.\");\n if (\n files.some(\n (file) =>\n !(file instanceof Blob) ||\n (!IMAGE_TYPES.includes(file.type) && file.type !== \"application/pdf\"),\n )\n )\n invalid(\"Attach PNG, JPEG, WebP or GIF images, or PDFs.\");\n const blobs = files as File[];\n if (blobs.some((file) => file.size > AI_LIMITS.attachmentMaxBytes))\n invalid(\"Each attachment must be 5 MB or smaller.\");\n if (\n blobs.reduce((total, file) => total + file.size, 0) >\n AI_LIMITS.attachmentsTotalMaxBytes\n )\n invalid(\"Attachments must be 10 MB or smaller together.\");\n if (blobs.some((file) => file.name.length > 255))\n invalid(\"An attachment's file name must be at most 255 characters.\");\n attachments = blobs.map((file) => ({\n file,\n kind: file.type === \"application/pdf\" ? \"pdf\" : \"image\",\n name: file.name,\n }));\n const json = (field: string): unknown => {\n const raw = form.get(field);\n if (raw === null) return null;\n try {\n return JSON.parse(String(raw));\n } catch {\n return invalid(`${field} must be JSON.`);\n }\n };\n body = {\n message: form.get(AI_GENERATE_FIELDS.message),\n selectedBlockId: form.get(AI_GENERATE_FIELDS.selectedBlockId),\n revision: form.get(AI_GENERATE_FIELDS.revision),\n mergeTags: json(AI_GENERATE_FIELDS.mergeTags),\n content: json(AI_GENERATE_FIELDS.content),\n };\n } else {\n body = await request.json().catch(() => invalid(\"Send a JSON body.\"));\n }\n if (!isRecord(body)) invalid(\"Send a JSON object.\");\n\n switch (match.route) {\n case \"altText\": {\n if (\n typeof body.blockId !== \"string\" ||\n body.blockId.length === 0 ||\n body.blockId.length > 200\n )\n invalid(\"blockId must be 1 to 200 characters.\");\n let url: URL | null = null;\n if (\n isRecord(body.image) &&\n typeof body.image.url === \"string\" &&\n body.image.url.length <= 2048\n ) {\n try {\n url = new URL(body.image.url);\n } catch {\n /* Invalid URL. */\n }\n }\n if (!url || (url.protocol !== \"http:\" && url.protocol !== \"https:\"))\n invalid(\n \"image.url must be an http: or https: URL of at most 2,048 characters.\",\n );\n for (const key of [\"currentAlt\", \"linkHref\"] as const) {\n if (\n body[key] !== null &&\n (typeof body[key] !== \"string\" || body[key].length > 2048)\n )\n invalid(\n `${key} must be a string of at most 2,048 characters or null.`,\n );\n }\n if (!isBoundedText(body.nearbyText, 2000))\n invalid(\"nearbyText must be at most 2,000 code points.\");\n if (\n typeof body.locale !== \"string\" ||\n body.locale.length > 35 ||\n !/^[A-Za-z]{2,3}(-[A-Za-z0-9]{1,8})*$/.test(body.locale)\n )\n invalid(\"locale must be a BCP 47 tag of at most 35 characters.\");\n if (\n typeof body.maxLength !== \"number\" ||\n !Number.isInteger(body.maxLength) ||\n body.maxLength < 1 ||\n body.maxLength > 1000\n )\n invalid(\"maxLength must be an integer from 1 to 1,000.\");\n return {\n ...base,\n route: \"altText\",\n body: {\n blockId: body.blockId,\n image: { url: (body.image as { url: string }).url },\n currentAlt: body.currentAlt as string | null,\n linkHref: body.linkHref as string | null,\n nearbyText: body.nearbyText,\n locale: body.locale,\n maxLength: body.maxLength,\n },\n };\n }\n case \"settleReplay\": {\n if (\n (body.direction !== \"undo\" && body.direction !== \"redo\") ||\n typeof body.applied !== \"boolean\"\n )\n invalid(\n \"A replay outcome must have direction undo or redo and boolean applied.\",\n );\n return {\n ...base,\n route: \"settleReplay\",\n message: match.message!,\n replayId: match.replayId!,\n body: { direction: body.direction, applied: body.applied },\n };\n }\n case \"generate\": {\n const message = body.message;\n if (\n typeof message !== \"string\" ||\n message.trim() === \"\" ||\n Array.from(message).length > AI_LIMITS.messageMaxChars\n ) {\n invalid(\"The message must be 1 to 4,000 characters.\");\n }\n if (\n typeof body.selectedBlockId === \"string\" &&\n !isBoundedText(body.selectedBlockId, 200)\n )\n invalid(\"selectedBlockId must be at most 200 characters.\");\n return {\n ...base,\n route: \"generate\",\n body: {\n message,\n attachments,\n selectedBlockId:\n typeof body.selectedBlockId === \"string\"\n ? body.selectedBlockId\n : null,\n mergeTags: mergeTags(body.mergeTags),\n content: content(body.content),\n revision: revision(body.revision),\n },\n };\n }\n case \"fixFinding\":\n return {\n ...base,\n route: \"fixFinding\",\n body: {\n finding: finding(body.finding),\n mergeTags: mergeTags(body.mergeTags),\n content: content(body.content),\n revision: revision(body.revision),\n },\n };\n case \"rewriteText\": {\n const html = body.html;\n if (\n typeof html !== \"string\" ||\n html === \"\" ||\n Array.from(html).length > AI_LIMITS.rewriteHtmlMaxChars\n ) {\n invalid(\"html must be 1 to 20,000 characters.\");\n }\n const preset = body.preset ?? null;\n if (preset !== null && !PRESETS.includes(preset as AiRewritePreset))\n invalid(\"Unknown preset.\");\n const instruction = optionalText(\n body.instruction,\n AI_LIMITS.instructionMaxChars,\n \"The instruction must be at most 500 characters.\",\n );\n if (preset === null && instruction === null)\n invalid(\"Choose a preset or write an instruction.\");\n const language = body.language === \"\" ? null : (body.language ?? null);\n if (\n language !== null &&\n (typeof language !== \"string\" ||\n !/^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(language))\n ) {\n invalid(\"language must be a BCP 47 tag.\");\n }\n if (preset === \"translate\" && language === null)\n invalid(\"Choose a language to translate into.\");\n return {\n ...base,\n route: \"rewriteText\",\n body: {\n html,\n preset: preset as AiRewritePreset | null,\n language,\n instruction,\n mergeTags: mergeTags(body.mergeTags),\n },\n };\n }\n }\n}\n"],"mappings":"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"}