swarm-mail 0.1.0 → 0.1.3
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 +28 -0
- package/dist/adapter.d.ts +36 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16710 -0
- package/{src/pglite.ts → dist/pglite.d.ts} +7 -93
- package/dist/pglite.d.ts.map +1 -0
- package/dist/streams/agent-mail.d.ts +139 -0
- package/dist/streams/agent-mail.d.ts.map +1 -0
- package/dist/streams/debug.d.ts +173 -0
- package/dist/streams/debug.d.ts.map +1 -0
- package/dist/streams/effect/ask.d.ts +124 -0
- package/dist/streams/effect/ask.d.ts.map +1 -0
- package/dist/streams/effect/cursor.d.ts +87 -0
- package/dist/streams/effect/cursor.d.ts.map +1 -0
- package/dist/streams/effect/deferred.d.ts +108 -0
- package/dist/streams/effect/deferred.d.ts.map +1 -0
- package/{src/streams/effect/index.ts → dist/streams/effect/index.d.ts} +1 -0
- package/dist/streams/effect/index.d.ts.map +1 -0
- package/{src/streams/effect/layers.ts → dist/streams/effect/layers.d.ts} +8 -33
- package/dist/streams/effect/layers.d.ts.map +1 -0
- package/dist/streams/effect/lock.d.ts +137 -0
- package/dist/streams/effect/lock.d.ts.map +1 -0
- package/dist/streams/effect/mailbox.d.ts +98 -0
- package/dist/streams/effect/mailbox.d.ts.map +1 -0
- package/dist/streams/events.d.ts +487 -0
- package/dist/streams/events.d.ts.map +1 -0
- package/dist/streams/index.d.ts +106 -0
- package/dist/streams/index.d.ts.map +1 -0
- package/dist/streams/migrations.d.ts +102 -0
- package/dist/streams/migrations.d.ts.map +1 -0
- package/dist/streams/projections.d.ts +173 -0
- package/dist/streams/projections.d.ts.map +1 -0
- package/dist/streams/store.d.ts +171 -0
- package/dist/streams/store.d.ts.map +1 -0
- package/dist/streams/swarm-mail.d.ts +153 -0
- package/dist/streams/swarm-mail.d.ts.map +1 -0
- package/dist/types/adapter.d.ts +267 -0
- package/dist/types/adapter.d.ts.map +1 -0
- package/dist/types/database.d.ts +117 -0
- package/dist/types/database.d.ts.map +1 -0
- package/{src/types/index.ts → dist/types/index.d.ts} +2 -15
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +21 -5
- package/src/adapter.ts +0 -306
- package/src/index.ts +0 -57
- package/src/streams/agent-mail.test.ts +0 -777
- package/src/streams/agent-mail.ts +0 -535
- package/src/streams/debug.test.ts +0 -500
- package/src/streams/debug.ts +0 -727
- package/src/streams/effect/ask.integration.test.ts +0 -314
- package/src/streams/effect/ask.ts +0 -202
- package/src/streams/effect/cursor.integration.test.ts +0 -418
- package/src/streams/effect/cursor.ts +0 -288
- package/src/streams/effect/deferred.test.ts +0 -357
- package/src/streams/effect/deferred.ts +0 -445
- package/src/streams/effect/lock.test.ts +0 -385
- package/src/streams/effect/lock.ts +0 -399
- package/src/streams/effect/mailbox.test.ts +0 -260
- package/src/streams/effect/mailbox.ts +0 -318
- package/src/streams/events.test.ts +0 -924
- package/src/streams/events.ts +0 -329
- package/src/streams/index.test.ts +0 -229
- package/src/streams/index.ts +0 -578
- package/src/streams/migrations.test.ts +0 -359
- package/src/streams/migrations.ts +0 -362
- package/src/streams/projections.test.ts +0 -611
- package/src/streams/projections.ts +0 -564
- package/src/streams/store.integration.test.ts +0 -658
- package/src/streams/store.ts +0 -1129
- package/src/streams/swarm-mail.ts +0 -552
- package/src/types/adapter.ts +0 -392
- package/src/types/database.ts +0 -127
- package/tsconfig.json +0 -22
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ask Pattern Integration Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests request/response communication between agents using
|
|
5
|
-
* DurableMailbox + DurableDeferred pattern.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
9
|
-
import { Effect } from "effect";
|
|
10
|
-
import { ask, respond, askWithMailbox } from "./ask";
|
|
11
|
-
import { DurableMailbox } from "./mailbox";
|
|
12
|
-
import { DurableDeferred } from "./deferred";
|
|
13
|
-
import { DurableAskLive } from "./layers";
|
|
14
|
-
import { resetDatabase, closeDatabase } from "../index";
|
|
15
|
-
import { randomUUID } from "node:crypto";
|
|
16
|
-
|
|
17
|
-
// ============================================================================
|
|
18
|
-
// Test Fixtures
|
|
19
|
-
// ============================================================================
|
|
20
|
-
|
|
21
|
-
interface TestRequest {
|
|
22
|
-
action: string;
|
|
23
|
-
userId: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface TestResponse {
|
|
27
|
-
status: "success" | "error";
|
|
28
|
-
data?: unknown;
|
|
29
|
-
message?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// ============================================================================
|
|
33
|
-
// Setup/Teardown
|
|
34
|
-
// ============================================================================
|
|
35
|
-
|
|
36
|
-
let testDbPath: string;
|
|
37
|
-
|
|
38
|
-
beforeEach(async () => {
|
|
39
|
-
testDbPath = `/tmp/ask-test-${randomUUID()}`;
|
|
40
|
-
await resetDatabase(testDbPath);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
afterEach(async () => {
|
|
44
|
-
await closeDatabase(testDbPath);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// ============================================================================
|
|
48
|
-
// Tests
|
|
49
|
-
// ============================================================================
|
|
50
|
-
|
|
51
|
-
describe("Ask Pattern", () => {
|
|
52
|
-
it("should send request and receive response via ask()", async () => {
|
|
53
|
-
const program = Effect.gen(function* () {
|
|
54
|
-
const mailboxService = yield* DurableMailbox;
|
|
55
|
-
|
|
56
|
-
// Create mailboxes for both agents
|
|
57
|
-
const agentA = yield* mailboxService.create({
|
|
58
|
-
agent: "agent-a",
|
|
59
|
-
projectKey: "test-proj",
|
|
60
|
-
projectPath: testDbPath,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const agentB = yield* mailboxService.create({
|
|
64
|
-
agent: "agent-b",
|
|
65
|
-
projectKey: "test-proj",
|
|
66
|
-
projectPath: testDbPath,
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// Agent B: Listen for request and respond
|
|
70
|
-
const responder = Effect.promise(async () => {
|
|
71
|
-
for await (const envelope of agentB.receive<TestRequest>()) {
|
|
72
|
-
expect(envelope.payload.action).toBe("getUserData");
|
|
73
|
-
expect(envelope.payload.userId).toBe(123);
|
|
74
|
-
expect(envelope.replyTo).toBeDefined();
|
|
75
|
-
|
|
76
|
-
// Send response
|
|
77
|
-
await Effect.runPromise(
|
|
78
|
-
respond<TestResponse>(
|
|
79
|
-
envelope,
|
|
80
|
-
{
|
|
81
|
-
status: "success",
|
|
82
|
-
data: { username: "testuser", id: 123 },
|
|
83
|
-
},
|
|
84
|
-
testDbPath,
|
|
85
|
-
).pipe(Effect.provide(DurableAskLive)),
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
await Effect.runPromise(envelope.commit());
|
|
89
|
-
break; // Exit after first message
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// Start responder in background
|
|
94
|
-
Effect.runFork(responder.pipe(Effect.provide(DurableAskLive)));
|
|
95
|
-
|
|
96
|
-
// Agent A: Send request via ask()
|
|
97
|
-
const response = yield* ask<TestRequest, TestResponse>({
|
|
98
|
-
mailbox: agentA,
|
|
99
|
-
to: "agent-b",
|
|
100
|
-
payload: { action: "getUserData", userId: 123 },
|
|
101
|
-
ttlSeconds: 5,
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
expect(response.status).toBe("success");
|
|
105
|
-
expect(response.data).toEqual({ username: "testuser", id: 123 });
|
|
106
|
-
|
|
107
|
-
return response;
|
|
108
|
-
}).pipe(Effect.provide(DurableAskLive));
|
|
109
|
-
|
|
110
|
-
const result = await Effect.runPromise(program);
|
|
111
|
-
expect(result.status).toBe("success");
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("should timeout when no response received", async () => {
|
|
115
|
-
const program = Effect.gen(function* () {
|
|
116
|
-
const mailboxService = yield* DurableMailbox;
|
|
117
|
-
|
|
118
|
-
const agentA = yield* mailboxService.create({
|
|
119
|
-
agent: "agent-a",
|
|
120
|
-
projectKey: "test-proj",
|
|
121
|
-
projectPath: testDbPath,
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// No one listening, should timeout after 1 second
|
|
125
|
-
const response = yield* ask<TestRequest, TestResponse>({
|
|
126
|
-
mailbox: agentA,
|
|
127
|
-
to: "agent-b-nonexistent",
|
|
128
|
-
payload: { action: "getUserData", userId: 123 },
|
|
129
|
-
ttlSeconds: 1,
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
return response;
|
|
133
|
-
}).pipe(Effect.provide(DurableAskLive));
|
|
134
|
-
|
|
135
|
-
// Expect timeout error
|
|
136
|
-
const result = await Effect.runPromise(Effect.either(program));
|
|
137
|
-
expect(result._tag).toBe("Left");
|
|
138
|
-
if (result._tag === "Left") {
|
|
139
|
-
expect(result.left).toHaveProperty("_tag", "TimeoutError");
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("should support askWithMailbox for one-off requests", async () => {
|
|
144
|
-
const program = Effect.gen(function* () {
|
|
145
|
-
const mailboxService = yield* DurableMailbox;
|
|
146
|
-
|
|
147
|
-
// Agent B: Listen for request
|
|
148
|
-
const agentB = yield* mailboxService.create({
|
|
149
|
-
agent: "agent-b",
|
|
150
|
-
projectKey: "test-proj",
|
|
151
|
-
projectPath: testDbPath,
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
const responder = Effect.promise(async () => {
|
|
155
|
-
for await (const envelope of agentB.receive<TestRequest>()) {
|
|
156
|
-
await Effect.runPromise(
|
|
157
|
-
respond<TestResponse>(
|
|
158
|
-
envelope,
|
|
159
|
-
{ status: "success", message: "One-off response" },
|
|
160
|
-
testDbPath,
|
|
161
|
-
).pipe(Effect.provide(DurableAskLive)),
|
|
162
|
-
);
|
|
163
|
-
await Effect.runPromise(envelope.commit());
|
|
164
|
-
break;
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
Effect.runFork(responder.pipe(Effect.provide(DurableAskLive)));
|
|
169
|
-
|
|
170
|
-
// Use askWithMailbox (creates mailbox automatically)
|
|
171
|
-
const response = yield* askWithMailbox<TestRequest, TestResponse>({
|
|
172
|
-
agent: "agent-a",
|
|
173
|
-
projectKey: "test-proj",
|
|
174
|
-
to: "agent-b",
|
|
175
|
-
payload: { action: "ping", userId: 0 },
|
|
176
|
-
ttlSeconds: 5,
|
|
177
|
-
projectPath: testDbPath,
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
expect(response.status).toBe("success");
|
|
181
|
-
expect(response.message).toBe("One-off response");
|
|
182
|
-
|
|
183
|
-
return response;
|
|
184
|
-
}).pipe(Effect.provide(DurableAskLive));
|
|
185
|
-
|
|
186
|
-
const result = await Effect.runPromise(program);
|
|
187
|
-
expect(result.status).toBe("success");
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it("should handle multiple concurrent asks", async () => {
|
|
191
|
-
const program = Effect.gen(function* () {
|
|
192
|
-
const mailboxService = yield* DurableMailbox;
|
|
193
|
-
|
|
194
|
-
const agentA = yield* mailboxService.create({
|
|
195
|
-
agent: "agent-a",
|
|
196
|
-
projectKey: "test-proj",
|
|
197
|
-
projectPath: testDbPath,
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
const agentB = yield* mailboxService.create({
|
|
201
|
-
agent: "agent-b",
|
|
202
|
-
projectKey: "test-proj",
|
|
203
|
-
projectPath: testDbPath,
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
// Agent B: Respond to all requests (limit to 3)
|
|
207
|
-
const responder = Effect.promise(async () => {
|
|
208
|
-
let count = 0;
|
|
209
|
-
for await (const envelope of agentB.receive<TestRequest>()) {
|
|
210
|
-
await Effect.runPromise(
|
|
211
|
-
respond<TestResponse>(
|
|
212
|
-
envelope,
|
|
213
|
-
{
|
|
214
|
-
status: "success",
|
|
215
|
-
data: { userId: envelope.payload.userId },
|
|
216
|
-
},
|
|
217
|
-
testDbPath,
|
|
218
|
-
).pipe(Effect.provide(DurableAskLive)),
|
|
219
|
-
);
|
|
220
|
-
await Effect.runPromise(envelope.commit());
|
|
221
|
-
count++;
|
|
222
|
-
if (count >= 3) break; // Exit after 3 responses
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
Effect.runFork(responder.pipe(Effect.provide(DurableAskLive)));
|
|
227
|
-
|
|
228
|
-
// Send 3 concurrent requests
|
|
229
|
-
const requests = [
|
|
230
|
-
ask<TestRequest, TestResponse>({
|
|
231
|
-
mailbox: agentA,
|
|
232
|
-
to: "agent-b",
|
|
233
|
-
payload: { action: "getData", userId: 1 },
|
|
234
|
-
ttlSeconds: 5,
|
|
235
|
-
}),
|
|
236
|
-
ask<TestRequest, TestResponse>({
|
|
237
|
-
mailbox: agentA,
|
|
238
|
-
to: "agent-b",
|
|
239
|
-
payload: { action: "getData", userId: 2 },
|
|
240
|
-
ttlSeconds: 5,
|
|
241
|
-
}),
|
|
242
|
-
ask<TestRequest, TestResponse>({
|
|
243
|
-
mailbox: agentA,
|
|
244
|
-
to: "agent-b",
|
|
245
|
-
payload: { action: "getData", userId: 3 },
|
|
246
|
-
ttlSeconds: 5,
|
|
247
|
-
}),
|
|
248
|
-
];
|
|
249
|
-
|
|
250
|
-
const responses = yield* Effect.all(requests, { concurrency: 3 });
|
|
251
|
-
|
|
252
|
-
expect(responses).toHaveLength(3);
|
|
253
|
-
// Sort by userId to avoid flaky ordering - concurrent responses may arrive in any order
|
|
254
|
-
const sortedData = responses
|
|
255
|
-
.map((r) => r.data)
|
|
256
|
-
.sort((a: any, b: any) => a.userId - b.userId);
|
|
257
|
-
expect(sortedData).toEqual([{ userId: 1 }, { userId: 2 }, { userId: 3 }]);
|
|
258
|
-
|
|
259
|
-
return responses;
|
|
260
|
-
}).pipe(Effect.provide(DurableAskLive));
|
|
261
|
-
|
|
262
|
-
const results = await Effect.runPromise(program);
|
|
263
|
-
expect(results).toHaveLength(3);
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
it("should support thread IDs for conversation tracking", async () => {
|
|
267
|
-
const program = Effect.gen(function* () {
|
|
268
|
-
const mailboxService = yield* DurableMailbox;
|
|
269
|
-
|
|
270
|
-
const agentA = yield* mailboxService.create({
|
|
271
|
-
agent: "agent-a",
|
|
272
|
-
projectKey: "test-proj",
|
|
273
|
-
projectPath: testDbPath,
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
const agentB = yield* mailboxService.create({
|
|
277
|
-
agent: "agent-b",
|
|
278
|
-
projectKey: "test-proj",
|
|
279
|
-
projectPath: testDbPath,
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
const responder = Effect.promise(async () => {
|
|
283
|
-
for await (const envelope of agentB.receive<TestRequest>()) {
|
|
284
|
-
expect(envelope.threadId).toBe("conversation-123");
|
|
285
|
-
await Effect.runPromise(
|
|
286
|
-
respond<TestResponse>(
|
|
287
|
-
envelope,
|
|
288
|
-
{ status: "success" },
|
|
289
|
-
testDbPath,
|
|
290
|
-
).pipe(Effect.provide(DurableAskLive)),
|
|
291
|
-
);
|
|
292
|
-
await Effect.runPromise(envelope.commit());
|
|
293
|
-
break;
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
Effect.runFork(responder.pipe(Effect.provide(DurableAskLive)));
|
|
298
|
-
|
|
299
|
-
const response = yield* ask<TestRequest, TestResponse>({
|
|
300
|
-
mailbox: agentA,
|
|
301
|
-
to: "agent-b",
|
|
302
|
-
payload: { action: "test", userId: 0 },
|
|
303
|
-
threadId: "conversation-123",
|
|
304
|
-
ttlSeconds: 5,
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
expect(response.status).toBe("success");
|
|
308
|
-
|
|
309
|
-
return response;
|
|
310
|
-
}).pipe(Effect.provide(DurableAskLive));
|
|
311
|
-
|
|
312
|
-
await Effect.runPromise(program);
|
|
313
|
-
});
|
|
314
|
-
});
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ask Pattern - Request/Response over Durable Streams
|
|
3
|
-
*
|
|
4
|
-
* Combines DurableMailbox (message passing) + DurableDeferred (distributed promise)
|
|
5
|
-
* for synchronous-style request/response communication between agents.
|
|
6
|
-
*
|
|
7
|
-
* Based on Kyle Matthews' pattern from Agent Mail.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```typescript
|
|
11
|
-
* // Agent A asks Agent B for data
|
|
12
|
-
* const result = yield* ask<Request, Response>({
|
|
13
|
-
* mailbox: myMailbox,
|
|
14
|
-
* to: "agent-b",
|
|
15
|
-
* payload: { query: "getUserData", userId: 123 },
|
|
16
|
-
* ttlSeconds: 30,
|
|
17
|
-
* });
|
|
18
|
-
*
|
|
19
|
-
* // Agent B receives request and responds
|
|
20
|
-
* for await (const envelope of mailbox.receive()) {
|
|
21
|
-
* const response = processRequest(envelope.payload);
|
|
22
|
-
* if (envelope.replyTo) {
|
|
23
|
-
* yield* DurableDeferred.resolve(envelope.replyTo, response);
|
|
24
|
-
* }
|
|
25
|
-
* yield* envelope.commit();
|
|
26
|
-
* }
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
import { Effect } from "effect";
|
|
31
|
-
import { DurableCursor } from "./cursor";
|
|
32
|
-
import { DurableMailbox, type Mailbox } from "./mailbox";
|
|
33
|
-
import { DurableDeferred, type DeferredConfig } from "./deferred";
|
|
34
|
-
import type { TimeoutError, NotFoundError } from "./deferred";
|
|
35
|
-
|
|
36
|
-
// ============================================================================
|
|
37
|
-
// Types
|
|
38
|
-
// ============================================================================
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Configuration for ask() request
|
|
42
|
-
*/
|
|
43
|
-
export interface AskConfig<Req> {
|
|
44
|
-
/** Mailbox to send message from */
|
|
45
|
-
readonly mailbox: Mailbox;
|
|
46
|
-
/** Recipient agent(s) */
|
|
47
|
-
readonly to: string | string[];
|
|
48
|
-
/** Request payload */
|
|
49
|
-
readonly payload: Req;
|
|
50
|
-
/** Time-to-live in seconds before timeout (default: 60) */
|
|
51
|
-
readonly ttlSeconds?: number;
|
|
52
|
-
/** Optional thread ID for conversation tracking */
|
|
53
|
-
readonly threadId?: string;
|
|
54
|
-
/** Optional importance level */
|
|
55
|
-
readonly importance?: "low" | "normal" | "high" | "urgent";
|
|
56
|
-
/** Optional project path for database isolation */
|
|
57
|
-
readonly projectPath?: string;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ============================================================================
|
|
61
|
-
// Ask Implementation
|
|
62
|
-
// ============================================================================
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Request/response pattern combining mailbox send + deferred await
|
|
66
|
-
*
|
|
67
|
-
* Creates a deferred promise, sends message with replyTo URL, then blocks
|
|
68
|
-
* until the recipient resolves the deferred (or timeout).
|
|
69
|
-
*
|
|
70
|
-
* @template Req - Request payload type
|
|
71
|
-
* @template Res - Response type
|
|
72
|
-
* @param config - Ask configuration
|
|
73
|
-
* @returns Effect that resolves with response or fails with timeout/not-found
|
|
74
|
-
*
|
|
75
|
-
* @example
|
|
76
|
-
* ```typescript
|
|
77
|
-
* const program = Effect.gen(function* () {
|
|
78
|
-
* const mailbox = yield* DurableMailbox;
|
|
79
|
-
* const myMailbox = yield* mailbox.create({ agent: "worker-1", projectKey: "proj" });
|
|
80
|
-
*
|
|
81
|
-
* const response = yield* ask<Request, Response>({
|
|
82
|
-
* mailbox: myMailbox,
|
|
83
|
-
* to: "worker-2",
|
|
84
|
-
* payload: { task: "getData" },
|
|
85
|
-
* ttlSeconds: 30,
|
|
86
|
-
* });
|
|
87
|
-
*
|
|
88
|
-
* console.log("Got response:", response);
|
|
89
|
-
* });
|
|
90
|
-
* ```
|
|
91
|
-
*/
|
|
92
|
-
export function ask<Req, Res>(
|
|
93
|
-
config: AskConfig<Req>,
|
|
94
|
-
): Effect.Effect<Res, TimeoutError | NotFoundError, DurableDeferred> {
|
|
95
|
-
return Effect.gen(function* () {
|
|
96
|
-
const deferred = yield* DurableDeferred;
|
|
97
|
-
|
|
98
|
-
// Create deferred for response
|
|
99
|
-
const deferredConfig: DeferredConfig = {
|
|
100
|
-
ttlSeconds: config.ttlSeconds ?? 60,
|
|
101
|
-
projectPath: config.projectPath,
|
|
102
|
-
};
|
|
103
|
-
const responseHandle = yield* deferred.create<Res>(deferredConfig);
|
|
104
|
-
|
|
105
|
-
// Send message with replyTo URL
|
|
106
|
-
yield* config.mailbox.send(config.to, {
|
|
107
|
-
payload: config.payload,
|
|
108
|
-
replyTo: responseHandle.url,
|
|
109
|
-
threadId: config.threadId,
|
|
110
|
-
importance: config.importance,
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// Block until response or timeout
|
|
114
|
-
const response = yield* responseHandle.value;
|
|
115
|
-
|
|
116
|
-
return response;
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// ============================================================================
|
|
121
|
-
// Convenience Variants
|
|
122
|
-
// ============================================================================
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Ask pattern with automatic mailbox creation
|
|
126
|
-
*
|
|
127
|
-
* Simpler variant when you don't need to reuse the mailbox.
|
|
128
|
-
*
|
|
129
|
-
* @example
|
|
130
|
-
* ```typescript
|
|
131
|
-
* const response = yield* askWithMailbox({
|
|
132
|
-
* agent: "worker-1",
|
|
133
|
-
* projectKey: "proj",
|
|
134
|
-
* to: "worker-2",
|
|
135
|
-
* payload: { task: "getData" },
|
|
136
|
-
* });
|
|
137
|
-
* ```
|
|
138
|
-
*/
|
|
139
|
-
export function askWithMailbox<Req, Res>(config: {
|
|
140
|
-
readonly agent: string;
|
|
141
|
-
readonly projectKey: string;
|
|
142
|
-
readonly to: string | string[];
|
|
143
|
-
readonly payload: Req;
|
|
144
|
-
readonly ttlSeconds?: number;
|
|
145
|
-
readonly threadId?: string;
|
|
146
|
-
readonly importance?: "low" | "normal" | "high" | "urgent";
|
|
147
|
-
readonly projectPath?: string;
|
|
148
|
-
}): Effect.Effect<
|
|
149
|
-
Res,
|
|
150
|
-
TimeoutError | NotFoundError,
|
|
151
|
-
DurableDeferred | DurableMailbox | DurableCursor
|
|
152
|
-
> {
|
|
153
|
-
return Effect.gen(function* () {
|
|
154
|
-
const mailboxService = yield* DurableMailbox;
|
|
155
|
-
|
|
156
|
-
const mailbox = yield* mailboxService.create({
|
|
157
|
-
agent: config.agent,
|
|
158
|
-
projectKey: config.projectKey,
|
|
159
|
-
projectPath: config.projectPath,
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
return yield* ask<Req, Res>({
|
|
163
|
-
mailbox,
|
|
164
|
-
to: config.to,
|
|
165
|
-
payload: config.payload,
|
|
166
|
-
ttlSeconds: config.ttlSeconds,
|
|
167
|
-
threadId: config.threadId,
|
|
168
|
-
importance: config.importance,
|
|
169
|
-
projectPath: config.projectPath,
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Respond to a message envelope by resolving its replyTo deferred
|
|
176
|
-
*
|
|
177
|
-
* Helper for the receiver side of ask() pattern.
|
|
178
|
-
*
|
|
179
|
-
* @example
|
|
180
|
-
* ```typescript
|
|
181
|
-
* for await (const envelope of mailbox.receive()) {
|
|
182
|
-
* const response = processRequest(envelope.payload);
|
|
183
|
-
* yield* respond(envelope, response);
|
|
184
|
-
* yield* envelope.commit();
|
|
185
|
-
* }
|
|
186
|
-
* ```
|
|
187
|
-
*/
|
|
188
|
-
export function respond<T>(
|
|
189
|
-
envelope: { readonly replyTo?: string },
|
|
190
|
-
value: T,
|
|
191
|
-
projectPath?: string,
|
|
192
|
-
): Effect.Effect<void, NotFoundError, DurableDeferred> {
|
|
193
|
-
return Effect.gen(function* () {
|
|
194
|
-
if (!envelope.replyTo) {
|
|
195
|
-
// No replyTo - this wasn't an ask() request, just return
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const deferred = yield* DurableDeferred;
|
|
200
|
-
yield* deferred.resolve(envelope.replyTo, value, projectPath);
|
|
201
|
-
});
|
|
202
|
-
}
|