best-email-builder 0.5.1 → 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 (43) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +74 -8
  3. package/dist/ai-server.js +116 -1
  4. package/dist/ai-server.js.map +1 -1
  5. package/dist/ai.js +639 -176
  6. package/dist/ai.js.map +1 -1
  7. package/dist/builder.js +4 -4
  8. package/dist/{chunk-2Y7CDXZM.js → chunk-3RPWH5PC.js} +3 -3
  9. package/dist/{chunk-H4W65GBX.js → chunk-C6WHIBGH.js} +2 -2
  10. package/dist/{chunk-H4W65GBX.js.map → chunk-C6WHIBGH.js.map} +1 -1
  11. package/dist/{chunk-BAA5XMWY.js → chunk-CTZ56YJG.js} +252 -28
  12. package/dist/chunk-CTZ56YJG.js.map +1 -0
  13. package/dist/{chunk-7QQH6MTV.js → chunk-QH5OI43L.js} +4 -4
  14. package/dist/{chunk-N7DEVAIP.js → chunk-QRUMXCGW.js} +2 -2
  15. package/dist/{chunk-2RKVJQSD.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 +5 -0
  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 +31 -5
  26. package/dist/types/ai/createAi.d.ts +5 -3
  27. package/dist/types/ai/draft.d.ts +10 -4
  28. package/dist/types/ai/generator.d.ts +10 -4
  29. package/dist/types/ai/index.d.ts +3 -2
  30. package/dist/types/ai/keys.d.ts +28 -26
  31. package/dist/types/ai/providers.d.ts +58 -1
  32. package/dist/types/ai/rewrite.d.ts +2 -1
  33. package/dist/types/ai/server.d.ts +3 -1
  34. package/dist/types/ai/subjects.d.ts +2 -1
  35. package/dist/types/core/constants.d.ts +1 -1
  36. package/dist/types/engine/NativeAdapter.d.ts +1 -1
  37. package/dist/types/server/handler.d.ts +3 -0
  38. package/package.json +1 -1
  39. package/dist/chunk-BAA5XMWY.js.map +0 -1
  40. /package/dist/{chunk-2Y7CDXZM.js.map → chunk-3RPWH5PC.js.map} +0 -0
  41. /package/dist/{chunk-7QQH6MTV.js.map → chunk-QH5OI43L.js.map} +0 -0
  42. /package/dist/{chunk-N7DEVAIP.js.map → chunk-QRUMXCGW.js.map} +0 -0
  43. /package/dist/{chunk-2RKVJQSD.js.map → chunk-UMVBT7NW.js.map} +0 -0
@@ -40,6 +40,7 @@ var GENERATION_ERRORS = {
40
40
  // The provider's side (providers.js). Never 401 to the browser: the key is the AI's, not the user's session.
41
41
  KEY_MISSING: { status: 502, message: "Connect an AI provider first: it needs a key." },
42
42
  KEY_REFUSED: { status: 502, message: "The AI provider refused the key. Check it, or use another one." },
43
+ MODEL_UNAVAILABLE: { status: 502, message: "The AI provider does not have this model. Choose another one." },
43
44
  MODEL_CREDITS: { status: 502, message: "The AI provider account has no credits left." },
44
45
  MODEL_BUSY: { status: 503, message: "The AI is busy right now. Try again in a minute." },
45
46
  MODEL_ERROR: { status: 502, message: "The AI provider could not answer. Try again." }
@@ -963,7 +964,7 @@ function reviewRewrite(original, rewritten, format) {
963
964
  return problems;
964
965
  }
965
966
  function createRewriter({ model, maxAttempts = 2 }) {
966
- return async function rewrite(input, { signal } = {}) {
967
+ return async function rewrite(input, { signal, user } = {}) {
967
968
  const request = cleanRewriteInput(input);
968
969
  const messages = [{ role: "user", content: buildRewritePrompt(request) }];
969
970
  let lastProblem = "";
@@ -971,7 +972,7 @@ function createRewriter({ model, maxAttempts = 2 }) {
971
972
  signal?.throwIfAborted();
972
973
  let answer;
973
974
  try {
974
- answer = await model({ system: REWRITE_SYSTEM_PROMPT, messages, schema: REWRITE_SCHEMA, signal, task: "rewrite" });
975
+ answer = await model({ system: REWRITE_SYSTEM_PROMPT, messages, schema: REWRITE_SCHEMA, signal, task: "rewrite", user });
975
976
  } catch (error) {
976
977
  if (error instanceof EmailGenerationError && error.code === "REFUSED") throw new EmailGenerationError("REWRITE_REFUSED");
977
978
  throw error;
@@ -1213,7 +1214,7 @@ ${request.text}`;
1213
1214
  return { usable: usable.slice(0, SUBJECT_COUNT), problems: usable.length >= SUBJECT_COUNT ? [] : problems };
1214
1215
  }
1215
1216
  function createSubjectSuggester({ model, maxAttempts = 2 }) {
1216
- return async function suggestSubjects(input, { signal } = {}) {
1217
+ return async function suggestSubjects(input, { signal, user } = {}) {
1217
1218
  const request = cleanSubjectsInput(input);
1218
1219
  const messages = [{ role: "user", content: buildSubjectsPrompt(request) }];
1219
1220
  let best = [];
@@ -1221,7 +1222,7 @@ function createSubjectSuggester({ model, maxAttempts = 2 }) {
1221
1222
  signal?.throwIfAborted();
1222
1223
  let answer;
1223
1224
  try {
1224
- answer = await model({ system: SUBJECTS_SYSTEM_PROMPT, messages, schema: SUBJECTS_SCHEMA, signal, task: "subjects" });
1225
+ answer = await model({ system: SUBJECTS_SYSTEM_PROMPT, messages, schema: SUBJECTS_SCHEMA, signal, task: "subjects", user });
1225
1226
  } catch (error) {
1226
1227
  if (error instanceof EmailGenerationError && error.code === "REFUSED") throw new EmailGenerationError("SUBJECTS_REFUSED");
1227
1228
  throw error;
@@ -1360,6 +1361,7 @@ var fr = {
1360
1361
  "No usable subject came back. Try again.": "Aucun objet utilisable n'a \xE9t\xE9 propos\xE9. R\xE9essayez.",
1361
1362
  "Connect an AI provider first: it needs a key.": "Connectez d'abord un fournisseur d'IA : il faut une cl\xE9.",
1362
1363
  "The AI provider refused the key. Check it, or use another one.": "Le fournisseur d'IA a refus\xE9 la cl\xE9. V\xE9rifiez-la ou utilisez-en une autre.",
1364
+ "The AI provider does not have this model. Choose another one.": "Le fournisseur d'IA n'a pas ce mod\xE8le. Choisissez-en un autre.",
1363
1365
  "The AI provider account has no credits left.": "Le compte du fournisseur d'IA n'a plus de cr\xE9dits.",
1364
1366
  "The AI is busy right now. Try again in a minute.": "L'IA est occup\xE9e pour le moment. R\xE9essayez dans une minute.",
1365
1367
  "The AI provider could not answer. Try again.": "Le fournisseur d'IA n'a pas pu r\xE9pondre. R\xE9essayez.",
@@ -1384,7 +1386,25 @@ var fr = {
1384
1386
  "Free models, with a free account": "Mod\xE8les gratuits, avec un compte gratuit",
1385
1387
  "Free tier, with rate limits": "Offre gratuite, avec des limites de d\xE9bit",
1386
1388
  'Free "Experiment" plan': "Offre gratuite \xAB Experiment \xBB",
1387
- "Runs on your own machine": "Tourne sur votre propre machine"
1389
+ "Runs on your own machine": "Tourne sur votre propre machine",
1390
+ "AI provider": "Fournisseur d'IA",
1391
+ "Key saved": "Cl\xE9 enregistr\xE9e",
1392
+ "A key for {provider} is saved in this browser: Connect uses it, or paste another one.": "Une cl\xE9 {provider} est enregistr\xE9e dans ce navigateur : Connecter l'utilise, ou collez-en une autre.",
1393
+ "Model": "Mod\xE8le",
1394
+ "Choose a model": "Choisir un mod\xE8le",
1395
+ "Free": "Gratuit",
1396
+ "Free only": "Gratuits",
1397
+ "Search the models": "Rechercher un mod\xE8le",
1398
+ "Search the models\u2026": "Rechercher un mod\xE8le\u2026",
1399
+ "Use \u201C{model}\u201D": "Utiliser \xAB {model} \xBB",
1400
+ "No model matches.": "Aucun mod\xE8le ne correspond.",
1401
+ "Loading the models of {provider}\u2026": "Chargement des mod\xE8les de {provider}\u2026",
1402
+ "Paste the key to see all the models of {provider}.": "Collez la cl\xE9 pour voir tous les mod\xE8les de {provider}.",
1403
+ "{provider} refused this key: its models could not be listed.": "{provider} a refus\xE9 cette cl\xE9 : ses mod\xE8les n'ont pas pu \xEAtre list\xE9s.",
1404
+ "The list of models could not be loaded. The default model works, or type the id of another.": "La liste des mod\xE8les n'a pas pu \xEAtre charg\xE9e. Le mod\xE8le par d\xE9faut fonctionne, ou tapez l'identifiant d'un autre.",
1405
+ "{count} models, the free ones first.": "{count} mod\xE8les, les gratuits en premier.",
1406
+ "{count} models.": "{count} mod\xE8les.",
1407
+ "{provider} lists no models here: type the id of the one you want.": "{provider} ne liste aucun mod\xE8le ici : tapez l'identifiant de celui que vous voulez."
1388
1408
  };
1389
1409
 
1390
1410
  // src/ai/i18n/index.js
@@ -1421,7 +1441,7 @@ function createEmailGenerator({ model, maxAttempts = 2, compile } = (
1421
1441
  {}
1422
1442
  )) {
1423
1443
  if (typeof model !== "function") throw new Error("createEmailGenerator: `model` must be a function");
1424
- async function generate(input, { signal } = {}) {
1444
+ async function generate(input, { signal, user } = {}) {
1425
1445
  const request = cleanGenerateInput(input);
1426
1446
  if (!request.purpose && !request.details) {
1427
1447
  throw new EmailGenerationError("NOTHING_TO_GENERATE");
@@ -1433,7 +1453,7 @@ function createEmailGenerator({ model, maxAttempts = 2, compile } = (
1433
1453
  let lastProblem = "";
1434
1454
  for (let attempt = 1; attempt <= Math.max(1, maxAttempts); attempt += 1) {
1435
1455
  signal?.throwIfAborted();
1436
- const answer = await model({ system: SYSTEM_PROMPT, messages, schema: EMAIL_DRAFT_SCHEMA, signal, task: "generate" });
1456
+ const answer = await model({ system: SYSTEM_PROMPT, messages, schema: EMAIL_DRAFT_SCHEMA, signal, task: "generate", user });
1437
1457
  let problems;
1438
1458
  try {
1439
1459
  const draft = parseDraft(answer);
@@ -1483,6 +1503,7 @@ var AI_PROVIDERS = Object.freeze({
1483
1503
  keyUrl: "https://openrouter.ai/keys",
1484
1504
  keyHint: "sk-or-",
1485
1505
  free: "Free models, with a free account",
1506
+ publicModels: true,
1486
1507
  headers: { "X-Title": "Best Email Builder" },
1487
1508
  // Most free models reason before they answer: 5 000 to 12 000 tokens
1488
1509
  // before the email itself, which a limit of 16 000 cut short. Less
@@ -1585,6 +1606,66 @@ function resolveProvider(provider) {
1585
1606
  ] : AI_PROVIDERS["openai-compatible"];
1586
1607
  return define({ ...base, ...provider });
1587
1608
  }
1609
+ var NOT_A_WRITER = /embed|tts|audio|transcri|whisper|speech|voxtral|image|imagen|dall-e|sora|veo|lyria|music|moderation|realtime|(^|[-_/.])live($|[-_.])|search|computer-use|robotics|\baqa\b|nano-banana|deep-research|antigravity|guard|safety|ocr|rerank|translate|customtools|codex|^(davinci|babbage)|turbo-instruct/i;
1610
+ function writes(entry) {
1611
+ const id = typeof entry?.id === "string" ? entry.id : "";
1612
+ if (!id || NOT_A_WRITER.test(id)) return false;
1613
+ const outputs = entry.architecture?.output_modalities;
1614
+ if (Array.isArray(outputs) && !outputs.every((output) => output === "text")) return false;
1615
+ return entry.capabilities?.completion_chat !== false;
1616
+ }
1617
+ var entryOf = (entry) => {
1618
+ const id = String(entry.id).replace(/^models\//, "");
1619
+ const price = entry.pricing;
1620
+ return {
1621
+ id,
1622
+ name: String(entry.display_name || entry.name || id),
1623
+ free: /:free$/.test(id) || price != null && Number(price.prompt) === 0 && Number(price.completion) === 0,
1624
+ created: Number(entry.created) || (entry.created_at ? Date.parse(entry.created_at) / 1e3 : 0) || 0
1625
+ };
1626
+ };
1627
+ var versionOf = (id) => Number(/(\d+(?:\.\d+)?)/.exec(id)?.[1] || 0);
1628
+ function orderModels(entries, defaultModel) {
1629
+ const seen = /* @__PURE__ */ new Set();
1630
+ const models = entries.filter((entry) => entry?.id && !seen.has(entry.id) && seen.add(entry.id));
1631
+ const rank = (entry) => entry.id === defaultModel ? 0 : entry.free ? 1 : 2;
1632
+ const dated = models.some((entry) => entry.created);
1633
+ return models.map((entry, at) => ({ entry, at })).sort(
1634
+ (a, b) => rank(a.entry) - rank(b.entry) || (dated ? (b.entry.created || 0) - (a.entry.created || 0) : versionOf(b.entry.id) - versionOf(a.entry.id)) || a.at - b.at
1635
+ ).map(({ entry }) => Object.freeze({ id: entry.id, name: entry.name || entry.id, free: Boolean(entry.free) }));
1636
+ }
1637
+ async function listProviderModels(options = {}) {
1638
+ const { apiKey = "", fetch: fetchImpl, signal } = options;
1639
+ const provider = resolveProvider(options.provider ?? "openai-compatible");
1640
+ if (provider.listModels) return orderModels(await provider.listModels({ apiKey, fetch: fetchImpl, signal }), provider.model);
1641
+ const baseUrl = String(provider.baseUrl).replace(/\/+$/, "");
1642
+ if (!baseUrl) return [];
1643
+ if (!apiKey && !provider.keyless && !provider.publicModels) throw new EmailGenerationError("KEY_MISSING", `${provider.name}: no key`);
1644
+ let response;
1645
+ try {
1646
+ response = await (fetchImpl || globalThis.fetch)(`${baseUrl}/models`, {
1647
+ headers: { ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}, ...provider.headers },
1648
+ signal
1649
+ });
1650
+ } catch (error) {
1651
+ if (signal?.aborted || /** @type {any} */
1652
+ error?.name === "AbortError") throw error;
1653
+ throw new EmailGenerationError("MODEL_ERROR", `${provider.name}: ${/** @type {any} */
1654
+ error?.message || error}`);
1655
+ }
1656
+ const text2 = await response.text();
1657
+ if (!response.ok) {
1658
+ throw new EmailGenerationError(failureCode(response.status, errorMessage(text2)), `${provider.name} ${response.status}: ${text2.slice(0, 300)}`);
1659
+ }
1660
+ let data;
1661
+ try {
1662
+ data = JSON.parse(text2);
1663
+ } catch {
1664
+ throw new EmailGenerationError("MODEL_ERROR", `${provider.name}: not JSON \u2014 ${text2.slice(0, 200)}`);
1665
+ }
1666
+ const entries = Array.isArray(data?.data) ? data.data : Array.isArray(data?.models) ? data.models : [];
1667
+ return orderModels(entries.filter(writes).map(entryOf), provider.model);
1668
+ }
1588
1669
  var answerInstruction = (schema) => `
1589
1670
 
1590
1671
  # Answer format
@@ -1614,6 +1695,15 @@ function refusal(status, message) {
1614
1695
  if (/\bmodels?\b|harness|region|country|territory|not available|not allowed to (sample|access|use)/i.test(message)) return "MODEL_ERROR";
1615
1696
  return "KEY_REFUSED";
1616
1697
  }
1698
+ var missingModel = (status, message) => status === 404 && /\bmodel/i.test(message) || (status === 400 || status === 404) && /not a valid model|(unknown|invalid) model|model\b.*\b(not found|does not exist|no longer available)/i.test(message);
1699
+ function failureCode(status, message) {
1700
+ if (missingModel(status, message)) return "MODEL_UNAVAILABLE";
1701
+ const refused = refusal(status, message);
1702
+ if (refused) return refused;
1703
+ if (status === 402 || /credit balance|insufficient[ _](quota|credits|balance)|exceeded your current quota/i.test(message)) return "MODEL_CREDITS";
1704
+ if (status === 429 || status === 503 || status === 529) return "MODEL_BUSY";
1705
+ return "MODEL_ERROR";
1706
+ }
1617
1707
  function textOf(message) {
1618
1708
  const content = message?.content;
1619
1709
  if (typeof content === "string") return content;
@@ -1673,11 +1763,7 @@ function providerEmailModel(options = {}) {
1673
1763
  continue;
1674
1764
  }
1675
1765
  const detail = `${provider.name} ${response.status}: ${text2.slice(0, 300)}`;
1676
- const refused = refusal(response.status, errorMessage(text2));
1677
- if (refused) throw new EmailGenerationError(refused, detail);
1678
- if (response.status === 402) throw new EmailGenerationError("MODEL_CREDITS", detail);
1679
- if (response.status === 429 || response.status === 503 || response.status === 529) throw new EmailGenerationError("MODEL_BUSY", detail);
1680
- throw new EmailGenerationError("MODEL_ERROR", detail);
1766
+ throw new EmailGenerationError(failureCode(response.status, errorMessage(text2)), detail);
1681
1767
  }
1682
1768
  let data;
1683
1769
  try {
@@ -1688,10 +1774,7 @@ function providerEmailModel(options = {}) {
1688
1774
  if (data?.error) {
1689
1775
  const code = Number(data.error.code) || 0;
1690
1776
  const detail = `${provider.name}: ${data.error.message || JSON.stringify(data.error).slice(0, 300)}`;
1691
- const refused = refusal(code, String(data.error.metadata?.raw || data.error.message || ""));
1692
- if (refused) throw new EmailGenerationError(refused, detail);
1693
- if (code === 429 || code === 503) throw new EmailGenerationError("MODEL_BUSY", detail);
1694
- throw new EmailGenerationError("MODEL_ERROR", detail);
1777
+ throw new EmailGenerationError(failureCode(code, String(data.error.metadata?.raw || data.error.message || "")), detail);
1695
1778
  }
1696
1779
  const choice = data?.choices?.[0];
1697
1780
  if (choice?.message?.refusal) throw new EmailGenerationError("REFUSED", String(choice.message.refusal));
@@ -1719,16 +1802,21 @@ function claudeEmailModel({
1719
1802
  throw new Error("claudeEmailModel: pass an Anthropic client \u2014 `new Anthropic()` from @anthropic-ai/sdk");
1720
1803
  }
1721
1804
  return async ({ system, messages, schema, signal, task }) => {
1722
- const message = await client.messages.stream(
1723
- {
1724
- model,
1725
- max_tokens: maxTokens,
1726
- system,
1727
- messages,
1728
- output_config: { effort: task === "rewrite" || task === "subjects" ? rewriteEffort : effort, format: { type: "json_schema", schema } }
1729
- },
1730
- { signal }
1731
- ).finalMessage();
1805
+ let message;
1806
+ try {
1807
+ message = await client.messages.stream(
1808
+ {
1809
+ model,
1810
+ max_tokens: maxTokens,
1811
+ system,
1812
+ messages,
1813
+ output_config: { effort: task === "rewrite" || task === "subjects" ? rewriteEffort : effort, format: { type: "json_schema", schema } }
1814
+ },
1815
+ { signal }
1816
+ ).finalMessage();
1817
+ } catch (error) {
1818
+ throw sdkFailure(error, signal);
1819
+ }
1732
1820
  if (message.stop_reason === "refusal") {
1733
1821
  throw new EmailGenerationError("REFUSED");
1734
1822
  }
@@ -1738,6 +1826,136 @@ function claudeEmailModel({
1738
1826
  return message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
1739
1827
  };
1740
1828
  }
1829
+ function sdkFailure(error, signal) {
1830
+ const { status, name } = (
1831
+ /** @type {any} */
1832
+ error || {}
1833
+ );
1834
+ if (signal?.aborted || name === "AbortError" || name === "APIUserAbortError" || error instanceof EmailGenerationError) return error;
1835
+ const text2 = String(
1836
+ /** @type {any} */
1837
+ error?.error?.error?.message || /** @type {any} */
1838
+ error?.message || error
1839
+ );
1840
+ if (typeof status !== "number") return new EmailGenerationError("MODEL_ERROR", `Claude: ${text2.slice(0, 300)}`);
1841
+ return new EmailGenerationError(failureCode(status, text2), `Claude ${status}: ${text2.slice(0, 300)}`);
1842
+ }
1843
+ function claudeProvider(options) {
1844
+ const { client, ...rest } = options || /** @type {any} */
1845
+ {};
1846
+ if (typeof client !== "function") {
1847
+ throw new TypeError("claudeProvider: `client` is a function that makes the Anthropic client from a key");
1848
+ }
1849
+ return Object.freeze({
1850
+ id: "anthropic",
1851
+ name: "Claude",
1852
+ baseUrl: "https://api.anthropic.com",
1853
+ model: rest.model || DEFAULT_CLAUDE_MODEL,
1854
+ keyUrl: "https://console.anthropic.com/settings/keys",
1855
+ keyHint: "sk-ant-",
1856
+ free: null,
1857
+ format: (
1858
+ /** @type {const} */
1859
+ "json_schema"
1860
+ ),
1861
+ tokens: (
1862
+ /** @type {const} */
1863
+ "max_tokens"
1864
+ ),
1865
+ maxTokens: rest.maxTokens ?? 32e3,
1866
+ createModel: ({ apiKey, model }) => {
1867
+ let current = null;
1868
+ return async (request) => {
1869
+ const key = String(await apiKey() || "").trim();
1870
+ if (!key) throw new EmailGenerationError("KEY_MISSING", "Claude: no key");
1871
+ if (!current || current.key !== key) {
1872
+ current = { key, model: claudeEmailModel({ ...rest, ...model ? { model } : {}, client: await client(key) }) };
1873
+ }
1874
+ return current.model(request);
1875
+ };
1876
+ },
1877
+ listModels: async ({ apiKey, signal }) => {
1878
+ const key = String(apiKey || "").trim();
1879
+ if (!key) throw new EmailGenerationError("KEY_MISSING", "Claude: no key");
1880
+ try {
1881
+ const page = await (await client(key)).models.list({ limit: 100 }, { signal });
1882
+ return (page?.data || []).map((entry) => ({
1883
+ id: String(entry.id),
1884
+ name: String(entry.display_name || entry.id),
1885
+ created: entry.created_at ? Date.parse(entry.created_at) / 1e3 : 0
1886
+ }));
1887
+ } catch (error) {
1888
+ throw sdkFailure(error, signal);
1889
+ }
1890
+ }
1891
+ });
1892
+ }
1893
+
1894
+ // src/ai/agent.js
1895
+ var AGENT_PROTOCOL = "best-email-builder/agent@1";
1896
+ var AGENT_ERROR_CODES = Object.freeze(["REFUSED", "TRUNCATED", "MODEL_UNAVAILABLE", "MODEL_CREDITS", "MODEL_BUSY", "MODEL_ERROR"]);
1897
+ var DEFAULT_TIMEOUT = 18e4;
1898
+ var statusCode = (status) => status === 429 || status === 503 || status === 504 || status === 529 ? "MODEL_BUSY" : "MODEL_ERROR";
1899
+ var isAgentCode = (code) => typeof code === "string" && AGENT_ERROR_CODES.includes(
1900
+ /** @type {any} */
1901
+ code
1902
+ );
1903
+ var errorText = (data) => {
1904
+ const error = data?.error ?? data?.detail;
1905
+ if (typeof error === "string") return error;
1906
+ return String(error?.message || (error ? JSON.stringify(error) : ""));
1907
+ };
1908
+ function agentEmailModel(options) {
1909
+ const { url, headers, fetch: fetchImpl, timeout = DEFAULT_TIMEOUT, name = "Agent" } = options || /** @type {any} */
1910
+ {};
1911
+ if (typeof url !== "string" || !url) throw new TypeError("agentEmailModel: `url` is required \u2014 the endpoint of your agent");
1912
+ if (headers !== void 0 && typeof headers !== "function" && (typeof headers !== "object" || headers === null)) {
1913
+ throw new TypeError("agentEmailModel: `headers` is an object, or a function of { task, user } that returns one");
1914
+ }
1915
+ const emailModel = async ({ system, messages, schema, signal, task = "generate", user }) => {
1916
+ const call = fetchImpl || globalThis.fetch;
1917
+ const own = typeof headers === "function" ? await headers({ task, user }) : headers;
1918
+ signal?.throwIfAborted();
1919
+ const controller = new AbortController();
1920
+ const stop = () => controller.abort(signal?.reason);
1921
+ signal?.addEventListener("abort", stop, { once: true });
1922
+ let late = false;
1923
+ const timer = timeout > 0 ? setTimeout(() => (late = true, controller.abort()), timeout) : null;
1924
+ let response;
1925
+ let text2;
1926
+ try {
1927
+ response = await call(url, {
1928
+ method: "POST",
1929
+ headers: { "Content-Type": "application/json", Accept: "application/json", ...own },
1930
+ body: JSON.stringify({ protocol: AGENT_PROTOCOL, task, system: system + answerInstruction(schema), messages, schema }),
1931
+ signal: controller.signal
1932
+ });
1933
+ text2 = await response.text();
1934
+ } catch (error) {
1935
+ if (signal?.aborted) throw signal.reason ?? error;
1936
+ if (late) throw new EmailGenerationError("MODEL_BUSY", `${name}: no answer after ${Math.round(timeout / 1e3)} s`);
1937
+ throw new EmailGenerationError("MODEL_ERROR", `${name}: ${/** @type {any} */
1938
+ error?.message || error}`);
1939
+ } finally {
1940
+ if (timer) clearTimeout(timer);
1941
+ signal?.removeEventListener("abort", stop);
1942
+ }
1943
+ let data = null;
1944
+ try {
1945
+ data = JSON.parse(text2);
1946
+ } catch {
1947
+ }
1948
+ if (!response.ok || data && typeof data === "object" && "error" in data && !("text" in data) && !("output" in data)) {
1949
+ const said = data ? errorText(data) : text2;
1950
+ const detail = `${name} ${response.status}: ${said.slice(0, 300)}`;
1951
+ throw new EmailGenerationError(isAgentCode(data?.code) ? data.code : statusCode(response.ok ? 502 : response.status), detail);
1952
+ }
1953
+ if (typeof data?.text === "string") return data.text;
1954
+ if (data?.output && typeof data.output === "object") return JSON.stringify(data.output);
1955
+ return text2;
1956
+ };
1957
+ return Object.assign(emailModel, { url });
1958
+ }
1741
1959
 
1742
1960
  export {
1743
1961
  GENERATION_ERRORS,
@@ -1768,8 +1986,14 @@ export {
1768
1986
  createEmailGenerator,
1769
1987
  AI_PROVIDERS,
1770
1988
  resolveProvider,
1989
+ listProviderModels,
1990
+ failureCode,
1771
1991
  providerEmailModel,
1772
1992
  DEFAULT_CLAUDE_MODEL,
1773
- claudeEmailModel
1993
+ claudeEmailModel,
1994
+ claudeProvider,
1995
+ AGENT_PROTOCOL,
1996
+ AGENT_ERROR_CODES,
1997
+ agentEmailModel
1774
1998
  };
1775
- //# sourceMappingURL=chunk-BAA5XMWY.js.map
1999
+ //# sourceMappingURL=chunk-CTZ56YJG.js.map