best-email-builder 0.8.0 → 0.10.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 (70) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +7 -5
  3. package/dist/ai-server.js +21 -32
  4. package/dist/ai-server.js.map +1 -1
  5. package/dist/ai.js +432 -307
  6. package/dist/ai.js.map +1 -1
  7. package/dist/builder.js +8 -9
  8. package/dist/{chunk-SHHHYRPC.js → chunk-24E4GOHU.js} +518 -1505
  9. package/dist/chunk-24E4GOHU.js.map +1 -0
  10. package/dist/chunk-7G7C6G3A.js +837 -0
  11. package/dist/chunk-7G7C6G3A.js.map +1 -0
  12. package/dist/{chunk-G4U6PSF6.js → chunk-A6VTFUSW.js} +2 -2
  13. package/dist/{chunk-P7ABBLX6.js → chunk-KTTVBNOU.js} +2 -2
  14. package/dist/{chunk-6W3QUZR7.js → chunk-M4R53NYT.js} +158 -36
  15. package/dist/chunk-M4R53NYT.js.map +1 -0
  16. package/dist/{chunk-62HBUBBI.js → chunk-MBZI6NVP.js} +31 -732
  17. package/dist/chunk-MBZI6NVP.js.map +1 -0
  18. package/dist/{chunk-LUM7J5FF.js → chunk-OLTD3S3N.js} +2 -2
  19. package/dist/{chunk-ITIG5NTJ.js → chunk-SCQKWAXR.js} +7 -7
  20. package/dist/{chunk-WM2EEV4Q.js → chunk-TUA6E3BR.js} +76 -39
  21. package/dist/chunk-TUA6E3BR.js.map +1 -0
  22. package/dist/{chunk-AXIM6E7T.js → chunk-VQRVBQIB.js} +2 -2
  23. package/dist/{chunk-DXWIVGHP.js → chunk-XDCGYHLF.js} +4 -41
  24. package/dist/chunk-XDCGYHLF.js.map +1 -0
  25. package/dist/index.js +17 -18
  26. package/dist/index.js.map +1 -1
  27. package/dist/library.js +5 -5
  28. package/dist/server.js +13 -19
  29. package/dist/server.js.map +1 -1
  30. package/dist/styles.css +1 -1
  31. package/dist/types/ai/ConnectAi.d.ts +3 -1
  32. package/dist/types/ai/ModelPicker.d.ts +7 -3
  33. package/dist/types/ai/ProofreadButton.d.ts +4 -0
  34. package/dist/types/ai/SubjectIdeas.d.ts +1 -1
  35. package/dist/types/ai/agent.d.ts +1 -1
  36. package/dist/types/ai/agentEndpoint.d.ts +3 -11
  37. package/dist/types/ai/claude.d.ts +3 -3
  38. package/dist/types/ai/createAi.d.ts +9 -14
  39. package/dist/types/ai/diff.d.ts +24 -0
  40. package/dist/types/ai/{draft.d.ts → errors.d.ts} +30 -81
  41. package/dist/types/ai/generator.d.ts +25 -91
  42. package/dist/types/ai/index.d.ts +2 -2
  43. package/dist/types/ai/keys.d.ts +15 -3
  44. package/dist/types/ai/proofread.d.ts +99 -0
  45. package/dist/types/ai/rewrite.d.ts +27 -8
  46. package/dist/types/ai/server.d.ts +4 -4
  47. package/dist/types/ai/subjects.d.ts +6 -0
  48. package/dist/types/core/EmailEditor.d.ts +10 -0
  49. package/dist/types/core/constants.d.ts +1 -1
  50. package/dist/types/engine/NativeAdapter.d.ts +1 -1
  51. package/dist/types/server/handler.d.ts +5 -3
  52. package/dist/types/ui/primitives/searchShortcut.d.ts +2 -2
  53. package/package.json +1 -1
  54. package/dist/chunk-62HBUBBI.js.map +0 -1
  55. package/dist/chunk-6W3QUZR7.js.map +0 -1
  56. package/dist/chunk-DXWIVGHP.js.map +0 -1
  57. package/dist/chunk-FAEVYSIF.js +0 -119
  58. package/dist/chunk-FAEVYSIF.js.map +0 -1
  59. package/dist/chunk-OWVXKMLI.js +0 -121
  60. package/dist/chunk-OWVXKMLI.js.map +0 -1
  61. package/dist/chunk-SHHHYRPC.js.map +0 -1
  62. package/dist/chunk-WM2EEV4Q.js.map +0 -1
  63. package/dist/types/ai/GenerateButton.d.ts +0 -4
  64. package/dist/types/ai/prompt.d.ts +0 -76
  65. package/dist/types/ai/purposes.d.ts +0 -48
  66. /package/dist/{chunk-G4U6PSF6.js.map → chunk-A6VTFUSW.js.map} +0 -0
  67. /package/dist/{chunk-P7ABBLX6.js.map → chunk-KTTVBNOU.js.map} +0 -0
  68. /package/dist/{chunk-LUM7J5FF.js.map → chunk-OLTD3S3N.js.map} +0 -0
  69. /package/dist/{chunk-ITIG5NTJ.js.map → chunk-SCQKWAXR.js.map} +0 -0
  70. /package/dist/{chunk-AXIM6E7T.js.map → chunk-VQRVBQIB.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,82 @@ All notable changes are documented here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.10.0] — 2026-10-19
