@agentcomms/resend 0.15.1 → 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/README.md +4 -0
- package/dist/cli.mjs +2 -2
- package/dist/index.d.mts +301 -7
- package/dist/index.mjs +202 -3
- package/dist/{send-BcM5UKMt.mjs → send-BwyJM23a.mjs} +760 -115
- package/dist/{server-EV5f6d28.mjs → server-BFAo5PpV.mjs} +1 -1
- package/dist/{stdio-entry-Ea4hMItp.mjs → stdio-entry-BGq1Q5V_.mjs} +1 -1
- package/package.json +13 -3
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,204 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as createResendMcpServer } from "./server-
|
|
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-EV5f6d28.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 };
|