best-email-builder 0.6.0 → 0.7.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 (42) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +41 -4
  3. package/dist/ai-server.js +114 -1
  4. package/dist/ai-server.js.map +1 -1
  5. package/dist/ai.js +504 -169
  6. package/dist/ai.js.map +1 -1
  7. package/dist/builder.js +4 -4
  8. package/dist/{chunk-5W4TUUPD.js → chunk-3RPWH5PC.js} +3 -3
  9. package/dist/{chunk-QPAZLCPO.js → chunk-C6WHIBGH.js} +2 -2
  10. package/dist/{chunk-QPAZLCPO.js.map → chunk-C6WHIBGH.js.map} +1 -1
  11. package/dist/{chunk-NZIQF7IW.js → chunk-CTZ56YJG.js} +192 -23
  12. package/dist/chunk-CTZ56YJG.js.map +1 -0
  13. package/dist/{chunk-NC2YDZSP.js → chunk-QH5OI43L.js} +4 -4
  14. package/dist/{chunk-JU3OUO6X.js → chunk-QRUMXCGW.js} +2 -2
  15. package/dist/{chunk-XEFBPP5Y.js → chunk-UMVBT7NW.js} +3 -3
  16. package/dist/index.js +5 -5
  17. package/dist/library.js +4 -4
  18. package/dist/server.js +4 -4
  19. package/dist/server.js.map +1 -1
  20. package/dist/styles.css +1 -1
  21. package/dist/types/ai/ConnectAi.d.ts +2 -1
  22. package/dist/types/ai/ModelPicker.d.ts +34 -0
  23. package/dist/types/ai/agent.d.ts +31 -0
  24. package/dist/types/ai/agentEndpoint.d.ts +69 -0
  25. package/dist/types/ai/claude.d.ts +2 -1
  26. package/dist/types/ai/draft.d.ts +10 -4
  27. package/dist/types/ai/generator.d.ts +10 -4
  28. package/dist/types/ai/index.d.ts +2 -1
  29. package/dist/types/ai/keys.d.ts +14 -1
  30. package/dist/types/ai/providers.d.ts +48 -5
  31. package/dist/types/ai/rewrite.d.ts +2 -1
  32. package/dist/types/ai/server.d.ts +3 -1
  33. package/dist/types/ai/subjects.d.ts +2 -1
  34. package/dist/types/core/constants.d.ts +1 -1
  35. package/dist/types/engine/NativeAdapter.d.ts +1 -1
  36. package/dist/types/server/handler.d.ts +3 -0
  37. package/package.json +1 -1
  38. package/dist/chunk-NZIQF7IW.js.map +0 -1
  39. /package/dist/{chunk-5W4TUUPD.js.map → chunk-3RPWH5PC.js.map} +0 -0
  40. /package/dist/{chunk-NC2YDZSP.js.map → chunk-QH5OI43L.js.map} +0 -0
  41. /package/dist/{chunk-JU3OUO6X.js.map → chunk-QRUMXCGW.js.map} +0 -0
  42. /package/dist/{chunk-XEFBPP5Y.js.map → chunk-UMVBT7NW.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,43 @@ All notable changes are documented here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.7.0] — 2026-10-15