10
+
11
+ The AI no longer writes whole emails: it checks and writes their texts.
12
+ This breaks code that used *Generate* (see *Removed*).
13
+
14
+ ### Added
15
+ - **Proofread**, in the toolbar: the spelling, grammar and punctuation
16
+ mistakes of every text the reader sees (subject, preview text, text
17
+ blocks, buttons, menu links), found in one call. The dialog lists the
18
+ texts with a mistake, the old words struck out and the new ones next to
19
+ them; each correction is applied on its own or all at once, and the
20
+ corrections applied together are one step of the history. A text edited
21
+ since it was read is left as it is. A correction is kept only when it
22
+ changes words, keeps the markup, links and merge tags, and touches at most
23
+ a third of the text; otherwise the model gets one repair turn, then it is
24
+ left out.
25
+ - `ai.proofread`, `generator.proofread` and the handler's
26
+ `POST {base}/proofread` (60 per user per hour, `rateLimits.proofreadPerHour`).
27
+ `PROOFREAD_SYSTEM_PROMPT`, `createProofreader` and `reviewFix` are exported
28
+ from `best-email-builder/ai/server`, `diffWords` from both AI entries.
29
+ - **Write with AI**, in the properties of a text block: a topic ("the sale
30
+ ends Sunday"), and the text is written anew to fit the rest of the email,
31
+ a title as one line, a `[placeholder]` where a fact is missing. It is the
32
+ `write` action of `rewrite` (`{ action: 'write', instruction, context }`).
33
+ - `editor.setContents([{ id, content }])`: the contents of several
34
+ elements changed in one step of the history.
35
+
36
+ ### Changed
37
+ - `claudeEmailModel` uses `effort: 'low'` for every task by default (every
38
+ answer is a short one now); `effort` changes it. `rewriteEffort` still
39
+ works as its old name.
40
+ - The agent protocol (`best-email-builder/agent@1`) has two tasks,
41
+ `rewrite` and `subjects`: a proofread is sent as `rewrite`, so agents
42
+ written for 0.9 keep working. `checkAgent` tries a proofread, a rewrite
43
+ and subject ideas.
44
+ - The live demo explains Proofread and Write with AI.
45
+
46
+ ### Removed
47
+ - **Generate**: the toolbar button, `ai.generate`, `generator.generate`, the
48
+ handler's `POST {base}/generate` (now 404) and `rateLimits.generatePerHour`.
49
+ `createAi({ generate: fn })` throws; `generate: false` is still accepted.
50
+ - The 68 purposes and the email prompt: `EMAIL_PURPOSES`,
51
+ `PURPOSE_CATEGORIES`, `purposeById`, `purposesByCategory`, `SYSTEM_PROMPT`,
52
+ `EMAIL_DRAFT_SCHEMA`, `buildUserPrompt`, `cleanGenerateInput`,
53
+ `MAX_DETAILS_LENGTH`, `normalizeGeneratedMjml`, `parseDraft` and
54
+ `reviewDraft`.
55
+ - The `compile` option of `createEmailGenerator` and `checkAgent`: nothing
56
+ compiles MJML any more.
57
+
58
+ ### Fixed
59
+ - The gallery's *Welcome*, *Event invitation* and *Newsletter* were
60
+ translated only when the AI module was loaded (its dictionary had the same
61
+ words). The core has them now, and `npm run check:i18n` checks the
62
+ gallery's names and descriptions.
63
+
64
+ ## [0.9.0] — 2026-10-16
65
+
66
+ ### Added
67
+ - **An OpenAI-compatible API of the user's own** in the Connect dialog: add
68
+ `'openai-compatible'` to `createAi({ provider: [...], askKey: true })`
69
+ and the user gives its address (LM Studio, vLLM, Ollama on another
70
+ machine, a proxy…), a key only if it needs one, and a model from its
71
+ `/models` list. The address is remembered like the key. `ai.key` gains
72
+ `address(id?)`, `setAddress(url, id?)` and `ready()`; `listModels` takes
73
+ an `address`. A provider without a key of its own (Ollama) connects
74
+ without one, once a model is chosen.
75
+ - The live demo offers it too, and its language list is now a list of its
76
+ own instead of the browser's: by mouse, or by keyboard (arrows, Home/End,
77
+ type a letter, Enter, Esc).
78
+
79
+ ### Changed
80
+ - The Connect dialog and the list of models show what is free in words
81
+ only: no gift icons.
82
+ - `ai.key.state().connected` is true once the provider in use can write:
83
+ its key (unless it needs none), its address and its model.
84
+
9
85
  ## [0.8.0] — 2026-10-16
