agents 0.0.0-1232c19 → 0.0.0-14616d3
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 +131 -25
- package/dist/ai-chat-agent.d.ts +11 -7
- package/dist/ai-chat-agent.js +146 -47
- package/dist/ai-chat-agent.js.map +1 -1
- package/dist/ai-chat-v5-migration.d.ts +152 -0
- package/dist/ai-chat-v5-migration.js +19 -0
- package/dist/ai-chat-v5-migration.js.map +1 -0
- package/dist/ai-react.d.ts +61 -70
- package/dist/ai-react.js +144 -37
- package/dist/ai-react.js.map +1 -1
- package/dist/ai-types.d.ts +36 -19
- package/dist/ai-types.js +6 -0
- package/dist/chunk-AVYJQSLW.js +17 -0
- package/dist/chunk-AVYJQSLW.js.map +1 -0
- package/dist/{chunk-767EASBA.js → chunk-LL2AFX7V.js} +5 -2
- package/dist/chunk-LL2AFX7V.js.map +1 -0
- package/dist/{chunk-ZRRXJUAA.js → chunk-O4KIASSE.js} +632 -118
- package/dist/chunk-O4KIASSE.js.map +1 -0
- package/dist/{chunk-NKZZ66QY.js → chunk-QEVM4BVL.js} +5 -5
- package/dist/chunk-QEVM4BVL.js.map +1 -0
- package/dist/chunk-UJVEAURM.js +150 -0
- package/dist/chunk-UJVEAURM.js.map +1 -0
- package/dist/{chunk-E3LCYPCB.js → chunk-VYENMKFS.js} +163 -20
- package/dist/chunk-VYENMKFS.js.map +1 -0
- package/dist/client-CpU7236R.d.ts +4607 -0
- package/dist/client.d.ts +2 -2
- package/dist/client.js +2 -1
- package/dist/index.d.ts +164 -20
- package/dist/index.js +13 -4
- package/dist/mcp/client.d.ts +9 -781
- package/dist/mcp/client.js +1 -1
- package/dist/mcp/do-oauth-client-provider.d.ts +1 -0
- package/dist/mcp/do-oauth-client-provider.js +1 -1
- package/dist/mcp/index.d.ts +35 -7
- package/dist/mcp/index.js +191 -18
- package/dist/mcp/index.js.map +1 -1
- package/dist/observability/index.d.ts +46 -0
- package/dist/observability/index.js +11 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/react.d.ts +9 -5
- package/dist/react.js +7 -5
- package/dist/react.js.map +1 -1
- package/dist/schedule.d.ts +79 -5
- package/dist/schedule.js +15 -2
- package/dist/schedule.js.map +1 -1
- package/package.json +41 -28
- package/src/index.ts +841 -153
- package/dist/chunk-767EASBA.js.map +0 -1
- package/dist/chunk-E3LCYPCB.js.map +0 -1
- package/dist/chunk-NKZZ66QY.js.map +0 -1
- package/dist/chunk-ZRRXJUAA.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { env } from "cloudflare:workers";
|
|
1
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
3
|
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
4
|
import type { SSEClientTransportOptions } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
@@ -6,23 +7,26 @@ import type {
|
|
|
6
7
|
Prompt,
|
|
7
8
|
Resource,
|
|
8
9
|
ServerCapabilities,
|
|
9
|
-
Tool
|
|
10
|
+
Tool
|
|
10
11
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
11
12
|
import { parseCronExpression } from "cron-schedule";
|
|
12
13
|
import { nanoid } from "nanoid";
|
|
14
|
+
import { EmailMessage } from "cloudflare:email";
|
|
13
15
|
import {
|
|
14
16
|
type Connection,
|
|
15
17
|
type ConnectionContext,
|
|
16
|
-
getServerByName,
|
|
17
18
|
type PartyServerOptions,
|
|
18
|
-
routePartykitRequest,
|
|
19
19
|
Server,
|
|
20
20
|
type WSMessage,
|
|
21
|
+
getServerByName,
|
|
22
|
+
routePartykitRequest
|
|
21
23
|
} from "partyserver";
|
|
22
24
|
import { camelCaseToKebabCase } from "./client";
|
|
23
25
|
import { MCPClientManager } from "./mcp/client";
|
|
24
26
|
// import type { MCPClientConnection } from "./mcp/client-connection";
|
|
25
27
|
import { DurableObjectOAuthClientProvider } from "./mcp/do-oauth-client-provider";
|
|
28
|
+
import { genericObservability, type Observability } from "./observability";
|
|
29
|
+
import { MessageType } from "./ai-types";
|
|
26
30
|
|
|
27
31
|
export type { Connection, ConnectionContext, WSMessage } from "partyserver";
|
|
28
32
|
|
|
@@ -40,7 +44,7 @@ export type RPCRequest = {
|
|
|
40
44
|
* State update message from client
|
|
41
45
|
*/
|
|
42
46
|
export type StateUpdateMessage = {
|
|
43
|
-
type:
|
|
47
|
+
type: MessageType.CF_AGENT_STATE;
|
|
44
48
|
state: unknown;
|
|
45
49
|
};
|
|
46
50
|
|
|
@@ -48,7 +52,7 @@ export type StateUpdateMessage = {
|
|
|
48
52
|
* RPC response message to client
|
|
49
53
|
*/
|
|
50
54
|
export type RPCResponse = {
|
|
51
|
-
type:
|
|
55
|
+
type: MessageType.RPC;
|
|
52
56
|
id: string;
|
|
53
57
|
} & (
|
|
54
58
|
| {
|
|
@@ -75,7 +79,7 @@ function isRPCRequest(msg: unknown): msg is RPCRequest {
|
|
|
75
79
|
typeof msg === "object" &&
|
|
76
80
|
msg !== null &&
|
|
77
81
|
"type" in msg &&
|
|
78
|
-
msg.type ===
|
|
82
|
+
msg.type === MessageType.RPC &&
|
|
79
83
|
"id" in msg &&
|
|
80
84
|
typeof msg.id === "string" &&
|
|
81
85
|
"method" in msg &&
|
|
@@ -93,7 +97,7 @@ function isStateUpdateMessage(msg: unknown): msg is StateUpdateMessage {
|
|
|
93
97
|
typeof msg === "object" &&
|
|
94
98
|
msg !== null &&
|
|
95
99
|
"type" in msg &&
|
|
96
|
-
msg.type ===
|
|
100
|
+
msg.type === MessageType.CF_AGENT_STATE &&
|
|
97
101
|
"state" in msg
|
|
98
102
|
);
|
|
99
103
|
}
|
|
@@ -114,7 +118,7 @@ const callableMetadata = new Map<Function, CallableMetadata>();
|
|
|
114
118
|
* Decorator that marks a method as callable by clients
|
|
115
119
|
* @param metadata Optional metadata about the callable method
|
|
116
120
|
*/
|
|
117
|
-
export function
|
|
121
|
+
export function callable(metadata: CallableMetadata = {}) {
|
|
118
122
|
return function callableDecorator<This, Args extends unknown[], Return>(
|
|
119
123
|
target: (this: This, ...args: Args) => Return,
|
|
120
124
|
// biome-ignore lint/correctness/noUnusedFunctionParameters: later
|
|
@@ -128,6 +132,30 @@ export function unstable_callable(metadata: CallableMetadata = {}) {
|
|
|
128
132
|
};
|
|
129
133
|
}
|
|
130
134
|
|
|
135
|
+
let didWarnAboutUnstableCallable = false;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Decorator that marks a method as callable by clients
|
|
139
|
+
* @deprecated this has been renamed to callable, and unstable_callable will be removed in the next major version
|
|
140
|
+
* @param metadata Optional metadata about the callable method
|
|
141
|
+
*/
|
|
142
|
+
export const unstable_callable = (metadata: CallableMetadata = {}) => {
|
|
143
|
+
if (!didWarnAboutUnstableCallable) {
|
|
144
|
+
didWarnAboutUnstableCallable = true;
|
|
145
|
+
console.warn(
|
|
146
|
+
"unstable_callable is deprecated, use callable instead. unstable_callable will be removed in the next major version."
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
callable(metadata);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export type QueueItem<T = string> = {
|
|
153
|
+
id: string;
|
|
154
|
+
payload: T;
|
|
155
|
+
callback: keyof Agent<unknown>;
|
|
156
|
+
created_at: number;
|
|
157
|
+
};
|
|
158
|
+
|
|
131
159
|
/**
|
|
132
160
|
* Represents a scheduled task within an Agent
|
|
133
161
|
* @template T Type of the payload data
|
|
@@ -173,7 +201,7 @@ function getNextCronTime(cron: string) {
|
|
|
173
201
|
* MCP Server state update message from server -> Client
|
|
174
202
|
*/
|
|
175
203
|
export type MCPServerMessage = {
|
|
176
|
-
type:
|
|
204
|
+
type: MessageType.CF_AGENT_MCP_SERVERS;
|
|
177
205
|
mcp: MCPServersState;
|
|
178
206
|
};
|
|
179
207
|
|
|
@@ -217,23 +245,26 @@ const STATE_WAS_CHANGED = "cf_state_was_changed";
|
|
|
217
245
|
const DEFAULT_STATE = {} as unknown;
|
|
218
246
|
|
|
219
247
|
const agentContext = new AsyncLocalStorage<{
|
|
220
|
-
agent: Agent<unknown>;
|
|
248
|
+
agent: Agent<unknown, unknown>;
|
|
221
249
|
connection: Connection | undefined;
|
|
222
250
|
request: Request | undefined;
|
|
251
|
+
email: AgentEmail | undefined;
|
|
223
252
|
}>();
|
|
224
253
|
|
|
225
254
|
export function getCurrentAgent<
|
|
226
|
-
T extends Agent<unknown, unknown> = Agent<unknown, unknown
|
|
255
|
+
T extends Agent<unknown, unknown> = Agent<unknown, unknown>
|
|
227
256
|
>(): {
|
|
228
257
|
agent: T | undefined;
|
|
229
258
|
connection: Connection | undefined;
|
|
230
|
-
request: Request
|
|
259
|
+
request: Request | undefined;
|
|
260
|
+
email: AgentEmail | undefined;
|
|
231
261
|
} {
|
|
232
262
|
const store = agentContext.getStore() as
|
|
233
263
|
| {
|
|
234
264
|
agent: T;
|
|
235
265
|
connection: Connection | undefined;
|
|
236
|
-
request: Request
|
|
266
|
+
request: Request | undefined;
|
|
267
|
+
email: AgentEmail | undefined;
|
|
237
268
|
}
|
|
238
269
|
| undefined;
|
|
239
270
|
if (!store) {
|
|
@@ -241,17 +272,43 @@ export function getCurrentAgent<
|
|
|
241
272
|
agent: undefined,
|
|
242
273
|
connection: undefined,
|
|
243
274
|
request: undefined,
|
|
275
|
+
email: undefined
|
|
244
276
|
};
|
|
245
277
|
}
|
|
246
278
|
return store;
|
|
247
279
|
}
|
|
248
280
|
|
|
281
|
+
/**
|
|
282
|
+
* Wraps a method to run within the agent context, ensuring getCurrentAgent() works properly
|
|
283
|
+
* @param agent The agent instance
|
|
284
|
+
* @param method The method to wrap
|
|
285
|
+
* @returns A wrapped method that runs within the agent context
|
|
286
|
+
*/
|
|
287
|
+
|
|
288
|
+
// biome-ignore lint/suspicious/noExplicitAny: I can't typescript
|
|
289
|
+
function withAgentContext<T extends (...args: any[]) => any>(
|
|
290
|
+
method: T
|
|
291
|
+
): (this: Agent<unknown, unknown>, ...args: Parameters<T>) => ReturnType<T> {
|
|
292
|
+
return function (...args: Parameters<T>): ReturnType<T> {
|
|
293
|
+
const { connection, request, email, agent } = getCurrentAgent();
|
|
294
|
+
|
|
295
|
+
if (agent === this) {
|
|
296
|
+
// already wrapped, so we can just call the method
|
|
297
|
+
return method.apply(this, args);
|
|
298
|
+
}
|
|
299
|
+
// not wrapped, so we need to wrap it
|
|
300
|
+
return agentContext.run({ agent: this, connection, request, email }, () => {
|
|
301
|
+
return method.apply(this, args);
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
249
306
|
/**
|
|
250
307
|
* Base class for creating Agent implementations
|
|
251
308
|
* @template Env Environment type containing bindings
|
|
252
309
|
* @template State State type to store within the Agent
|
|
253
310
|
*/
|
|
254
|
-
export class Agent<Env, State = unknown> extends Server<Env> {
|
|
311
|
+
export class Agent<Env = typeof env, State = unknown> extends Server<Env> {
|
|
255
312
|
private _state = DEFAULT_STATE as State;
|
|
256
313
|
|
|
257
314
|
private _ParentClass: typeof Agent<Env, State> =
|
|
@@ -313,9 +370,14 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
313
370
|
*/
|
|
314
371
|
static options = {
|
|
315
372
|
/** Whether the Agent should hibernate when inactive */
|
|
316
|
-
hibernate: true
|
|
373
|
+
hibernate: true // default to hibernate
|
|
317
374
|
};
|
|
318
375
|
|
|
376
|
+
/**
|
|
377
|
+
* The observability implementation to use for the Agent
|
|
378
|
+
*/
|
|
379
|
+
observability?: Observability = genericObservability;
|
|
380
|
+
|
|
319
381
|
/**
|
|
320
382
|
* Execute SQL queries against the Agent's database
|
|
321
383
|
* @template T Type of the returned rows
|
|
@@ -345,6 +407,9 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
345
407
|
constructor(ctx: AgentContext, env: Env) {
|
|
346
408
|
super(ctx, env);
|
|
347
409
|
|
|
410
|
+
// Auto-wrap custom methods with agent context
|
|
411
|
+
this._autoWrapCustomMethods();
|
|
412
|
+
|
|
348
413
|
this.sql`
|
|
349
414
|
CREATE TABLE IF NOT EXISTS cf_agents_state (
|
|
350
415
|
id TEXT PRIMARY KEY NOT NULL,
|
|
@@ -352,6 +417,15 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
352
417
|
)
|
|
353
418
|
`;
|
|
354
419
|
|
|
420
|
+
this.sql`
|
|
421
|
+
CREATE TABLE IF NOT EXISTS cf_agents_queues (
|
|
422
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
423
|
+
payload TEXT,
|
|
424
|
+
callback TEXT,
|
|
425
|
+
created_at INTEGER DEFAULT (unixepoch())
|
|
426
|
+
)
|
|
427
|
+
`;
|
|
428
|
+
|
|
355
429
|
void this.ctx.blockConcurrencyWhile(async () => {
|
|
356
430
|
return this._tryCatch(async () => {
|
|
357
431
|
// Create alarms table if it doesn't exist
|
|
@@ -388,7 +462,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
388
462
|
const _onRequest = this.onRequest.bind(this);
|
|
389
463
|
this.onRequest = (request: Request) => {
|
|
390
464
|
return agentContext.run(
|
|
391
|
-
{ agent: this, connection: undefined, request },
|
|
465
|
+
{ agent: this, connection: undefined, request, email: undefined },
|
|
392
466
|
async () => {
|
|
393
467
|
if (this.mcp.isCallbackRequest(request)) {
|
|
394
468
|
await this.mcp.handleCallbackRequest(request);
|
|
@@ -397,14 +471,14 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
397
471
|
this.broadcast(
|
|
398
472
|
JSON.stringify({
|
|
399
473
|
mcp: this.getMcpServers(),
|
|
400
|
-
type:
|
|
474
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
401
475
|
})
|
|
402
476
|
);
|
|
403
477
|
|
|
404
478
|
// We probably should let the user configure this response/redirect, but this is fine for now.
|
|
405
479
|
return new Response("<script>window.close();</script>", {
|
|
406
480
|
headers: { "content-type": "text/html" },
|
|
407
|
-
status: 200
|
|
481
|
+
status: 200
|
|
408
482
|
});
|
|
409
483
|
}
|
|
410
484
|
|
|
@@ -416,7 +490,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
416
490
|
const _onMessage = this.onMessage.bind(this);
|
|
417
491
|
this.onMessage = async (connection: Connection, message: WSMessage) => {
|
|
418
492
|
return agentContext.run(
|
|
419
|
-
{ agent: this, connection, request: undefined },
|
|
493
|
+
{ agent: this, connection, request: undefined, email: undefined },
|
|
420
494
|
async () => {
|
|
421
495
|
if (typeof message !== "string") {
|
|
422
496
|
return this._tryCatch(() => _onMessage(connection, message));
|
|
@@ -460,12 +534,27 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
460
534
|
|
|
461
535
|
// For regular methods, execute and send response
|
|
462
536
|
const result = await methodFn.apply(this, args);
|
|
537
|
+
|
|
538
|
+
this.observability?.emit(
|
|
539
|
+
{
|
|
540
|
+
displayMessage: `RPC call to ${method}`,
|
|
541
|
+
id: nanoid(),
|
|
542
|
+
payload: {
|
|
543
|
+
method,
|
|
544
|
+
streaming: metadata?.streaming
|
|
545
|
+
},
|
|
546
|
+
timestamp: Date.now(),
|
|
547
|
+
type: "rpc"
|
|
548
|
+
},
|
|
549
|
+
this.ctx
|
|
550
|
+
);
|
|
551
|
+
|
|
463
552
|
const response: RPCResponse = {
|
|
464
553
|
done: true,
|
|
465
554
|
id,
|
|
466
555
|
result,
|
|
467
556
|
success: true,
|
|
468
|
-
type:
|
|
557
|
+
type: MessageType.RPC
|
|
469
558
|
};
|
|
470
559
|
connection.send(JSON.stringify(response));
|
|
471
560
|
} catch (e) {
|
|
@@ -475,7 +564,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
475
564
|
e instanceof Error ? e.message : "Unknown error occurred",
|
|
476
565
|
id: parsed.id,
|
|
477
566
|
success: false,
|
|
478
|
-
type:
|
|
567
|
+
type: MessageType.RPC
|
|
479
568
|
};
|
|
480
569
|
connection.send(JSON.stringify(response));
|
|
481
570
|
console.error("RPC error:", e);
|
|
@@ -493,27 +582,37 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
493
582
|
// TODO: This is a hack to ensure the state is sent after the connection is established
|
|
494
583
|
// must fix this
|
|
495
584
|
return agentContext.run(
|
|
496
|
-
{ agent: this, connection, request: ctx.request },
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
if (this.state) {
|
|
500
|
-
connection.send(
|
|
501
|
-
JSON.stringify({
|
|
502
|
-
state: this.state,
|
|
503
|
-
type: "cf_agent_state",
|
|
504
|
-
})
|
|
505
|
-
);
|
|
506
|
-
}
|
|
507
|
-
|
|
585
|
+
{ agent: this, connection, request: ctx.request, email: undefined },
|
|
586
|
+
() => {
|
|
587
|
+
if (this.state) {
|
|
508
588
|
connection.send(
|
|
509
589
|
JSON.stringify({
|
|
510
|
-
|
|
511
|
-
type:
|
|
590
|
+
state: this.state,
|
|
591
|
+
type: MessageType.CF_AGENT_STATE
|
|
512
592
|
})
|
|
513
593
|
);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
connection.send(
|
|
597
|
+
JSON.stringify({
|
|
598
|
+
mcp: this.getMcpServers(),
|
|
599
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
600
|
+
})
|
|
601
|
+
);
|
|
514
602
|
|
|
515
|
-
|
|
516
|
-
|
|
603
|
+
this.observability?.emit(
|
|
604
|
+
{
|
|
605
|
+
displayMessage: "Connection established",
|
|
606
|
+
id: nanoid(),
|
|
607
|
+
payload: {
|
|
608
|
+
connectionId: connection.id
|
|
609
|
+
},
|
|
610
|
+
timestamp: Date.now(),
|
|
611
|
+
type: "connect"
|
|
612
|
+
},
|
|
613
|
+
this.ctx
|
|
614
|
+
);
|
|
615
|
+
return this._tryCatch(() => _onConnect(connection, ctx));
|
|
517
616
|
}
|
|
518
617
|
);
|
|
519
618
|
};
|
|
@@ -521,18 +620,29 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
521
620
|
const _onStart = this.onStart.bind(this);
|
|
522
621
|
this.onStart = async () => {
|
|
523
622
|
return agentContext.run(
|
|
524
|
-
{
|
|
623
|
+
{
|
|
624
|
+
agent: this,
|
|
625
|
+
connection: undefined,
|
|
626
|
+
request: undefined,
|
|
627
|
+
email: undefined
|
|
628
|
+
},
|
|
525
629
|
async () => {
|
|
526
|
-
|
|
630
|
+
await this._tryCatch(() => {
|
|
631
|
+
const servers = this.sql<MCPServerRow>`
|
|
527
632
|
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
|
|
528
633
|
`;
|
|
529
634
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
635
|
+
this.broadcast(
|
|
636
|
+
JSON.stringify({
|
|
637
|
+
mcp: this.getMcpServers(),
|
|
638
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
639
|
+
})
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
// from DO storage, reconnect to all servers not currently in the oauth flow using our saved auth information
|
|
643
|
+
if (servers && Array.isArray(servers) && servers.length > 0) {
|
|
644
|
+
servers.forEach((server) => {
|
|
645
|
+
this._connectToMcpServerInternal(
|
|
536
646
|
server.name,
|
|
537
647
|
server.server_url,
|
|
538
648
|
server.callback_url,
|
|
@@ -541,20 +651,35 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
541
651
|
: undefined,
|
|
542
652
|
{
|
|
543
653
|
id: server.id,
|
|
544
|
-
oauthClientId: server.client_id ?? undefined
|
|
654
|
+
oauthClientId: server.client_id ?? undefined
|
|
545
655
|
}
|
|
546
|
-
)
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
656
|
+
)
|
|
657
|
+
.then(() => {
|
|
658
|
+
// Broadcast updated MCP servers state after each server connects
|
|
659
|
+
this.broadcast(
|
|
660
|
+
JSON.stringify({
|
|
661
|
+
mcp: this.getMcpServers(),
|
|
662
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
663
|
+
})
|
|
664
|
+
);
|
|
665
|
+
})
|
|
666
|
+
.catch((error) => {
|
|
667
|
+
console.error(
|
|
668
|
+
`Error connecting to MCP server: ${server.name} (${server.server_url})`,
|
|
669
|
+
error
|
|
670
|
+
);
|
|
671
|
+
// Still broadcast even if connection fails, so clients know about the failure
|
|
672
|
+
this.broadcast(
|
|
673
|
+
JSON.stringify({
|
|
674
|
+
mcp: this.getMcpServers(),
|
|
675
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
676
|
+
})
|
|
677
|
+
);
|
|
678
|
+
});
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
return _onStart();
|
|
682
|
+
});
|
|
558
683
|
}
|
|
559
684
|
);
|
|
560
685
|
};
|
|
@@ -576,15 +701,25 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
576
701
|
this.broadcast(
|
|
577
702
|
JSON.stringify({
|
|
578
703
|
state: state,
|
|
579
|
-
type:
|
|
704
|
+
type: MessageType.CF_AGENT_STATE
|
|
580
705
|
}),
|
|
581
706
|
source !== "server" ? [source.id] : []
|
|
582
707
|
);
|
|
583
708
|
return this._tryCatch(() => {
|
|
584
|
-
const { connection, request } = agentContext.getStore() || {};
|
|
709
|
+
const { connection, request, email } = agentContext.getStore() || {};
|
|
585
710
|
return agentContext.run(
|
|
586
|
-
{ agent: this, connection, request },
|
|
711
|
+
{ agent: this, connection, request, email },
|
|
587
712
|
async () => {
|
|
713
|
+
this.observability?.emit(
|
|
714
|
+
{
|
|
715
|
+
displayMessage: "State updated",
|
|
716
|
+
id: nanoid(),
|
|
717
|
+
payload: {},
|
|
718
|
+
timestamp: Date.now(),
|
|
719
|
+
type: "state:update"
|
|
720
|
+
},
|
|
721
|
+
this.ctx
|
|
722
|
+
);
|
|
588
723
|
return this.onStateUpdate(state, source);
|
|
589
724
|
}
|
|
590
725
|
);
|
|
@@ -610,19 +745,83 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
610
745
|
}
|
|
611
746
|
|
|
612
747
|
/**
|
|
613
|
-
* Called when the Agent receives an email
|
|
748
|
+
* Called when the Agent receives an email via routeAgentEmail()
|
|
749
|
+
* Override this method to handle incoming emails
|
|
614
750
|
* @param email Email message to process
|
|
615
751
|
*/
|
|
616
|
-
|
|
617
|
-
|
|
752
|
+
async _onEmail(email: AgentEmail) {
|
|
753
|
+
// nb: we use this roundabout way of getting to onEmail
|
|
754
|
+
// because of https://github.com/cloudflare/workerd/issues/4499
|
|
618
755
|
return agentContext.run(
|
|
619
|
-
{ agent: this, connection: undefined, request: undefined },
|
|
756
|
+
{ agent: this, connection: undefined, request: undefined, email: email },
|
|
620
757
|
async () => {
|
|
621
|
-
|
|
758
|
+
if ("onEmail" in this && typeof this.onEmail === "function") {
|
|
759
|
+
return this._tryCatch(() =>
|
|
760
|
+
(this.onEmail as (email: AgentEmail) => Promise<void>)(email)
|
|
761
|
+
);
|
|
762
|
+
} else {
|
|
763
|
+
console.log("Received email from:", email.from, "to:", email.to);
|
|
764
|
+
console.log("Subject:", email.headers.get("subject"));
|
|
765
|
+
console.log(
|
|
766
|
+
"Implement onEmail(email: AgentEmail): Promise<void> in your agent to process emails"
|
|
767
|
+
);
|
|
768
|
+
}
|
|
622
769
|
}
|
|
623
770
|
);
|
|
624
771
|
}
|
|
625
772
|
|
|
773
|
+
/**
|
|
774
|
+
* Reply to an email
|
|
775
|
+
* @param email The email to reply to
|
|
776
|
+
* @param options Options for the reply
|
|
777
|
+
* @returns void
|
|
778
|
+
*/
|
|
779
|
+
async replyToEmail(
|
|
780
|
+
email: AgentEmail,
|
|
781
|
+
options: {
|
|
782
|
+
fromName: string;
|
|
783
|
+
subject?: string | undefined;
|
|
784
|
+
body: string;
|
|
785
|
+
contentType?: string;
|
|
786
|
+
headers?: Record<string, string>;
|
|
787
|
+
}
|
|
788
|
+
): Promise<void> {
|
|
789
|
+
return this._tryCatch(async () => {
|
|
790
|
+
const agentName = camelCaseToKebabCase(this._ParentClass.name);
|
|
791
|
+
const agentId = this.name;
|
|
792
|
+
|
|
793
|
+
const { createMimeMessage } = await import("mimetext");
|
|
794
|
+
const msg = createMimeMessage();
|
|
795
|
+
msg.setSender({ addr: email.to, name: options.fromName });
|
|
796
|
+
msg.setRecipient(email.from);
|
|
797
|
+
msg.setSubject(
|
|
798
|
+
options.subject || `Re: ${email.headers.get("subject")}` || "No subject"
|
|
799
|
+
);
|
|
800
|
+
msg.addMessage({
|
|
801
|
+
contentType: options.contentType || "text/plain",
|
|
802
|
+
data: options.body
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
const domain = email.from.split("@")[1];
|
|
806
|
+
const messageId = `<${agentId}@${domain}>`;
|
|
807
|
+
msg.setHeader("In-Reply-To", email.headers.get("Message-ID")!);
|
|
808
|
+
msg.setHeader("Message-ID", messageId);
|
|
809
|
+
msg.setHeader("X-Agent-Name", agentName);
|
|
810
|
+
msg.setHeader("X-Agent-ID", agentId);
|
|
811
|
+
|
|
812
|
+
if (options.headers) {
|
|
813
|
+
for (const [key, value] of Object.entries(options.headers)) {
|
|
814
|
+
msg.setHeader(key, value);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
await email.reply({
|
|
818
|
+
from: email.to,
|
|
819
|
+
raw: msg.asRaw(),
|
|
820
|
+
to: email.from
|
|
821
|
+
});
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
|
|
626
825
|
private async _tryCatch<T>(fn: () => T | Promise<T>) {
|
|
627
826
|
try {
|
|
628
827
|
return await fn();
|
|
@@ -631,6 +830,73 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
631
830
|
}
|
|
632
831
|
}
|
|
633
832
|
|
|
833
|
+
/**
|
|
834
|
+
* Automatically wrap custom methods with agent context
|
|
835
|
+
* This ensures getCurrentAgent() works in all custom methods without decorators
|
|
836
|
+
*/
|
|
837
|
+
private _autoWrapCustomMethods() {
|
|
838
|
+
// Collect all methods from base prototypes (Agent and Server)
|
|
839
|
+
const basePrototypes = [Agent.prototype, Server.prototype];
|
|
840
|
+
const baseMethods = new Set<string>();
|
|
841
|
+
for (const baseProto of basePrototypes) {
|
|
842
|
+
let proto = baseProto;
|
|
843
|
+
while (proto && proto !== Object.prototype) {
|
|
844
|
+
const methodNames = Object.getOwnPropertyNames(proto);
|
|
845
|
+
for (const methodName of methodNames) {
|
|
846
|
+
baseMethods.add(methodName);
|
|
847
|
+
}
|
|
848
|
+
proto = Object.getPrototypeOf(proto);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
// Get all methods from the current instance's prototype chain
|
|
852
|
+
let proto = Object.getPrototypeOf(this);
|
|
853
|
+
let depth = 0;
|
|
854
|
+
while (proto && proto !== Object.prototype && depth < 10) {
|
|
855
|
+
const methodNames = Object.getOwnPropertyNames(proto);
|
|
856
|
+
for (const methodName of methodNames) {
|
|
857
|
+
// Skip if it's a private method or not a function or a getter
|
|
858
|
+
if (
|
|
859
|
+
baseMethods.has(methodName) ||
|
|
860
|
+
methodName.startsWith("_") ||
|
|
861
|
+
typeof this[methodName as keyof this] !== "function" ||
|
|
862
|
+
!!Object.getOwnPropertyDescriptor(proto, methodName)?.get
|
|
863
|
+
) {
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
// If the method doesn't exist in base prototypes, it's a custom method
|
|
867
|
+
if (!baseMethods.has(methodName)) {
|
|
868
|
+
const descriptor = Object.getOwnPropertyDescriptor(proto, methodName);
|
|
869
|
+
if (descriptor && typeof descriptor.value === "function") {
|
|
870
|
+
// Wrap the custom method with context
|
|
871
|
+
|
|
872
|
+
const wrappedFunction = withAgentContext(
|
|
873
|
+
// biome-ignore lint/suspicious/noExplicitAny: I can't typescript
|
|
874
|
+
this[methodName as keyof this] as (...args: any[]) => any
|
|
875
|
+
// biome-ignore lint/suspicious/noExplicitAny: I can't typescript
|
|
876
|
+
) as any;
|
|
877
|
+
|
|
878
|
+
// if the method is callable, copy the metadata from the original method
|
|
879
|
+
if (this._isCallable(methodName)) {
|
|
880
|
+
callableMetadata.set(
|
|
881
|
+
wrappedFunction,
|
|
882
|
+
callableMetadata.get(
|
|
883
|
+
this[methodName as keyof this] as Function
|
|
884
|
+
)!
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// set the wrapped function on the prototype
|
|
889
|
+
this.constructor.prototype[methodName as keyof this] =
|
|
890
|
+
wrappedFunction;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
proto = Object.getPrototypeOf(proto);
|
|
896
|
+
depth++;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
634
900
|
override onError(
|
|
635
901
|
connection: Connection,
|
|
636
902
|
error: unknown
|
|
@@ -665,6 +931,131 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
665
931
|
throw new Error("Not implemented");
|
|
666
932
|
}
|
|
667
933
|
|
|
934
|
+
/**
|
|
935
|
+
* Queue a task to be executed in the future
|
|
936
|
+
* @param payload Payload to pass to the callback
|
|
937
|
+
* @param callback Name of the method to call
|
|
938
|
+
* @returns The ID of the queued task
|
|
939
|
+
*/
|
|
940
|
+
async queue<T = unknown>(callback: keyof this, payload: T): Promise<string> {
|
|
941
|
+
const id = nanoid(9);
|
|
942
|
+
if (typeof callback !== "string") {
|
|
943
|
+
throw new Error("Callback must be a string");
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
if (typeof this[callback] !== "function") {
|
|
947
|
+
throw new Error(`this.${callback} is not a function`);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
this.sql`
|
|
951
|
+
INSERT OR REPLACE INTO cf_agents_queues (id, payload, callback)
|
|
952
|
+
VALUES (${id}, ${JSON.stringify(payload)}, ${callback})
|
|
953
|
+
`;
|
|
954
|
+
|
|
955
|
+
void this._flushQueue().catch((e) => {
|
|
956
|
+
console.error("Error flushing queue:", e);
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
return id;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
private _flushingQueue = false;
|
|
963
|
+
|
|
964
|
+
private async _flushQueue() {
|
|
965
|
+
if (this._flushingQueue) {
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
this._flushingQueue = true;
|
|
969
|
+
while (true) {
|
|
970
|
+
const result = this.sql<QueueItem<string>>`
|
|
971
|
+
SELECT * FROM cf_agents_queues
|
|
972
|
+
ORDER BY created_at ASC
|
|
973
|
+
`;
|
|
974
|
+
|
|
975
|
+
if (!result || result.length === 0) {
|
|
976
|
+
break;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
for (const row of result || []) {
|
|
980
|
+
const callback = this[row.callback as keyof Agent<Env>];
|
|
981
|
+
if (!callback) {
|
|
982
|
+
console.error(`callback ${row.callback} not found`);
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
985
|
+
const { connection, request, email } = agentContext.getStore() || {};
|
|
986
|
+
await agentContext.run(
|
|
987
|
+
{
|
|
988
|
+
agent: this,
|
|
989
|
+
connection,
|
|
990
|
+
request,
|
|
991
|
+
email
|
|
992
|
+
},
|
|
993
|
+
async () => {
|
|
994
|
+
// TODO: add retries and backoff
|
|
995
|
+
await (
|
|
996
|
+
callback as (
|
|
997
|
+
payload: unknown,
|
|
998
|
+
queueItem: QueueItem<string>
|
|
999
|
+
) => Promise<void>
|
|
1000
|
+
).bind(this)(JSON.parse(row.payload as string), row);
|
|
1001
|
+
await this.dequeue(row.id);
|
|
1002
|
+
}
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
this._flushingQueue = false;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* Dequeue a task by ID
|
|
1011
|
+
* @param id ID of the task to dequeue
|
|
1012
|
+
*/
|
|
1013
|
+
async dequeue(id: string) {
|
|
1014
|
+
this.sql`DELETE FROM cf_agents_queues WHERE id = ${id}`;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Dequeue all tasks
|
|
1019
|
+
*/
|
|
1020
|
+
async dequeueAll() {
|
|
1021
|
+
this.sql`DELETE FROM cf_agents_queues`;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* Dequeue all tasks by callback
|
|
1026
|
+
* @param callback Name of the callback to dequeue
|
|
1027
|
+
*/
|
|
1028
|
+
async dequeueAllByCallback(callback: string) {
|
|
1029
|
+
this.sql`DELETE FROM cf_agents_queues WHERE callback = ${callback}`;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
/**
|
|
1033
|
+
* Get a queued task by ID
|
|
1034
|
+
* @param id ID of the task to get
|
|
1035
|
+
* @returns The task or undefined if not found
|
|
1036
|
+
*/
|
|
1037
|
+
async getQueue(id: string): Promise<QueueItem<string> | undefined> {
|
|
1038
|
+
const result = this.sql<QueueItem<string>>`
|
|
1039
|
+
SELECT * FROM cf_agents_queues WHERE id = ${id}
|
|
1040
|
+
`;
|
|
1041
|
+
return result
|
|
1042
|
+
? { ...result[0], payload: JSON.parse(result[0].payload) }
|
|
1043
|
+
: undefined;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* Get all queues by key and value
|
|
1048
|
+
* @param key Key to filter by
|
|
1049
|
+
* @param value Value to filter by
|
|
1050
|
+
* @returns Array of matching QueueItem objects
|
|
1051
|
+
*/
|
|
1052
|
+
async getQueues(key: string, value: string): Promise<QueueItem<string>[]> {
|
|
1053
|
+
const result = this.sql<QueueItem<string>>`
|
|
1054
|
+
SELECT * FROM cf_agents_queues
|
|
1055
|
+
`;
|
|
1056
|
+
return result.filter((row) => JSON.parse(row.payload)[key] === value);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
668
1059
|
/**
|
|
669
1060
|
* Schedule a task to be executed in the future
|
|
670
1061
|
* @template T Type of the payload data
|
|
@@ -680,6 +1071,21 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
680
1071
|
): Promise<Schedule<T>> {
|
|
681
1072
|
const id = nanoid(9);
|
|
682
1073
|
|
|
1074
|
+
const emitScheduleCreate = (schedule: Schedule<T>) =>
|
|
1075
|
+
this.observability?.emit(
|
|
1076
|
+
{
|
|
1077
|
+
displayMessage: `Schedule ${schedule.id} created`,
|
|
1078
|
+
id: nanoid(),
|
|
1079
|
+
payload: {
|
|
1080
|
+
callback: callback as string,
|
|
1081
|
+
id: id
|
|
1082
|
+
},
|
|
1083
|
+
timestamp: Date.now(),
|
|
1084
|
+
type: "schedule:create"
|
|
1085
|
+
},
|
|
1086
|
+
this.ctx
|
|
1087
|
+
);
|
|
1088
|
+
|
|
683
1089
|
if (typeof callback !== "string") {
|
|
684
1090
|
throw new Error("Callback must be a string");
|
|
685
1091
|
}
|
|
@@ -699,13 +1105,17 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
699
1105
|
|
|
700
1106
|
await this._scheduleNextAlarm();
|
|
701
1107
|
|
|
702
|
-
|
|
1108
|
+
const schedule: Schedule<T> = {
|
|
703
1109
|
callback: callback,
|
|
704
1110
|
id,
|
|
705
1111
|
payload: payload as T,
|
|
706
1112
|
time: timestamp,
|
|
707
|
-
type: "scheduled"
|
|
1113
|
+
type: "scheduled"
|
|
708
1114
|
};
|
|
1115
|
+
|
|
1116
|
+
emitScheduleCreate(schedule);
|
|
1117
|
+
|
|
1118
|
+
return schedule;
|
|
709
1119
|
}
|
|
710
1120
|
if (typeof when === "number") {
|
|
711
1121
|
const time = new Date(Date.now() + when * 1000);
|
|
@@ -720,14 +1130,18 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
720
1130
|
|
|
721
1131
|
await this._scheduleNextAlarm();
|
|
722
1132
|
|
|
723
|
-
|
|
1133
|
+
const schedule: Schedule<T> = {
|
|
724
1134
|
callback: callback,
|
|
725
1135
|
delayInSeconds: when,
|
|
726
1136
|
id,
|
|
727
1137
|
payload: payload as T,
|
|
728
1138
|
time: timestamp,
|
|
729
|
-
type: "delayed"
|
|
1139
|
+
type: "delayed"
|
|
730
1140
|
};
|
|
1141
|
+
|
|
1142
|
+
emitScheduleCreate(schedule);
|
|
1143
|
+
|
|
1144
|
+
return schedule;
|
|
731
1145
|
}
|
|
732
1146
|
if (typeof when === "string") {
|
|
733
1147
|
const nextExecutionTime = getNextCronTime(when);
|
|
@@ -742,14 +1156,18 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
742
1156
|
|
|
743
1157
|
await this._scheduleNextAlarm();
|
|
744
1158
|
|
|
745
|
-
|
|
1159
|
+
const schedule: Schedule<T> = {
|
|
746
1160
|
callback: callback,
|
|
747
1161
|
cron: when,
|
|
748
1162
|
id,
|
|
749
1163
|
payload: payload as T,
|
|
750
1164
|
time: timestamp,
|
|
751
|
-
type: "cron"
|
|
1165
|
+
type: "cron"
|
|
752
1166
|
};
|
|
1167
|
+
|
|
1168
|
+
emitScheduleCreate(schedule);
|
|
1169
|
+
|
|
1170
|
+
return schedule;
|
|
753
1171
|
}
|
|
754
1172
|
throw new Error("Invalid schedule type");
|
|
755
1173
|
}
|
|
@@ -813,7 +1231,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
813
1231
|
.toArray()
|
|
814
1232
|
.map((row) => ({
|
|
815
1233
|
...row,
|
|
816
|
-
payload: JSON.parse(row.payload as string) as T
|
|
1234
|
+
payload: JSON.parse(row.payload as string) as T
|
|
817
1235
|
})) as Schedule<T>[];
|
|
818
1236
|
|
|
819
1237
|
return result;
|
|
@@ -825,6 +1243,22 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
825
1243
|
* @returns true if the task was cancelled, false otherwise
|
|
826
1244
|
*/
|
|
827
1245
|
async cancelSchedule(id: string): Promise<boolean> {
|
|
1246
|
+
const schedule = await this.getSchedule(id);
|
|
1247
|
+
if (schedule) {
|
|
1248
|
+
this.observability?.emit(
|
|
1249
|
+
{
|
|
1250
|
+
displayMessage: `Schedule ${id} cancelled`,
|
|
1251
|
+
id: nanoid(),
|
|
1252
|
+
payload: {
|
|
1253
|
+
callback: schedule.callback,
|
|
1254
|
+
id: schedule.id
|
|
1255
|
+
},
|
|
1256
|
+
timestamp: Date.now(),
|
|
1257
|
+
type: "schedule:cancel"
|
|
1258
|
+
},
|
|
1259
|
+
this.ctx
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
828
1262
|
this.sql`DELETE FROM cf_agents_schedules WHERE id = ${id}`;
|
|
829
1263
|
|
|
830
1264
|
await this._scheduleNextAlarm();
|
|
@@ -834,9 +1268,9 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
834
1268
|
private async _scheduleNextAlarm() {
|
|
835
1269
|
// Find the next schedule that needs to be executed
|
|
836
1270
|
const result = this.sql`
|
|
837
|
-
SELECT time FROM cf_agents_schedules
|
|
1271
|
+
SELECT time FROM cf_agents_schedules
|
|
838
1272
|
WHERE time > ${Math.floor(Date.now() / 1000)}
|
|
839
|
-
ORDER BY time ASC
|
|
1273
|
+
ORDER BY time ASC
|
|
840
1274
|
LIMIT 1
|
|
841
1275
|
`;
|
|
842
1276
|
if (!result) return;
|
|
@@ -863,40 +1297,61 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
863
1297
|
SELECT * FROM cf_agents_schedules WHERE time <= ${now}
|
|
864
1298
|
`;
|
|
865
1299
|
|
|
866
|
-
|
|
867
|
-
const
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
await agentContext.run(
|
|
873
|
-
{ agent: this, connection: undefined, request: undefined },
|
|
874
|
-
async () => {
|
|
875
|
-
try {
|
|
876
|
-
await (
|
|
877
|
-
callback as (
|
|
878
|
-
payload: unknown,
|
|
879
|
-
schedule: Schedule<unknown>
|
|
880
|
-
) => Promise<void>
|
|
881
|
-
).bind(this)(JSON.parse(row.payload as string), row);
|
|
882
|
-
} catch (e) {
|
|
883
|
-
console.error(`error executing callback "${row.callback}"`, e);
|
|
884
|
-
}
|
|
1300
|
+
if (result && Array.isArray(result)) {
|
|
1301
|
+
for (const row of result) {
|
|
1302
|
+
const callback = this[row.callback as keyof Agent<Env>];
|
|
1303
|
+
if (!callback) {
|
|
1304
|
+
console.error(`callback ${row.callback} not found`);
|
|
1305
|
+
continue;
|
|
885
1306
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
1307
|
+
await agentContext.run(
|
|
1308
|
+
{
|
|
1309
|
+
agent: this,
|
|
1310
|
+
connection: undefined,
|
|
1311
|
+
request: undefined,
|
|
1312
|
+
email: undefined
|
|
1313
|
+
},
|
|
1314
|
+
async () => {
|
|
1315
|
+
try {
|
|
1316
|
+
this.observability?.emit(
|
|
1317
|
+
{
|
|
1318
|
+
displayMessage: `Schedule ${row.id} executed`,
|
|
1319
|
+
id: nanoid(),
|
|
1320
|
+
payload: {
|
|
1321
|
+
callback: row.callback,
|
|
1322
|
+
id: row.id
|
|
1323
|
+
},
|
|
1324
|
+
timestamp: Date.now(),
|
|
1325
|
+
type: "schedule:execute"
|
|
1326
|
+
},
|
|
1327
|
+
this.ctx
|
|
1328
|
+
);
|
|
891
1329
|
|
|
892
|
-
|
|
1330
|
+
await (
|
|
1331
|
+
callback as (
|
|
1332
|
+
payload: unknown,
|
|
1333
|
+
schedule: Schedule<unknown>
|
|
1334
|
+
) => Promise<void>
|
|
1335
|
+
).bind(this)(JSON.parse(row.payload as string), row);
|
|
1336
|
+
} catch (e) {
|
|
1337
|
+
console.error(`error executing callback "${row.callback}"`, e);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
);
|
|
1341
|
+
if (row.type === "cron") {
|
|
1342
|
+
// Update next execution time for cron schedules
|
|
1343
|
+
const nextExecutionTime = getNextCronTime(row.cron);
|
|
1344
|
+
const nextTimestamp = Math.floor(nextExecutionTime.getTime() / 1000);
|
|
1345
|
+
|
|
1346
|
+
this.sql`
|
|
893
1347
|
UPDATE cf_agents_schedules SET time = ${nextTimestamp} WHERE id = ${row.id}
|
|
894
1348
|
`;
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
1349
|
+
} else {
|
|
1350
|
+
// Delete one-time schedules after execution
|
|
1351
|
+
this.sql`
|
|
898
1352
|
DELETE FROM cf_agents_schedules WHERE id = ${row.id}
|
|
899
1353
|
`;
|
|
1354
|
+
}
|
|
900
1355
|
}
|
|
901
1356
|
}
|
|
902
1357
|
|
|
@@ -912,11 +1367,23 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
912
1367
|
this.sql`DROP TABLE IF EXISTS cf_agents_state`;
|
|
913
1368
|
this.sql`DROP TABLE IF EXISTS cf_agents_schedules`;
|
|
914
1369
|
this.sql`DROP TABLE IF EXISTS cf_agents_mcp_servers`;
|
|
1370
|
+
this.sql`DROP TABLE IF EXISTS cf_agents_queues`;
|
|
915
1371
|
|
|
916
1372
|
// delete all alarms
|
|
917
1373
|
await this.ctx.storage.deleteAlarm();
|
|
918
1374
|
await this.ctx.storage.deleteAll();
|
|
919
1375
|
this.ctx.abort("destroyed"); // enforce that the agent is evicted
|
|
1376
|
+
|
|
1377
|
+
this.observability?.emit(
|
|
1378
|
+
{
|
|
1379
|
+
displayMessage: "Agent destroyed",
|
|
1380
|
+
id: nanoid(),
|
|
1381
|
+
payload: {},
|
|
1382
|
+
timestamp: Date.now(),
|
|
1383
|
+
type: "destroy"
|
|
1384
|
+
},
|
|
1385
|
+
this.ctx
|
|
1386
|
+
);
|
|
920
1387
|
}
|
|
921
1388
|
|
|
922
1389
|
/**
|
|
@@ -956,11 +1423,24 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
956
1423
|
callbackUrl,
|
|
957
1424
|
options
|
|
958
1425
|
);
|
|
1426
|
+
this.sql`
|
|
1427
|
+
INSERT
|
|
1428
|
+
OR REPLACE INTO cf_agents_mcp_servers (id, name, server_url, client_id, auth_url, callback_url, server_options)
|
|
1429
|
+
VALUES (
|
|
1430
|
+
${result.id},
|
|
1431
|
+
${serverName},
|
|
1432
|
+
${url},
|
|
1433
|
+
${result.clientId ?? null},
|
|
1434
|
+
${result.authUrl ?? null},
|
|
1435
|
+
${callbackUrl},
|
|
1436
|
+
${options ? JSON.stringify(options) : null}
|
|
1437
|
+
);
|
|
1438
|
+
`;
|
|
959
1439
|
|
|
960
1440
|
this.broadcast(
|
|
961
1441
|
JSON.stringify({
|
|
962
1442
|
mcp: this.getMcpServers(),
|
|
963
|
-
type:
|
|
1443
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
964
1444
|
})
|
|
965
1445
|
);
|
|
966
1446
|
|
|
@@ -968,7 +1448,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
968
1448
|
}
|
|
969
1449
|
|
|
970
1450
|
async _connectToMcpServerInternal(
|
|
971
|
-
|
|
1451
|
+
_serverName: string,
|
|
972
1452
|
url: string,
|
|
973
1453
|
callbackUrl: string,
|
|
974
1454
|
// it's important that any options here are serializable because we put them into our sqlite DB for reconnection purposes
|
|
@@ -989,7 +1469,11 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
989
1469
|
id: string;
|
|
990
1470
|
oauthClientId?: string;
|
|
991
1471
|
}
|
|
992
|
-
): Promise<{
|
|
1472
|
+
): Promise<{
|
|
1473
|
+
id: string;
|
|
1474
|
+
authUrl: string | undefined;
|
|
1475
|
+
clientId: string | undefined;
|
|
1476
|
+
}> {
|
|
993
1477
|
const authProvider = new DurableObjectOAuthClientProvider(
|
|
994
1478
|
this.ctx.storage,
|
|
995
1479
|
this.name,
|
|
@@ -1012,12 +1496,12 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1012
1496
|
fetch: (url, init) =>
|
|
1013
1497
|
fetch(url, {
|
|
1014
1498
|
...init,
|
|
1015
|
-
headers: options?.transport?.headers
|
|
1016
|
-
})
|
|
1499
|
+
headers: options?.transport?.headers
|
|
1500
|
+
})
|
|
1017
1501
|
},
|
|
1018
1502
|
requestInit: {
|
|
1019
|
-
headers: options?.transport?.headers
|
|
1020
|
-
}
|
|
1503
|
+
headers: options?.transport?.headers
|
|
1504
|
+
}
|
|
1021
1505
|
};
|
|
1022
1506
|
}
|
|
1023
1507
|
|
|
@@ -1026,26 +1510,14 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1026
1510
|
reconnect,
|
|
1027
1511
|
transport: {
|
|
1028
1512
|
...headerTransportOpts,
|
|
1029
|
-
authProvider
|
|
1030
|
-
}
|
|
1513
|
+
authProvider
|
|
1514
|
+
}
|
|
1031
1515
|
});
|
|
1032
1516
|
|
|
1033
|
-
this.sql`
|
|
1034
|
-
INSERT OR REPLACE INTO cf_agents_mcp_servers (id, name, server_url, client_id, auth_url, callback_url, server_options)
|
|
1035
|
-
VALUES (
|
|
1036
|
-
${id},
|
|
1037
|
-
${serverName},
|
|
1038
|
-
${url},
|
|
1039
|
-
${clientId ?? null},
|
|
1040
|
-
${authUrl ?? null},
|
|
1041
|
-
${callbackUrl},
|
|
1042
|
-
${options ? JSON.stringify(options) : null}
|
|
1043
|
-
);
|
|
1044
|
-
`;
|
|
1045
|
-
|
|
1046
1517
|
return {
|
|
1047
1518
|
authUrl,
|
|
1048
|
-
|
|
1519
|
+
clientId,
|
|
1520
|
+
id
|
|
1049
1521
|
};
|
|
1050
1522
|
}
|
|
1051
1523
|
|
|
@@ -1057,7 +1529,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1057
1529
|
this.broadcast(
|
|
1058
1530
|
JSON.stringify({
|
|
1059
1531
|
mcp: this.getMcpServers(),
|
|
1060
|
-
type:
|
|
1532
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
1061
1533
|
})
|
|
1062
1534
|
);
|
|
1063
1535
|
}
|
|
@@ -1067,24 +1539,26 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1067
1539
|
prompts: this.mcp.listPrompts(),
|
|
1068
1540
|
resources: this.mcp.listResources(),
|
|
1069
1541
|
servers: {},
|
|
1070
|
-
tools: this.mcp.listTools()
|
|
1542
|
+
tools: this.mcp.listTools()
|
|
1071
1543
|
};
|
|
1072
1544
|
|
|
1073
1545
|
const servers = this.sql<MCPServerRow>`
|
|
1074
1546
|
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
|
|
1075
1547
|
`;
|
|
1076
1548
|
|
|
1077
|
-
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1549
|
+
if (servers && Array.isArray(servers) && servers.length > 0) {
|
|
1550
|
+
for (const server of servers) {
|
|
1551
|
+
const serverConn = this.mcp.mcpConnections[server.id];
|
|
1552
|
+
mcpState.servers[server.id] = {
|
|
1553
|
+
auth_url: server.auth_url,
|
|
1554
|
+
capabilities: serverConn?.serverCapabilities ?? null,
|
|
1555
|
+
instructions: serverConn?.instructions ?? null,
|
|
1556
|
+
name: server.name,
|
|
1557
|
+
server_url: server.server_url,
|
|
1558
|
+
// mark as "authenticating" because the server isn't automatically connected, so it's pending authenticating
|
|
1559
|
+
state: serverConn?.connectionState ?? "authenticating"
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1088
1562
|
}
|
|
1089
1563
|
|
|
1090
1564
|
return mcpState;
|
|
@@ -1131,14 +1605,14 @@ export async function routeAgentRequest<Env>(
|
|
|
1131
1605
|
"Access-Control-Allow-Credentials": "true",
|
|
1132
1606
|
"Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS",
|
|
1133
1607
|
"Access-Control-Allow-Origin": "*",
|
|
1134
|
-
"Access-Control-Max-Age": "86400"
|
|
1608
|
+
"Access-Control-Max-Age": "86400"
|
|
1135
1609
|
}
|
|
1136
1610
|
: options?.cors;
|
|
1137
1611
|
|
|
1138
1612
|
if (request.method === "OPTIONS") {
|
|
1139
1613
|
if (corsHeaders) {
|
|
1140
1614
|
return new Response(null, {
|
|
1141
|
-
headers: corsHeaders
|
|
1615
|
+
headers: corsHeaders
|
|
1142
1616
|
});
|
|
1143
1617
|
}
|
|
1144
1618
|
console.warn(
|
|
@@ -1151,7 +1625,7 @@ export async function routeAgentRequest<Env>(
|
|
|
1151
1625
|
env as Record<string, unknown>,
|
|
1152
1626
|
{
|
|
1153
1627
|
prefix: "agents",
|
|
1154
|
-
...(options as PartyServerOptions<Record<string, unknown>>)
|
|
1628
|
+
...(options as PartyServerOptions<Record<string, unknown>>)
|
|
1155
1629
|
}
|
|
1156
1630
|
);
|
|
1157
1631
|
|
|
@@ -1164,24 +1638,238 @@ export async function routeAgentRequest<Env>(
|
|
|
1164
1638
|
response = new Response(response.body, {
|
|
1165
1639
|
headers: {
|
|
1166
1640
|
...response.headers,
|
|
1167
|
-
...corsHeaders
|
|
1168
|
-
}
|
|
1641
|
+
...corsHeaders
|
|
1642
|
+
}
|
|
1169
1643
|
});
|
|
1170
1644
|
}
|
|
1171
1645
|
return response;
|
|
1172
1646
|
}
|
|
1173
1647
|
|
|
1648
|
+
export type EmailResolver<Env> = (
|
|
1649
|
+
email: ForwardableEmailMessage,
|
|
1650
|
+
env: Env
|
|
1651
|
+
) => Promise<{
|
|
1652
|
+
agentName: string;
|
|
1653
|
+
agentId: string;
|
|
1654
|
+
} | null>;
|
|
1655
|
+
|
|
1656
|
+
/**
|
|
1657
|
+
* Create a resolver that uses the message-id header to determine the agent to route the email to
|
|
1658
|
+
* @returns A function that resolves the agent to route the email to
|
|
1659
|
+
*/
|
|
1660
|
+
export function createHeaderBasedEmailResolver<Env>(): EmailResolver<Env> {
|
|
1661
|
+
return async (email: ForwardableEmailMessage, _env: Env) => {
|
|
1662
|
+
const messageId = email.headers.get("message-id");
|
|
1663
|
+
if (messageId) {
|
|
1664
|
+
const messageIdMatch = messageId.match(/<([^@]+)@([^>]+)>/);
|
|
1665
|
+
if (messageIdMatch) {
|
|
1666
|
+
const [, agentId, domain] = messageIdMatch;
|
|
1667
|
+
const agentName = domain.split(".")[0];
|
|
1668
|
+
return { agentName, agentId };
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
const references = email.headers.get("references");
|
|
1673
|
+
if (references) {
|
|
1674
|
+
const referencesMatch = references.match(
|
|
1675
|
+
/<([A-Za-z0-9+/]{43}=)@([^>]+)>/
|
|
1676
|
+
);
|
|
1677
|
+
if (referencesMatch) {
|
|
1678
|
+
const [, base64Id, domain] = referencesMatch;
|
|
1679
|
+
const agentId = Buffer.from(base64Id, "base64").toString("hex");
|
|
1680
|
+
const agentName = domain.split(".")[0];
|
|
1681
|
+
return { agentName, agentId };
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
const agentName = email.headers.get("x-agent-name");
|
|
1686
|
+
const agentId = email.headers.get("x-agent-id");
|
|
1687
|
+
if (agentName && agentId) {
|
|
1688
|
+
return { agentName, agentId };
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
return null;
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
/**
|
|
1696
|
+
* Create a resolver that uses the email address to determine the agent to route the email to
|
|
1697
|
+
* @param defaultAgentName The default agent name to use if the email address does not contain a sub-address
|
|
1698
|
+
* @returns A function that resolves the agent to route the email to
|
|
1699
|
+
*/
|
|
1700
|
+
export function createAddressBasedEmailResolver<Env>(
|
|
1701
|
+
defaultAgentName: string
|
|
1702
|
+
): EmailResolver<Env> {
|
|
1703
|
+
return async (email: ForwardableEmailMessage, _env: Env) => {
|
|
1704
|
+
const emailMatch = email.to.match(/^([^+@]+)(?:\+([^@]+))?@(.+)$/);
|
|
1705
|
+
if (!emailMatch) {
|
|
1706
|
+
return null;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
const [, localPart, subAddress] = emailMatch;
|
|
1710
|
+
|
|
1711
|
+
if (subAddress) {
|
|
1712
|
+
return {
|
|
1713
|
+
agentName: localPart,
|
|
1714
|
+
agentId: subAddress
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
// Option 2: Use defaultAgentName namespace, localPart as agentId
|
|
1719
|
+
// Common for catch-all email routing to a single EmailAgent namespace
|
|
1720
|
+
return {
|
|
1721
|
+
agentName: defaultAgentName,
|
|
1722
|
+
agentId: localPart
|
|
1723
|
+
};
|
|
1724
|
+
};
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
/**
|
|
1728
|
+
* Create a resolver that uses the agentName and agentId to determine the agent to route the email to
|
|
1729
|
+
* @param agentName The name of the agent to route the email to
|
|
1730
|
+
* @param agentId The id of the agent to route the email to
|
|
1731
|
+
* @returns A function that resolves the agent to route the email to
|
|
1732
|
+
*/
|
|
1733
|
+
export function createCatchAllEmailResolver<Env>(
|
|
1734
|
+
agentName: string,
|
|
1735
|
+
agentId: string
|
|
1736
|
+
): EmailResolver<Env> {
|
|
1737
|
+
return async () => ({ agentName, agentId });
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
export type EmailRoutingOptions<Env> = AgentOptions<Env> & {
|
|
1741
|
+
resolver: EmailResolver<Env>;
|
|
1742
|
+
};
|
|
1743
|
+
|
|
1744
|
+
// Cache the agent namespace map for email routing
|
|
1745
|
+
// This maps both kebab-case and original names to namespaces
|
|
1746
|
+
const agentMapCache = new WeakMap<
|
|
1747
|
+
Record<string, unknown>,
|
|
1748
|
+
Record<string, unknown>
|
|
1749
|
+
>();
|
|
1750
|
+
|
|
1174
1751
|
/**
|
|
1175
1752
|
* Route an email to the appropriate Agent
|
|
1176
|
-
* @param email
|
|
1177
|
-
* @param env
|
|
1178
|
-
* @param options
|
|
1753
|
+
* @param email The email to route
|
|
1754
|
+
* @param env The environment containing the Agent bindings
|
|
1755
|
+
* @param options The options for routing the email
|
|
1756
|
+
* @returns A promise that resolves when the email has been routed
|
|
1179
1757
|
*/
|
|
1180
1758
|
export async function routeAgentEmail<Env>(
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
): Promise<void> {
|
|
1759
|
+
email: ForwardableEmailMessage,
|
|
1760
|
+
env: Env,
|
|
1761
|
+
options: EmailRoutingOptions<Env>
|
|
1762
|
+
): Promise<void> {
|
|
1763
|
+
const routingInfo = await options.resolver(email, env);
|
|
1764
|
+
|
|
1765
|
+
if (!routingInfo) {
|
|
1766
|
+
console.warn("No routing information found for email, dropping message");
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
// Build a map that includes both original names and kebab-case versions
|
|
1771
|
+
if (!agentMapCache.has(env as Record<string, unknown>)) {
|
|
1772
|
+
const map: Record<string, unknown> = {};
|
|
1773
|
+
for (const [key, value] of Object.entries(env as Record<string, unknown>)) {
|
|
1774
|
+
if (
|
|
1775
|
+
value &&
|
|
1776
|
+
typeof value === "object" &&
|
|
1777
|
+
"idFromName" in value &&
|
|
1778
|
+
typeof value.idFromName === "function"
|
|
1779
|
+
) {
|
|
1780
|
+
// Add both the original name and kebab-case version
|
|
1781
|
+
map[key] = value;
|
|
1782
|
+
map[camelCaseToKebabCase(key)] = value;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
agentMapCache.set(env as Record<string, unknown>, map);
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
const agentMap = agentMapCache.get(env as Record<string, unknown>)!;
|
|
1789
|
+
const namespace = agentMap[routingInfo.agentName];
|
|
1790
|
+
|
|
1791
|
+
if (!namespace) {
|
|
1792
|
+
// Provide helpful error message listing available agents
|
|
1793
|
+
const availableAgents = Object.keys(agentMap)
|
|
1794
|
+
.filter((key) => !key.includes("-")) // Show only original names, not kebab-case duplicates
|
|
1795
|
+
.join(", ");
|
|
1796
|
+
throw new Error(
|
|
1797
|
+
`Agent namespace '${routingInfo.agentName}' not found in environment. Available agents: ${availableAgents}`
|
|
1798
|
+
);
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
const agent = await getAgentByName(
|
|
1802
|
+
namespace as unknown as AgentNamespace<Agent<Env>>,
|
|
1803
|
+
routingInfo.agentId
|
|
1804
|
+
);
|
|
1805
|
+
|
|
1806
|
+
// let's make a serialisable version of the email
|
|
1807
|
+
const serialisableEmail: AgentEmail = {
|
|
1808
|
+
getRaw: async () => {
|
|
1809
|
+
const reader = email.raw.getReader();
|
|
1810
|
+
const chunks: Uint8Array[] = [];
|
|
1811
|
+
|
|
1812
|
+
let done = false;
|
|
1813
|
+
while (!done) {
|
|
1814
|
+
const { value, done: readerDone } = await reader.read();
|
|
1815
|
+
done = readerDone;
|
|
1816
|
+
if (value) {
|
|
1817
|
+
chunks.push(value);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
1822
|
+
const combined = new Uint8Array(totalLength);
|
|
1823
|
+
let offset = 0;
|
|
1824
|
+
for (const chunk of chunks) {
|
|
1825
|
+
combined.set(chunk, offset);
|
|
1826
|
+
offset += chunk.length;
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
return combined;
|
|
1830
|
+
},
|
|
1831
|
+
headers: email.headers,
|
|
1832
|
+
rawSize: email.rawSize,
|
|
1833
|
+
setReject: (reason: string) => {
|
|
1834
|
+
email.setReject(reason);
|
|
1835
|
+
},
|
|
1836
|
+
forward: (rcptTo: string, headers?: Headers) => {
|
|
1837
|
+
return email.forward(rcptTo, headers);
|
|
1838
|
+
},
|
|
1839
|
+
reply: (options: { from: string; to: string; raw: string }) => {
|
|
1840
|
+
return email.reply(
|
|
1841
|
+
new EmailMessage(options.from, options.to, options.raw)
|
|
1842
|
+
);
|
|
1843
|
+
},
|
|
1844
|
+
from: email.from,
|
|
1845
|
+
to: email.to
|
|
1846
|
+
};
|
|
1847
|
+
|
|
1848
|
+
await agent._onEmail(serialisableEmail);
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
export type AgentEmail = {
|
|
1852
|
+
from: string;
|
|
1853
|
+
to: string;
|
|
1854
|
+
getRaw: () => Promise<Uint8Array>;
|
|
1855
|
+
headers: Headers;
|
|
1856
|
+
rawSize: number;
|
|
1857
|
+
setReject: (reason: string) => void;
|
|
1858
|
+
forward: (rcptTo: string, headers?: Headers) => Promise<void>;
|
|
1859
|
+
reply: (options: { from: string; to: string; raw: string }) => Promise<void>;
|
|
1860
|
+
};
|
|
1861
|
+
|
|
1862
|
+
export type EmailSendOptions = {
|
|
1863
|
+
to: string;
|
|
1864
|
+
subject: string;
|
|
1865
|
+
body: string;
|
|
1866
|
+
contentType?: string;
|
|
1867
|
+
headers?: Record<string, string>;
|
|
1868
|
+
includeRoutingHeaders?: boolean;
|
|
1869
|
+
agentName?: string;
|
|
1870
|
+
agentId?: string;
|
|
1871
|
+
domain?: string;
|
|
1872
|
+
};
|
|
1185
1873
|
|
|
1186
1874
|
/**
|
|
1187
1875
|
* Get or create an Agent by name
|
|
@@ -1229,7 +1917,7 @@ export class StreamingResponse {
|
|
|
1229
1917
|
id: this._id,
|
|
1230
1918
|
result: chunk,
|
|
1231
1919
|
success: true,
|
|
1232
|
-
type:
|
|
1920
|
+
type: MessageType.RPC
|
|
1233
1921
|
};
|
|
1234
1922
|
this._connection.send(JSON.stringify(response));
|
|
1235
1923
|
}
|
|
@@ -1248,7 +1936,7 @@ export class StreamingResponse {
|
|
|
1248
1936
|
id: this._id,
|
|
1249
1937
|
result: finalChunk,
|
|
1250
1938
|
success: true,
|
|
1251
|
-
type:
|
|
1939
|
+
type: MessageType.RPC
|
|
1252
1940
|
};
|
|
1253
1941
|
this._connection.send(JSON.stringify(response));
|
|
1254
1942
|
}
|