best-email-builder 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +110 -0
- package/README.md +7 -2
- package/dist/ai-server.js +21 -32
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.js +309 -254
- package/dist/ai.js.map +1 -1
- package/dist/builder.js +9 -11
- package/dist/chunk-47WQSNER.js +875 -0
- package/dist/chunk-47WQSNER.js.map +1 -0
- package/dist/{chunk-AS2EPMA6.js → chunk-7HDOSA5G.js} +695 -170
- package/dist/chunk-7HDOSA5G.js.map +1 -0
- package/dist/{chunk-62HBUBBI.js → chunk-A2MEYOR3.js} +301 -768
- package/dist/chunk-A2MEYOR3.js.map +1 -0
- package/dist/{chunk-D4JBPAJ5.js → chunk-ACAQ2QTU.js} +83 -48
- package/dist/chunk-ACAQ2QTU.js.map +1 -0
- package/dist/chunk-COZXOBR3.js +294 -0
- package/dist/chunk-COZXOBR3.js.map +1 -0
- package/dist/{chunk-LUM7J5FF.js → chunk-NV6IV4CU.js} +2 -2
- package/dist/{chunk-P7ABBLX6.js → chunk-ONYPTZFK.js} +2 -2
- package/dist/{chunk-AXIM6E7T.js → chunk-PFGTD5CS.js} +2 -2
- package/dist/{chunk-VJKF6SR4.js → chunk-PYD5FOL2.js} +464 -1511
- package/dist/chunk-PYD5FOL2.js.map +1 -0
- package/dist/chunk-SQJ3TKEL.js +855 -0
- package/dist/chunk-SQJ3TKEL.js.map +1 -0
- package/dist/{chunk-G4U6PSF6.js → chunk-YQGPBUZ3.js} +2 -2
- package/dist/index.js +32 -28
- package/dist/index.js.map +1 -1
- package/dist/library.js +12 -10
- package/dist/server.js +182 -26
- package/dist/server.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/ai/ProofreadButton.d.ts +4 -0
- package/dist/types/ai/SubjectIdeas.d.ts +1 -1
- package/dist/types/ai/agent.d.ts +1 -1
- package/dist/types/ai/agentEndpoint.d.ts +3 -11
- package/dist/types/ai/claude.d.ts +3 -3
- package/dist/types/ai/createAi.d.ts +9 -14
- package/dist/types/ai/diff.d.ts +24 -0
- package/dist/types/ai/{draft.d.ts → errors.d.ts} +30 -81
- package/dist/types/ai/generator.d.ts +25 -91
- package/dist/types/ai/index.d.ts +2 -2
- package/dist/types/ai/proofread.d.ts +99 -0
- package/dist/types/ai/rewrite.d.ts +27 -8
- package/dist/types/ai/server.d.ts +4 -4
- package/dist/types/ai/subjects.d.ts +6 -0
- package/dist/types/core/EmailEditor.d.ts +10 -0
- package/dist/types/core/constants.d.ts +1 -1
- package/dist/types/engine/NativeAdapter.d.ts +1 -1
- package/dist/types/features/tags/tags.d.ts +55 -0
- package/dist/types/features/templates/gallery.d.ts +4 -14
- package/dist/types/features/templates/thumbnails.d.ts +13 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/library.d.ts +2 -1
- package/dist/types/server/handler.d.ts +6 -3
- package/dist/types/server/index.d.ts +2 -0
- package/dist/types/server/tags.d.ts +64 -0
- package/dist/types/storage/client.d.ts +33 -4
- package/dist/types/ui/EmailStudio.d.ts +8 -8
- package/dist/types/ui/library/TagBadge.d.ts +19 -0
- package/dist/types/ui/library/TagsDialog.d.ts +43 -0
- package/dist/types/ui/library/TemplateLibrary.d.ts +6 -14
- package/dist/types/ui/primitives/misc.d.ts +9 -0
- package/dist/types/ui/primitives/searchShortcut.d.ts +2 -2
- package/package.json +1 -1
- package/dist/chunk-4IXP5YVN.js +0 -119
- package/dist/chunk-4IXP5YVN.js.map +0 -1
- package/dist/chunk-62HBUBBI.js.map +0 -1
- package/dist/chunk-AS2EPMA6.js.map +0 -1
- package/dist/chunk-D4JBPAJ5.js.map +0 -1
- package/dist/chunk-FFSSAMHH.js +0 -527
- package/dist/chunk-FFSSAMHH.js.map +0 -1
- package/dist/chunk-GWXY3MWQ.js +0 -595
- package/dist/chunk-GWXY3MWQ.js.map +0 -1
- package/dist/chunk-OWVXKMLI.js +0 -121
- package/dist/chunk-OWVXKMLI.js.map +0 -1
- package/dist/chunk-VJKF6SR4.js.map +0 -1
- package/dist/types/ai/GenerateButton.d.ts +0 -4
- package/dist/types/ai/prompt.d.ts +0 -76
- package/dist/types/ai/purposes.d.ts +0 -48
- /package/dist/{chunk-LUM7J5FF.js.map → chunk-NV6IV4CU.js.map} +0 -0
- /package/dist/{chunk-P7ABBLX6.js.map → chunk-ONYPTZFK.js.map} +0 -0
- /package/dist/{chunk-AXIM6E7T.js.map → chunk-PFGTD5CS.js.map} +0 -0
- /package/dist/{chunk-G4U6PSF6.js.map → chunk-YQGPBUZ3.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,116 @@ All notable changes are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.11.0] — 2026-10-19
|
|
10
|
+
|
|
11
|
+
The library loses its bases and gains tags, which also filter it; the
|
|
12
|
+
editor's *Templates* dialog gains a search and tag filters. This breaks
|
|
13
|
+
code that passed `sender` or `templates` to `TemplateLibrary`, or read
|
|
14
|
+
`onCreate`'s argument (see *Changed* and *Removed*).
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **Tags** on templates, in the library: up to three per template
|
|
18
|
+
(`MAX_TEMPLATE_TAGS`), shown in the list before the template's date, and
|
|
19
|
+
found by the search. The *Tags* line of the details opens a dialog to tick
|
|
20
|
+
them and to create, rename, recolor or delete the workspace's tags (a name
|
|
21
|
+
of 24 characters at most, unique case aside, one of the twelve
|
|
22
|
+
`TAG_COLORS`). Each change is saved at once; a deleted tag leaves every
|
|
23
|
+
template; a copy keeps its template's tags.
|
|
24
|
+
- The storage methods `listTags()`, `saveTag(tag, { create })`,
|
|
25
|
+
`deleteTag(id)` and `setTemplateTags(id, tagIds)`, in the memory storage
|
|
26
|
+
and in `createHttpStorage({ tags: true })`. `list()` and `load()` give each
|
|
27
|
+
template's `tags` (ids).
|
|
28
|
+
- On the server: the `tagStore` option (`createMemoryTagRepository()` is the
|
|
29
|
+
reference), the routes `GET`/`POST {base}/tags`, `PUT`/`DELETE
|
|
30
|
+
{base}/tags/:id` and `PUT {base}/templates/:id/tags`, `createTagService`.
|
|
31
|
+
`validateTag`, `TAG_COLORS`, `TAG_LIMITS`, `MAX_TEMPLATE_TAGS` and
|
|
32
|
+
`MAX_TAGS` are exported (`validateTag`, `TAG_COLORS` and
|
|
33
|
+
`MAX_TEMPLATE_TAGS` from the browser entries too).
|
|
34
|
+
- `TemplateLibrary`'s `deleteDisabled`: a sentence shown instead of deleting,
|
|
35
|
+
*Delete* staying in place, dimmed. For a public demo.
|
|
36
|
+
- `createMemoryStorage({ compile })`: the templates keep their compiled HTML,
|
|
37
|
+
as on the server, so the library shows *Copy HTML* and *Download*.
|
|
38
|
+
- Tag filters under the library's search: the tags some template carries,
|
|
39
|
+
one or several (a template with any of them), or *All*; with the search,
|
|
40
|
+
both apply.
|
|
41
|
+
- The editor's *Templates* dialog has a search (names, descriptions, tags)
|
|
42
|
+
and the bases' tags as filters: one or several (a base with any of them),
|
|
43
|
+
or *All*. A gallery entry takes `tags`, translated like its name.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
- `TemplateLibrary` lists only the templates; a new one starts from a base
|
|
47
|
+
with the editor's *Templates* button. `onCreate()` takes no argument.
|
|
48
|
+
- `EmailStudio` passes `sender` and `templates` to the editor only, which
|
|
49
|
+
keeps its *Templates* button.
|
|
50
|
+
- The live demo opens on five templates of the gallery, three of them
|
|
51
|
+
tagged; its *Delete* is turned off.
|
|
52
|
+
- The *Templates* dialog's cards all have the same height: a name or a
|
|
53
|
+
description too long fades out at the card's edge, and the thumbnails show
|
|
54
|
+
no scrollbar. Only the cards scroll; the search and the tags stay.
|
|
55
|
+
|
|
56
|
+
### Removed
|
|
57
|
+
- The *Builder* badge of every row of the library: the same on every
|
|
58
|
+
template, it only looked like a tag. The details still give the origin.
|
|
59
|
+
- The *Bases* section of the library, its *Start from this base* button, and
|
|
60
|
+
`TemplateLibrary`'s `sender` and `templates` props.
|
|
61
|
+
- `TEMPLATE_GALLERY` and `buildGallery` from `best-email-builder/library`
|
|
62
|
+
(they stay in `best-email-builder/builder` and `best-email-builder`).
|
|
63
|
+
|
|
64
|
+
## [0.10.0] — 2026-10-19
|
|
65
|
+
|
|
66
|
+
The AI no longer writes whole emails: it checks and writes their texts.
|
|
67
|
+
This breaks code that used *Generate* (see *Removed*).
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
- **Proofread**, in the toolbar: the spelling, grammar and punctuation
|
|
71
|
+
mistakes of every text the reader sees (subject, preview text, text
|
|
72
|
+
blocks, buttons, menu links), found in one call. The dialog lists the
|
|
73
|
+
texts with a mistake, the old words struck out and the new ones next to
|
|
74
|
+
them; each correction is applied on its own or all at once, and the
|
|
75
|
+
corrections applied together are one step of the history. A text edited
|
|
76
|
+
since it was read is left as it is. A correction is kept only when it
|
|
77
|
+
changes words, keeps the markup, links and merge tags, and touches at most
|
|
78
|
+
a third of the text; otherwise the model gets one repair turn, then it is
|
|
79
|
+
left out.
|
|
80
|
+
- `ai.proofread`, `generator.proofread` and the handler's
|
|
81
|
+
`POST {base}/proofread` (60 per user per hour, `rateLimits.proofreadPerHour`).
|
|
82
|
+
`PROOFREAD_SYSTEM_PROMPT`, `createProofreader` and `reviewFix` are exported
|
|
83
|
+
from `best-email-builder/ai/server`, `diffWords` from both AI entries.
|
|
84
|
+
- **Write with AI**, in the properties of a text block: a topic ("the sale
|
|
85
|
+
ends Sunday"), and the text is written anew to fit the rest of the email,
|
|
86
|
+
a title as one line, a `[placeholder]` where a fact is missing. It is the
|
|
87
|
+
`write` action of `rewrite` (`{ action: 'write', instruction, context }`).
|
|
88
|
+
- `editor.setContents([{ id, content }])`: the contents of several
|
|
89
|
+
elements changed in one step of the history.
|
|
90
|
+
|
|
91
|
+
### Changed
|
|
92
|
+
- `claudeEmailModel` uses `effort: 'low'` for every task by default (every
|
|
93
|
+
answer is a short one now); `effort` changes it. `rewriteEffort` still
|
|
94
|
+
works as its old name.
|
|
95
|
+
- The agent protocol (`best-email-builder/agent@1`) has two tasks,
|
|
96
|
+
`rewrite` and `subjects`: a proofread is sent as `rewrite`, so agents
|
|
97
|
+
written for 0.9 keep working. `checkAgent` tries a proofread, a rewrite
|
|
98
|
+
and subject ideas.
|
|
99
|
+
- The live demo explains Proofread and Write with AI.
|
|
100
|
+
|
|
101
|
+
### Removed
|
|
102
|
+
- **Generate**: the toolbar button, `ai.generate`, `generator.generate`, the
|
|
103
|
+
handler's `POST {base}/generate` (now 404) and `rateLimits.generatePerHour`.
|
|
104
|
+
`createAi({ generate: fn })` throws; `generate: false` is still accepted.
|
|
105
|
+
- The 68 purposes and the email prompt: `EMAIL_PURPOSES`,
|
|
106
|
+
`PURPOSE_CATEGORIES`, `purposeById`, `purposesByCategory`, `SYSTEM_PROMPT`,
|
|
107
|
+
`EMAIL_DRAFT_SCHEMA`, `buildUserPrompt`, `cleanGenerateInput`,
|
|
108
|
+
`MAX_DETAILS_LENGTH`, `normalizeGeneratedMjml`, `parseDraft` and
|
|
109
|
+
`reviewDraft`.
|
|
110
|
+
- The `compile` option of `createEmailGenerator` and `checkAgent`: nothing
|
|
111
|
+
compiles MJML any more.
|
|
112
|
+
|
|
113
|
+
### Fixed
|
|
114
|
+
- The gallery's *Welcome*, *Event invitation* and *Newsletter* were
|
|
115
|
+
translated only when the AI module was loaded (its dictionary had the same
|
|
116
|
+
words). The core has them now, and `npm run check:i18n` checks the
|
|
117
|
+
gallery's names and descriptions.
|
|
118
|
+
|
|
9
119
|
## [0.9.0] — 2026-10-16
|
|
10
120
|
|
|
11
121
|
### Added
|
package/README.md
CHANGED
|
@@ -65,7 +65,9 @@ once and given away.
|
|
|
65
65
|
unsubscribe link.
|
|
66
66
|
|
|
67
67
|
**AI, optional**
|
|
68
|
-
- *
|
|
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.
|
|
@@ -74,6 +76,9 @@ once and given away.
|
|
|
74
76
|
server, or in the page with each user's own key.
|
|
75
77
|
|
|
76
78
|
**Your app**
|
|
79
|
+
- A template library: search, favorites, up to three colored tags per
|
|
80
|
+
template that also filter the list, and *Edit*, *Duplicate*, *Copy HTML*,
|
|
81
|
+
*Download*, *Delete*.
|
|
77
82
|
- Merge-tag variables, including ones your users create.
|
|
78
83
|
- Your brand read from your website: logo, colors and font.
|
|
79
84
|
- One Fetch-API handler for Next.js, Hono, Bun, Deno, Cloudflare Workers and Express.
|
|
@@ -140,7 +145,7 @@ the layout, Next.js, Express and image hosting.
|
|
|
140
145
|
| [Editor and library](docs/EDITOR.md) | The three ways to mount them, every prop, theming. |
|
|
141
146
|
| [Server](docs/SERVER.md) | Routes, handler options, the storage contract, your database. |
|
|
142
147
|
| [Variables and sending](docs/VARIABLES.md) | Merge tags, custom variables, personalizing at send time. |
|
|
143
|
-
| [AI](docs/AI.md) |
|
|
148
|
+
| [AI](docs/AI.md) | Proofread, Write, Rewrite, Subjects. Where the model runs, and the providers. |
|
|
144
149
|
| [Your own AI agent](docs/AGENT.md) | The `agent@1` protocol. |
|
|
145
150
|
| [Brand](docs/BRAND.md) | Logo, colors and font, read from a website. |
|
|
146
151
|
| [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
|
-
|
|
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
|
-
|
|
26
|
+
reviewFix,
|
|
35
27
|
reviewRewrite,
|
|
36
28
|
reviewSubjects
|
|
37
|
-
} from "./chunk-
|
|
38
|
-
import "./chunk-
|
|
39
|
-
import "./chunk-
|
|
40
|
-
import "./chunk-62HBUBBI.js";
|
|
29
|
+
} from "./chunk-PYD5FOL2.js";
|
|
30
|
+
import "./chunk-PFGTD5CS.js";
|
|
31
|
+
import "./chunk-A2MEYOR3.js";
|
|
41
32
|
|
|
42
33
|
// src/ai/agentEndpoint.js
|
|
43
|
-
var TASKS = ["
|
|
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
|
-
|
|
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
|
|
115
|
+
const generator = createEmailGenerator({ model });
|
|
119
116
|
const steps = [
|
|
120
|
-
["
|
|
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
|
-
|
|
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
|
-
|
|
168
|
+
reviewFix,
|
|
180
169
|
reviewRewrite,
|
|
181
170
|
reviewSubjects
|
|
182
171
|
};
|
package/dist/ai-server.js.map
CHANGED
|
@@ -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":[]}
|