10
86
 
11
87
  ### Added
package/README.md CHANGED
@@ -65,13 +65,15 @@ once and given away.
65
65
  unsubscribe link.
66
66
 
67
67
  **AI, optional**
68
- - *Generate* a complete email from one of 68 purposes.
68
+ - *Proofread* the whole email: spelling, grammar and punctuation in every
69
+ text, each correction shown word by word, applied in one step.
70
+ - *Write* a text on a topic, to fit the rest of the email.
69
71
  - *Rewrite* any text: shorter, longer, fixed, friendlier, more formal, or
70
72
  translated into 13 languages.
71
73
  - *Suggest* three subject lines drawn from the email.
72
- - OpenRouter, Gemini, Groq, Mistral, OpenAI, DeepSeek, Cerebras, Ollama,
73
- Claude or your own agent. The model runs on your server, or in the page
74
- with each user's own key.
74
+ - OpenRouter, Gemini, Groq, Mistral, OpenAI, DeepSeek, Cerebras, Ollama, any
75
+ OpenAI-compatible API, Claude or your own agent. The model runs on your
76
+ server, or in the page with each user's own key.
75
77
 
76
78
  **Your app**
77
79
  - Merge-tag variables, including ones your users create.
@@ -140,7 +142,7 @@ the layout, Next.js, Express and image hosting.
140
142
  | [Editor and library](docs/EDITOR.md) | The three ways to mount them, every prop, theming. |
141
143
  | [Server](docs/SERVER.md) | Routes, handler options, the storage contract, your database. |
142
144
  | [Variables and sending](docs/VARIABLES.md) | Merge tags, custom variables, personalizing at send time. |
143
- | [AI](docs/AI.md) | Generate, Rewrite, Subjects. Where the model runs, and the providers. |
145
+ | [AI](docs/AI.md) | Proofread, Write, Rewrite, Subjects. Where the model runs, and the providers. |
144
146
  | [Your own AI agent](docs/AGENT.md) | The `agent@1` protocol. |
145
147
  | [Brand](docs/BRAND.md) | Logo, colors and font, read from a website. |
146
148
  | [Languages](docs/LANGUAGES.md) | Seven built in, your own strings, adding one. |
