@agentcomms/resend 0.15.2 → 0.16.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/dist/index.mjs CHANGED
@@ -1,5 +1,204 @@
1
- import { At as RESEND_API_ORIGIN, Dt as PACKAGE_NAME, Mt as ROUTES, Nt as VERSION, Ot as RESEND_CALLER, jt as RESEND_DOWNLOAD_ORIGIN, kt as REFUSED } from "./send-V6GB4P5p.mjs";
2
- import { t as createResendMcpServer } from "./server-CwVG8XOL.mjs";
1
+ import { Bt as RESEND_DOWNLOAD_ORIGIN, Et as stripInvisible, H as CommsError, Ht as VERSION, It as PACKAGE_NAME, Lt as RESEND_CALLER, N as resendRequest, P as ResendContext, Rt as REFUSED, Vt as ROUTES, b as readBody, ft as neutralise, h as resendId, pt as newBoundary, v as addressesOf, y as attachmentRisks, z as keyPermissionOf, zt as RESEND_API_ORIGIN } from "./send-BwyJM23a.mjs";
2
+ import { t as createResendMcpServer } from "./server-BFAo5PpV.mjs";
3
+ //#region src/operations/events.ts
4
+ const BODY_LIMIT = 2e4;
5
+ const STATUS = /* @__PURE__ */ new Set([
6
+ "scheduled",
7
+ "sent",
8
+ "delivered",
9
+ "delivery_delayed",
10
+ "bounced",
11
+ "complained",
12
+ "opened",
13
+ "clicked",
14
+ "failed",
15
+ "suppressed",
16
+ "canceled",
17
+ "queued"
18
+ ]);
19
+ /**
20
+ * Converts the already-capped body produced by the Resend reader into the
21
+ * event representation. `bodyTruncated` is that reader's fact: this layer
22
+ * must not recompute it from Unicode code points.
23
+ */
24
+ function normaliseResendEventBody(value, bodyTruncated) {
25
+ let body = value;
26
+ if (bodyTruncated && body.charCodeAt(body.length - 1) >= 55296 && body.charCodeAt(body.length - 1) <= 56319) body = body.slice(0, -1);
27
+ for (let index = 0; index < body.length; index += 1) {
28
+ const code = body.charCodeAt(index);
29
+ if (code >= 55296 && code <= 56319) {
30
+ const next = body.charCodeAt(index + 1);
31
+ if (!(next >= 56320 && next <= 57343)) throw new CommsError("BAD_DATA", "Resend body is not a Unicode scalar sequence");
32
+ index += 1;
33
+ } else if (code >= 56320 && code <= 57343) throw new CommsError("BAD_DATA", "Resend body is not a Unicode scalar sequence");
34
+ }
35
+ if ([...body].length > BODY_LIMIT) throw new CommsError("BAD_DATA", "Resend body exceeds the Unicode code-point limit");
36
+ return {
37
+ body,
38
+ bodyTruncated
39
+ };
40
+ }
41
+ function text(value) {
42
+ return typeof value === "string" ? value : null;
43
+ }
44
+ function cleanText(value) {
45
+ return neutralise(stripInvisible(typeof value === "string" ? value : "").text).text;
46
+ }
47
+ function cleanAddress(entry) {
48
+ return {
49
+ address: cleanText(entry.address),
50
+ name: entry.name === "" ? null : cleanText(entry.name)
51
+ };
52
+ }
53
+ function unwrapReadBody(value) {
54
+ const firstNewline = value.indexOf("\n");
55
+ const finalNewline = value.lastIndexOf("\n<");
56
+ if (firstNewline < 0 || finalNewline <= firstNewline) throw new CommsError("BAD_DATA", "Resend read body did not have its expected untrusted-content envelope");
57
+ return value.slice(firstNewline + 1, finalNewline);
58
+ }
59
+ function cleanBody(entry, emailId) {
60
+ const read = readBody(text(entry.html), text(entry.text), {
61
+ account: "resend-event-reader",
62
+ id: emailId,
63
+ boundary: newBoundary()
64
+ });
65
+ return normaliseResendEventBody(unwrapReadBody(read.body), read.truncated);
66
+ }
67
+ function receivedCandidate(entry) {
68
+ const emailId = resendId(entry.id, "received email id");
69
+ const receivedAt = text(entry.created_at);
70
+ if (receivedAt === null) throw new CommsError("BAD_DATA", "Resend received detail has no received time");
71
+ const from = addressesOf(entry.from)[0];
72
+ const attachments = (Array.isArray(entry.attachments) ? entry.attachments : []).map((value) => {
73
+ const attachment = value;
74
+ const rawFilename = typeof attachment.filename === "string" ? attachment.filename : "";
75
+ const filename = cleanText(rawFilename);
76
+ return {
77
+ id: cleanText(attachment.id),
78
+ filename,
79
+ contentType: text(attachment.content_type) === null ? null : cleanText(attachment.content_type),
80
+ size: typeof attachment.size === "number" && Number.isSafeInteger(attachment.size) ? attachment.size : null,
81
+ inline: attachment.content_disposition === "inline",
82
+ riskFlags: [...new Set(attachmentRisks(rawFilename))].sort()
83
+ };
84
+ });
85
+ const auth = entry.authentication ?? null;
86
+ const body = cleanBody(entry, emailId);
87
+ return {
88
+ emailId,
89
+ receivedAt,
90
+ subject: cleanText(entry.subject),
91
+ body: body.body,
92
+ bodyTruncated: body.bodyTruncated,
93
+ attachments,
94
+ attachmentCount: attachments.length,
95
+ from: from === void 0 ? null : cleanAddress(from),
96
+ replyTo: addressesOf(entry.reply_to).map(cleanAddress),
97
+ to: addressesOf(entry.to).map((entry) => cleanText(entry.address)),
98
+ cc: addressesOf(entry.cc).map((entry) => cleanText(entry.address)),
99
+ receivedFor: addressesOf(entry.received_for).map((entry) => cleanText(entry.address)),
100
+ messageId: text(entry.message_id) === null ? null : cleanText(entry.message_id),
101
+ authentication: auth ? {
102
+ spf: text(auth.spf),
103
+ dkim: text(auth.dkim),
104
+ dmarc: text(auth.dmarc),
105
+ evaluatedBy: "resend"
106
+ } : {
107
+ spf: null,
108
+ dkim: null,
109
+ dmarc: null,
110
+ evaluatedBy: null
111
+ }
112
+ };
113
+ }
114
+ function sentItem(entry) {
115
+ const id = resendId(entry.id, "sent email id");
116
+ const lastEvent = text(entry.last_event);
117
+ if (lastEvent === null || !STATUS.has(lastEvent)) throw new CommsError("BAD_DATA", "Resend sent detail has an unsupported status");
118
+ const from = addressesOf(entry.from)[0];
119
+ return {
120
+ id,
121
+ lastEvent,
122
+ from: from === void 0 ? null : cleanAddress(from),
123
+ to: addressesOf(entry.to).map((address) => cleanText(address.address)),
124
+ cc: addressesOf(entry.cc).map((address) => cleanText(address.address)),
125
+ bcc: addressesOf(entry.bcc).map((address) => cleanText(address.address)),
126
+ subject: cleanText(entry.subject),
127
+ createdAt: text(entry.created_at),
128
+ scheduledAt: text(entry.scheduled_at),
129
+ messageId: text(entry.message_id) === null ? null : cleanText(entry.message_id)
130
+ };
131
+ }
132
+ async function readable(context, name, accountId, work) {
133
+ const named = accountId === void 0 ? await context.accounts.require(name) : await context.accounts.findById(accountId) ?? (() => {
134
+ throw new CommsError("CONFIG", "the Resend event account identity changed before its provider call", { details: {
135
+ reason: "ACCOUNT_CHANGED",
136
+ accountId
137
+ } });
138
+ })();
139
+ if (accountId !== void 0 && named.account.id !== accountId) throw new CommsError("CONFIG", "the Resend event account identity changed before its provider call", { details: {
140
+ reason: "ACCOUNT_CHANGED",
141
+ accountId
142
+ } });
143
+ if (keyPermissionOf(named.account) !== "full_access") throw new CommsError("SOURCE_UNAVAILABLE", "Resend event polling requires a full-access key");
144
+ return work(await context.transport(named));
145
+ }
146
+ /** A structural read adapter: no permit, no sender, and each request is labelled background-event. */
147
+ function createResendEventReader(context, account, accountId) {
148
+ return {
149
+ async listReceived(after) {
150
+ return readable(context, account, accountId, async (transport) => {
151
+ const page = await resendRequest(transport, "GET", "/emails/receiving", {
152
+ query: {
153
+ limit: 100,
154
+ after
155
+ },
156
+ throttlePriority: "background-event"
157
+ });
158
+ const emails = (page.data ?? []).map((entry) => ({ id: resendId(entry.id, "received email id") }));
159
+ return {
160
+ emails,
161
+ next: page.has_more === true ? emails.at(-1)?.id ?? null : null
162
+ };
163
+ });
164
+ },
165
+ async getReceived(id) {
166
+ const emailId = resendId(id, "received email id");
167
+ try {
168
+ return await readable(context, account, accountId, async (transport) => {
169
+ return {
170
+ kind: "candidate",
171
+ candidate: receivedCandidate(await resendRequest(transport, "GET", `/emails/receiving/${emailId}`, { throttlePriority: "background-event" }))
172
+ };
173
+ });
174
+ } catch (error) {
175
+ if (error instanceof CommsError && error.code === "NOT_FOUND") return { kind: "vanished" };
176
+ throw error;
177
+ }
178
+ },
179
+ async listSent(after) {
180
+ return readable(context, account, accountId, async (transport) => {
181
+ const page = await resendRequest(transport, "GET", "/emails", {
182
+ query: {
183
+ limit: 100,
184
+ after
185
+ },
186
+ throttlePriority: "background-event"
187
+ });
188
+ const emails = (page.data ?? []).map(sentItem);
189
+ return {
190
+ emails,
191
+ next: page.has_more === true ? emails.at(-1)?.id ?? null : null
192
+ };
193
+ });
194
+ }
195
+ };
196
+ }
197
+ /** Opens Resend's guarded, read-only context for the held event owner. */
198
+ function createResendEventReaderForPaths(input) {
199
+ return createResendEventReader(new ResendContext({ pathOverrides: input.pathOverrides }), input.account, input.accountId);
200
+ }
201
+ //#endregion
3
202
  //#region src/index.ts
4
203
  /**
5
204
  * The library entry of `@agentcomms/resend`.
@@ -12,4 +211,4 @@ import { t as createResendMcpServer } from "./server-CwVG8XOL.mjs";
12
211
  * call grants nothing.
13
212
  */
14
213
  //#endregion
15
- export { PACKAGE_NAME, REFUSED, RESEND_API_ORIGIN, RESEND_CALLER, RESEND_DOWNLOAD_ORIGIN, ROUTES, VERSION, createResendMcpServer };
214
+ export { PACKAGE_NAME, REFUSED, RESEND_API_ORIGIN, RESEND_CALLER, RESEND_DOWNLOAD_ORIGIN, ROUTES, VERSION, createResendEventReader, createResendEventReaderForPaths, createResendMcpServer, normaliseResendEventBody };