@skillkit/messaging 1.8.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/LICENSE +190 -0
- package/README.md +189 -0
- package/dist/index.d.ts +226 -0
- package/dist/index.js +854 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,854 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var DEFAULT_MESSAGING_CONFIG = {
|
|
3
|
+
maxInboxSize: 1e3,
|
|
4
|
+
retentionDays: 30
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/storage/inbox.ts
|
|
8
|
+
import { readFile, writeFile, mkdir, readdir, unlink } from "fs/promises";
|
|
9
|
+
import { existsSync } from "fs";
|
|
10
|
+
import { join, dirname } from "path";
|
|
11
|
+
import { homedir } from "os";
|
|
12
|
+
var InboxStorage = class {
|
|
13
|
+
basePath;
|
|
14
|
+
constructor(agentId, basePath) {
|
|
15
|
+
this.basePath = basePath ?? join(homedir(), ".skillkit", "messages", "inbox", agentId);
|
|
16
|
+
}
|
|
17
|
+
async initialize() {
|
|
18
|
+
await mkdir(this.basePath, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
async save(message) {
|
|
21
|
+
const filePath = this.getMessagePath(message.id);
|
|
22
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
23
|
+
await writeFile(filePath, JSON.stringify(message, null, 2), "utf-8");
|
|
24
|
+
}
|
|
25
|
+
async get(messageId) {
|
|
26
|
+
const filePath = this.getMessagePath(messageId);
|
|
27
|
+
if (!existsSync(filePath)) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const content = await readFile(filePath, "utf-8");
|
|
32
|
+
return JSON.parse(content);
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async delete(messageId) {
|
|
38
|
+
const filePath = this.getMessagePath(messageId);
|
|
39
|
+
if (!existsSync(filePath)) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
await unlink(filePath);
|
|
44
|
+
return true;
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async list(filter = {}) {
|
|
50
|
+
if (!existsSync(this.basePath)) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
const files = await readdir(this.basePath);
|
|
54
|
+
const messages = [];
|
|
55
|
+
for (const file of files) {
|
|
56
|
+
if (!file.endsWith(".json")) continue;
|
|
57
|
+
const filePath = join(this.basePath, file);
|
|
58
|
+
try {
|
|
59
|
+
const content = await readFile(filePath, "utf-8");
|
|
60
|
+
const message = JSON.parse(content);
|
|
61
|
+
if (this.matchesFilter(message, filter)) {
|
|
62
|
+
messages.push(message);
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
messages.sort((a, b) => {
|
|
68
|
+
const priorityOrder = { urgent: 0, high: 1, normal: 2, low: 3 };
|
|
69
|
+
const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
|
|
70
|
+
if (priorityDiff !== 0) return priorityDiff;
|
|
71
|
+
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
|
|
72
|
+
});
|
|
73
|
+
return filter.limit ? messages.slice(0, filter.limit) : messages;
|
|
74
|
+
}
|
|
75
|
+
async markAsRead(messageId) {
|
|
76
|
+
const message = await this.get(messageId);
|
|
77
|
+
if (!message) return null;
|
|
78
|
+
message.status = "read";
|
|
79
|
+
message.readAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
80
|
+
await this.save(message);
|
|
81
|
+
return message;
|
|
82
|
+
}
|
|
83
|
+
async getUnread() {
|
|
84
|
+
return this.list({ status: "unread" });
|
|
85
|
+
}
|
|
86
|
+
async getSummary() {
|
|
87
|
+
const messages = await this.list();
|
|
88
|
+
const summary = {
|
|
89
|
+
total: messages.length,
|
|
90
|
+
unread: 0,
|
|
91
|
+
byPriority: { low: 0, normal: 0, high: 0, urgent: 0 },
|
|
92
|
+
byType: { request: 0, response: 0, notification: 0, update: 0 }
|
|
93
|
+
};
|
|
94
|
+
for (const message of messages) {
|
|
95
|
+
if (message.status === "unread") {
|
|
96
|
+
summary.unread++;
|
|
97
|
+
}
|
|
98
|
+
summary.byPriority[message.priority]++;
|
|
99
|
+
summary.byType[message.type]++;
|
|
100
|
+
}
|
|
101
|
+
return summary;
|
|
102
|
+
}
|
|
103
|
+
async count() {
|
|
104
|
+
if (!existsSync(this.basePath)) return 0;
|
|
105
|
+
const files = await readdir(this.basePath);
|
|
106
|
+
return files.filter((f) => f.endsWith(".json")).length;
|
|
107
|
+
}
|
|
108
|
+
async clear() {
|
|
109
|
+
if (!existsSync(this.basePath)) return 0;
|
|
110
|
+
const files = await readdir(this.basePath);
|
|
111
|
+
let deleted = 0;
|
|
112
|
+
for (const file of files) {
|
|
113
|
+
if (!file.endsWith(".json")) continue;
|
|
114
|
+
try {
|
|
115
|
+
await unlink(join(this.basePath, file));
|
|
116
|
+
deleted++;
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return deleted;
|
|
121
|
+
}
|
|
122
|
+
getMessagePath(messageId) {
|
|
123
|
+
return join(this.basePath, `${messageId}.json`);
|
|
124
|
+
}
|
|
125
|
+
matchesFilter(message, filter) {
|
|
126
|
+
if (filter.from && message.from !== filter.from) return false;
|
|
127
|
+
if (filter.to && message.to !== filter.to) return false;
|
|
128
|
+
if (filter.type && message.type !== filter.type) return false;
|
|
129
|
+
if (filter.priority && message.priority !== filter.priority) return false;
|
|
130
|
+
if (filter.status && message.status !== filter.status) return false;
|
|
131
|
+
if (filter.threadId && message.threadId !== filter.threadId) return false;
|
|
132
|
+
if (filter.since) {
|
|
133
|
+
const since = new Date(filter.since).getTime();
|
|
134
|
+
const created = new Date(message.createdAt).getTime();
|
|
135
|
+
if (created < since) return false;
|
|
136
|
+
}
|
|
137
|
+
if (filter.until) {
|
|
138
|
+
const until = new Date(filter.until).getTime();
|
|
139
|
+
const created = new Date(message.createdAt).getTime();
|
|
140
|
+
if (created > until) return false;
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// src/storage/sent.ts
|
|
147
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, readdir as readdir2, unlink as unlink2 } from "fs/promises";
|
|
148
|
+
import { existsSync as existsSync2 } from "fs";
|
|
149
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
150
|
+
import { homedir as homedir2 } from "os";
|
|
151
|
+
var SentStorage = class {
|
|
152
|
+
basePath;
|
|
153
|
+
constructor(agentId, basePath) {
|
|
154
|
+
this.basePath = basePath ?? join2(homedir2(), ".skillkit", "messages", "sent", agentId);
|
|
155
|
+
}
|
|
156
|
+
async initialize() {
|
|
157
|
+
await mkdir2(this.basePath, { recursive: true });
|
|
158
|
+
}
|
|
159
|
+
async save(message) {
|
|
160
|
+
const filePath = this.getMessagePath(message.id);
|
|
161
|
+
await mkdir2(dirname2(filePath), { recursive: true });
|
|
162
|
+
await writeFile2(filePath, JSON.stringify(message, null, 2), "utf-8");
|
|
163
|
+
}
|
|
164
|
+
async get(messageId) {
|
|
165
|
+
const filePath = this.getMessagePath(messageId);
|
|
166
|
+
if (!existsSync2(filePath)) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
const content = await readFile2(filePath, "utf-8");
|
|
171
|
+
return JSON.parse(content);
|
|
172
|
+
} catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async delete(messageId) {
|
|
177
|
+
const filePath = this.getMessagePath(messageId);
|
|
178
|
+
if (!existsSync2(filePath)) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
await unlink2(filePath);
|
|
183
|
+
return true;
|
|
184
|
+
} catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async list(filter = {}) {
|
|
189
|
+
if (!existsSync2(this.basePath)) {
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
const files = await readdir2(this.basePath);
|
|
193
|
+
const messages = [];
|
|
194
|
+
for (const file of files) {
|
|
195
|
+
if (!file.endsWith(".json")) continue;
|
|
196
|
+
const filePath = join2(this.basePath, file);
|
|
197
|
+
try {
|
|
198
|
+
const content = await readFile2(filePath, "utf-8");
|
|
199
|
+
const message = JSON.parse(content);
|
|
200
|
+
if (this.matchesFilter(message, filter)) {
|
|
201
|
+
messages.push(message);
|
|
202
|
+
}
|
|
203
|
+
} catch {
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
messages.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
207
|
+
return filter.limit ? messages.slice(0, filter.limit) : messages;
|
|
208
|
+
}
|
|
209
|
+
async getByThread(threadId) {
|
|
210
|
+
return this.list({ threadId });
|
|
211
|
+
}
|
|
212
|
+
async getByRecipient(to) {
|
|
213
|
+
return this.list({ to });
|
|
214
|
+
}
|
|
215
|
+
async count() {
|
|
216
|
+
if (!existsSync2(this.basePath)) return 0;
|
|
217
|
+
const files = await readdir2(this.basePath);
|
|
218
|
+
return files.filter((f) => f.endsWith(".json")).length;
|
|
219
|
+
}
|
|
220
|
+
async clear() {
|
|
221
|
+
if (!existsSync2(this.basePath)) return 0;
|
|
222
|
+
const files = await readdir2(this.basePath);
|
|
223
|
+
let deleted = 0;
|
|
224
|
+
for (const file of files) {
|
|
225
|
+
if (!file.endsWith(".json")) continue;
|
|
226
|
+
try {
|
|
227
|
+
await unlink2(join2(this.basePath, file));
|
|
228
|
+
deleted++;
|
|
229
|
+
} catch {
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return deleted;
|
|
233
|
+
}
|
|
234
|
+
getMessagePath(messageId) {
|
|
235
|
+
return join2(this.basePath, `${messageId}.json`);
|
|
236
|
+
}
|
|
237
|
+
matchesFilter(message, filter) {
|
|
238
|
+
if (filter.to && message.to !== filter.to) return false;
|
|
239
|
+
if (filter.type && message.type !== filter.type) return false;
|
|
240
|
+
if (filter.priority && message.priority !== filter.priority) return false;
|
|
241
|
+
if (filter.threadId && message.threadId !== filter.threadId) return false;
|
|
242
|
+
if (filter.since) {
|
|
243
|
+
const since = new Date(filter.since).getTime();
|
|
244
|
+
const created = new Date(message.createdAt).getTime();
|
|
245
|
+
if (created < since) return false;
|
|
246
|
+
}
|
|
247
|
+
if (filter.until) {
|
|
248
|
+
const until = new Date(filter.until).getTime();
|
|
249
|
+
const created = new Date(message.createdAt).getTime();
|
|
250
|
+
if (created > until) return false;
|
|
251
|
+
}
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// src/storage/archived.ts
|
|
257
|
+
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, readdir as readdir3, unlink as unlink3 } from "fs/promises";
|
|
258
|
+
import { existsSync as existsSync3 } from "fs";
|
|
259
|
+
import { join as join3, dirname as dirname3 } from "path";
|
|
260
|
+
import { homedir as homedir3 } from "os";
|
|
261
|
+
var ArchivedStorage = class {
|
|
262
|
+
basePath;
|
|
263
|
+
constructor(agentId, basePath) {
|
|
264
|
+
this.basePath = basePath ?? join3(homedir3(), ".skillkit", "messages", "archived", agentId);
|
|
265
|
+
}
|
|
266
|
+
async initialize() {
|
|
267
|
+
await mkdir3(this.basePath, { recursive: true });
|
|
268
|
+
}
|
|
269
|
+
async save(message) {
|
|
270
|
+
const archivedMessage = {
|
|
271
|
+
...message,
|
|
272
|
+
status: "archived"
|
|
273
|
+
};
|
|
274
|
+
const filePath = this.getMessagePath(message.id);
|
|
275
|
+
await mkdir3(dirname3(filePath), { recursive: true });
|
|
276
|
+
await writeFile3(filePath, JSON.stringify(archivedMessage, null, 2), "utf-8");
|
|
277
|
+
}
|
|
278
|
+
async get(messageId) {
|
|
279
|
+
const filePath = this.getMessagePath(messageId);
|
|
280
|
+
if (!existsSync3(filePath)) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
try {
|
|
284
|
+
const content = await readFile3(filePath, "utf-8");
|
|
285
|
+
return JSON.parse(content);
|
|
286
|
+
} catch {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async delete(messageId) {
|
|
291
|
+
const filePath = this.getMessagePath(messageId);
|
|
292
|
+
if (!existsSync3(filePath)) {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
await unlink3(filePath);
|
|
297
|
+
return true;
|
|
298
|
+
} catch {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async list(filter = {}) {
|
|
303
|
+
if (!existsSync3(this.basePath)) {
|
|
304
|
+
return [];
|
|
305
|
+
}
|
|
306
|
+
const files = await readdir3(this.basePath);
|
|
307
|
+
const messages = [];
|
|
308
|
+
for (const file of files) {
|
|
309
|
+
if (!file.endsWith(".json")) continue;
|
|
310
|
+
const filePath = join3(this.basePath, file);
|
|
311
|
+
try {
|
|
312
|
+
const content = await readFile3(filePath, "utf-8");
|
|
313
|
+
const message = JSON.parse(content);
|
|
314
|
+
if (this.matchesFilter(message, filter)) {
|
|
315
|
+
messages.push(message);
|
|
316
|
+
}
|
|
317
|
+
} catch {
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
messages.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
321
|
+
return filter.limit ? messages.slice(0, filter.limit) : messages;
|
|
322
|
+
}
|
|
323
|
+
async search(query, limit = 50) {
|
|
324
|
+
const messages = await this.list();
|
|
325
|
+
const lowerQuery = query.toLowerCase();
|
|
326
|
+
return messages.filter((m) => {
|
|
327
|
+
const subject = m.subject.toLowerCase();
|
|
328
|
+
const bodyStr = typeof m.body === "string" ? m.body.toLowerCase() : JSON.stringify(m.body).toLowerCase();
|
|
329
|
+
return subject.includes(lowerQuery) || bodyStr.includes(lowerQuery);
|
|
330
|
+
}).slice(0, limit);
|
|
331
|
+
}
|
|
332
|
+
async count() {
|
|
333
|
+
if (!existsSync3(this.basePath)) return 0;
|
|
334
|
+
const files = await readdir3(this.basePath);
|
|
335
|
+
return files.filter((f) => f.endsWith(".json")).length;
|
|
336
|
+
}
|
|
337
|
+
async clear() {
|
|
338
|
+
if (!existsSync3(this.basePath)) return 0;
|
|
339
|
+
const files = await readdir3(this.basePath);
|
|
340
|
+
let deleted = 0;
|
|
341
|
+
for (const file of files) {
|
|
342
|
+
if (!file.endsWith(".json")) continue;
|
|
343
|
+
try {
|
|
344
|
+
await unlink3(join3(this.basePath, file));
|
|
345
|
+
deleted++;
|
|
346
|
+
} catch {
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return deleted;
|
|
350
|
+
}
|
|
351
|
+
async pruneOlderThan(days) {
|
|
352
|
+
const messages = await this.list();
|
|
353
|
+
const cutoff = Date.now() - days * 24 * 60 * 60 * 1e3;
|
|
354
|
+
let deleted = 0;
|
|
355
|
+
for (const message of messages) {
|
|
356
|
+
const createdAt = new Date(message.createdAt).getTime();
|
|
357
|
+
if (createdAt < cutoff) {
|
|
358
|
+
if (await this.delete(message.id)) {
|
|
359
|
+
deleted++;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return deleted;
|
|
364
|
+
}
|
|
365
|
+
getMessagePath(messageId) {
|
|
366
|
+
return join3(this.basePath, `${messageId}.json`);
|
|
367
|
+
}
|
|
368
|
+
matchesFilter(message, filter) {
|
|
369
|
+
if (filter.from && message.from !== filter.from) return false;
|
|
370
|
+
if (filter.to && message.to !== filter.to) return false;
|
|
371
|
+
if (filter.type && message.type !== filter.type) return false;
|
|
372
|
+
if (filter.priority && message.priority !== filter.priority) return false;
|
|
373
|
+
if (filter.threadId && message.threadId !== filter.threadId) return false;
|
|
374
|
+
if (filter.since) {
|
|
375
|
+
const since = new Date(filter.since).getTime();
|
|
376
|
+
const created = new Date(message.createdAt).getTime();
|
|
377
|
+
if (created < since) return false;
|
|
378
|
+
}
|
|
379
|
+
if (filter.until) {
|
|
380
|
+
const until = new Date(filter.until).getTime();
|
|
381
|
+
const created = new Date(message.createdAt).getTime();
|
|
382
|
+
if (created > until) return false;
|
|
383
|
+
}
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// src/message/builder.ts
|
|
389
|
+
import { randomUUID } from "crypto";
|
|
390
|
+
var MessageBuilder = class _MessageBuilder {
|
|
391
|
+
message;
|
|
392
|
+
constructor() {
|
|
393
|
+
this.message = {
|
|
394
|
+
id: randomUUID(),
|
|
395
|
+
type: "notification",
|
|
396
|
+
priority: "normal",
|
|
397
|
+
status: "unread",
|
|
398
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
static create() {
|
|
402
|
+
return new _MessageBuilder();
|
|
403
|
+
}
|
|
404
|
+
from(agentId) {
|
|
405
|
+
this.message.from = agentId;
|
|
406
|
+
return this;
|
|
407
|
+
}
|
|
408
|
+
to(recipient) {
|
|
409
|
+
this.message.to = recipient;
|
|
410
|
+
return this;
|
|
411
|
+
}
|
|
412
|
+
subject(subject) {
|
|
413
|
+
this.message.subject = subject;
|
|
414
|
+
return this;
|
|
415
|
+
}
|
|
416
|
+
body(body) {
|
|
417
|
+
this.message.body = body;
|
|
418
|
+
return this;
|
|
419
|
+
}
|
|
420
|
+
type(type) {
|
|
421
|
+
this.message.type = type;
|
|
422
|
+
return this;
|
|
423
|
+
}
|
|
424
|
+
priority(priority) {
|
|
425
|
+
this.message.priority = priority;
|
|
426
|
+
return this;
|
|
427
|
+
}
|
|
428
|
+
replyTo(messageId) {
|
|
429
|
+
this.message.replyTo = messageId;
|
|
430
|
+
return this;
|
|
431
|
+
}
|
|
432
|
+
threadId(threadId) {
|
|
433
|
+
this.message.threadId = threadId;
|
|
434
|
+
return this;
|
|
435
|
+
}
|
|
436
|
+
metadata(metadata) {
|
|
437
|
+
this.message.metadata = metadata;
|
|
438
|
+
return this;
|
|
439
|
+
}
|
|
440
|
+
request() {
|
|
441
|
+
this.message.type = "request";
|
|
442
|
+
return this;
|
|
443
|
+
}
|
|
444
|
+
response() {
|
|
445
|
+
this.message.type = "response";
|
|
446
|
+
return this;
|
|
447
|
+
}
|
|
448
|
+
notification() {
|
|
449
|
+
this.message.type = "notification";
|
|
450
|
+
return this;
|
|
451
|
+
}
|
|
452
|
+
update() {
|
|
453
|
+
this.message.type = "update";
|
|
454
|
+
return this;
|
|
455
|
+
}
|
|
456
|
+
low() {
|
|
457
|
+
this.message.priority = "low";
|
|
458
|
+
return this;
|
|
459
|
+
}
|
|
460
|
+
normal() {
|
|
461
|
+
this.message.priority = "normal";
|
|
462
|
+
return this;
|
|
463
|
+
}
|
|
464
|
+
high() {
|
|
465
|
+
this.message.priority = "high";
|
|
466
|
+
return this;
|
|
467
|
+
}
|
|
468
|
+
urgent() {
|
|
469
|
+
this.message.priority = "urgent";
|
|
470
|
+
return this;
|
|
471
|
+
}
|
|
472
|
+
build() {
|
|
473
|
+
if (!this.message.from) {
|
|
474
|
+
throw new Error('Message must have a "from" field');
|
|
475
|
+
}
|
|
476
|
+
if (!this.message.to) {
|
|
477
|
+
throw new Error('Message must have a "to" field');
|
|
478
|
+
}
|
|
479
|
+
if (!this.message.subject) {
|
|
480
|
+
throw new Error("Message must have a subject");
|
|
481
|
+
}
|
|
482
|
+
if (this.message.body === void 0) {
|
|
483
|
+
throw new Error("Message must have a body");
|
|
484
|
+
}
|
|
485
|
+
return this.message;
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
function createMessage(from, input) {
|
|
489
|
+
const builder = MessageBuilder.create().from(from).to(input.to).subject(input.subject).body(input.body);
|
|
490
|
+
if (input.type) builder.type(input.type);
|
|
491
|
+
if (input.priority) builder.priority(input.priority);
|
|
492
|
+
if (input.replyTo) builder.replyTo(input.replyTo);
|
|
493
|
+
if (input.threadId) builder.threadId(input.threadId);
|
|
494
|
+
if (input.metadata) builder.metadata(input.metadata);
|
|
495
|
+
return builder.build();
|
|
496
|
+
}
|
|
497
|
+
function createReply(originalMessage, from, body) {
|
|
498
|
+
return MessageBuilder.create().from(from).to(originalMessage.from).subject(`Re: ${originalMessage.subject}`).body(body).type("response").priority(originalMessage.priority).replyTo(originalMessage.id).threadId(originalMessage.threadId ?? originalMessage.id).build();
|
|
499
|
+
}
|
|
500
|
+
function createForward(originalMessage, from, to, additionalBody) {
|
|
501
|
+
const forwardBody = {
|
|
502
|
+
forwardedFrom: originalMessage.from,
|
|
503
|
+
originalSubject: originalMessage.subject,
|
|
504
|
+
originalBody: originalMessage.body,
|
|
505
|
+
...additionalBody ? { note: additionalBody } : {}
|
|
506
|
+
};
|
|
507
|
+
return MessageBuilder.create().from(from).to(to).subject(`Fwd: ${originalMessage.subject}`).body(forwardBody).type(originalMessage.type).priority(originalMessage.priority).build();
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// src/message/router.ts
|
|
511
|
+
var MessageRouter = class {
|
|
512
|
+
config;
|
|
513
|
+
localAgents = /* @__PURE__ */ new Map();
|
|
514
|
+
remoteHandler;
|
|
515
|
+
constructor(config) {
|
|
516
|
+
this.config = config;
|
|
517
|
+
this.localAgents.set(config.localAgentId, config.inboxStorage);
|
|
518
|
+
}
|
|
519
|
+
registerLocalAgent(agentId, inbox) {
|
|
520
|
+
this.localAgents.set(agentId, inbox);
|
|
521
|
+
}
|
|
522
|
+
unregisterLocalAgent(agentId) {
|
|
523
|
+
if (agentId !== this.config.localAgentId) {
|
|
524
|
+
this.localAgents.delete(agentId);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
setRemoteHandler(handler) {
|
|
528
|
+
this.remoteHandler = handler;
|
|
529
|
+
}
|
|
530
|
+
async route(message) {
|
|
531
|
+
const recipient = this.parseRecipient(message.to);
|
|
532
|
+
await this.config.sentStorage.save(message);
|
|
533
|
+
if (recipient.isLocal) {
|
|
534
|
+
return this.deliverLocal(message, recipient.agentId);
|
|
535
|
+
}
|
|
536
|
+
return this.deliverRemote(message, recipient.host, recipient.agentId);
|
|
537
|
+
}
|
|
538
|
+
async deliverLocal(message, agentId) {
|
|
539
|
+
const inbox = this.localAgents.get(agentId);
|
|
540
|
+
if (!inbox) {
|
|
541
|
+
return {
|
|
542
|
+
messageId: message.id,
|
|
543
|
+
delivered: false,
|
|
544
|
+
error: `Agent ${agentId} not found locally`,
|
|
545
|
+
via: "local"
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
try {
|
|
549
|
+
await inbox.save(message);
|
|
550
|
+
return {
|
|
551
|
+
messageId: message.id,
|
|
552
|
+
delivered: true,
|
|
553
|
+
deliveredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
554
|
+
via: "local"
|
|
555
|
+
};
|
|
556
|
+
} catch (err) {
|
|
557
|
+
return {
|
|
558
|
+
messageId: message.id,
|
|
559
|
+
delivered: false,
|
|
560
|
+
error: err.message,
|
|
561
|
+
via: "local"
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
async deliverRemote(message, hostAddress, _agentId) {
|
|
566
|
+
if (!this.remoteHandler) {
|
|
567
|
+
return {
|
|
568
|
+
messageId: message.id,
|
|
569
|
+
delivered: false,
|
|
570
|
+
error: "No remote delivery handler configured",
|
|
571
|
+
via: "remote"
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
try {
|
|
575
|
+
return await this.remoteHandler(message, hostAddress);
|
|
576
|
+
} catch (err) {
|
|
577
|
+
return {
|
|
578
|
+
messageId: message.id,
|
|
579
|
+
delivered: false,
|
|
580
|
+
error: err.message,
|
|
581
|
+
via: "remote"
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
async archive(messageId) {
|
|
586
|
+
const message = await this.config.inboxStorage.get(messageId);
|
|
587
|
+
if (!message) return false;
|
|
588
|
+
await this.config.archivedStorage.save(message);
|
|
589
|
+
await this.config.inboxStorage.delete(messageId);
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
async unarchive(messageId) {
|
|
593
|
+
const message = await this.config.archivedStorage.get(messageId);
|
|
594
|
+
if (!message) return false;
|
|
595
|
+
message.status = "read";
|
|
596
|
+
await this.config.inboxStorage.save(message);
|
|
597
|
+
await this.config.archivedStorage.delete(messageId);
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
getLocalAgents() {
|
|
601
|
+
return Array.from(this.localAgents.keys());
|
|
602
|
+
}
|
|
603
|
+
isLocalAgent(agentId) {
|
|
604
|
+
return this.localAgents.has(agentId);
|
|
605
|
+
}
|
|
606
|
+
parseRecipient(to) {
|
|
607
|
+
if (to.includes("@")) {
|
|
608
|
+
const [agentId, host] = to.split("@");
|
|
609
|
+
return { agentId, host, isLocal: false };
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
agentId: to,
|
|
613
|
+
isLocal: this.localAgents.has(to)
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
// src/transport/local.ts
|
|
619
|
+
var LocalTransport = class {
|
|
620
|
+
agents = /* @__PURE__ */ new Map();
|
|
621
|
+
register(agentId, inbox) {
|
|
622
|
+
this.agents.set(agentId, inbox);
|
|
623
|
+
}
|
|
624
|
+
unregister(agentId) {
|
|
625
|
+
this.agents.delete(agentId);
|
|
626
|
+
}
|
|
627
|
+
async deliver(message) {
|
|
628
|
+
const inbox = this.agents.get(message.to);
|
|
629
|
+
if (!inbox) {
|
|
630
|
+
return {
|
|
631
|
+
messageId: message.id,
|
|
632
|
+
delivered: false,
|
|
633
|
+
error: `Agent ${message.to} not found locally`,
|
|
634
|
+
via: "local"
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
try {
|
|
638
|
+
await inbox.save(message);
|
|
639
|
+
return {
|
|
640
|
+
messageId: message.id,
|
|
641
|
+
delivered: true,
|
|
642
|
+
deliveredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
643
|
+
via: "local"
|
|
644
|
+
};
|
|
645
|
+
} catch (err) {
|
|
646
|
+
return {
|
|
647
|
+
messageId: message.id,
|
|
648
|
+
delivered: false,
|
|
649
|
+
error: err.message,
|
|
650
|
+
via: "local"
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
async broadcast(message, excludeFrom = true) {
|
|
655
|
+
const results = /* @__PURE__ */ new Map();
|
|
656
|
+
for (const [agentId] of this.agents) {
|
|
657
|
+
if (excludeFrom && agentId === message.from) continue;
|
|
658
|
+
const targetMessage = { ...message, to: agentId };
|
|
659
|
+
const result = await this.deliver(targetMessage);
|
|
660
|
+
results.set(agentId, result);
|
|
661
|
+
}
|
|
662
|
+
return results;
|
|
663
|
+
}
|
|
664
|
+
getRegisteredAgents() {
|
|
665
|
+
return Array.from(this.agents.keys());
|
|
666
|
+
}
|
|
667
|
+
isRegistered(agentId) {
|
|
668
|
+
return this.agents.has(agentId);
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
var globalTransport = null;
|
|
672
|
+
function getLocalTransport() {
|
|
673
|
+
if (!globalTransport) {
|
|
674
|
+
globalTransport = new LocalTransport();
|
|
675
|
+
}
|
|
676
|
+
return globalTransport;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// src/transport/remote.ts
|
|
680
|
+
import got from "got";
|
|
681
|
+
var RemoteTransport = class {
|
|
682
|
+
options;
|
|
683
|
+
constructor(options = {}) {
|
|
684
|
+
this.options = {
|
|
685
|
+
timeout: options.timeout ?? 1e4,
|
|
686
|
+
retries: options.retries ?? 2
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
async deliver(message, hostAddress, hostPort) {
|
|
690
|
+
const url = `http://${hostAddress}:${hostPort}/message`;
|
|
691
|
+
try {
|
|
692
|
+
const response = await got.post(url, {
|
|
693
|
+
json: message,
|
|
694
|
+
timeout: { request: this.options.timeout },
|
|
695
|
+
retry: { limit: this.options.retries },
|
|
696
|
+
throwHttpErrors: false
|
|
697
|
+
});
|
|
698
|
+
if (response.statusCode === 200 || response.statusCode === 201) {
|
|
699
|
+
return {
|
|
700
|
+
messageId: message.id,
|
|
701
|
+
delivered: true,
|
|
702
|
+
deliveredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
703
|
+
via: "remote"
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
return {
|
|
707
|
+
messageId: message.id,
|
|
708
|
+
delivered: false,
|
|
709
|
+
error: `HTTP ${response.statusCode}: ${response.body}`,
|
|
710
|
+
via: "remote"
|
|
711
|
+
};
|
|
712
|
+
} catch (err) {
|
|
713
|
+
return {
|
|
714
|
+
messageId: message.id,
|
|
715
|
+
delivered: false,
|
|
716
|
+
error: err.message || "Connection failed",
|
|
717
|
+
via: "remote"
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
async deliverToAgent(message, agentAddress) {
|
|
722
|
+
const parts = agentAddress.split("@");
|
|
723
|
+
if (parts.length !== 2) {
|
|
724
|
+
return {
|
|
725
|
+
messageId: message.id,
|
|
726
|
+
delivered: false,
|
|
727
|
+
error: "Invalid agent address format. Expected: agentId@host:port",
|
|
728
|
+
via: "remote"
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
const [, hostPart] = parts;
|
|
732
|
+
const [host, portStr] = hostPart.split(":");
|
|
733
|
+
const port = portStr ? parseInt(portStr, 10) : 9876;
|
|
734
|
+
return this.deliver(message, host, port);
|
|
735
|
+
}
|
|
736
|
+
async broadcast(message, hosts) {
|
|
737
|
+
const results = /* @__PURE__ */ new Map();
|
|
738
|
+
await Promise.all(
|
|
739
|
+
hosts.map(async ({ address, port }) => {
|
|
740
|
+
const result = await this.deliver(message, address, port);
|
|
741
|
+
results.set(`${address}:${port}`, result);
|
|
742
|
+
})
|
|
743
|
+
);
|
|
744
|
+
return results;
|
|
745
|
+
}
|
|
746
|
+
async ping(hostAddress, hostPort) {
|
|
747
|
+
const url = `http://${hostAddress}:${hostPort}/health`;
|
|
748
|
+
try {
|
|
749
|
+
const response = await got.get(url, {
|
|
750
|
+
timeout: { request: 5e3 },
|
|
751
|
+
retry: { limit: 0 },
|
|
752
|
+
throwHttpErrors: false
|
|
753
|
+
});
|
|
754
|
+
return response.statusCode === 200;
|
|
755
|
+
} catch {
|
|
756
|
+
return false;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
// src/index.ts
|
|
762
|
+
var MessagingService = class {
|
|
763
|
+
config;
|
|
764
|
+
inbox;
|
|
765
|
+
sent;
|
|
766
|
+
archived;
|
|
767
|
+
router;
|
|
768
|
+
constructor(config) {
|
|
769
|
+
this.config = config;
|
|
770
|
+
this.inbox = new InboxStorage(config.agentId, config.storagePath);
|
|
771
|
+
this.sent = new SentStorage(config.agentId, config.storagePath);
|
|
772
|
+
this.archived = new ArchivedStorage(config.agentId, config.storagePath);
|
|
773
|
+
this.router = new MessageRouter({
|
|
774
|
+
localAgentId: config.agentId,
|
|
775
|
+
inboxStorage: this.inbox,
|
|
776
|
+
sentStorage: this.sent,
|
|
777
|
+
archivedStorage: this.archived
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
async initialize() {
|
|
781
|
+
await this.inbox.initialize();
|
|
782
|
+
await this.sent.initialize();
|
|
783
|
+
await this.archived.initialize();
|
|
784
|
+
}
|
|
785
|
+
async send(input) {
|
|
786
|
+
const message = createMessage(this.config.agentId, input);
|
|
787
|
+
return this.router.route(message);
|
|
788
|
+
}
|
|
789
|
+
async reply(messageId, body) {
|
|
790
|
+
const original = await this.inbox.get(messageId);
|
|
791
|
+
if (!original) {
|
|
792
|
+
throw new Error(`Message ${messageId} not found`);
|
|
793
|
+
}
|
|
794
|
+
const reply = createReply(original, this.config.agentId, body);
|
|
795
|
+
return this.router.route(reply);
|
|
796
|
+
}
|
|
797
|
+
async forward(messageId, to, note) {
|
|
798
|
+
const original = await this.inbox.get(messageId);
|
|
799
|
+
if (!original) {
|
|
800
|
+
throw new Error(`Message ${messageId} not found`);
|
|
801
|
+
}
|
|
802
|
+
const forwarded = createForward(original, this.config.agentId, to, note);
|
|
803
|
+
return this.router.route(forwarded);
|
|
804
|
+
}
|
|
805
|
+
async getInbox(filter) {
|
|
806
|
+
return this.inbox.list(filter);
|
|
807
|
+
}
|
|
808
|
+
async getSent(filter) {
|
|
809
|
+
return this.sent.list(filter);
|
|
810
|
+
}
|
|
811
|
+
async getArchived(filter) {
|
|
812
|
+
return this.archived.list(filter);
|
|
813
|
+
}
|
|
814
|
+
async getMessage(messageId) {
|
|
815
|
+
return this.inbox.get(messageId);
|
|
816
|
+
}
|
|
817
|
+
async markAsRead(messageId) {
|
|
818
|
+
return this.inbox.markAsRead(messageId);
|
|
819
|
+
}
|
|
820
|
+
async archive(messageId) {
|
|
821
|
+
return this.router.archive(messageId);
|
|
822
|
+
}
|
|
823
|
+
async deleteMessage(messageId) {
|
|
824
|
+
return this.inbox.delete(messageId);
|
|
825
|
+
}
|
|
826
|
+
async getInboxSummary() {
|
|
827
|
+
return this.inbox.getSummary();
|
|
828
|
+
}
|
|
829
|
+
getRouter() {
|
|
830
|
+
return this.router;
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
async function createMessagingService(agentId, storagePath) {
|
|
834
|
+
const service = new MessagingService({ agentId, storagePath });
|
|
835
|
+
await service.initialize();
|
|
836
|
+
return service;
|
|
837
|
+
}
|
|
838
|
+
export {
|
|
839
|
+
ArchivedStorage,
|
|
840
|
+
DEFAULT_MESSAGING_CONFIG,
|
|
841
|
+
InboxStorage,
|
|
842
|
+
LocalTransport,
|
|
843
|
+
MessageBuilder,
|
|
844
|
+
MessageRouter,
|
|
845
|
+
MessagingService,
|
|
846
|
+
RemoteTransport,
|
|
847
|
+
SentStorage,
|
|
848
|
+
createForward,
|
|
849
|
+
createMessage,
|
|
850
|
+
createMessagingService,
|
|
851
|
+
createReply,
|
|
852
|
+
getLocalTransport
|
|
853
|
+
};
|
|
854
|
+
//# sourceMappingURL=index.js.map
|