package/dist/ai-server.js CHANGED
@@ -4,43 +4,34 @@ import {
4
4
  AI_MESSAGES,
5
5
  AI_PROVIDERS,
6
6
  DEFAULT_CLAUDE_MODEL,
7
- EMAIL_DRAFT_SCHEMA,
8
- EMAIL_PURPOSES,
9
7
  EmailGenerationError,
10
8
  GENERATION_ERRORS,
11
- MAX_DETAILS_LENGTH,
12
- PURPOSE_CATEGORIES,
9
+ PROOFREAD_SYSTEM_PROMPT,
13
10
  REWRITE_ACTIONS,
14
11
  REWRITE_LANGUAGES,
15
12
  REWRITE_SYSTEM_PROMPT,
16
13
  SUBJECTS_SYSTEM_PROMPT,
17
- SYSTEM_PROMPT,
18
14
  agentEmailModel,
19
- buildUserPrompt,
20
15
  claudeEmailModel,
21
- cleanGenerateInput,
22
16
  createEmailGenerator,
17
+ createProofreader,
23
18
  createRewriter,
24
19
  createSubjectSuggester,
20
+ diffWords,
25
21
  emailWords,
26
22
  failureCode,
27
23
  listProviderModels,
28
- normalizeGeneratedMjml,
29
- parseDraft,
30
24
  providerEmailModel,
31
- purposeById,
32
- purposesByCategory,
33
25
  resolveProvider,
34
- reviewDraft,
26
+ reviewFix,
35
27
  reviewRewrite,
36
28
  reviewSubjects
37
- } from "./chunk-SHHHYRPC.js";
38
- import "./chunk-OWVXKMLI.js";
39
- import "./chunk-AXIM6E7T.js";
40
- import "./chunk-62HBUBBI.js";
29
+ } from "./chunk-24E4GOHU.js";
30
+ import "./chunk-VQRVBQIB.js";
31
+ import "./chunk-MBZI6NVP.js";
41
32
 
42
33
  // src/ai/agentEndpoint.js
43
- var TASKS = ["generate", "rewrite", "subjects"];
34
+ var TASKS = ["rewrite", "subjects"];
44
35
  var MAX_BODY_BYTES = 2 * 1024 * 1024;
45
36
  var MAX_MESSAGES = 20;
46
37
  var json = (body, status = 200) => new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" } });
