agents 0.0.0-2662748 → 0.0.0-2684ade
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 +12 -8
- package/dist/ai-chat-agent.js +166 -59
- 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 +63 -72
- package/dist/ai-react.js +161 -54
- 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-OYJXQRRH.js → chunk-DS7BJNPH.js} +167 -34
- package/dist/chunk-DS7BJNPH.js.map +1 -0
- package/dist/{chunk-P3RZJ72N.js → chunk-EGCWEPQL.js} +639 -132
- package/dist/chunk-EGCWEPQL.js.map +1 -0
- package/dist/{chunk-BZXOAZUX.js → chunk-PVQZBKN7.js} +5 -5
- package/dist/chunk-PVQZBKN7.js.map +1 -0
- package/dist/{chunk-VCSB47AK.js → chunk-QEVM4BVL.js} +10 -10
- 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/client-DgyzBU_8.d.ts +4601 -0
- package/dist/client.d.ts +2 -2
- package/dist/client.js +2 -1
- package/dist/index.d.ts +158 -21
- package/dist/index.js +11 -4
- package/dist/mcp/client.d.ts +9 -781
- package/dist/mcp/client.js +1 -1
- package/dist/mcp/do-oauth-client-provider.js +1 -1
- package/dist/mcp/index.d.ts +38 -10
- package/dist/mcp/index.js +233 -59
- 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 +12 -8
- package/dist/react.js +12 -10
- package/dist/react.js.map +1 -1
- package/dist/schedule.d.ts +6 -6
- package/dist/schedule.js +4 -4
- package/dist/schedule.js.map +1 -1
- package/package.json +83 -70
- package/src/index.ts +838 -169
- package/dist/chunk-BZXOAZUX.js.map +0 -1
- package/dist/chunk-OYJXQRRH.js.map +0 -1
- package/dist/chunk-P3RZJ72N.js.map +0 -1
- package/dist/chunk-VCSB47AK.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -1,30 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
type Connection,
|
|
6
|
-
type ConnectionContext,
|
|
7
|
-
type PartyServerOptions,
|
|
8
|
-
type WSMessage,
|
|
9
|
-
} from "partyserver";
|
|
10
|
-
|
|
11
|
-
import { parseCronExpression } from "cron-schedule";
|
|
12
|
-
import { nanoid } from "nanoid";
|
|
1
|
+
import type { env } from "cloudflare:workers";
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
4
|
+
import type { SSEClientTransportOptions } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
13
5
|
|
|
14
6
|
import type {
|
|
15
7
|
Prompt,
|
|
16
8
|
Resource,
|
|
17
9
|
ServerCapabilities,
|
|
18
|
-
Tool
|
|
10
|
+
Tool
|
|
19
11
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
20
|
-
import {
|
|
12
|
+
import { parseCronExpression } from "cron-schedule";
|
|
13
|
+
import { nanoid } from "nanoid";
|
|
14
|
+
import { EmailMessage } from "cloudflare:email";
|
|
15
|
+
import {
|
|
16
|
+
type Connection,
|
|
17
|
+
type ConnectionContext,
|
|
18
|
+
type PartyServerOptions,
|
|
19
|
+
Server,
|
|
20
|
+
type WSMessage,
|
|
21
|
+
getServerByName,
|
|
22
|
+
routePartykitRequest
|
|
23
|
+
} from "partyserver";
|
|
24
|
+
import { camelCaseToKebabCase } from "./client";
|
|
21
25
|
import { MCPClientManager } from "./mcp/client";
|
|
26
|
+
// import type { MCPClientConnection } from "./mcp/client-connection";
|
|
22
27
|
import { DurableObjectOAuthClientProvider } from "./mcp/do-oauth-client-provider";
|
|
23
|
-
|
|
24
|
-
import
|
|
25
|
-
import type { SSEClientTransportOptions } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
26
|
-
|
|
27
|
-
import { camelCaseToKebabCase } from "./client";
|
|
28
|
+
import { genericObservability, type Observability } from "./observability";
|
|
29
|
+
import { MessageType } from "./ai-types";
|
|
28
30
|
|
|
29
31
|
export type { Connection, ConnectionContext, WSMessage } from "partyserver";
|
|
30
32
|
|
|
@@ -42,7 +44,7 @@ export type RPCRequest = {
|
|
|
42
44
|
* State update message from client
|
|
43
45
|
*/
|
|
44
46
|
export type StateUpdateMessage = {
|
|
45
|
-
type:
|
|
47
|
+
type: MessageType.CF_AGENT_STATE;
|
|
46
48
|
state: unknown;
|
|
47
49
|
};
|
|
48
50
|
|
|
@@ -50,7 +52,7 @@ export type StateUpdateMessage = {
|
|
|
50
52
|
* RPC response message to client
|
|
51
53
|
*/
|
|
52
54
|
export type RPCResponse = {
|
|
53
|
-
type:
|
|
55
|
+
type: MessageType.RPC;
|
|
54
56
|
id: string;
|
|
55
57
|
} & (
|
|
56
58
|
| {
|
|
@@ -77,7 +79,7 @@ function isRPCRequest(msg: unknown): msg is RPCRequest {
|
|
|
77
79
|
typeof msg === "object" &&
|
|
78
80
|
msg !== null &&
|
|
79
81
|
"type" in msg &&
|
|
80
|
-
msg.type ===
|
|
82
|
+
msg.type === MessageType.RPC &&
|
|
81
83
|
"id" in msg &&
|
|
82
84
|
typeof msg.id === "string" &&
|
|
83
85
|
"method" in msg &&
|
|
@@ -95,7 +97,7 @@ function isStateUpdateMessage(msg: unknown): msg is StateUpdateMessage {
|
|
|
95
97
|
typeof msg === "object" &&
|
|
96
98
|
msg !== null &&
|
|
97
99
|
"type" in msg &&
|
|
98
|
-
msg.type ===
|
|
100
|
+
msg.type === MessageType.CF_AGENT_STATE &&
|
|
99
101
|
"state" in msg
|
|
100
102
|
);
|
|
101
103
|
}
|
|
@@ -119,6 +121,7 @@ const callableMetadata = new Map<Function, CallableMetadata>();
|
|
|
119
121
|
export function unstable_callable(metadata: CallableMetadata = {}) {
|
|
120
122
|
return function callableDecorator<This, Args extends unknown[], Return>(
|
|
121
123
|
target: (this: This, ...args: Args) => Return,
|
|
124
|
+
// biome-ignore lint/correctness/noUnusedFunctionParameters: later
|
|
122
125
|
context: ClassMethodDecoratorContext
|
|
123
126
|
) {
|
|
124
127
|
if (!callableMetadata.has(target)) {
|
|
@@ -129,6 +132,13 @@ export function unstable_callable(metadata: CallableMetadata = {}) {
|
|
|
129
132
|
};
|
|
130
133
|
}
|
|
131
134
|
|
|
135
|
+
export type QueueItem<T = string> = {
|
|
136
|
+
id: string;
|
|
137
|
+
payload: T;
|
|
138
|
+
callback: keyof Agent<unknown>;
|
|
139
|
+
created_at: number;
|
|
140
|
+
};
|
|
141
|
+
|
|
132
142
|
/**
|
|
133
143
|
* Represents a scheduled task within an Agent
|
|
134
144
|
* @template T Type of the payload data
|
|
@@ -174,7 +184,7 @@ function getNextCronTime(cron: string) {
|
|
|
174
184
|
* MCP Server state update message from server -> Client
|
|
175
185
|
*/
|
|
176
186
|
export type MCPServerMessage = {
|
|
177
|
-
type:
|
|
187
|
+
type: MessageType.CF_AGENT_MCP_SERVERS;
|
|
178
188
|
mcp: MCPServersState;
|
|
179
189
|
};
|
|
180
190
|
|
|
@@ -218,23 +228,26 @@ const STATE_WAS_CHANGED = "cf_state_was_changed";
|
|
|
218
228
|
const DEFAULT_STATE = {} as unknown;
|
|
219
229
|
|
|
220
230
|
const agentContext = new AsyncLocalStorage<{
|
|
221
|
-
agent: Agent<unknown>;
|
|
231
|
+
agent: Agent<unknown, unknown>;
|
|
222
232
|
connection: Connection | undefined;
|
|
223
233
|
request: Request | undefined;
|
|
234
|
+
email: AgentEmail | undefined;
|
|
224
235
|
}>();
|
|
225
236
|
|
|
226
237
|
export function getCurrentAgent<
|
|
227
|
-
T extends Agent<unknown, unknown> = Agent<unknown, unknown
|
|
238
|
+
T extends Agent<unknown, unknown> = Agent<unknown, unknown>
|
|
228
239
|
>(): {
|
|
229
240
|
agent: T | undefined;
|
|
230
241
|
connection: Connection | undefined;
|
|
231
|
-
request: Request
|
|
242
|
+
request: Request | undefined;
|
|
243
|
+
email: AgentEmail | undefined;
|
|
232
244
|
} {
|
|
233
245
|
const store = agentContext.getStore() as
|
|
234
246
|
| {
|
|
235
247
|
agent: T;
|
|
236
248
|
connection: Connection | undefined;
|
|
237
|
-
request: Request
|
|
249
|
+
request: Request | undefined;
|
|
250
|
+
email: AgentEmail | undefined;
|
|
238
251
|
}
|
|
239
252
|
| undefined;
|
|
240
253
|
if (!store) {
|
|
@@ -242,17 +255,37 @@ export function getCurrentAgent<
|
|
|
242
255
|
agent: undefined,
|
|
243
256
|
connection: undefined,
|
|
244
257
|
request: undefined,
|
|
258
|
+
email: undefined
|
|
245
259
|
};
|
|
246
260
|
}
|
|
247
261
|
return store;
|
|
248
262
|
}
|
|
249
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Wraps a method to run within the agent context, ensuring getCurrentAgent() works properly
|
|
266
|
+
* @param agent The agent instance
|
|
267
|
+
* @param method The method to wrap
|
|
268
|
+
* @returns A wrapped method that runs within the agent context
|
|
269
|
+
*/
|
|
270
|
+
|
|
271
|
+
// biome-ignore lint/suspicious/noExplicitAny: I can't typescript
|
|
272
|
+
function withAgentContext<T extends (...args: any[]) => any>(
|
|
273
|
+
method: T
|
|
274
|
+
): (this: Agent<unknown, unknown>, ...args: Parameters<T>) => ReturnType<T> {
|
|
275
|
+
return function (...args: Parameters<T>): ReturnType<T> {
|
|
276
|
+
const { connection, request, email } = getCurrentAgent();
|
|
277
|
+
return agentContext.run({ agent: this, connection, request, email }, () => {
|
|
278
|
+
return method.apply(this, args);
|
|
279
|
+
});
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
250
283
|
/**
|
|
251
284
|
* Base class for creating Agent implementations
|
|
252
285
|
* @template Env Environment type containing bindings
|
|
253
286
|
* @template State State type to store within the Agent
|
|
254
287
|
*/
|
|
255
|
-
export class Agent<Env, State = unknown> extends Server<Env> {
|
|
288
|
+
export class Agent<Env = typeof env, State = unknown> extends Server<Env> {
|
|
256
289
|
private _state = DEFAULT_STATE as State;
|
|
257
290
|
|
|
258
291
|
private _ParentClass: typeof Agent<Env, State> =
|
|
@@ -314,9 +347,14 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
314
347
|
*/
|
|
315
348
|
static options = {
|
|
316
349
|
/** Whether the Agent should hibernate when inactive */
|
|
317
|
-
hibernate: true
|
|
350
|
+
hibernate: true // default to hibernate
|
|
318
351
|
};
|
|
319
352
|
|
|
353
|
+
/**
|
|
354
|
+
* The observability implementation to use for the Agent
|
|
355
|
+
*/
|
|
356
|
+
observability?: Observability = genericObservability;
|
|
357
|
+
|
|
320
358
|
/**
|
|
321
359
|
* Execute SQL queries against the Agent's database
|
|
322
360
|
* @template T Type of the returned rows
|
|
@@ -346,6 +384,9 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
346
384
|
constructor(ctx: AgentContext, env: Env) {
|
|
347
385
|
super(ctx, env);
|
|
348
386
|
|
|
387
|
+
// Auto-wrap custom methods with agent context
|
|
388
|
+
this._autoWrapCustomMethods();
|
|
389
|
+
|
|
349
390
|
this.sql`
|
|
350
391
|
CREATE TABLE IF NOT EXISTS cf_agents_state (
|
|
351
392
|
id TEXT PRIMARY KEY NOT NULL,
|
|
@@ -353,6 +394,15 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
353
394
|
)
|
|
354
395
|
`;
|
|
355
396
|
|
|
397
|
+
this.sql`
|
|
398
|
+
CREATE TABLE IF NOT EXISTS cf_agents_queues (
|
|
399
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
400
|
+
payload TEXT,
|
|
401
|
+
callback TEXT,
|
|
402
|
+
created_at INTEGER DEFAULT (unixepoch())
|
|
403
|
+
)
|
|
404
|
+
`;
|
|
405
|
+
|
|
356
406
|
void this.ctx.blockConcurrencyWhile(async () => {
|
|
357
407
|
return this._tryCatch(async () => {
|
|
358
408
|
// Create alarms table if it doesn't exist
|
|
@@ -389,7 +439,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
389
439
|
const _onRequest = this.onRequest.bind(this);
|
|
390
440
|
this.onRequest = (request: Request) => {
|
|
391
441
|
return agentContext.run(
|
|
392
|
-
{ agent: this, connection: undefined, request },
|
|
442
|
+
{ agent: this, connection: undefined, request, email: undefined },
|
|
393
443
|
async () => {
|
|
394
444
|
if (this.mcp.isCallbackRequest(request)) {
|
|
395
445
|
await this.mcp.handleCallbackRequest(request);
|
|
@@ -397,15 +447,15 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
397
447
|
// after the MCP connection handshake, we can send updated mcp state
|
|
398
448
|
this.broadcast(
|
|
399
449
|
JSON.stringify({
|
|
400
|
-
type: "cf_agent_mcp_servers",
|
|
401
450
|
mcp: this.getMcpServers(),
|
|
451
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
402
452
|
})
|
|
403
453
|
);
|
|
404
454
|
|
|
405
455
|
// We probably should let the user configure this response/redirect, but this is fine for now.
|
|
406
456
|
return new Response("<script>window.close();</script>", {
|
|
407
|
-
status: 200,
|
|
408
457
|
headers: { "content-type": "text/html" },
|
|
458
|
+
status: 200
|
|
409
459
|
});
|
|
410
460
|
}
|
|
411
461
|
|
|
@@ -417,7 +467,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
417
467
|
const _onMessage = this.onMessage.bind(this);
|
|
418
468
|
this.onMessage = async (connection: Connection, message: WSMessage) => {
|
|
419
469
|
return agentContext.run(
|
|
420
|
-
{ agent: this, connection, request: undefined },
|
|
470
|
+
{ agent: this, connection, request: undefined, email: undefined },
|
|
421
471
|
async () => {
|
|
422
472
|
if (typeof message !== "string") {
|
|
423
473
|
return this._tryCatch(() => _onMessage(connection, message));
|
|
@@ -426,7 +476,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
426
476
|
let parsed: unknown;
|
|
427
477
|
try {
|
|
428
478
|
parsed = JSON.parse(message);
|
|
429
|
-
} catch (
|
|
479
|
+
} catch (_e) {
|
|
430
480
|
// silently fail and let the onMessage handler handle it
|
|
431
481
|
return this._tryCatch(() => _onMessage(connection, message));
|
|
432
482
|
}
|
|
@@ -461,22 +511,37 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
461
511
|
|
|
462
512
|
// For regular methods, execute and send response
|
|
463
513
|
const result = await methodFn.apply(this, args);
|
|
514
|
+
|
|
515
|
+
this.observability?.emit(
|
|
516
|
+
{
|
|
517
|
+
displayMessage: `RPC call to ${method}`,
|
|
518
|
+
id: nanoid(),
|
|
519
|
+
payload: {
|
|
520
|
+
method,
|
|
521
|
+
streaming: metadata?.streaming
|
|
522
|
+
},
|
|
523
|
+
timestamp: Date.now(),
|
|
524
|
+
type: "rpc"
|
|
525
|
+
},
|
|
526
|
+
this.ctx
|
|
527
|
+
);
|
|
528
|
+
|
|
464
529
|
const response: RPCResponse = {
|
|
465
|
-
|
|
530
|
+
done: true,
|
|
466
531
|
id,
|
|
467
|
-
success: true,
|
|
468
532
|
result,
|
|
469
|
-
|
|
533
|
+
success: true,
|
|
534
|
+
type: MessageType.RPC
|
|
470
535
|
};
|
|
471
536
|
connection.send(JSON.stringify(response));
|
|
472
537
|
} catch (e) {
|
|
473
538
|
// Send error response
|
|
474
539
|
const response: RPCResponse = {
|
|
475
|
-
type: "rpc",
|
|
476
|
-
id: parsed.id,
|
|
477
|
-
success: false,
|
|
478
540
|
error:
|
|
479
541
|
e instanceof Error ? e.message : "Unknown error occurred",
|
|
542
|
+
id: parsed.id,
|
|
543
|
+
success: false,
|
|
544
|
+
type: MessageType.RPC
|
|
480
545
|
};
|
|
481
546
|
connection.send(JSON.stringify(response));
|
|
482
547
|
console.error("RPC error:", e);
|
|
@@ -494,25 +559,37 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
494
559
|
// TODO: This is a hack to ensure the state is sent after the connection is established
|
|
495
560
|
// must fix this
|
|
496
561
|
return agentContext.run(
|
|
497
|
-
{ agent: this, connection, request: ctx.request },
|
|
562
|
+
{ agent: this, connection, request: ctx.request, email: undefined },
|
|
498
563
|
async () => {
|
|
499
564
|
setTimeout(() => {
|
|
500
565
|
if (this.state) {
|
|
501
566
|
connection.send(
|
|
502
567
|
JSON.stringify({
|
|
503
|
-
type: "cf_agent_state",
|
|
504
568
|
state: this.state,
|
|
569
|
+
type: MessageType.CF_AGENT_STATE
|
|
505
570
|
})
|
|
506
571
|
);
|
|
507
572
|
}
|
|
508
573
|
|
|
509
574
|
connection.send(
|
|
510
575
|
JSON.stringify({
|
|
511
|
-
type: "cf_agent_mcp_servers",
|
|
512
576
|
mcp: this.getMcpServers(),
|
|
577
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
513
578
|
})
|
|
514
579
|
);
|
|
515
580
|
|
|
581
|
+
this.observability?.emit(
|
|
582
|
+
{
|
|
583
|
+
displayMessage: "Connection established",
|
|
584
|
+
id: nanoid(),
|
|
585
|
+
payload: {
|
|
586
|
+
connectionId: connection.id
|
|
587
|
+
},
|
|
588
|
+
timestamp: Date.now(),
|
|
589
|
+
type: "connect"
|
|
590
|
+
},
|
|
591
|
+
this.ctx
|
|
592
|
+
);
|
|
516
593
|
return this._tryCatch(() => _onConnect(connection, ctx));
|
|
517
594
|
}, 20);
|
|
518
595
|
}
|
|
@@ -522,18 +599,29 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
522
599
|
const _onStart = this.onStart.bind(this);
|
|
523
600
|
this.onStart = async () => {
|
|
524
601
|
return agentContext.run(
|
|
525
|
-
{
|
|
602
|
+
{
|
|
603
|
+
agent: this,
|
|
604
|
+
connection: undefined,
|
|
605
|
+
request: undefined,
|
|
606
|
+
email: undefined
|
|
607
|
+
},
|
|
526
608
|
async () => {
|
|
527
|
-
|
|
609
|
+
await this._tryCatch(() => {
|
|
610
|
+
const servers = this.sql<MCPServerRow>`
|
|
528
611
|
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
|
|
529
612
|
`;
|
|
530
613
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
614
|
+
this.broadcast(
|
|
615
|
+
JSON.stringify({
|
|
616
|
+
mcp: this.getMcpServers(),
|
|
617
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
618
|
+
})
|
|
619
|
+
);
|
|
620
|
+
|
|
621
|
+
// from DO storage, reconnect to all servers not currently in the oauth flow using our saved auth information
|
|
622
|
+
if (servers && Array.isArray(servers) && servers.length > 0) {
|
|
623
|
+
servers.forEach((server) => {
|
|
624
|
+
this._connectToMcpServerInternal(
|
|
537
625
|
server.name,
|
|
538
626
|
server.server_url,
|
|
539
627
|
server.callback_url,
|
|
@@ -542,20 +630,35 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
542
630
|
: undefined,
|
|
543
631
|
{
|
|
544
632
|
id: server.id,
|
|
545
|
-
oauthClientId: server.client_id ?? undefined
|
|
633
|
+
oauthClientId: server.client_id ?? undefined
|
|
546
634
|
}
|
|
547
|
-
)
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
635
|
+
)
|
|
636
|
+
.then(() => {
|
|
637
|
+
// Broadcast updated MCP servers state after each server connects
|
|
638
|
+
this.broadcast(
|
|
639
|
+
JSON.stringify({
|
|
640
|
+
mcp: this.getMcpServers(),
|
|
641
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
642
|
+
})
|
|
643
|
+
);
|
|
644
|
+
})
|
|
645
|
+
.catch((error) => {
|
|
646
|
+
console.error(
|
|
647
|
+
`Error connecting to MCP server: ${server.name} (${server.server_url})`,
|
|
648
|
+
error
|
|
649
|
+
);
|
|
650
|
+
// Still broadcast even if connection fails, so clients know about the failure
|
|
651
|
+
this.broadcast(
|
|
652
|
+
JSON.stringify({
|
|
653
|
+
mcp: this.getMcpServers(),
|
|
654
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
655
|
+
})
|
|
656
|
+
);
|
|
657
|
+
});
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
return _onStart();
|
|
661
|
+
});
|
|
559
662
|
}
|
|
560
663
|
);
|
|
561
664
|
};
|
|
@@ -576,16 +679,26 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
576
679
|
`;
|
|
577
680
|
this.broadcast(
|
|
578
681
|
JSON.stringify({
|
|
579
|
-
type: "cf_agent_state",
|
|
580
682
|
state: state,
|
|
683
|
+
type: MessageType.CF_AGENT_STATE
|
|
581
684
|
}),
|
|
582
685
|
source !== "server" ? [source.id] : []
|
|
583
686
|
);
|
|
584
687
|
return this._tryCatch(() => {
|
|
585
|
-
const { connection, request } = agentContext.getStore() || {};
|
|
688
|
+
const { connection, request, email } = agentContext.getStore() || {};
|
|
586
689
|
return agentContext.run(
|
|
587
|
-
{ agent: this, connection, request },
|
|
690
|
+
{ agent: this, connection, request, email },
|
|
588
691
|
async () => {
|
|
692
|
+
this.observability?.emit(
|
|
693
|
+
{
|
|
694
|
+
displayMessage: "State updated",
|
|
695
|
+
id: nanoid(),
|
|
696
|
+
payload: {},
|
|
697
|
+
timestamp: Date.now(),
|
|
698
|
+
type: "state:update"
|
|
699
|
+
},
|
|
700
|
+
this.ctx
|
|
701
|
+
);
|
|
589
702
|
return this.onStateUpdate(state, source);
|
|
590
703
|
}
|
|
591
704
|
);
|
|
@@ -605,23 +718,89 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
605
718
|
* @param state Updated state
|
|
606
719
|
* @param source Source of the state update ("server" or a client connection)
|
|
607
720
|
*/
|
|
721
|
+
// biome-ignore lint/correctness/noUnusedFunctionParameters: overridden later
|
|
608
722
|
onStateUpdate(state: State | undefined, source: Connection | "server") {
|
|
609
723
|
// override this to handle state updates
|
|
610
724
|
}
|
|
611
725
|
|
|
612
726
|
/**
|
|
613
|
-
* Called when the Agent receives an email
|
|
727
|
+
* Called when the Agent receives an email via routeAgentEmail()
|
|
728
|
+
* Override this method to handle incoming emails
|
|
614
729
|
* @param email Email message to process
|
|
615
730
|
*/
|
|
616
|
-
|
|
731
|
+
async _onEmail(email: AgentEmail) {
|
|
732
|
+
// nb: we use this roundabout way of getting to onEmail
|
|
733
|
+
// because of https://github.com/cloudflare/workerd/issues/4499
|
|
617
734
|
return agentContext.run(
|
|
618
|
-
{ agent: this, connection: undefined, request: undefined },
|
|
735
|
+
{ agent: this, connection: undefined, request: undefined, email: email },
|
|
619
736
|
async () => {
|
|
620
|
-
|
|
737
|
+
if ("onEmail" in this && typeof this.onEmail === "function") {
|
|
738
|
+
return this._tryCatch(() =>
|
|
739
|
+
(this.onEmail as (email: AgentEmail) => Promise<void>)(email)
|
|
740
|
+
);
|
|
741
|
+
} else {
|
|
742
|
+
console.log("Received email from:", email.from, "to:", email.to);
|
|
743
|
+
console.log("Subject:", email.headers.get("subject"));
|
|
744
|
+
console.log(
|
|
745
|
+
"Implement onEmail(email: AgentEmail): Promise<void> in your agent to process emails"
|
|
746
|
+
);
|
|
747
|
+
}
|
|
621
748
|
}
|
|
622
749
|
);
|
|
623
750
|
}
|
|
624
751
|
|
|
752
|
+
/**
|
|
753
|
+
* Reply to an email
|
|
754
|
+
* @param email The email to reply to
|
|
755
|
+
* @param options Options for the reply
|
|
756
|
+
* @returns void
|
|
757
|
+
*/
|
|
758
|
+
async replyToEmail(
|
|
759
|
+
email: AgentEmail,
|
|
760
|
+
options: {
|
|
761
|
+
fromName: string;
|
|
762
|
+
subject?: string | undefined;
|
|
763
|
+
body: string;
|
|
764
|
+
contentType?: string;
|
|
765
|
+
headers?: Record<string, string>;
|
|
766
|
+
}
|
|
767
|
+
): Promise<void> {
|
|
768
|
+
return this._tryCatch(async () => {
|
|
769
|
+
const agentName = camelCaseToKebabCase(this._ParentClass.name);
|
|
770
|
+
const agentId = this.name;
|
|
771
|
+
|
|
772
|
+
const { createMimeMessage } = await import("mimetext");
|
|
773
|
+
const msg = createMimeMessage();
|
|
774
|
+
msg.setSender({ addr: email.to, name: options.fromName });
|
|
775
|
+
msg.setRecipient(email.from);
|
|
776
|
+
msg.setSubject(
|
|
777
|
+
options.subject || `Re: ${email.headers.get("subject")}` || "No subject"
|
|
778
|
+
);
|
|
779
|
+
msg.addMessage({
|
|
780
|
+
contentType: options.contentType || "text/plain",
|
|
781
|
+
data: options.body
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
const domain = email.from.split("@")[1];
|
|
785
|
+
const messageId = `<${agentId}@${domain}>`;
|
|
786
|
+
msg.setHeader("In-Reply-To", email.headers.get("Message-ID")!);
|
|
787
|
+
msg.setHeader("Message-ID", messageId);
|
|
788
|
+
msg.setHeader("X-Agent-Name", agentName);
|
|
789
|
+
msg.setHeader("X-Agent-ID", agentId);
|
|
790
|
+
|
|
791
|
+
if (options.headers) {
|
|
792
|
+
for (const [key, value] of Object.entries(options.headers)) {
|
|
793
|
+
msg.setHeader(key, value);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
await email.reply({
|
|
797
|
+
from: email.to,
|
|
798
|
+
raw: msg.asRaw(),
|
|
799
|
+
to: email.from
|
|
800
|
+
});
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
|
|
625
804
|
private async _tryCatch<T>(fn: () => T | Promise<T>) {
|
|
626
805
|
try {
|
|
627
806
|
return await fn();
|
|
@@ -630,6 +809,72 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
630
809
|
}
|
|
631
810
|
}
|
|
632
811
|
|
|
812
|
+
/**
|
|
813
|
+
* Automatically wrap custom methods with agent context
|
|
814
|
+
* This ensures getCurrentAgent() works in all custom methods without decorators
|
|
815
|
+
*/
|
|
816
|
+
private _autoWrapCustomMethods() {
|
|
817
|
+
// Collect all methods from base prototypes (Agent and Server)
|
|
818
|
+
const basePrototypes = [Agent.prototype, Server.prototype];
|
|
819
|
+
const baseMethods = new Set<string>();
|
|
820
|
+
for (const baseProto of basePrototypes) {
|
|
821
|
+
let proto = baseProto;
|
|
822
|
+
while (proto && proto !== Object.prototype) {
|
|
823
|
+
const methodNames = Object.getOwnPropertyNames(proto);
|
|
824
|
+
for (const methodName of methodNames) {
|
|
825
|
+
baseMethods.add(methodName);
|
|
826
|
+
}
|
|
827
|
+
proto = Object.getPrototypeOf(proto);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
// Get all methods from the current instance's prototype chain
|
|
831
|
+
let proto = Object.getPrototypeOf(this);
|
|
832
|
+
let depth = 0;
|
|
833
|
+
while (proto && proto !== Object.prototype && depth < 10) {
|
|
834
|
+
const methodNames = Object.getOwnPropertyNames(proto);
|
|
835
|
+
for (const methodName of methodNames) {
|
|
836
|
+
// Skip if it's a private method or not a function
|
|
837
|
+
if (
|
|
838
|
+
baseMethods.has(methodName) ||
|
|
839
|
+
methodName.startsWith("_") ||
|
|
840
|
+
typeof this[methodName as keyof this] !== "function"
|
|
841
|
+
) {
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
// If the method doesn't exist in base prototypes, it's a custom method
|
|
845
|
+
if (!baseMethods.has(methodName)) {
|
|
846
|
+
const descriptor = Object.getOwnPropertyDescriptor(proto, methodName);
|
|
847
|
+
if (descriptor && typeof descriptor.value === "function") {
|
|
848
|
+
// Wrap the custom method with context
|
|
849
|
+
|
|
850
|
+
const wrappedFunction = withAgentContext(
|
|
851
|
+
// biome-ignore lint/suspicious/noExplicitAny: I can't typescript
|
|
852
|
+
this[methodName as keyof this] as (...args: any[]) => any
|
|
853
|
+
// biome-ignore lint/suspicious/noExplicitAny: I can't typescript
|
|
854
|
+
) as any;
|
|
855
|
+
|
|
856
|
+
// if the method is callable, copy the metadata from the original method
|
|
857
|
+
if (this._isCallable(methodName)) {
|
|
858
|
+
callableMetadata.set(
|
|
859
|
+
wrappedFunction,
|
|
860
|
+
callableMetadata.get(
|
|
861
|
+
this[methodName as keyof this] as Function
|
|
862
|
+
)!
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// set the wrapped function on the prototype
|
|
867
|
+
this.constructor.prototype[methodName as keyof this] =
|
|
868
|
+
wrappedFunction;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
proto = Object.getPrototypeOf(proto);
|
|
874
|
+
depth++;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
633
878
|
override onError(
|
|
634
879
|
connection: Connection,
|
|
635
880
|
error: unknown
|
|
@@ -664,6 +909,131 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
664
909
|
throw new Error("Not implemented");
|
|
665
910
|
}
|
|
666
911
|
|
|
912
|
+
/**
|
|
913
|
+
* Queue a task to be executed in the future
|
|
914
|
+
* @param payload Payload to pass to the callback
|
|
915
|
+
* @param callback Name of the method to call
|
|
916
|
+
* @returns The ID of the queued task
|
|
917
|
+
*/
|
|
918
|
+
async queue<T = unknown>(callback: keyof this, payload: T): Promise<string> {
|
|
919
|
+
const id = nanoid(9);
|
|
920
|
+
if (typeof callback !== "string") {
|
|
921
|
+
throw new Error("Callback must be a string");
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
if (typeof this[callback] !== "function") {
|
|
925
|
+
throw new Error(`this.${callback} is not a function`);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
this.sql`
|
|
929
|
+
INSERT OR REPLACE INTO cf_agents_queues (id, payload, callback)
|
|
930
|
+
VALUES (${id}, ${JSON.stringify(payload)}, ${callback})
|
|
931
|
+
`;
|
|
932
|
+
|
|
933
|
+
void this._flushQueue().catch((e) => {
|
|
934
|
+
console.error("Error flushing queue:", e);
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
return id;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
private _flushingQueue = false;
|
|
941
|
+
|
|
942
|
+
private async _flushQueue() {
|
|
943
|
+
if (this._flushingQueue) {
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
this._flushingQueue = true;
|
|
947
|
+
while (true) {
|
|
948
|
+
const result = this.sql<QueueItem<string>>`
|
|
949
|
+
SELECT * FROM cf_agents_queues
|
|
950
|
+
ORDER BY created_at ASC
|
|
951
|
+
`;
|
|
952
|
+
|
|
953
|
+
if (!result || result.length === 0) {
|
|
954
|
+
break;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
for (const row of result || []) {
|
|
958
|
+
const callback = this[row.callback as keyof Agent<Env>];
|
|
959
|
+
if (!callback) {
|
|
960
|
+
console.error(`callback ${row.callback} not found`);
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
const { connection, request, email } = agentContext.getStore() || {};
|
|
964
|
+
await agentContext.run(
|
|
965
|
+
{
|
|
966
|
+
agent: this,
|
|
967
|
+
connection,
|
|
968
|
+
request,
|
|
969
|
+
email
|
|
970
|
+
},
|
|
971
|
+
async () => {
|
|
972
|
+
// TODO: add retries and backoff
|
|
973
|
+
await (
|
|
974
|
+
callback as (
|
|
975
|
+
payload: unknown,
|
|
976
|
+
queueItem: QueueItem<string>
|
|
977
|
+
) => Promise<void>
|
|
978
|
+
).bind(this)(JSON.parse(row.payload as string), row);
|
|
979
|
+
await this.dequeue(row.id);
|
|
980
|
+
}
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
this._flushingQueue = false;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* Dequeue a task by ID
|
|
989
|
+
* @param id ID of the task to dequeue
|
|
990
|
+
*/
|
|
991
|
+
async dequeue(id: string) {
|
|
992
|
+
this.sql`DELETE FROM cf_agents_queues WHERE id = ${id}`;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* Dequeue all tasks
|
|
997
|
+
*/
|
|
998
|
+
async dequeueAll() {
|
|
999
|
+
this.sql`DELETE FROM cf_agents_queues`;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* Dequeue all tasks by callback
|
|
1004
|
+
* @param callback Name of the callback to dequeue
|
|
1005
|
+
*/
|
|
1006
|
+
async dequeueAllByCallback(callback: string) {
|
|
1007
|
+
this.sql`DELETE FROM cf_agents_queues WHERE callback = ${callback}`;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
/**
|
|
1011
|
+
* Get a queued task by ID
|
|
1012
|
+
* @param id ID of the task to get
|
|
1013
|
+
* @returns The task or undefined if not found
|
|
1014
|
+
*/
|
|
1015
|
+
async getQueue(id: string): Promise<QueueItem<string> | undefined> {
|
|
1016
|
+
const result = this.sql<QueueItem<string>>`
|
|
1017
|
+
SELECT * FROM cf_agents_queues WHERE id = ${id}
|
|
1018
|
+
`;
|
|
1019
|
+
return result
|
|
1020
|
+
? { ...result[0], payload: JSON.parse(result[0].payload) }
|
|
1021
|
+
: undefined;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* Get all queues by key and value
|
|
1026
|
+
* @param key Key to filter by
|
|
1027
|
+
* @param value Value to filter by
|
|
1028
|
+
* @returns Array of matching QueueItem objects
|
|
1029
|
+
*/
|
|
1030
|
+
async getQueues(key: string, value: string): Promise<QueueItem<string>[]> {
|
|
1031
|
+
const result = this.sql<QueueItem<string>>`
|
|
1032
|
+
SELECT * FROM cf_agents_queues
|
|
1033
|
+
`;
|
|
1034
|
+
return result.filter((row) => JSON.parse(row.payload)[key] === value);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
667
1037
|
/**
|
|
668
1038
|
* Schedule a task to be executed in the future
|
|
669
1039
|
* @template T Type of the payload data
|
|
@@ -679,6 +1049,21 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
679
1049
|
): Promise<Schedule<T>> {
|
|
680
1050
|
const id = nanoid(9);
|
|
681
1051
|
|
|
1052
|
+
const emitScheduleCreate = (schedule: Schedule<T>) =>
|
|
1053
|
+
this.observability?.emit(
|
|
1054
|
+
{
|
|
1055
|
+
displayMessage: `Schedule ${schedule.id} created`,
|
|
1056
|
+
id: nanoid(),
|
|
1057
|
+
payload: {
|
|
1058
|
+
callback: callback as string,
|
|
1059
|
+
id: id
|
|
1060
|
+
},
|
|
1061
|
+
timestamp: Date.now(),
|
|
1062
|
+
type: "schedule:create"
|
|
1063
|
+
},
|
|
1064
|
+
this.ctx
|
|
1065
|
+
);
|
|
1066
|
+
|
|
682
1067
|
if (typeof callback !== "string") {
|
|
683
1068
|
throw new Error("Callback must be a string");
|
|
684
1069
|
}
|
|
@@ -698,13 +1083,17 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
698
1083
|
|
|
699
1084
|
await this._scheduleNextAlarm();
|
|
700
1085
|
|
|
701
|
-
|
|
702
|
-
id,
|
|
1086
|
+
const schedule: Schedule<T> = {
|
|
703
1087
|
callback: callback,
|
|
1088
|
+
id,
|
|
704
1089
|
payload: payload as T,
|
|
705
1090
|
time: timestamp,
|
|
706
|
-
type: "scheduled"
|
|
1091
|
+
type: "scheduled"
|
|
707
1092
|
};
|
|
1093
|
+
|
|
1094
|
+
emitScheduleCreate(schedule);
|
|
1095
|
+
|
|
1096
|
+
return schedule;
|
|
708
1097
|
}
|
|
709
1098
|
if (typeof when === "number") {
|
|
710
1099
|
const time = new Date(Date.now() + when * 1000);
|
|
@@ -719,14 +1108,18 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
719
1108
|
|
|
720
1109
|
await this._scheduleNextAlarm();
|
|
721
1110
|
|
|
722
|
-
|
|
723
|
-
id,
|
|
1111
|
+
const schedule: Schedule<T> = {
|
|
724
1112
|
callback: callback,
|
|
725
|
-
payload: payload as T,
|
|
726
1113
|
delayInSeconds: when,
|
|
1114
|
+
id,
|
|
1115
|
+
payload: payload as T,
|
|
727
1116
|
time: timestamp,
|
|
728
|
-
type: "delayed"
|
|
1117
|
+
type: "delayed"
|
|
729
1118
|
};
|
|
1119
|
+
|
|
1120
|
+
emitScheduleCreate(schedule);
|
|
1121
|
+
|
|
1122
|
+
return schedule;
|
|
730
1123
|
}
|
|
731
1124
|
if (typeof when === "string") {
|
|
732
1125
|
const nextExecutionTime = getNextCronTime(when);
|
|
@@ -741,14 +1134,18 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
741
1134
|
|
|
742
1135
|
await this._scheduleNextAlarm();
|
|
743
1136
|
|
|
744
|
-
|
|
745
|
-
id,
|
|
1137
|
+
const schedule: Schedule<T> = {
|
|
746
1138
|
callback: callback,
|
|
747
|
-
payload: payload as T,
|
|
748
1139
|
cron: when,
|
|
1140
|
+
id,
|
|
1141
|
+
payload: payload as T,
|
|
749
1142
|
time: timestamp,
|
|
750
|
-
type: "cron"
|
|
1143
|
+
type: "cron"
|
|
751
1144
|
};
|
|
1145
|
+
|
|
1146
|
+
emitScheduleCreate(schedule);
|
|
1147
|
+
|
|
1148
|
+
return schedule;
|
|
752
1149
|
}
|
|
753
1150
|
throw new Error("Invalid schedule type");
|
|
754
1151
|
}
|
|
@@ -812,7 +1209,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
812
1209
|
.toArray()
|
|
813
1210
|
.map((row) => ({
|
|
814
1211
|
...row,
|
|
815
|
-
payload: JSON.parse(row.payload as string) as T
|
|
1212
|
+
payload: JSON.parse(row.payload as string) as T
|
|
816
1213
|
})) as Schedule<T>[];
|
|
817
1214
|
|
|
818
1215
|
return result;
|
|
@@ -824,6 +1221,22 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
824
1221
|
* @returns true if the task was cancelled, false otherwise
|
|
825
1222
|
*/
|
|
826
1223
|
async cancelSchedule(id: string): Promise<boolean> {
|
|
1224
|
+
const schedule = await this.getSchedule(id);
|
|
1225
|
+
if (schedule) {
|
|
1226
|
+
this.observability?.emit(
|
|
1227
|
+
{
|
|
1228
|
+
displayMessage: `Schedule ${id} cancelled`,
|
|
1229
|
+
id: nanoid(),
|
|
1230
|
+
payload: {
|
|
1231
|
+
callback: schedule.callback,
|
|
1232
|
+
id: schedule.id
|
|
1233
|
+
},
|
|
1234
|
+
timestamp: Date.now(),
|
|
1235
|
+
type: "schedule:cancel"
|
|
1236
|
+
},
|
|
1237
|
+
this.ctx
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
827
1240
|
this.sql`DELETE FROM cf_agents_schedules WHERE id = ${id}`;
|
|
828
1241
|
|
|
829
1242
|
await this._scheduleNextAlarm();
|
|
@@ -833,9 +1246,9 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
833
1246
|
private async _scheduleNextAlarm() {
|
|
834
1247
|
// Find the next schedule that needs to be executed
|
|
835
1248
|
const result = this.sql`
|
|
836
|
-
SELECT time FROM cf_agents_schedules
|
|
1249
|
+
SELECT time FROM cf_agents_schedules
|
|
837
1250
|
WHERE time > ${Math.floor(Date.now() / 1000)}
|
|
838
|
-
ORDER BY time ASC
|
|
1251
|
+
ORDER BY time ASC
|
|
839
1252
|
LIMIT 1
|
|
840
1253
|
`;
|
|
841
1254
|
if (!result) return;
|
|
@@ -862,40 +1275,61 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
862
1275
|
SELECT * FROM cf_agents_schedules WHERE time <= ${now}
|
|
863
1276
|
`;
|
|
864
1277
|
|
|
865
|
-
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
await agentContext.run(
|
|
872
|
-
{ agent: this, connection: undefined, request: undefined },
|
|
873
|
-
async () => {
|
|
874
|
-
try {
|
|
875
|
-
await (
|
|
876
|
-
callback as (
|
|
877
|
-
payload: unknown,
|
|
878
|
-
schedule: Schedule<unknown>
|
|
879
|
-
) => Promise<void>
|
|
880
|
-
).bind(this)(JSON.parse(row.payload as string), row);
|
|
881
|
-
} catch (e) {
|
|
882
|
-
console.error(`error executing callback "${row.callback}"`, e);
|
|
883
|
-
}
|
|
1278
|
+
if (result && Array.isArray(result)) {
|
|
1279
|
+
for (const row of result) {
|
|
1280
|
+
const callback = this[row.callback as keyof Agent<Env>];
|
|
1281
|
+
if (!callback) {
|
|
1282
|
+
console.error(`callback ${row.callback} not found`);
|
|
1283
|
+
continue;
|
|
884
1284
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
1285
|
+
await agentContext.run(
|
|
1286
|
+
{
|
|
1287
|
+
agent: this,
|
|
1288
|
+
connection: undefined,
|
|
1289
|
+
request: undefined,
|
|
1290
|
+
email: undefined
|
|
1291
|
+
},
|
|
1292
|
+
async () => {
|
|
1293
|
+
try {
|
|
1294
|
+
this.observability?.emit(
|
|
1295
|
+
{
|
|
1296
|
+
displayMessage: `Schedule ${row.id} executed`,
|
|
1297
|
+
id: nanoid(),
|
|
1298
|
+
payload: {
|
|
1299
|
+
callback: row.callback,
|
|
1300
|
+
id: row.id
|
|
1301
|
+
},
|
|
1302
|
+
timestamp: Date.now(),
|
|
1303
|
+
type: "schedule:execute"
|
|
1304
|
+
},
|
|
1305
|
+
this.ctx
|
|
1306
|
+
);
|
|
890
1307
|
|
|
891
|
-
|
|
1308
|
+
await (
|
|
1309
|
+
callback as (
|
|
1310
|
+
payload: unknown,
|
|
1311
|
+
schedule: Schedule<unknown>
|
|
1312
|
+
) => Promise<void>
|
|
1313
|
+
).bind(this)(JSON.parse(row.payload as string), row);
|
|
1314
|
+
} catch (e) {
|
|
1315
|
+
console.error(`error executing callback "${row.callback}"`, e);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
);
|
|
1319
|
+
if (row.type === "cron") {
|
|
1320
|
+
// Update next execution time for cron schedules
|
|
1321
|
+
const nextExecutionTime = getNextCronTime(row.cron);
|
|
1322
|
+
const nextTimestamp = Math.floor(nextExecutionTime.getTime() / 1000);
|
|
1323
|
+
|
|
1324
|
+
this.sql`
|
|
892
1325
|
UPDATE cf_agents_schedules SET time = ${nextTimestamp} WHERE id = ${row.id}
|
|
893
1326
|
`;
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1327
|
+
} else {
|
|
1328
|
+
// Delete one-time schedules after execution
|
|
1329
|
+
this.sql`
|
|
897
1330
|
DELETE FROM cf_agents_schedules WHERE id = ${row.id}
|
|
898
1331
|
`;
|
|
1332
|
+
}
|
|
899
1333
|
}
|
|
900
1334
|
}
|
|
901
1335
|
|
|
@@ -911,10 +1345,23 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
911
1345
|
this.sql`DROP TABLE IF EXISTS cf_agents_state`;
|
|
912
1346
|
this.sql`DROP TABLE IF EXISTS cf_agents_schedules`;
|
|
913
1347
|
this.sql`DROP TABLE IF EXISTS cf_agents_mcp_servers`;
|
|
1348
|
+
this.sql`DROP TABLE IF EXISTS cf_agents_queues`;
|
|
914
1349
|
|
|
915
1350
|
// delete all alarms
|
|
916
1351
|
await this.ctx.storage.deleteAlarm();
|
|
917
1352
|
await this.ctx.storage.deleteAll();
|
|
1353
|
+
this.ctx.abort("destroyed"); // enforce that the agent is evicted
|
|
1354
|
+
|
|
1355
|
+
this.observability?.emit(
|
|
1356
|
+
{
|
|
1357
|
+
displayMessage: "Agent destroyed",
|
|
1358
|
+
id: nanoid(),
|
|
1359
|
+
payload: {},
|
|
1360
|
+
timestamp: Date.now(),
|
|
1361
|
+
type: "destroy"
|
|
1362
|
+
},
|
|
1363
|
+
this.ctx
|
|
1364
|
+
);
|
|
918
1365
|
}
|
|
919
1366
|
|
|
920
1367
|
/**
|
|
@@ -954,11 +1401,24 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
954
1401
|
callbackUrl,
|
|
955
1402
|
options
|
|
956
1403
|
);
|
|
1404
|
+
this.sql`
|
|
1405
|
+
INSERT
|
|
1406
|
+
OR REPLACE INTO cf_agents_mcp_servers (id, name, server_url, client_id, auth_url, callback_url, server_options)
|
|
1407
|
+
VALUES (
|
|
1408
|
+
${result.id},
|
|
1409
|
+
${serverName},
|
|
1410
|
+
${url},
|
|
1411
|
+
${result.clientId ?? null},
|
|
1412
|
+
${result.authUrl ?? null},
|
|
1413
|
+
${callbackUrl},
|
|
1414
|
+
${options ? JSON.stringify(options) : null}
|
|
1415
|
+
);
|
|
1416
|
+
`;
|
|
957
1417
|
|
|
958
1418
|
this.broadcast(
|
|
959
1419
|
JSON.stringify({
|
|
960
|
-
type: "cf_agent_mcp_servers",
|
|
961
1420
|
mcp: this.getMcpServers(),
|
|
1421
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
962
1422
|
})
|
|
963
1423
|
);
|
|
964
1424
|
|
|
@@ -966,7 +1426,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
966
1426
|
}
|
|
967
1427
|
|
|
968
1428
|
async _connectToMcpServerInternal(
|
|
969
|
-
|
|
1429
|
+
_serverName: string,
|
|
970
1430
|
url: string,
|
|
971
1431
|
callbackUrl: string,
|
|
972
1432
|
// it's important that any options here are serializable because we put them into our sqlite DB for reconnection purposes
|
|
@@ -987,7 +1447,11 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
987
1447
|
id: string;
|
|
988
1448
|
oauthClientId?: string;
|
|
989
1449
|
}
|
|
990
|
-
): Promise<{
|
|
1450
|
+
): Promise<{
|
|
1451
|
+
id: string;
|
|
1452
|
+
authUrl: string | undefined;
|
|
1453
|
+
clientId: string | undefined;
|
|
1454
|
+
}> {
|
|
991
1455
|
const authProvider = new DurableObjectOAuthClientProvider(
|
|
992
1456
|
this.ctx.storage,
|
|
993
1457
|
this.name,
|
|
@@ -1010,40 +1474,28 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1010
1474
|
fetch: (url, init) =>
|
|
1011
1475
|
fetch(url, {
|
|
1012
1476
|
...init,
|
|
1013
|
-
headers: options?.transport?.headers
|
|
1014
|
-
})
|
|
1477
|
+
headers: options?.transport?.headers
|
|
1478
|
+
})
|
|
1015
1479
|
},
|
|
1016
1480
|
requestInit: {
|
|
1017
|
-
headers: options?.transport?.headers
|
|
1018
|
-
}
|
|
1481
|
+
headers: options?.transport?.headers
|
|
1482
|
+
}
|
|
1019
1483
|
};
|
|
1020
1484
|
}
|
|
1021
1485
|
|
|
1022
1486
|
const { id, authUrl, clientId } = await this.mcp.connect(url, {
|
|
1487
|
+
client: options?.client,
|
|
1023
1488
|
reconnect,
|
|
1024
1489
|
transport: {
|
|
1025
1490
|
...headerTransportOpts,
|
|
1026
|
-
authProvider
|
|
1027
|
-
}
|
|
1028
|
-
client: options?.client,
|
|
1491
|
+
authProvider
|
|
1492
|
+
}
|
|
1029
1493
|
});
|
|
1030
1494
|
|
|
1031
|
-
this.sql`
|
|
1032
|
-
INSERT OR REPLACE INTO cf_agents_mcp_servers (id, name, server_url, client_id, auth_url, callback_url, server_options)
|
|
1033
|
-
VALUES (
|
|
1034
|
-
${id},
|
|
1035
|
-
${serverName},
|
|
1036
|
-
${url},
|
|
1037
|
-
${clientId ?? null},
|
|
1038
|
-
${authUrl ?? null},
|
|
1039
|
-
${callbackUrl},
|
|
1040
|
-
${options ? JSON.stringify(options) : null}
|
|
1041
|
-
);
|
|
1042
|
-
`;
|
|
1043
|
-
|
|
1044
1495
|
return {
|
|
1045
|
-
id,
|
|
1046
1496
|
authUrl,
|
|
1497
|
+
clientId,
|
|
1498
|
+
id
|
|
1047
1499
|
};
|
|
1048
1500
|
}
|
|
1049
1501
|
|
|
@@ -1054,34 +1506,37 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1054
1506
|
`;
|
|
1055
1507
|
this.broadcast(
|
|
1056
1508
|
JSON.stringify({
|
|
1057
|
-
type: "cf_agent_mcp_servers",
|
|
1058
1509
|
mcp: this.getMcpServers(),
|
|
1510
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
1059
1511
|
})
|
|
1060
1512
|
);
|
|
1061
1513
|
}
|
|
1062
1514
|
|
|
1063
1515
|
getMcpServers(): MCPServersState {
|
|
1064
1516
|
const mcpState: MCPServersState = {
|
|
1065
|
-
servers: {},
|
|
1066
|
-
tools: this.mcp.listTools(),
|
|
1067
1517
|
prompts: this.mcp.listPrompts(),
|
|
1068
1518
|
resources: this.mcp.listResources(),
|
|
1519
|
+
servers: {},
|
|
1520
|
+
tools: this.mcp.listTools()
|
|
1069
1521
|
};
|
|
1070
1522
|
|
|
1071
1523
|
const servers = this.sql<MCPServerRow>`
|
|
1072
1524
|
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
|
|
1073
1525
|
`;
|
|
1074
1526
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1527
|
+
if (servers && Array.isArray(servers) && servers.length > 0) {
|
|
1528
|
+
for (const server of servers) {
|
|
1529
|
+
const serverConn = this.mcp.mcpConnections[server.id];
|
|
1530
|
+
mcpState.servers[server.id] = {
|
|
1531
|
+
auth_url: server.auth_url,
|
|
1532
|
+
capabilities: serverConn?.serverCapabilities ?? null,
|
|
1533
|
+
instructions: serverConn?.instructions ?? null,
|
|
1534
|
+
name: server.name,
|
|
1535
|
+
server_url: server.server_url,
|
|
1536
|
+
// mark as "authenticating" because the server isn't automatically connected, so it's pending authenticating
|
|
1537
|
+
state: serverConn?.connectionState ?? "authenticating"
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1085
1540
|
}
|
|
1086
1541
|
|
|
1087
1542
|
return mcpState;
|
|
@@ -1125,17 +1580,17 @@ export async function routeAgentRequest<Env>(
|
|
|
1125
1580
|
const corsHeaders =
|
|
1126
1581
|
options?.cors === true
|
|
1127
1582
|
? {
|
|
1128
|
-
"Access-Control-Allow-Origin": "*",
|
|
1129
|
-
"Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS",
|
|
1130
1583
|
"Access-Control-Allow-Credentials": "true",
|
|
1131
|
-
"Access-Control-
|
|
1584
|
+
"Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS",
|
|
1585
|
+
"Access-Control-Allow-Origin": "*",
|
|
1586
|
+
"Access-Control-Max-Age": "86400"
|
|
1132
1587
|
}
|
|
1133
1588
|
: options?.cors;
|
|
1134
1589
|
|
|
1135
1590
|
if (request.method === "OPTIONS") {
|
|
1136
1591
|
if (corsHeaders) {
|
|
1137
1592
|
return new Response(null, {
|
|
1138
|
-
headers: corsHeaders
|
|
1593
|
+
headers: corsHeaders
|
|
1139
1594
|
});
|
|
1140
1595
|
}
|
|
1141
1596
|
console.warn(
|
|
@@ -1148,7 +1603,7 @@ export async function routeAgentRequest<Env>(
|
|
|
1148
1603
|
env as Record<string, unknown>,
|
|
1149
1604
|
{
|
|
1150
1605
|
prefix: "agents",
|
|
1151
|
-
...(options as PartyServerOptions<Record<string, unknown>>)
|
|
1606
|
+
...(options as PartyServerOptions<Record<string, unknown>>)
|
|
1152
1607
|
}
|
|
1153
1608
|
);
|
|
1154
1609
|
|
|
@@ -1161,24 +1616,238 @@ export async function routeAgentRequest<Env>(
|
|
|
1161
1616
|
response = new Response(response.body, {
|
|
1162
1617
|
headers: {
|
|
1163
1618
|
...response.headers,
|
|
1164
|
-
...corsHeaders
|
|
1165
|
-
}
|
|
1619
|
+
...corsHeaders
|
|
1620
|
+
}
|
|
1166
1621
|
});
|
|
1167
1622
|
}
|
|
1168
1623
|
return response;
|
|
1169
1624
|
}
|
|
1170
1625
|
|
|
1626
|
+
export type EmailResolver<Env> = (
|
|
1627
|
+
email: ForwardableEmailMessage,
|
|
1628
|
+
env: Env
|
|
1629
|
+
) => Promise<{
|
|
1630
|
+
agentName: string;
|
|
1631
|
+
agentId: string;
|
|
1632
|
+
} | null>;
|
|
1633
|
+
|
|
1634
|
+
/**
|
|
1635
|
+
* Create a resolver that uses the message-id header to determine the agent to route the email to
|
|
1636
|
+
* @returns A function that resolves the agent to route the email to
|
|
1637
|
+
*/
|
|
1638
|
+
export function createHeaderBasedEmailResolver<Env>(): EmailResolver<Env> {
|
|
1639
|
+
return async (email: ForwardableEmailMessage, _env: Env) => {
|
|
1640
|
+
const messageId = email.headers.get("message-id");
|
|
1641
|
+
if (messageId) {
|
|
1642
|
+
const messageIdMatch = messageId.match(/<([^@]+)@([^>]+)>/);
|
|
1643
|
+
if (messageIdMatch) {
|
|
1644
|
+
const [, agentId, domain] = messageIdMatch;
|
|
1645
|
+
const agentName = domain.split(".")[0];
|
|
1646
|
+
return { agentName, agentId };
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
const references = email.headers.get("references");
|
|
1651
|
+
if (references) {
|
|
1652
|
+
const referencesMatch = references.match(
|
|
1653
|
+
/<([A-Za-z0-9+/]{43}=)@([^>]+)>/
|
|
1654
|
+
);
|
|
1655
|
+
if (referencesMatch) {
|
|
1656
|
+
const [, base64Id, domain] = referencesMatch;
|
|
1657
|
+
const agentId = Buffer.from(base64Id, "base64").toString("hex");
|
|
1658
|
+
const agentName = domain.split(".")[0];
|
|
1659
|
+
return { agentName, agentId };
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
const agentName = email.headers.get("x-agent-name");
|
|
1664
|
+
const agentId = email.headers.get("x-agent-id");
|
|
1665
|
+
if (agentName && agentId) {
|
|
1666
|
+
return { agentName, agentId };
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
return null;
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
/**
|
|
1674
|
+
* Create a resolver that uses the email address to determine the agent to route the email to
|
|
1675
|
+
* @param defaultAgentName The default agent name to use if the email address does not contain a sub-address
|
|
1676
|
+
* @returns A function that resolves the agent to route the email to
|
|
1677
|
+
*/
|
|
1678
|
+
export function createAddressBasedEmailResolver<Env>(
|
|
1679
|
+
defaultAgentName: string
|
|
1680
|
+
): EmailResolver<Env> {
|
|
1681
|
+
return async (email: ForwardableEmailMessage, _env: Env) => {
|
|
1682
|
+
const emailMatch = email.to.match(/^([^+@]+)(?:\+([^@]+))?@(.+)$/);
|
|
1683
|
+
if (!emailMatch) {
|
|
1684
|
+
return null;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
const [, localPart, subAddress] = emailMatch;
|
|
1688
|
+
|
|
1689
|
+
if (subAddress) {
|
|
1690
|
+
return {
|
|
1691
|
+
agentName: localPart,
|
|
1692
|
+
agentId: subAddress
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// Option 2: Use defaultAgentName namespace, localPart as agentId
|
|
1697
|
+
// Common for catch-all email routing to a single EmailAgent namespace
|
|
1698
|
+
return {
|
|
1699
|
+
agentName: defaultAgentName,
|
|
1700
|
+
agentId: localPart
|
|
1701
|
+
};
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
/**
|
|
1706
|
+
* Create a resolver that uses the agentName and agentId to determine the agent to route the email to
|
|
1707
|
+
* @param agentName The name of the agent to route the email to
|
|
1708
|
+
* @param agentId The id of the agent to route the email to
|
|
1709
|
+
* @returns A function that resolves the agent to route the email to
|
|
1710
|
+
*/
|
|
1711
|
+
export function createCatchAllEmailResolver<Env>(
|
|
1712
|
+
agentName: string,
|
|
1713
|
+
agentId: string
|
|
1714
|
+
): EmailResolver<Env> {
|
|
1715
|
+
return async () => ({ agentName, agentId });
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
export type EmailRoutingOptions<Env> = AgentOptions<Env> & {
|
|
1719
|
+
resolver: EmailResolver<Env>;
|
|
1720
|
+
};
|
|
1721
|
+
|
|
1722
|
+
// Cache the agent namespace map for email routing
|
|
1723
|
+
// This maps both kebab-case and original names to namespaces
|
|
1724
|
+
const agentMapCache = new WeakMap<
|
|
1725
|
+
Record<string, unknown>,
|
|
1726
|
+
Record<string, unknown>
|
|
1727
|
+
>();
|
|
1728
|
+
|
|
1171
1729
|
/**
|
|
1172
1730
|
* Route an email to the appropriate Agent
|
|
1173
|
-
* @param email
|
|
1174
|
-
* @param env
|
|
1175
|
-
* @param options
|
|
1731
|
+
* @param email The email to route
|
|
1732
|
+
* @param env The environment containing the Agent bindings
|
|
1733
|
+
* @param options The options for routing the email
|
|
1734
|
+
* @returns A promise that resolves when the email has been routed
|
|
1176
1735
|
*/
|
|
1177
1736
|
export async function routeAgentEmail<Env>(
|
|
1178
1737
|
email: ForwardableEmailMessage,
|
|
1179
1738
|
env: Env,
|
|
1180
|
-
options
|
|
1181
|
-
): Promise<void> {
|
|
1739
|
+
options: EmailRoutingOptions<Env>
|
|
1740
|
+
): Promise<void> {
|
|
1741
|
+
const routingInfo = await options.resolver(email, env);
|
|
1742
|
+
|
|
1743
|
+
if (!routingInfo) {
|
|
1744
|
+
console.warn("No routing information found for email, dropping message");
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
// Build a map that includes both original names and kebab-case versions
|
|
1749
|
+
if (!agentMapCache.has(env as Record<string, unknown>)) {
|
|
1750
|
+
const map: Record<string, unknown> = {};
|
|
1751
|
+
for (const [key, value] of Object.entries(env as Record<string, unknown>)) {
|
|
1752
|
+
if (
|
|
1753
|
+
value &&
|
|
1754
|
+
typeof value === "object" &&
|
|
1755
|
+
"idFromName" in value &&
|
|
1756
|
+
typeof value.idFromName === "function"
|
|
1757
|
+
) {
|
|
1758
|
+
// Add both the original name and kebab-case version
|
|
1759
|
+
map[key] = value;
|
|
1760
|
+
map[camelCaseToKebabCase(key)] = value;
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
agentMapCache.set(env as Record<string, unknown>, map);
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
const agentMap = agentMapCache.get(env as Record<string, unknown>)!;
|
|
1767
|
+
const namespace = agentMap[routingInfo.agentName];
|
|
1768
|
+
|
|
1769
|
+
if (!namespace) {
|
|
1770
|
+
// Provide helpful error message listing available agents
|
|
1771
|
+
const availableAgents = Object.keys(agentMap)
|
|
1772
|
+
.filter((key) => !key.includes("-")) // Show only original names, not kebab-case duplicates
|
|
1773
|
+
.join(", ");
|
|
1774
|
+
throw new Error(
|
|
1775
|
+
`Agent namespace '${routingInfo.agentName}' not found in environment. Available agents: ${availableAgents}`
|
|
1776
|
+
);
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
const agent = await getAgentByName(
|
|
1780
|
+
namespace as unknown as AgentNamespace<Agent<Env>>,
|
|
1781
|
+
routingInfo.agentId
|
|
1782
|
+
);
|
|
1783
|
+
|
|
1784
|
+
// let's make a serialisable version of the email
|
|
1785
|
+
const serialisableEmail: AgentEmail = {
|
|
1786
|
+
getRaw: async () => {
|
|
1787
|
+
const reader = email.raw.getReader();
|
|
1788
|
+
const chunks: Uint8Array[] = [];
|
|
1789
|
+
|
|
1790
|
+
let done = false;
|
|
1791
|
+
while (!done) {
|
|
1792
|
+
const { value, done: readerDone } = await reader.read();
|
|
1793
|
+
done = readerDone;
|
|
1794
|
+
if (value) {
|
|
1795
|
+
chunks.push(value);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
1800
|
+
const combined = new Uint8Array(totalLength);
|
|
1801
|
+
let offset = 0;
|
|
1802
|
+
for (const chunk of chunks) {
|
|
1803
|
+
combined.set(chunk, offset);
|
|
1804
|
+
offset += chunk.length;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
return combined;
|
|
1808
|
+
},
|
|
1809
|
+
headers: email.headers,
|
|
1810
|
+
rawSize: email.rawSize,
|
|
1811
|
+
setReject: (reason: string) => {
|
|
1812
|
+
email.setReject(reason);
|
|
1813
|
+
},
|
|
1814
|
+
forward: (rcptTo: string, headers?: Headers) => {
|
|
1815
|
+
return email.forward(rcptTo, headers);
|
|
1816
|
+
},
|
|
1817
|
+
reply: (options: { from: string; to: string; raw: string }) => {
|
|
1818
|
+
return email.reply(
|
|
1819
|
+
new EmailMessage(options.from, options.to, options.raw)
|
|
1820
|
+
);
|
|
1821
|
+
},
|
|
1822
|
+
from: email.from,
|
|
1823
|
+
to: email.to
|
|
1824
|
+
};
|
|
1825
|
+
|
|
1826
|
+
await agent._onEmail(serialisableEmail);
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
export type AgentEmail = {
|
|
1830
|
+
from: string;
|
|
1831
|
+
to: string;
|
|
1832
|
+
getRaw: () => Promise<Uint8Array>;
|
|
1833
|
+
headers: Headers;
|
|
1834
|
+
rawSize: number;
|
|
1835
|
+
setReject: (reason: string) => void;
|
|
1836
|
+
forward: (rcptTo: string, headers?: Headers) => Promise<void>;
|
|
1837
|
+
reply: (options: { from: string; to: string; raw: string }) => Promise<void>;
|
|
1838
|
+
};
|
|
1839
|
+
|
|
1840
|
+
export type EmailSendOptions = {
|
|
1841
|
+
to: string;
|
|
1842
|
+
subject: string;
|
|
1843
|
+
body: string;
|
|
1844
|
+
contentType?: string;
|
|
1845
|
+
headers?: Record<string, string>;
|
|
1846
|
+
includeRoutingHeaders?: boolean;
|
|
1847
|
+
agentName?: string;
|
|
1848
|
+
agentId?: string;
|
|
1849
|
+
domain?: string;
|
|
1850
|
+
};
|
|
1182
1851
|
|
|
1183
1852
|
/**
|
|
1184
1853
|
* Get or create an Agent by name
|
|
@@ -1222,11 +1891,11 @@ export class StreamingResponse {
|
|
|
1222
1891
|
throw new Error("StreamingResponse is already closed");
|
|
1223
1892
|
}
|
|
1224
1893
|
const response: RPCResponse = {
|
|
1225
|
-
|
|
1894
|
+
done: false,
|
|
1226
1895
|
id: this._id,
|
|
1227
|
-
success: true,
|
|
1228
1896
|
result: chunk,
|
|
1229
|
-
|
|
1897
|
+
success: true,
|
|
1898
|
+
type: MessageType.RPC
|
|
1230
1899
|
};
|
|
1231
1900
|
this._connection.send(JSON.stringify(response));
|
|
1232
1901
|
}
|
|
@@ -1241,11 +1910,11 @@ export class StreamingResponse {
|
|
|
1241
1910
|
}
|
|
1242
1911
|
this._closed = true;
|
|
1243
1912
|
const response: RPCResponse = {
|
|
1244
|
-
|
|
1913
|
+
done: true,
|
|
1245
1914
|
id: this._id,
|
|
1246
|
-
success: true,
|
|
1247
1915
|
result: finalChunk,
|
|
1248
|
-
|
|
1916
|
+
success: true,
|
|
1917
|
+
type: MessageType.RPC
|
|
1249
1918
|
};
|
|
1250
1919
|
this._connection.send(JSON.stringify(response));
|
|
1251
1920
|
}
|