10
+
11
+ ### Added
12
+ - **The user chooses the model.** In the Connect dialog, under the key, a
13
+ field opens the provider's list of models in place: the default first,
14
+ then the free ones (marked, with a *Free only* filter), then the newest;
15
+ searchable, and an id the list does not have can be used as typed. The
16
+ list is asked with the key (OpenRouter's needs none, so it shows before
17
+ any key); embeddings, speech, pictures and video are left out. The model
18
+ chosen is remembered for each provider and sent with each request.
19
+ `ai.key` gains `model(id?)`, `chooseModel(model, id?)` and
20
+ `listModels(id?, { apiKey, signal })`.
21
+ - **`listProviderModels({ provider, apiKey })`** — the models a provider
22
+ offers for writing, from `/ai` and `/ai/server`. `claudeProvider` lists
23
+ Claude's through the application's SDK and takes the model chosen.
24
+ - The live demo says which model is connected, and its link is now *Change
25
+ provider or model*.
26
+ - **Your own AI agent as the builder's AI** — `agentEmailModel({ url, headers })`
27
+ sends each model turn to an agent of the application's own over a small
28
+ HTTP protocol, `best-email-builder/agent@1` (`{ task, system, messages,
29
+ schema }` in, `{ text }` out, errors as codes the interface explains).
30
+ Generate, Rewrite and Subjects then go through the agent (its tools, its
31
+ knowledge, its users' rights) and its answers are checked like any model's.
32
+ On the server, `headers({ task, user })` gets the user `authorize` returned,
33
+ to forward who asks; the handler now hands that user to the generator and
34
+ its model. The agent's side: `createAgentEndpoint({ complete })` in
35
+ JavaScript, a FastAPI router to copy in `examples/agent/python`;
36
+ `checkAgent({ url, headers })` tries an agent with the builder's own checks.
37
+ See docs/AGENT.md.
38
+ - **`MODEL_UNAVAILABLE`** — a model the provider does not have, or no longer
39
+ (a 404 about the model, OpenRouter's "not a valid model ID"): *Choose
40
+ another one*, instead of *Try again*.
41
+
42
+ ### Changed
43
+ - *Get a key* opens the provider's page in a new tab with `window.open`,
44
+ also where a link's target is ignored (some embedded browsers).
45
+
9
46
  ## [0.6.0] — 2026-10-15
10
47
 
11
48
  ### Added
package/README.md CHANGED
@@ -373,14 +373,46 @@ key is kept needs nothing pasted.
373
373
  const ai = createAi({ provider: ['openai', 'mistral', 'gemini', 'openrouter'], askKey: true });
374
374
  ```
375
375
 
376
+ Under the key, the user can choose the model: the field opens the provider's
377
+ own list (asked with the key; OpenRouter's needs none), the default model
378
+ first, then the free ones, marked, then the newest — searchable, with *Free
379
+ only*, and an id the list does not have can be typed. Embeddings, speech,
380
+ pictures and video are left out. The model chosen is remembered for each
381
+ provider; without the list, the default one still works.
382
+
376
383
  `ai.key` reads and changes it: `provider` (the one in use), `providers`,
377
- `choose(id)`, `get(id?)`, `set(key)`, `forget()`, `request()` (opens the
378
- dialog — to change provider, for one), `subscribe(fn)`, `state()`
379
- (`{ asking, refused, connected }`). This is what the live demo does.
384
+ `choose(id)`, `get(id?)`, `set(key)`, `forget()`, `model(id?)`,
385
+ `chooseModel(model, id?)`, `listModels(id?, { apiKey, signal })`, `request()`
386
+ (opens the dialog — to change provider or model, for one), `subscribe(fn)`,
387
+ `state()` (`{ asking, refused, connected }`). This is what the live demo does.
380
388
 
381
389
  **In the page, with one key for everyone** — prototypes and internal tools
382
390
  only, the key is in the page's code: `createAi({ provider: 'groq', apiKey })`.
383
391
 
392
+ **Your own AI agent** — the assistant your application already has (its
393
+ model, its tools, its knowledge base, its users' rights) becomes the
394
+ builder's AI: Generate, Rewrite and Subjects go through it. It speaks a small
395
+ HTTP protocol, `best-email-builder/agent@1`: one POST per model turn
396
+ (`{ task, system, messages, schema }` in, `{ text }` out). The builder still
397
+ writes the prompts and checks every answer, so the agent needs to know nothing
398
+ of MJML.
399
+
400
+ ```js
401
+ generator: createEmailGenerator({
402
+ model: agentEmailModel({
403
+ url: 'http://agent.internal:8800/email-builder',
404
+ headers: ({ user }) => ({ 'X-Auth-User': signFor(user) }), // who asks: the user authorize returned
405
+ }),
406
+ compile: compileMjml,
407
+ }),
408
+ ```
409
+
410
+ The agent's side is one FastAPI file to copy
411
+ ([`examples/agent/python`](examples/agent/python)) or `createAgentEndpoint`
412
+ in JavaScript, and `checkAgent({ url, headers })` tries an agent with the
413
+ builder's own checks. The protocol, the errors and both sides:
414
+ [docs/AGENT.md](docs/AGENT.md).
415
+
384
416
  #### Providers
385
417
 
386
418
  Every provider here speaks the OpenAI *chat completions* API; nothing to
@@ -413,6 +445,10 @@ providerEmailModel({ provider: { baseUrl: 'http://localhost:1234/v1', model: 'qw
413
445
  (`reasoning: { effort: 'low' }`) and given room for it (`maxTokens: 32000`;
414
446
  16 000 elsewhere). For drafts in seconds, use a free tier with a key of its
415
447
  own (Gemini, Groq, Cerebras) or a paid model.
448
+ - `listProviderModels({ provider, apiKey })` lists the models a provider
449
+ offers for writing, as the dialog does (`{ id, name, free }`, the default
450
+ first): its `/models`, or the provider's own `listModels` (`claudeProvider`
451
+ has one).
416
452
  - `body` adds fields of your own to each request — OpenRouter's fallback list,
417
453
  for one: `providerEmailModel({ provider: 'openrouter', apiKey, body: { models: [...] } })`.
418
454
  It cannot change the model, the messages or the output limit.
@@ -421,7 +457,8 @@ providerEmailModel({ provider: { baseUrl: 'http://localhost:1234/v1', model: 'qw
421
457
  remembered. A model whose structured output breaks the JSON (some do on the
422
458
  quotes of HTML) can be told to skip it: `provider: { …, format: 'text' }`.
423
459
  - The provider's failures become errors the interface explains, in its
424
- language: `KEY_MISSING`, `KEY_REFUSED`, `MODEL_CREDITS`, `MODEL_BUSY`,
460
+ language: `KEY_MISSING`, `KEY_REFUSED`, `MODEL_UNAVAILABLE` (the provider
461
+ does not have that model, or no longer), `MODEL_CREDITS`, `MODEL_BUSY`,
425
462
  `MODEL_ERROR` (with the provider's own text in `detail`). An exhausted
426
463
  quota is `MODEL_CREDITS`, not `MODEL_BUSY`, even when it comes as a 429
427
464
  (OpenAI's `insufficient_quota`) or a 400 (Anthropic's credit balance);
package/dist/ai-server.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import {
2
+ AGENT_ERROR_CODES,
3
+ AGENT_PROTOCOL,
2
4
  AI_MESSAGES,
3
5
  AI_PROVIDERS,
4
6
  DEFAULT_CLAUDE_MODEL,
@@ -13,6 +15,7 @@ import {
13
15
  REWRITE_SYSTEM_PROMPT,
14
16
  SUBJECTS_SYSTEM_PROMPT,
15
17
  SYSTEM_PROMPT,
18
+ agentEmailModel,
16
19
  buildUserPrompt,
17
20
  claudeEmailModel,
18
21
  cleanGenerateInput,
@@ -21,6 +24,7 @@ import {
21
24
  createSubjectSuggester,
22
25
  emailWords,
23
26
  failureCode,
27
+ listProviderModels,
24
28
  normalizeGeneratedMjml,
25
29
  parseDraft,
26
30
  providerEmailModel,
@@ -30,11 +34,116 @@ import {
30
34
  reviewDraft,
31
35
  reviewRewrite,
32
36
  reviewSubjects
33
- } from "./chunk-NZIQF7IW.js";
37
+ } from "./chunk-CTZ56YJG.js";
34
38
  import "./chunk-OWVXKMLI.js";
35
39
  import "./chunk-W424KPCR.js";
36
40
  import "./chunk-JI36FNWE.js";
41
+
42
+ // src/ai/agentEndpoint.js
43
+ var TASKS = ["generate", "rewrite", "subjects"];
44
+ var MAX_BODY_BYTES = 2 * 1024 * 1024;
45
+ var MAX_MESSAGES = 20;
46
+ var json = (body, status = 200) => new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" } });
47
+ var refuse = (error, code, status) => json({ error, code }, status);
48
+ function problemOf(body) {
49
+ if (!body || typeof body !== "object") return "The body is not a JSON object";
50
+ if (body.protocol !== AGENT_PROTOCOL) return `Unsupported protocol ${JSON.stringify(body.protocol)} \u2014 this endpoint speaks ${AGENT_PROTOCOL}`;
51
+ if (!TASKS.includes(body.task)) return `Unknown task ${JSON.stringify(body.task)}`;
52
+ if (typeof body.system !== "string") return "`system` must be a string";
53
+ if (!body.schema || typeof body.schema !== "object") return "`schema` must be an object";
54
+ const { messages } = body;
55
+ if (!Array.isArray(messages) || !messages.length || messages.length > MAX_MESSAGES) return `\`messages\` must hold 1 to ${MAX_MESSAGES} messages`;
56
+ for (const message of messages) {
57
+ if (!message || message.role !== "user" && message.role !== "assistant" || typeof message.content !== "string") {
58
+ return 'Each message is { role: "user"|"assistant", content: string }';
59
+ }
60
+ }
61
+ return null;
62
+ }
63
+ function createAgentEndpoint({ complete, onError = (error) => console.error("[agent]", error) } = (
64
+ /** @type {any} */
65
+ {}
66
+ )) {
67
+ if (typeof complete !== "function") throw new TypeError("createAgentEndpoint: `complete` must be a function \u2014 your agent");
68
+ return async function agentEndpoint(request) {
69
+ if (request.method.toUpperCase() !== "POST") return refuse("POST only", "METHOD_NOT_ALLOWED", 405);
70
+ const length = Number(request.headers.get("content-length") || 0);
71
+ if (length > MAX_BODY_BYTES) return refuse("Request too large", "TOO_LARGE", 413);
72
+ const raw = await request.text();
73
+ if (raw.length > MAX_BODY_BYTES) return refuse("Request too large", "TOO_LARGE", 413);
74
+ let body;
75
+ try {
76
+ body = JSON.parse(raw);
77
+ } catch {
78
+ return refuse("Invalid JSON", "INVALID_JSON", 400);
79
+ }
80
+ const problem = problemOf(body);
81
+ if (problem) return refuse(problem, "INVALID_REQUEST", 400);
82
+ try {
83
+ const answer = await complete({
84
+ task: body.task,
85
+ system: body.system,
86
+ messages: body.messages.map((m) => ({ role: m.role, content: m.content })),
87
+ schema: body.schema,
88
+ request,
89
+ signal: request.signal
90
+ });
91
+ if (typeof answer === "string") return json({ text: answer });
92
+ if (answer && typeof answer === "object") return json({ output: answer });
93
+ return refuse("The agent gave no answer", "MODEL_ERROR", 502);
94
+ } catch (error) {
95
+ const code = (
96
+ /** @type {any} */
97
+ error?.code
98
+ );
99
+ if (AGENT_ERROR_CODES.includes(code)) {
100
+ const known = GENERATION_ERRORS[
101
+ /** @type {keyof typeof GENERATION_ERRORS} */
102
+ code
103
+ ];
104
+ return refuse(String(
105
+ /** @type {any} */
106
+ error?.detail || /** @type {any} */
107
+ error?.message || known.message
108
+ ), code, known.status);
109
+ }
110
+ onError(error, request);
111
+ return refuse("The agent could not answer", "MODEL_ERROR", 502);
112
+ }
113
+ };
114
+ }
115
+ 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" }) {
117
+ const model = agentEmailModel({ url, headers, fetch: fetchImpl, timeout });
118
+ const generator = createEmailGenerator({ model, compile });
119
+ const steps = [
120
+ ["generate", () => generator.generate({ purpose: "welcome", language }, { user }), (r) => ({ subject: r.subject, warnings: r.warnings })],
121
+ ["rewrite", () => generator.rewrite({ action: "shorter", text: SAMPLE_TEXT, format: "html" }, { user }), (r) => r.text],
122
+ [
123
+ "subjects",
124
+ () => 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 }),
125
+ (r) => r.subjects
126
+ ]
127
+ ];
128
+ const results = [];
129
+ for (const [task, run, sampleOf] of steps) {
130
+ const started = Date.now();
131
+ try {
132
+ const result = await run();
133
+ results.push({ task, ok: true, ms: Date.now() - started, attempts: result.attempts, sample: sampleOf(result) });
134
+ } catch (error) {
135
+ const failure = (
136
+ /** @type {any} */
137
+ error
138
+ );
139
+ results.push({ task, ok: false, ms: Date.now() - started, code: failure?.code || "ERROR", error: String(failure?.detail || failure?.message || error) });
140
+ }
141
+ }
142
+ return { ok: results.every((result) => result.ok), results };
143
+ }
37
144
  export {
145
+ AGENT_ERROR_CODES,
146
+ AGENT_PROTOCOL,
38
147
  AI_MESSAGES,
39
148
  AI_PROVIDERS,
40
149
  DEFAULT_CLAUDE_MODEL,
@@ -49,14 +158,18 @@ export {
49
158
  REWRITE_SYSTEM_PROMPT,
50
159
  SUBJECTS_SYSTEM_PROMPT,
51
160
  SYSTEM_PROMPT,
161
+ agentEmailModel,
52
162
  buildUserPrompt,
163
+ checkAgent,
53
164
  claudeEmailModel,
54
165
  cleanGenerateInput,
166
+ createAgentEndpoint,
55
167
  createEmailGenerator,
56
168
  createRewriter,
57
169
  createSubjectSuggester,
58
170
  emailWords,
59
171
  failureCode,
172
+ listProviderModels,
60
173
  normalizeGeneratedMjml,
61
174
  parseDraft,
62
175
  providerEmailModel,
@@ -1 +1 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","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, 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsBA,IAAM,QAAQ,CAAC,YAAY,WAAW,UAAU;AAEhD,IAAM,iBAAiB,IAAI,OAAO;AAClC,IAAM,eAAe;AAcrB,IAAM,OAAO,CAAC,MAAM,SAAS,QAC3B,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG,EAAE,QAAQ,SAAS,EAAE,gBAAgB,mCAAmC,iBAAiB,WAAW,EAAE,CAAC;AAG5I,IAAM,SAAS,CAAC,OAAO,MAAM,WAAW,KAAK,EAAE,OAAO,KAAK,GAAG,MAAM;AAOpE,SAAS,UAAU,MAAM;AACvB,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,MAAI,KAAK,aAAa,eAAgB,QAAO,wBAAwB,KAAK,UAAU,KAAK,QAAQ,CAAC,gCAA2B,cAAc;AAC3I,MAAI,CAAC,MAAM,SAAS,KAAK,IAAI,EAAG,QAAO,gBAAgB,KAAK,UAAU,KAAK,IAAI,CAAC;AAChF,MAAI,OAAO,KAAK,WAAW,SAAU,QAAO;AAC5C,MAAI,CAAC,KAAK,UAAU,OAAO,KAAK,WAAW,SAAU,QAAO;AAC5D,QAAM,EAAE,SAAS,IAAI;AACrB,MAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,CAAC,SAAS,UAAU,SAAS,SAAS,aAAc,QAAO,+BAA+B,YAAY;AACtI,aAAW,WAAW,UAAU;AAC9B,QAAI,CAAC,WAAY,QAAQ,SAAS,UAAU,QAAQ,SAAS,eAAgB,OAAO,QAAQ,YAAY,UAAU;AAChH,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAcO,SAAS,oBAAoB,EAAE,UAAU,UAAU,CAAC,UAAU,QAAQ,MAAM,WAAW,KAAK,EAAE;AAAA;AAAA,EAAwB,CAAC;AAAA,GAAI;AAChI,MAAI,OAAO,aAAa,WAAY,OAAM,IAAI,UAAU,sEAAiE;AAEzH,SAAO,eAAe,cAAc,SAAS;AAC3C,QAAI,QAAQ,OAAO,YAAY,MAAM,OAAQ,QAAO,OAAO,aAAa,sBAAsB,GAAG;AACjG,UAAM,SAAS,OAAO,QAAQ,QAAQ,IAAI,gBAAgB,KAAK,CAAC;AAChE,QAAI,SAAS,eAAgB,QAAO,OAAO,qBAAqB,aAAa,GAAG;AAChF,UAAM,MAAM,MAAM,QAAQ,KAAK;AAC/B,QAAI,IAAI,SAAS,eAAgB,QAAO,OAAO,qBAAqB,aAAa,GAAG;AAEpF,QAAI;AACJ,QAAI;AACF,aAAO,KAAK,MAAM,GAAG;AAAA,IACvB,QAAQ;AACN,aAAO,OAAO,gBAAgB,gBAAgB,GAAG;AAAA,IACnD;AACA,UAAM,UAAU,UAAU,IAAI;AAC9B,QAAI,QAAS,QAAO,OAAO,SAAS,mBAAmB,GAAG;AAE1D,QAAI;AACF,YAAM,SAAS,MAAM,SAAS;AAAA,QAC5B,MAAM,KAAK;AAAA,QACX,QAAQ,KAAK;AAAA,QACb,UAAU,KAAK,SAAS,IAAI,CAAoB,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,QAAQ,EAAE;AAAA,QAC5F,QAAQ,KAAK;AAAA,QACb;AAAA,QACA,QAAQ,QAAQ;AAAA,MAClB,CAAC;AACD,UAAI,OAAO,WAAW,SAAU,QAAO,KAAK,EAAE,MAAM,OAAO,CAAC;AAC5D,UAAI,UAAU,OAAO,WAAW,SAAU,QAAO,KAAK,EAAE,QAAQ,OAAO,CAAC;AACxE,aAAO,OAAO,4BAA4B,eAAe,GAAG;AAAA,IAC9D,SAAS,OAAO;AACd,YAAM;AAAA;AAAA,QAA2B,OAAQ;AAAA;AACzC,UAAI,kBAAkB,SAAS,IAAI,GAAG;AACpC,cAAM,QAAQ;AAAA;AAAA,UAAiE;AAAA,QAAK;AACpF,eAAO,OAAO;AAAA;AAAA,UAA2B,OAAQ;AAAA,UAA8B,OAAQ,WAAW,MAAM;AAAA,QAAO,GAAG,MAAM,MAAM,MAAM;AAAA,MACtI;AACA,cAAQ,OAAO,OAAO;AACtB,aAAO,OAAO,8BAA8B,eAAe,GAAG;AAAA,IAChE;AAAA,EACF;AACF;AAEA,IAAM,cACJ;AAqBF,eAAsB,WAAW,EAAE,KAAK,SAAS,MAAM,OAAO,WAAW,SAAS,SAAS,WAAW,KAAK,GAAG;AAC5G,QAAM,QAAQ,gBAAgB,EAAE,KAAK,SAAS,OAAO,WAAW,QAAQ,CAAC;AACzE,QAAM,YAAY,qBAAqB,EAAE,OAAO,QAAQ,CAAC;AAEzD,QAAM,QAAQ;AAAA,IACZ,CAAC,YAAY,MAAM,UAAU,SAAS,EAAE,SAAS,WAAW,SAAS,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,UAAU,EAAE,SAAS,EAAE;AAAA,IACxI,CAAC,WAAW,MAAM,UAAU,QAAQ,EAAE,QAAQ,WAAW,MAAM,aAAa,QAAQ,OAAO,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI;AAAA,IACtH;AAAA,MACE;AAAA,MACA,MAAM,UAAU,gBAAgB,EAAE,SAAS,wBAAwB,MAAM,uFAAuF,SAAS,GAAG,EAAE,KAAK,CAAC;AAAA,MACpL,CAAC,MAAM,EAAE;AAAA,IACX;AAAA,EACF;AACA,QAAM,UAAU,CAAC;AACjB,aAAW,CAAC,MAAM,KAAK,QAAQ,KAAK,OAAO;AACzC,UAAM,UAAU,KAAK,IAAI;AACzB,QAAI;AACF,YAAM,SAAS,MAAM,IAAI;AACzB,cAAQ,KAAK,EAAE,MAAM,IAAI,MAAM,IAAI,KAAK,IAAI,IAAI,SAAS,UAAU,OAAO,UAAU,QAAQ,SAAS,MAAM,EAAE,CAAC;AAAA,IAChH,SAAS,OAAO;AACd,YAAM;AAAA;AAAA,QAA8B;AAAA;AACpC,cAAQ,KAAK,EAAE,MAAM,IAAI,OAAO,IAAI,KAAK,IAAI,IAAI,SAAS,MAAM,SAAS,QAAQ,SAAS,OAAO,OAAO,SAAS,UAAU,SAAS,WAAW,KAAK,EAAE,CAAC;AAAA,IACzJ;AAAA,EACF;AACA,SAAO,EAAE,IAAI,QAAQ,MAAM,CAAC,WAAW,OAAO,EAAE,GAAG,QAAQ;AAC7D;","names":[]}