@@ -113,11 +104,17 @@ function createAgentEndpoint({ complete, onError = (error) => console.error("[ag
113
104
  };
114
105
  }
115
106
  var SAMPLE_TEXT = '<p>Our summer sale starts on Monday: 30% off everything in store, for three days only. It ends on Wednesday at midnight. <a href="https://example.com/sale">See the offers</a>.</p>';
116
- async function checkAgent({ url, headers, user, fetch: fetchImpl, timeout, compile, language = "en" }) {
107
+ var SAMPLE_EMAIL = [
108
+ { id: "subject", kind: "subject", format: "plain", text: "Our sumer sale starts on Monday" },
109
+ { id: "text-1", kind: "text", format: "html", text: SAMPLE_TEXT },
110
+ { id: "text-2", kind: "text", format: "html", text: "<p>We hope to see you their. <b>Every</b> product are on sale.</p>" },
111
+ { id: "button-1", kind: "button", format: "plain", text: "See the offers" }
112
+ ];
113
+ async function checkAgent({ url, headers, user, fetch: fetchImpl, timeout, language = "en" }) {
117
114
  const model = agentEmailModel({ url, headers, fetch: fetchImpl, timeout });
118
- const generator = createEmailGenerator({ model, compile });
115
+ const generator = createEmailGenerator({ model });
119
116
  const steps = [
120
- ["generate", () => generator.generate({ purpose: "welcome", language }, { user }), (r) => ({ subject: r.subject, warnings: r.warnings })],
117
+ ["proofread", () => generator.proofread({ texts: SAMPLE_EMAIL, language }, { user }), (r) => r.fixes],
121
118
  ["rewrite", () => generator.rewrite({ action: "shorter", text: SAMPLE_TEXT, format: "html" }, { user }), (r) => r.text],
122
119
  [
123
120
  "subjects",
@@ -147,36 +144,28 @@ export {
147
144
  AI_MESSAGES,
148
145
  AI_PROVIDERS,
149
146
  DEFAULT_CLAUDE_MODEL,
150
- EMAIL_DRAFT_SCHEMA,
151
- EMAIL_PURPOSES,
152
147
  EmailGenerationError,
153
148
  GENERATION_ERRORS,
154
- MAX_DETAILS_LENGTH,
155
- PURPOSE_CATEGORIES,
149
+ PROOFREAD_SYSTEM_PROMPT,
156
150
  REWRITE_ACTIONS,
157
151
  REWRITE_LANGUAGES,
158
152
  REWRITE_SYSTEM_PROMPT,
159
153
  SUBJECTS_SYSTEM_PROMPT,
160
- SYSTEM_PROMPT,
161
154
  agentEmailModel,
162
- buildUserPrompt,
163
155
  checkAgent,
164
156
  claudeEmailModel,
165
- cleanGenerateInput,
166
157
  createAgentEndpoint,
167
158
  createEmailGenerator,
159
+ createProofreader,
168
160
  createRewriter,
169
161
  createSubjectSuggester,
162
+ diffWords,
170
163
  emailWords,
171
164
  failureCode,
172
165
  listProviderModels,
173
- normalizeGeneratedMjml,
174
- parseDraft,
175
166
  providerEmailModel,
176
- purposeById,
177
- purposesByCategory,
178
167
  resolveProvider,
179
- reviewDraft,
168
+ reviewFix,
180
169
  reviewRewrite,
181
170
  reviewSubjects
182
171
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ai/agentEndpoint.js"],"sourcesContent":["// @ts-check\n/**\n * THE AGENT'S SIDE of `best-email-builder/agent@1` (see agent.js), for an\n * agent written in JavaScript — and `checkAgent`, to try any agent with the\n * builder's own checks before plugging it in.\n *\n * const endpoint = createAgentEndpoint({\n * complete: async ({ task, system, messages, schema, request }) => {\n * const who = await sessionOf(request); // your rights\n * return myAgent.answer({ system, messages, who }); // text, or the answer as an object\n * },\n * });\n * // Next.js: export const POST = endpoint; Express: app.post('/email-builder', toNodeListener(endpoint))\n *\n * const report = await checkAgent({ url: 'http://localhost:8800/email-builder', headers, compile: compileMjml });\n * // { ok, results: [{ task: 'generate', ok, ms, attempts, sample }, …] }\n */\n\nimport { AGENT_ERROR_CODES, AGENT_PROTOCOL, agentEmailModel } from './agent.js';\nimport { GENERATION_ERRORS } from './draft.js';\nimport { createEmailGenerator } from './generator.js';\n\nconst TASKS = ['generate', 'rewrite', 'subjects'];\n/** A repair turn carries the last answer: a whole email at most. */\nconst MAX_BODY_BYTES = 2 * 1024 * 1024;\nconst MAX_MESSAGES = 20;\n\n/**\n * @typedef {{\n * task: 'generate'|'rewrite'|'subjects',\n * system: string,\n * messages: {role: 'user'|'assistant', content: string}[],\n * schema: object,\n * request: Request,\n * signal: AbortSignal,\n * }} AgentTask\n */\n\n/** @param {unknown} body @param {number} status */\nconst json = (body, status = 200) =>\n new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-store' } });\n\n/** @param {string} error @param {string} code @param {number} status */\nconst refuse = (error, code, status) => json({ error, code }, status);\n\n/**\n * What the builder sent, checked; or why not.\n * @param {any} body\n * @returns {string|null} the problem\n */\nfunction problemOf(body) {\n if (!body || typeof body !== 'object') return 'The body is not a JSON object';\n if (body.protocol !== AGENT_PROTOCOL) return `Unsupported protocol ${JSON.stringify(body.protocol)} — this endpoint speaks ${AGENT_PROTOCOL}`;\n if (!TASKS.includes(body.task)) return `Unknown task ${JSON.stringify(body.task)}`;\n if (typeof body.system !== 'string') return '`system` must be a string';\n if (!body.schema || typeof body.schema !== 'object') return '`schema` must be an object';\n const { messages } = body;\n if (!Array.isArray(messages) || !messages.length || messages.length > MAX_MESSAGES) return `\\`messages\\` must hold 1 to ${MAX_MESSAGES} messages`;\n for (const message of messages) {\n if (!message || (message.role !== 'user' && message.role !== 'assistant') || typeof message.content !== 'string') {\n return 'Each message is { role: \"user\"|\"assistant\", content: string }';\n }\n }\n return null;\n}\n\n/**\n * @param {{\n * complete: (task: AgentTask) => Promise<string | object> | string | object,\n * onError?: (error: unknown, request: Request) => void,\n * }} options\n * `complete`: your agent. It answers with the text of its model, or with\n * the answer as an object. To fail with a reason the builder explains, throw\n * an error whose `code` is one of AGENT_ERROR_CODES ('REFUSED',\n * 'MODEL_BUSY'…); any other error is logged with `onError` and answered as\n * MODEL_ERROR, its message kept out of the response.\n * @returns {(request: Request) => Promise<Response>}\n */\nexport function createAgentEndpoint({ complete, onError = (error) => console.error('[agent]', error) } = /** @type {any} */ ({})) {\n if (typeof complete !== 'function') throw new TypeError('createAgentEndpoint: `complete` must be a function — your agent');\n\n return async function agentEndpoint(request) {\n if (request.method.toUpperCase() !== 'POST') return refuse('POST only', 'METHOD_NOT_ALLOWED', 405);\n const length = Number(request.headers.get('content-length') || 0);\n if (length > MAX_BODY_BYTES) return refuse('Request too large', 'TOO_LARGE', 413);\n const raw = await request.text();\n if (raw.length > MAX_BODY_BYTES) return refuse('Request too large', 'TOO_LARGE', 413);\n /** @type {any} */\n let body;\n try {\n body = JSON.parse(raw);\n } catch {\n return refuse('Invalid JSON', 'INVALID_JSON', 400);\n }\n const problem = problemOf(body);\n if (problem) return refuse(problem, 'INVALID_REQUEST', 400);\n\n try {\n const answer = await complete({\n task: body.task,\n system: body.system,\n messages: body.messages.map((/** @type {any} */ m) => ({ role: m.role, content: m.content })),\n schema: body.schema,\n request,\n signal: request.signal,\n });\n if (typeof answer === 'string') return json({ text: answer });\n if (answer && typeof answer === 'object') return json({ output: answer });\n return refuse('The agent gave no answer', 'MODEL_ERROR', 502);\n } catch (error) {\n const code = /** @type {any} */ (error)?.code;\n if (AGENT_ERROR_CODES.includes(code)) {\n const known = GENERATION_ERRORS[/** @type {keyof typeof GENERATION_ERRORS} */ (code)];\n return refuse(String(/** @type {any} */ (error)?.detail || /** @type {any} */ (error)?.message || known.message), code, known.status);\n }\n onError(error, request);\n return refuse('The agent could not answer', 'MODEL_ERROR', 502);\n }\n };\n}\n\nconst SAMPLE_TEXT =\n '<p>Our summer sale starts on Monday: 30% off everything in store, for three days only. ' +\n 'It ends on Wednesday at midnight. <a href=\"https://example.com/sale\">See the offers</a>.</p>';\n\n/**\n * Tries an agent with the builder's own checks: one email written, one text\n * rewritten, subject ideas — in turn, each as the builder would ask it (with\n * its repair turn). Three to six calls to the agent.\n * @param {{\n * url: string,\n * headers?: import('./agent.js').AgentHeaders,\n * user?: any,\n * fetch?: typeof fetch,\n * timeout?: number,\n * compile?: (mjml: string) => Promise<{warnings?: {message: string}[]}>,\n * language?: string,\n * }} options\n * `user`: what `headers` gets, as the handler's `authorize` would give it.\n * `compile`: `compileMjml` (best-email-builder/server), for MJML's own\n * warnings as on the server.\n * @returns {Promise<{ok: boolean, results: {task: string, ok: boolean, ms: number, attempts?: number, sample?: unknown, code?: string, error?: string}[]}>}\n */\nexport async function checkAgent({ url, headers, user, fetch: fetchImpl, timeout, compile, language = 'en' }) {\n const model = agentEmailModel({ url, headers, fetch: fetchImpl, timeout });\n const generator = createEmailGenerator({ model, compile });\n /** @type {[string, () => Promise<any>, (result: any) => unknown][]} */\n const steps = [\n ['generate', () => generator.generate({ purpose: 'welcome', language }, { user }), (r) => ({ subject: r.subject, warnings: r.warnings })],\n ['rewrite', () => generator.rewrite({ action: 'shorter', text: SAMPLE_TEXT, format: 'html' }, { user }), (r) => r.text],\n [\n 'subjects',\n () => generator.suggestSubjects({ subject: 'Summer sale: 30% off', text: 'Our summer sale starts on Monday: 30% off everything in store, for three days only.', language }, { user }),\n (r) => r.subjects,\n ],\n ];\n const results = [];\n for (const [task, run, sampleOf] of steps) {\n const started = Date.now();\n try {\n const result = await run();\n results.push({ task, ok: true, ms: Date.now() - started, attempts: result.attempts, sample: sampleOf(result) });\n } catch (error) {\n const failure = /** @type {any} */ (error);\n results.push({ task, ok: false, ms: Date.now() - started, code: failure?.code || 'ERROR', error: String(failure?.detail || failure?.message || error) });\n }\n }\n return { ok: results.every((result) => result.ok), results };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/ai/agentEndpoint.js"],"sourcesContent":["// @ts-check\n/**\n * THE AGENT'S SIDE of `best-email-builder/agent@1` (see agent.js), for an\n * agent written in JavaScript — and `checkAgent`, to try any agent with the\n * builder's own checks before plugging it in.\n *\n * const endpoint = createAgentEndpoint({\n * complete: async ({ task, system, messages, schema, request }) => {\n * const who = await sessionOf(request); // your rights\n * return myAgent.answer({ system, messages, who }); // text, or the answer as an object\n * },\n * });\n * // Next.js: export const POST = endpoint; Express: app.post('/email-builder', toNodeListener(endpoint))\n *\n * const report = await checkAgent({ url: 'http://localhost:8800/email-builder', headers });\n * // { ok, results: [{ task: 'proofread', ok, ms, attempts, sample }, …] }\n */\n\nimport { AGENT_ERROR_CODES, AGENT_PROTOCOL, agentEmailModel } from './agent.js';\nimport { GENERATION_ERRORS } from './errors.js';\nimport { createEmailGenerator } from './generator.js';\n\nconst TASKS = ['rewrite', 'subjects'];\n/** A repair turn carries the last answer: the texts of an email at most. */\nconst MAX_BODY_BYTES = 2 * 1024 * 1024;\nconst MAX_MESSAGES = 20;\n\n/**\n * @typedef {{\n * task: 'rewrite'|'subjects',\n * system: string,\n * messages: {role: 'user'|'assistant', content: string}[],\n * schema: object,\n * request: Request,\n * signal: AbortSignal,\n * }} AgentTask\n */\n\n/** @param {unknown} body @param {number} status */\nconst json = (body, status = 200) =>\n new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-store' } });\n\n/** @param {string} error @param {string} code @param {number} status */\nconst refuse = (error, code, status) => json({ error, code }, status);\n\n/**\n * What the builder sent, checked; or why not.\n * @param {any} body\n * @returns {string|null} the problem\n */\nfunction problemOf(body) {\n if (!body || typeof body !== 'object') return 'The body is not a JSON object';\n if (body.protocol !== AGENT_PROTOCOL) return `Unsupported protocol ${JSON.stringify(body.protocol)} — this endpoint speaks ${AGENT_PROTOCOL}`;\n if (!TASKS.includes(body.task)) return `Unknown task ${JSON.stringify(body.task)}`;\n if (typeof body.system !== 'string') return '`system` must be a string';\n if (!body.schema || typeof body.schema !== 'object') return '`schema` must be an object';\n const { messages } = body;\n if (!Array.isArray(messages) || !messages.length || messages.length > MAX_MESSAGES) return `\\`messages\\` must hold 1 to ${MAX_MESSAGES} messages`;\n for (const message of messages) {\n if (!message || (message.role !== 'user' && message.role !== 'assistant') || typeof message.content !== 'string') {\n return 'Each message is { role: \"user\"|\"assistant\", content: string }';\n }\n }\n return null;\n}\n\n/**\n * @param {{\n * complete: (task: AgentTask) => Promise<string | object> | string | object,\n * onError?: (error: unknown, request: Request) => void,\n * }} options\n * `complete`: your agent. It answers with the text of its model, or with\n * the answer as an object. To fail with a reason the builder explains, throw\n * an error whose `code` is one of AGENT_ERROR_CODES ('REFUSED',\n * 'MODEL_BUSY'…); any other error is logged with `onError` and answered as\n * MODEL_ERROR, its message kept out of the response.\n * @returns {(request: Request) => Promise<Response>}\n */\nexport function createAgentEndpoint({ complete, onError = (error) => console.error('[agent]', error) } = /** @type {any} */ ({})) {\n if (typeof complete !== 'function') throw new TypeError('createAgentEndpoint: `complete` must be a function — your agent');\n\n return async function agentEndpoint(request) {\n if (request.method.toUpperCase() !== 'POST') return refuse('POST only', 'METHOD_NOT_ALLOWED', 405);\n const length = Number(request.headers.get('content-length') || 0);\n if (length > MAX_BODY_BYTES) return refuse('Request too large', 'TOO_LARGE', 413);\n const raw = await request.text();\n if (raw.length > MAX_BODY_BYTES) return refuse('Request too large', 'TOO_LARGE', 413);\n /** @type {any} */\n let body;\n try {\n body = JSON.parse(raw);\n } catch {\n return refuse('Invalid JSON', 'INVALID_JSON', 400);\n }\n const problem = problemOf(body);\n if (problem) return refuse(problem, 'INVALID_REQUEST', 400);\n\n try {\n const answer = await complete({\n task: body.task,\n system: body.system,\n messages: body.messages.map((/** @type {any} */ m) => ({ role: m.role, content: m.content })),\n schema: body.schema,\n request,\n signal: request.signal,\n });\n if (typeof answer === 'string') return json({ text: answer });\n if (answer && typeof answer === 'object') return json({ output: answer });\n return refuse('The agent gave no answer', 'MODEL_ERROR', 502);\n } catch (error) {\n const code = /** @type {any} */ (error)?.code;\n if (AGENT_ERROR_CODES.includes(code)) {\n const known = GENERATION_ERRORS[/** @type {keyof typeof GENERATION_ERRORS} */ (code)];\n return refuse(String(/** @type {any} */ (error)?.detail || /** @type {any} */ (error)?.message || known.message), code, known.status);\n }\n onError(error, request);\n return refuse('The agent could not answer', 'MODEL_ERROR', 502);\n }\n };\n}\n\nconst SAMPLE_TEXT =\n '<p>Our summer sale starts on Monday: 30% off everything in store, for three days only. ' +\n 'It ends on Wednesday at midnight. <a href=\"https://example.com/sale\">See the offers</a>.</p>';\n\n/** An email with three mistakes to find, and one text without. @type {import('./proofread.js').ProofreadText[]} */\nconst SAMPLE_EMAIL = [\n { id: 'subject', kind: 'subject', format: 'plain', text: 'Our sumer sale starts on Monday' },\n { id: 'text-1', kind: 'text', format: 'html', text: SAMPLE_TEXT },\n { id: 'text-2', kind: 'text', format: 'html', text: '<p>We hope to see you their. <b>Every</b> product are on sale.</p>' },\n { id: 'button-1', kind: 'button', format: 'plain', text: 'See the offers' },\n];\n\n/**\n * Tries an agent with the builder's own checks: an email proofread, one text\n * rewritten, subject ideas — in turn, each as the builder would ask it (with\n * its repair turn). Three to six calls to the agent.\n * @param {{\n * url: string,\n * headers?: import('./agent.js').AgentHeaders,\n * user?: any,\n * fetch?: typeof fetch,\n * timeout?: number,\n * language?: string,\n * }} options\n * `user`: what `headers` gets, as the handler's `authorize` would give it.\n * @returns {Promise<{ok: boolean, results: {task: string, ok: boolean, ms: number, attempts?: number, sample?: unknown, code?: string, error?: string}[]}>}\n */\nexport async function checkAgent({ url, headers, user, fetch: fetchImpl, timeout, language = 'en' }) {\n const model = agentEmailModel({ url, headers, fetch: fetchImpl, timeout });\n const generator = createEmailGenerator({ model });\n /** @type {[string, () => Promise<any>, (result: any) => unknown][]} */\n const steps = [\n ['proofread', () => generator.proofread({ texts: SAMPLE_EMAIL, language }, { user }), (r) => r.fixes],\n ['rewrite', () => generator.rewrite({ action: 'shorter', text: SAMPLE_TEXT, format: 'html' }, { user }), (r) => r.text],\n [\n 'subjects',\n () => generator.suggestSubjects({ subject: 'Summer sale: 30% off', text: 'Our summer sale starts on Monday: 30% off everything in store, for three days only.', language }, { user }),\n (r) => r.subjects,\n ],\n ];\n const results = [];\n for (const [task, run, sampleOf] of steps) {\n const started = Date.now();\n try {\n const result = await run();\n results.push({ task, ok: true, ms: Date.now() - started, attempts: result.attempts, sample: sampleOf(result) });\n } catch (error) {\n const failure = /** @type {any} */ (error);\n results.push({ task, ok: false, ms: Date.now() - started, code: failure?.code || 'ERROR', error: String(failure?.detail || failure?.message || error) });\n }\n }\n return { ok: results.every((result) => result.ok), results };\n}\n"],"mappings":"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","names":[]}