agents 0.0.0-35ccf92 → 0.0.0-36f4ca4
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 +255 -27
- package/dist/_esm-LV5FJ3HK.js +3922 -0
- package/dist/_esm-LV5FJ3HK.js.map +1 -0
- package/dist/ai-chat-agent.d.ts +11 -7
- package/dist/ai-chat-agent.js +169 -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 +20 -0
- package/dist/ai-chat-v5-migration.js.map +1 -0
- package/dist/ai-react.d.ts +67 -70
- package/dist/ai-react.js +252 -99
- package/dist/ai-react.js.map +1 -1
- package/dist/ai-types.d.ts +37 -19
- package/dist/ai-types.js +7 -0
- package/dist/ccip-CMBYN64O.js +15 -0
- package/dist/ccip-CMBYN64O.js.map +1 -0
- package/dist/chunk-5Y6BEZDY.js +276 -0
- package/dist/chunk-5Y6BEZDY.js.map +1 -0
- package/dist/chunk-BER7KXUJ.js +18 -0
- package/dist/chunk-BER7KXUJ.js.map +1 -0
- package/dist/chunk-JJBFIGUC.js +5202 -0
- package/dist/chunk-JJBFIGUC.js.map +1 -0
- package/dist/chunk-PR4QN5HX.js +43 -0
- package/dist/chunk-PR4QN5HX.js.map +1 -0
- package/dist/{chunk-E3LCYPCB.js → chunk-QEPGNUG6.js} +213 -32
- package/dist/chunk-QEPGNUG6.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-RS5OCNEQ.js +1323 -0
- package/dist/chunk-RS5OCNEQ.js.map +1 -0
- package/dist/chunk-TYAY6AU6.js +159 -0
- package/dist/chunk-TYAY6AU6.js.map +1 -0
- package/dist/chunk-UJVEAURM.js +150 -0
- package/dist/chunk-UJVEAURM.js.map +1 -0
- package/dist/{chunk-767EASBA.js → chunk-XFS5ERG3.js} +24 -3
- package/dist/chunk-XFS5ERG3.js.map +1 -0
- package/dist/client-BohGLma8.d.ts +5041 -0
- package/dist/client.d.ts +2 -2
- package/dist/client.js +3 -1
- package/dist/index.d.ts +182 -23
- package/dist/index.js +14 -4
- package/dist/mcp/client.d.ts +9 -781
- package/dist/mcp/client.js +2 -1
- package/dist/mcp/do-oauth-client-provider.d.ts +9 -0
- package/dist/mcp/do-oauth-client-provider.js +2 -1
- package/dist/mcp/index.d.ts +57 -63
- package/dist/mcp/index.js +953 -637
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/x402.d.ts +37 -0
- package/dist/mcp/x402.js +3195 -0
- package/dist/mcp/x402.js.map +1 -0
- package/dist/observability/index.d.ts +46 -0
- package/dist/observability/index.js +12 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/react.d.ts +9 -5
- package/dist/react.js +8 -5
- package/dist/react.js.map +1 -1
- package/dist/schedule.d.ts +79 -5
- package/dist/schedule.js +17 -2
- package/dist/schedule.js.map +1 -1
- package/dist/secp256k1-M22GZP2U.js +2193 -0
- package/dist/secp256k1-M22GZP2U.js.map +1 -0
- package/package.json +51 -30
- package/src/index.ts +874 -144
- package/dist/chunk-767EASBA.js.map +0 -1
- package/dist/chunk-CGWTDCBQ.js +0 -791
- package/dist/chunk-CGWTDCBQ.js.map +0 -1
- package/dist/chunk-E3LCYPCB.js.map +0 -1
- package/dist/chunk-NKZZ66QY.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
|
-
// import type { MCPClientConnection } from "./mcp/client-connection";
|
|
25
26
|
import { DurableObjectOAuthClientProvider } from "./mcp/do-oauth-client-provider";
|
|
27
|
+
import type { TransportType } from "./mcp/types";
|
|
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
|
|
@@ -169,11 +197,13 @@ function getNextCronTime(cron: string) {
|
|
|
169
197
|
return interval.getNextDate();
|
|
170
198
|
}
|
|
171
199
|
|
|
200
|
+
export type { TransportType } from "./mcp/types";
|
|
201
|
+
|
|
172
202
|
/**
|
|
173
203
|
* MCP Server state update message from server -> Client
|
|
174
204
|
*/
|
|
175
205
|
export type MCPServerMessage = {
|
|
176
|
-
type:
|
|
206
|
+
type: MessageType.CF_AGENT_MCP_SERVERS;
|
|
177
207
|
mcp: MCPServersState;
|
|
178
208
|
};
|
|
179
209
|
|
|
@@ -217,23 +247,26 @@ const STATE_WAS_CHANGED = "cf_state_was_changed";
|
|
|
217
247
|
const DEFAULT_STATE = {} as unknown;
|
|
218
248
|
|
|
219
249
|
const agentContext = new AsyncLocalStorage<{
|
|
220
|
-
agent: Agent<unknown>;
|
|
250
|
+
agent: Agent<unknown, unknown>;
|
|
221
251
|
connection: Connection | undefined;
|
|
222
252
|
request: Request | undefined;
|
|
253
|
+
email: AgentEmail | undefined;
|
|
223
254
|
}>();
|
|
224
255
|
|
|
225
256
|
export function getCurrentAgent<
|
|
226
|
-
T extends Agent<unknown, unknown> = Agent<unknown, unknown
|
|
257
|
+
T extends Agent<unknown, unknown> = Agent<unknown, unknown>
|
|
227
258
|
>(): {
|
|
228
259
|
agent: T | undefined;
|
|
229
260
|
connection: Connection | undefined;
|
|
230
|
-
request: Request
|
|
261
|
+
request: Request | undefined;
|
|
262
|
+
email: AgentEmail | undefined;
|
|
231
263
|
} {
|
|
232
264
|
const store = agentContext.getStore() as
|
|
233
265
|
| {
|
|
234
266
|
agent: T;
|
|
235
267
|
connection: Connection | undefined;
|
|
236
|
-
request: Request
|
|
268
|
+
request: Request | undefined;
|
|
269
|
+
email: AgentEmail | undefined;
|
|
237
270
|
}
|
|
238
271
|
| undefined;
|
|
239
272
|
if (!store) {
|
|
@@ -241,23 +274,56 @@ export function getCurrentAgent<
|
|
|
241
274
|
agent: undefined,
|
|
242
275
|
connection: undefined,
|
|
243
276
|
request: undefined,
|
|
277
|
+
email: undefined
|
|
244
278
|
};
|
|
245
279
|
}
|
|
246
280
|
return store;
|
|
247
281
|
}
|
|
248
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Wraps a method to run within the agent context, ensuring getCurrentAgent() works properly
|
|
285
|
+
* @param agent The agent instance
|
|
286
|
+
* @param method The method to wrap
|
|
287
|
+
* @returns A wrapped method that runs within the agent context
|
|
288
|
+
*/
|
|
289
|
+
|
|
290
|
+
// biome-ignore lint/suspicious/noExplicitAny: I can't typescript
|
|
291
|
+
function withAgentContext<T extends (...args: any[]) => any>(
|
|
292
|
+
method: T
|
|
293
|
+
): (this: Agent<unknown, unknown>, ...args: Parameters<T>) => ReturnType<T> {
|
|
294
|
+
return function (...args: Parameters<T>): ReturnType<T> {
|
|
295
|
+
const { connection, request, email, agent } = getCurrentAgent();
|
|
296
|
+
|
|
297
|
+
if (agent === this) {
|
|
298
|
+
// already wrapped, so we can just call the method
|
|
299
|
+
return method.apply(this, args);
|
|
300
|
+
}
|
|
301
|
+
// not wrapped, so we need to wrap it
|
|
302
|
+
return agentContext.run({ agent: this, connection, request, email }, () => {
|
|
303
|
+
return method.apply(this, args);
|
|
304
|
+
});
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
249
308
|
/**
|
|
250
309
|
* Base class for creating Agent implementations
|
|
251
310
|
* @template Env Environment type containing bindings
|
|
252
311
|
* @template State State type to store within the Agent
|
|
253
312
|
*/
|
|
254
|
-
export class Agent<
|
|
313
|
+
export class Agent<
|
|
314
|
+
Env = typeof env,
|
|
315
|
+
State = unknown,
|
|
316
|
+
Props extends Record<string, unknown> = Record<string, unknown>
|
|
317
|
+
> extends Server<Env, Props> {
|
|
255
318
|
private _state = DEFAULT_STATE as State;
|
|
256
319
|
|
|
257
320
|
private _ParentClass: typeof Agent<Env, State> =
|
|
258
321
|
Object.getPrototypeOf(this).constructor;
|
|
259
322
|
|
|
260
|
-
mcp: MCPClientManager = new MCPClientManager(
|
|
323
|
+
readonly mcp: MCPClientManager = new MCPClientManager(
|
|
324
|
+
this._ParentClass.name,
|
|
325
|
+
"0.0.1"
|
|
326
|
+
);
|
|
261
327
|
|
|
262
328
|
/**
|
|
263
329
|
* Initial state for the Agent
|
|
@@ -313,9 +379,14 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
313
379
|
*/
|
|
314
380
|
static options = {
|
|
315
381
|
/** Whether the Agent should hibernate when inactive */
|
|
316
|
-
hibernate: true
|
|
382
|
+
hibernate: true // default to hibernate
|
|
317
383
|
};
|
|
318
384
|
|
|
385
|
+
/**
|
|
386
|
+
* The observability implementation to use for the Agent
|
|
387
|
+
*/
|
|
388
|
+
observability?: Observability = genericObservability;
|
|
389
|
+
|
|
319
390
|
/**
|
|
320
391
|
* Execute SQL queries against the Agent's database
|
|
321
392
|
* @template T Type of the returned rows
|
|
@@ -345,6 +416,12 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
345
416
|
constructor(ctx: AgentContext, env: Env) {
|
|
346
417
|
super(ctx, env);
|
|
347
418
|
|
|
419
|
+
if (!wrappedClasses.has(this.constructor)) {
|
|
420
|
+
// Auto-wrap custom methods with agent context
|
|
421
|
+
this._autoWrapCustomMethods();
|
|
422
|
+
wrappedClasses.add(this.constructor);
|
|
423
|
+
}
|
|
424
|
+
|
|
348
425
|
this.sql`
|
|
349
426
|
CREATE TABLE IF NOT EXISTS cf_agents_state (
|
|
350
427
|
id TEXT PRIMARY KEY NOT NULL,
|
|
@@ -352,6 +429,15 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
352
429
|
)
|
|
353
430
|
`;
|
|
354
431
|
|
|
432
|
+
this.sql`
|
|
433
|
+
CREATE TABLE IF NOT EXISTS cf_agents_queues (
|
|
434
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
435
|
+
payload TEXT,
|
|
436
|
+
callback TEXT,
|
|
437
|
+
created_at INTEGER DEFAULT (unixepoch())
|
|
438
|
+
)
|
|
439
|
+
`;
|
|
440
|
+
|
|
355
441
|
void this.ctx.blockConcurrencyWhile(async () => {
|
|
356
442
|
return this._tryCatch(async () => {
|
|
357
443
|
// Create alarms table if it doesn't exist
|
|
@@ -388,7 +474,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
388
474
|
const _onRequest = this.onRequest.bind(this);
|
|
389
475
|
this.onRequest = (request: Request) => {
|
|
390
476
|
return agentContext.run(
|
|
391
|
-
{ agent: this, connection: undefined, request },
|
|
477
|
+
{ agent: this, connection: undefined, request, email: undefined },
|
|
392
478
|
async () => {
|
|
393
479
|
if (this.mcp.isCallbackRequest(request)) {
|
|
394
480
|
await this.mcp.handleCallbackRequest(request);
|
|
@@ -397,14 +483,14 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
397
483
|
this.broadcast(
|
|
398
484
|
JSON.stringify({
|
|
399
485
|
mcp: this.getMcpServers(),
|
|
400
|
-
type:
|
|
486
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
401
487
|
})
|
|
402
488
|
);
|
|
403
489
|
|
|
404
490
|
// We probably should let the user configure this response/redirect, but this is fine for now.
|
|
405
491
|
return new Response("<script>window.close();</script>", {
|
|
406
492
|
headers: { "content-type": "text/html" },
|
|
407
|
-
status: 200
|
|
493
|
+
status: 200
|
|
408
494
|
});
|
|
409
495
|
}
|
|
410
496
|
|
|
@@ -416,7 +502,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
416
502
|
const _onMessage = this.onMessage.bind(this);
|
|
417
503
|
this.onMessage = async (connection: Connection, message: WSMessage) => {
|
|
418
504
|
return agentContext.run(
|
|
419
|
-
{ agent: this, connection, request: undefined },
|
|
505
|
+
{ agent: this, connection, request: undefined, email: undefined },
|
|
420
506
|
async () => {
|
|
421
507
|
if (typeof message !== "string") {
|
|
422
508
|
return this._tryCatch(() => _onMessage(connection, message));
|
|
@@ -460,12 +546,27 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
460
546
|
|
|
461
547
|
// For regular methods, execute and send response
|
|
462
548
|
const result = await methodFn.apply(this, args);
|
|
549
|
+
|
|
550
|
+
this.observability?.emit(
|
|
551
|
+
{
|
|
552
|
+
displayMessage: `RPC call to ${method}`,
|
|
553
|
+
id: nanoid(),
|
|
554
|
+
payload: {
|
|
555
|
+
method,
|
|
556
|
+
streaming: metadata?.streaming
|
|
557
|
+
},
|
|
558
|
+
timestamp: Date.now(),
|
|
559
|
+
type: "rpc"
|
|
560
|
+
},
|
|
561
|
+
this.ctx
|
|
562
|
+
);
|
|
563
|
+
|
|
463
564
|
const response: RPCResponse = {
|
|
464
565
|
done: true,
|
|
465
566
|
id,
|
|
466
567
|
result,
|
|
467
568
|
success: true,
|
|
468
|
-
type:
|
|
569
|
+
type: MessageType.RPC
|
|
469
570
|
};
|
|
470
571
|
connection.send(JSON.stringify(response));
|
|
471
572
|
} catch (e) {
|
|
@@ -475,7 +576,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
475
576
|
e instanceof Error ? e.message : "Unknown error occurred",
|
|
476
577
|
id: parsed.id,
|
|
477
578
|
success: false,
|
|
478
|
-
type:
|
|
579
|
+
type: MessageType.RPC
|
|
479
580
|
};
|
|
480
581
|
connection.send(JSON.stringify(response));
|
|
481
582
|
console.error("RPC error:", e);
|
|
@@ -493,65 +594,111 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
493
594
|
// TODO: This is a hack to ensure the state is sent after the connection is established
|
|
494
595
|
// must fix this
|
|
495
596
|
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
|
-
|
|
597
|
+
{ agent: this, connection, request: ctx.request, email: undefined },
|
|
598
|
+
() => {
|
|
599
|
+
if (this.state) {
|
|
508
600
|
connection.send(
|
|
509
601
|
JSON.stringify({
|
|
510
|
-
|
|
511
|
-
type:
|
|
602
|
+
state: this.state,
|
|
603
|
+
type: MessageType.CF_AGENT_STATE
|
|
512
604
|
})
|
|
513
605
|
);
|
|
606
|
+
}
|
|
514
607
|
|
|
515
|
-
|
|
516
|
-
|
|
608
|
+
connection.send(
|
|
609
|
+
JSON.stringify({
|
|
610
|
+
mcp: this.getMcpServers(),
|
|
611
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
612
|
+
})
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
this.observability?.emit(
|
|
616
|
+
{
|
|
617
|
+
displayMessage: "Connection established",
|
|
618
|
+
id: nanoid(),
|
|
619
|
+
payload: {
|
|
620
|
+
connectionId: connection.id
|
|
621
|
+
},
|
|
622
|
+
timestamp: Date.now(),
|
|
623
|
+
type: "connect"
|
|
624
|
+
},
|
|
625
|
+
this.ctx
|
|
626
|
+
);
|
|
627
|
+
return this._tryCatch(() => _onConnect(connection, ctx));
|
|
517
628
|
}
|
|
518
629
|
);
|
|
519
630
|
};
|
|
520
631
|
|
|
521
632
|
const _onStart = this.onStart.bind(this);
|
|
522
|
-
this.onStart = async () => {
|
|
633
|
+
this.onStart = async (props?: Props) => {
|
|
523
634
|
return agentContext.run(
|
|
524
|
-
{
|
|
635
|
+
{
|
|
636
|
+
agent: this,
|
|
637
|
+
connection: undefined,
|
|
638
|
+
request: undefined,
|
|
639
|
+
email: undefined
|
|
640
|
+
},
|
|
525
641
|
async () => {
|
|
526
|
-
|
|
642
|
+
await this._tryCatch(() => {
|
|
643
|
+
const servers = this.sql<MCPServerRow>`
|
|
527
644
|
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
|
|
528
645
|
`;
|
|
529
646
|
|
|
530
|
-
// from DO storage, reconnect to all servers not currently in the oauth flow using our saved auth information
|
|
531
|
-
Promise.allSettled(
|
|
532
|
-
servers.map((server) => {
|
|
533
|
-
return this._connectToMcpServerInternal(
|
|
534
|
-
server.name,
|
|
535
|
-
server.server_url,
|
|
536
|
-
server.callback_url,
|
|
537
|
-
server.server_options
|
|
538
|
-
? JSON.parse(server.server_options)
|
|
539
|
-
: undefined,
|
|
540
|
-
{
|
|
541
|
-
id: server.id,
|
|
542
|
-
oauthClientId: server.client_id ?? undefined,
|
|
543
|
-
}
|
|
544
|
-
);
|
|
545
|
-
})
|
|
546
|
-
).then((_results) => {
|
|
547
647
|
this.broadcast(
|
|
548
648
|
JSON.stringify({
|
|
549
|
-
type: "cf_agent_mcp_servers",
|
|
550
649
|
mcp: this.getMcpServers(),
|
|
650
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
551
651
|
})
|
|
552
652
|
);
|
|
653
|
+
|
|
654
|
+
// from DO storage, reconnect to all servers not currently in the oauth flow using our saved auth information
|
|
655
|
+
if (servers && Array.isArray(servers) && servers.length > 0) {
|
|
656
|
+
// Restore callback URLs for OAuth-enabled servers
|
|
657
|
+
servers.forEach((server) => {
|
|
658
|
+
if (server.callback_url) {
|
|
659
|
+
this.mcp.registerCallbackUrl(server.callback_url);
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
servers.forEach((server) => {
|
|
664
|
+
this._connectToMcpServerInternal(
|
|
665
|
+
server.name,
|
|
666
|
+
server.server_url,
|
|
667
|
+
server.callback_url,
|
|
668
|
+
server.server_options
|
|
669
|
+
? JSON.parse(server.server_options)
|
|
670
|
+
: undefined,
|
|
671
|
+
{
|
|
672
|
+
id: server.id,
|
|
673
|
+
oauthClientId: server.client_id ?? undefined
|
|
674
|
+
}
|
|
675
|
+
)
|
|
676
|
+
.then(() => {
|
|
677
|
+
// Broadcast updated MCP servers state after each server connects
|
|
678
|
+
this.broadcast(
|
|
679
|
+
JSON.stringify({
|
|
680
|
+
mcp: this.getMcpServers(),
|
|
681
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
682
|
+
})
|
|
683
|
+
);
|
|
684
|
+
})
|
|
685
|
+
.catch((error) => {
|
|
686
|
+
console.error(
|
|
687
|
+
`Error connecting to MCP server: ${server.name} (${server.server_url})`,
|
|
688
|
+
error
|
|
689
|
+
);
|
|
690
|
+
// Still broadcast even if connection fails, so clients know about the failure
|
|
691
|
+
this.broadcast(
|
|
692
|
+
JSON.stringify({
|
|
693
|
+
mcp: this.getMcpServers(),
|
|
694
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
695
|
+
})
|
|
696
|
+
);
|
|
697
|
+
});
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
return _onStart(props);
|
|
553
701
|
});
|
|
554
|
-
await this._tryCatch(() => _onStart());
|
|
555
702
|
}
|
|
556
703
|
);
|
|
557
704
|
};
|
|
@@ -573,15 +720,25 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
573
720
|
this.broadcast(
|
|
574
721
|
JSON.stringify({
|
|
575
722
|
state: state,
|
|
576
|
-
type:
|
|
723
|
+
type: MessageType.CF_AGENT_STATE
|
|
577
724
|
}),
|
|
578
725
|
source !== "server" ? [source.id] : []
|
|
579
726
|
);
|
|
580
727
|
return this._tryCatch(() => {
|
|
581
|
-
const { connection, request } = agentContext.getStore() || {};
|
|
728
|
+
const { connection, request, email } = agentContext.getStore() || {};
|
|
582
729
|
return agentContext.run(
|
|
583
|
-
{ agent: this, connection, request },
|
|
730
|
+
{ agent: this, connection, request, email },
|
|
584
731
|
async () => {
|
|
732
|
+
this.observability?.emit(
|
|
733
|
+
{
|
|
734
|
+
displayMessage: "State updated",
|
|
735
|
+
id: nanoid(),
|
|
736
|
+
payload: {},
|
|
737
|
+
timestamp: Date.now(),
|
|
738
|
+
type: "state:update"
|
|
739
|
+
},
|
|
740
|
+
this.ctx
|
|
741
|
+
);
|
|
585
742
|
return this.onStateUpdate(state, source);
|
|
586
743
|
}
|
|
587
744
|
);
|
|
@@ -607,19 +764,83 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
607
764
|
}
|
|
608
765
|
|
|
609
766
|
/**
|
|
610
|
-
* Called when the Agent receives an email
|
|
767
|
+
* Called when the Agent receives an email via routeAgentEmail()
|
|
768
|
+
* Override this method to handle incoming emails
|
|
611
769
|
* @param email Email message to process
|
|
612
770
|
*/
|
|
613
|
-
|
|
614
|
-
|
|
771
|
+
async _onEmail(email: AgentEmail) {
|
|
772
|
+
// nb: we use this roundabout way of getting to onEmail
|
|
773
|
+
// because of https://github.com/cloudflare/workerd/issues/4499
|
|
615
774
|
return agentContext.run(
|
|
616
|
-
{ agent: this, connection: undefined, request: undefined },
|
|
775
|
+
{ agent: this, connection: undefined, request: undefined, email: email },
|
|
617
776
|
async () => {
|
|
618
|
-
|
|
777
|
+
if ("onEmail" in this && typeof this.onEmail === "function") {
|
|
778
|
+
return this._tryCatch(() =>
|
|
779
|
+
(this.onEmail as (email: AgentEmail) => Promise<void>)(email)
|
|
780
|
+
);
|
|
781
|
+
} else {
|
|
782
|
+
console.log("Received email from:", email.from, "to:", email.to);
|
|
783
|
+
console.log("Subject:", email.headers.get("subject"));
|
|
784
|
+
console.log(
|
|
785
|
+
"Implement onEmail(email: AgentEmail): Promise<void> in your agent to process emails"
|
|
786
|
+
);
|
|
787
|
+
}
|
|
619
788
|
}
|
|
620
789
|
);
|
|
621
790
|
}
|
|
622
791
|
|
|
792
|
+
/**
|
|
793
|
+
* Reply to an email
|
|
794
|
+
* @param email The email to reply to
|
|
795
|
+
* @param options Options for the reply
|
|
796
|
+
* @returns void
|
|
797
|
+
*/
|
|
798
|
+
async replyToEmail(
|
|
799
|
+
email: AgentEmail,
|
|
800
|
+
options: {
|
|
801
|
+
fromName: string;
|
|
802
|
+
subject?: string | undefined;
|
|
803
|
+
body: string;
|
|
804
|
+
contentType?: string;
|
|
805
|
+
headers?: Record<string, string>;
|
|
806
|
+
}
|
|
807
|
+
): Promise<void> {
|
|
808
|
+
return this._tryCatch(async () => {
|
|
809
|
+
const agentName = camelCaseToKebabCase(this._ParentClass.name);
|
|
810
|
+
const agentId = this.name;
|
|
811
|
+
|
|
812
|
+
const { createMimeMessage } = await import("mimetext");
|
|
813
|
+
const msg = createMimeMessage();
|
|
814
|
+
msg.setSender({ addr: email.to, name: options.fromName });
|
|
815
|
+
msg.setRecipient(email.from);
|
|
816
|
+
msg.setSubject(
|
|
817
|
+
options.subject || `Re: ${email.headers.get("subject")}` || "No subject"
|
|
818
|
+
);
|
|
819
|
+
msg.addMessage({
|
|
820
|
+
contentType: options.contentType || "text/plain",
|
|
821
|
+
data: options.body
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
const domain = email.from.split("@")[1];
|
|
825
|
+
const messageId = `<${agentId}@${domain}>`;
|
|
826
|
+
msg.setHeader("In-Reply-To", email.headers.get("Message-ID")!);
|
|
827
|
+
msg.setHeader("Message-ID", messageId);
|
|
828
|
+
msg.setHeader("X-Agent-Name", agentName);
|
|
829
|
+
msg.setHeader("X-Agent-ID", agentId);
|
|
830
|
+
|
|
831
|
+
if (options.headers) {
|
|
832
|
+
for (const [key, value] of Object.entries(options.headers)) {
|
|
833
|
+
msg.setHeader(key, value);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
await email.reply({
|
|
837
|
+
from: email.to,
|
|
838
|
+
raw: msg.asRaw(),
|
|
839
|
+
to: email.from
|
|
840
|
+
});
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
|
|
623
844
|
private async _tryCatch<T>(fn: () => T | Promise<T>) {
|
|
624
845
|
try {
|
|
625
846
|
return await fn();
|
|
@@ -628,6 +849,68 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
628
849
|
}
|
|
629
850
|
}
|
|
630
851
|
|
|
852
|
+
/**
|
|
853
|
+
* Automatically wrap custom methods with agent context
|
|
854
|
+
* This ensures getCurrentAgent() works in all custom methods without decorators
|
|
855
|
+
*/
|
|
856
|
+
private _autoWrapCustomMethods() {
|
|
857
|
+
// Collect all methods from base prototypes (Agent and Server)
|
|
858
|
+
const basePrototypes = [Agent.prototype, Server.prototype];
|
|
859
|
+
const baseMethods = new Set<string>();
|
|
860
|
+
for (const baseProto of basePrototypes) {
|
|
861
|
+
let proto = baseProto;
|
|
862
|
+
while (proto && proto !== Object.prototype) {
|
|
863
|
+
const methodNames = Object.getOwnPropertyNames(proto);
|
|
864
|
+
for (const methodName of methodNames) {
|
|
865
|
+
baseMethods.add(methodName);
|
|
866
|
+
}
|
|
867
|
+
proto = Object.getPrototypeOf(proto);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
// Get all methods from the current instance's prototype chain
|
|
871
|
+
let proto = Object.getPrototypeOf(this);
|
|
872
|
+
let depth = 0;
|
|
873
|
+
while (proto && proto !== Object.prototype && depth < 10) {
|
|
874
|
+
const methodNames = Object.getOwnPropertyNames(proto);
|
|
875
|
+
for (const methodName of methodNames) {
|
|
876
|
+
const descriptor = Object.getOwnPropertyDescriptor(proto, methodName);
|
|
877
|
+
|
|
878
|
+
// Skip if it's a private method, a base method, a getter, or not a function,
|
|
879
|
+
if (
|
|
880
|
+
baseMethods.has(methodName) ||
|
|
881
|
+
methodName.startsWith("_") ||
|
|
882
|
+
!descriptor ||
|
|
883
|
+
!!descriptor.get ||
|
|
884
|
+
typeof descriptor.value !== "function"
|
|
885
|
+
) {
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// Now, methodName is confirmed to be a custom method/function
|
|
890
|
+
// Wrap the custom method with context
|
|
891
|
+
const wrappedFunction = withAgentContext(
|
|
892
|
+
// biome-ignore lint/suspicious/noExplicitAny: I can't typescript
|
|
893
|
+
this[methodName as keyof this] as (...args: any[]) => any
|
|
894
|
+
// biome-ignore lint/suspicious/noExplicitAny: I can't typescript
|
|
895
|
+
) as any;
|
|
896
|
+
|
|
897
|
+
// if the method is callable, copy the metadata from the original method
|
|
898
|
+
if (this._isCallable(methodName)) {
|
|
899
|
+
callableMetadata.set(
|
|
900
|
+
wrappedFunction,
|
|
901
|
+
callableMetadata.get(this[methodName as keyof this] as Function)!
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// set the wrapped function on the prototype
|
|
906
|
+
this.constructor.prototype[methodName as keyof this] = wrappedFunction;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
proto = Object.getPrototypeOf(proto);
|
|
910
|
+
depth++;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
631
914
|
override onError(
|
|
632
915
|
connection: Connection,
|
|
633
916
|
error: unknown
|
|
@@ -662,6 +945,131 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
662
945
|
throw new Error("Not implemented");
|
|
663
946
|
}
|
|
664
947
|
|
|
948
|
+
/**
|
|
949
|
+
* Queue a task to be executed in the future
|
|
950
|
+
* @param payload Payload to pass to the callback
|
|
951
|
+
* @param callback Name of the method to call
|
|
952
|
+
* @returns The ID of the queued task
|
|
953
|
+
*/
|
|
954
|
+
async queue<T = unknown>(callback: keyof this, payload: T): Promise<string> {
|
|
955
|
+
const id = nanoid(9);
|
|
956
|
+
if (typeof callback !== "string") {
|
|
957
|
+
throw new Error("Callback must be a string");
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
if (typeof this[callback] !== "function") {
|
|
961
|
+
throw new Error(`this.${callback} is not a function`);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
this.sql`
|
|
965
|
+
INSERT OR REPLACE INTO cf_agents_queues (id, payload, callback)
|
|
966
|
+
VALUES (${id}, ${JSON.stringify(payload)}, ${callback})
|
|
967
|
+
`;
|
|
968
|
+
|
|
969
|
+
void this._flushQueue().catch((e) => {
|
|
970
|
+
console.error("Error flushing queue:", e);
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
return id;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
private _flushingQueue = false;
|
|
977
|
+
|
|
978
|
+
private async _flushQueue() {
|
|
979
|
+
if (this._flushingQueue) {
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
this._flushingQueue = true;
|
|
983
|
+
while (true) {
|
|
984
|
+
const result = this.sql<QueueItem<string>>`
|
|
985
|
+
SELECT * FROM cf_agents_queues
|
|
986
|
+
ORDER BY created_at ASC
|
|
987
|
+
`;
|
|
988
|
+
|
|
989
|
+
if (!result || result.length === 0) {
|
|
990
|
+
break;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
for (const row of result || []) {
|
|
994
|
+
const callback = this[row.callback as keyof Agent<Env>];
|
|
995
|
+
if (!callback) {
|
|
996
|
+
console.error(`callback ${row.callback} not found`);
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
const { connection, request, email } = agentContext.getStore() || {};
|
|
1000
|
+
await agentContext.run(
|
|
1001
|
+
{
|
|
1002
|
+
agent: this,
|
|
1003
|
+
connection,
|
|
1004
|
+
request,
|
|
1005
|
+
email
|
|
1006
|
+
},
|
|
1007
|
+
async () => {
|
|
1008
|
+
// TODO: add retries and backoff
|
|
1009
|
+
await (
|
|
1010
|
+
callback as (
|
|
1011
|
+
payload: unknown,
|
|
1012
|
+
queueItem: QueueItem<string>
|
|
1013
|
+
) => Promise<void>
|
|
1014
|
+
).bind(this)(JSON.parse(row.payload as string), row);
|
|
1015
|
+
await this.dequeue(row.id);
|
|
1016
|
+
}
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
this._flushingQueue = false;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* Dequeue a task by ID
|
|
1025
|
+
* @param id ID of the task to dequeue
|
|
1026
|
+
*/
|
|
1027
|
+
async dequeue(id: string) {
|
|
1028
|
+
this.sql`DELETE FROM cf_agents_queues WHERE id = ${id}`;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/**
|
|
1032
|
+
* Dequeue all tasks
|
|
1033
|
+
*/
|
|
1034
|
+
async dequeueAll() {
|
|
1035
|
+
this.sql`DELETE FROM cf_agents_queues`;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* Dequeue all tasks by callback
|
|
1040
|
+
* @param callback Name of the callback to dequeue
|
|
1041
|
+
*/
|
|
1042
|
+
async dequeueAllByCallback(callback: string) {
|
|
1043
|
+
this.sql`DELETE FROM cf_agents_queues WHERE callback = ${callback}`;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* Get a queued task by ID
|
|
1048
|
+
* @param id ID of the task to get
|
|
1049
|
+
* @returns The task or undefined if not found
|
|
1050
|
+
*/
|
|
1051
|
+
async getQueue(id: string): Promise<QueueItem<string> | undefined> {
|
|
1052
|
+
const result = this.sql<QueueItem<string>>`
|
|
1053
|
+
SELECT * FROM cf_agents_queues WHERE id = ${id}
|
|
1054
|
+
`;
|
|
1055
|
+
return result
|
|
1056
|
+
? { ...result[0], payload: JSON.parse(result[0].payload) }
|
|
1057
|
+
: undefined;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Get all queues by key and value
|
|
1062
|
+
* @param key Key to filter by
|
|
1063
|
+
* @param value Value to filter by
|
|
1064
|
+
* @returns Array of matching QueueItem objects
|
|
1065
|
+
*/
|
|
1066
|
+
async getQueues(key: string, value: string): Promise<QueueItem<string>[]> {
|
|
1067
|
+
const result = this.sql<QueueItem<string>>`
|
|
1068
|
+
SELECT * FROM cf_agents_queues
|
|
1069
|
+
`;
|
|
1070
|
+
return result.filter((row) => JSON.parse(row.payload)[key] === value);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
665
1073
|
/**
|
|
666
1074
|
* Schedule a task to be executed in the future
|
|
667
1075
|
* @template T Type of the payload data
|
|
@@ -677,6 +1085,21 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
677
1085
|
): Promise<Schedule<T>> {
|
|
678
1086
|
const id = nanoid(9);
|
|
679
1087
|
|
|
1088
|
+
const emitScheduleCreate = (schedule: Schedule<T>) =>
|
|
1089
|
+
this.observability?.emit(
|
|
1090
|
+
{
|
|
1091
|
+
displayMessage: `Schedule ${schedule.id} created`,
|
|
1092
|
+
id: nanoid(),
|
|
1093
|
+
payload: {
|
|
1094
|
+
callback: callback as string,
|
|
1095
|
+
id: id
|
|
1096
|
+
},
|
|
1097
|
+
timestamp: Date.now(),
|
|
1098
|
+
type: "schedule:create"
|
|
1099
|
+
},
|
|
1100
|
+
this.ctx
|
|
1101
|
+
);
|
|
1102
|
+
|
|
680
1103
|
if (typeof callback !== "string") {
|
|
681
1104
|
throw new Error("Callback must be a string");
|
|
682
1105
|
}
|
|
@@ -696,13 +1119,17 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
696
1119
|
|
|
697
1120
|
await this._scheduleNextAlarm();
|
|
698
1121
|
|
|
699
|
-
|
|
1122
|
+
const schedule: Schedule<T> = {
|
|
700
1123
|
callback: callback,
|
|
701
1124
|
id,
|
|
702
1125
|
payload: payload as T,
|
|
703
1126
|
time: timestamp,
|
|
704
|
-
type: "scheduled"
|
|
1127
|
+
type: "scheduled"
|
|
705
1128
|
};
|
|
1129
|
+
|
|
1130
|
+
emitScheduleCreate(schedule);
|
|
1131
|
+
|
|
1132
|
+
return schedule;
|
|
706
1133
|
}
|
|
707
1134
|
if (typeof when === "number") {
|
|
708
1135
|
const time = new Date(Date.now() + when * 1000);
|
|
@@ -717,14 +1144,18 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
717
1144
|
|
|
718
1145
|
await this._scheduleNextAlarm();
|
|
719
1146
|
|
|
720
|
-
|
|
1147
|
+
const schedule: Schedule<T> = {
|
|
721
1148
|
callback: callback,
|
|
722
1149
|
delayInSeconds: when,
|
|
723
1150
|
id,
|
|
724
1151
|
payload: payload as T,
|
|
725
1152
|
time: timestamp,
|
|
726
|
-
type: "delayed"
|
|
1153
|
+
type: "delayed"
|
|
727
1154
|
};
|
|
1155
|
+
|
|
1156
|
+
emitScheduleCreate(schedule);
|
|
1157
|
+
|
|
1158
|
+
return schedule;
|
|
728
1159
|
}
|
|
729
1160
|
if (typeof when === "string") {
|
|
730
1161
|
const nextExecutionTime = getNextCronTime(when);
|
|
@@ -739,14 +1170,18 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
739
1170
|
|
|
740
1171
|
await this._scheduleNextAlarm();
|
|
741
1172
|
|
|
742
|
-
|
|
1173
|
+
const schedule: Schedule<T> = {
|
|
743
1174
|
callback: callback,
|
|
744
1175
|
cron: when,
|
|
745
1176
|
id,
|
|
746
1177
|
payload: payload as T,
|
|
747
1178
|
time: timestamp,
|
|
748
|
-
type: "cron"
|
|
1179
|
+
type: "cron"
|
|
749
1180
|
};
|
|
1181
|
+
|
|
1182
|
+
emitScheduleCreate(schedule);
|
|
1183
|
+
|
|
1184
|
+
return schedule;
|
|
750
1185
|
}
|
|
751
1186
|
throw new Error("Invalid schedule type");
|
|
752
1187
|
}
|
|
@@ -810,7 +1245,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
810
1245
|
.toArray()
|
|
811
1246
|
.map((row) => ({
|
|
812
1247
|
...row,
|
|
813
|
-
payload: JSON.parse(row.payload as string) as T
|
|
1248
|
+
payload: JSON.parse(row.payload as string) as T
|
|
814
1249
|
})) as Schedule<T>[];
|
|
815
1250
|
|
|
816
1251
|
return result;
|
|
@@ -822,6 +1257,22 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
822
1257
|
* @returns true if the task was cancelled, false otherwise
|
|
823
1258
|
*/
|
|
824
1259
|
async cancelSchedule(id: string): Promise<boolean> {
|
|
1260
|
+
const schedule = await this.getSchedule(id);
|
|
1261
|
+
if (schedule) {
|
|
1262
|
+
this.observability?.emit(
|
|
1263
|
+
{
|
|
1264
|
+
displayMessage: `Schedule ${id} cancelled`,
|
|
1265
|
+
id: nanoid(),
|
|
1266
|
+
payload: {
|
|
1267
|
+
callback: schedule.callback,
|
|
1268
|
+
id: schedule.id
|
|
1269
|
+
},
|
|
1270
|
+
timestamp: Date.now(),
|
|
1271
|
+
type: "schedule:cancel"
|
|
1272
|
+
},
|
|
1273
|
+
this.ctx
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
825
1276
|
this.sql`DELETE FROM cf_agents_schedules WHERE id = ${id}`;
|
|
826
1277
|
|
|
827
1278
|
await this._scheduleNextAlarm();
|
|
@@ -831,9 +1282,9 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
831
1282
|
private async _scheduleNextAlarm() {
|
|
832
1283
|
// Find the next schedule that needs to be executed
|
|
833
1284
|
const result = this.sql`
|
|
834
|
-
SELECT time FROM cf_agents_schedules
|
|
1285
|
+
SELECT time FROM cf_agents_schedules
|
|
835
1286
|
WHERE time > ${Math.floor(Date.now() / 1000)}
|
|
836
|
-
ORDER BY time ASC
|
|
1287
|
+
ORDER BY time ASC
|
|
837
1288
|
LIMIT 1
|
|
838
1289
|
`;
|
|
839
1290
|
if (!result) return;
|
|
@@ -860,40 +1311,61 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
860
1311
|
SELECT * FROM cf_agents_schedules WHERE time <= ${now}
|
|
861
1312
|
`;
|
|
862
1313
|
|
|
863
|
-
|
|
864
|
-
const
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
await agentContext.run(
|
|
870
|
-
{ agent: this, connection: undefined, request: undefined },
|
|
871
|
-
async () => {
|
|
872
|
-
try {
|
|
873
|
-
await (
|
|
874
|
-
callback as (
|
|
875
|
-
payload: unknown,
|
|
876
|
-
schedule: Schedule<unknown>
|
|
877
|
-
) => Promise<void>
|
|
878
|
-
).bind(this)(JSON.parse(row.payload as string), row);
|
|
879
|
-
} catch (e) {
|
|
880
|
-
console.error(`error executing callback "${row.callback}"`, e);
|
|
881
|
-
}
|
|
1314
|
+
if (result && Array.isArray(result)) {
|
|
1315
|
+
for (const row of result) {
|
|
1316
|
+
const callback = this[row.callback as keyof Agent<Env>];
|
|
1317
|
+
if (!callback) {
|
|
1318
|
+
console.error(`callback ${row.callback} not found`);
|
|
1319
|
+
continue;
|
|
882
1320
|
}
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1321
|
+
await agentContext.run(
|
|
1322
|
+
{
|
|
1323
|
+
agent: this,
|
|
1324
|
+
connection: undefined,
|
|
1325
|
+
request: undefined,
|
|
1326
|
+
email: undefined
|
|
1327
|
+
},
|
|
1328
|
+
async () => {
|
|
1329
|
+
try {
|
|
1330
|
+
this.observability?.emit(
|
|
1331
|
+
{
|
|
1332
|
+
displayMessage: `Schedule ${row.id} executed`,
|
|
1333
|
+
id: nanoid(),
|
|
1334
|
+
payload: {
|
|
1335
|
+
callback: row.callback,
|
|
1336
|
+
id: row.id
|
|
1337
|
+
},
|
|
1338
|
+
timestamp: Date.now(),
|
|
1339
|
+
type: "schedule:execute"
|
|
1340
|
+
},
|
|
1341
|
+
this.ctx
|
|
1342
|
+
);
|
|
888
1343
|
|
|
889
|
-
|
|
1344
|
+
await (
|
|
1345
|
+
callback as (
|
|
1346
|
+
payload: unknown,
|
|
1347
|
+
schedule: Schedule<unknown>
|
|
1348
|
+
) => Promise<void>
|
|
1349
|
+
).bind(this)(JSON.parse(row.payload as string), row);
|
|
1350
|
+
} catch (e) {
|
|
1351
|
+
console.error(`error executing callback "${row.callback}"`, e);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
);
|
|
1355
|
+
if (row.type === "cron") {
|
|
1356
|
+
// Update next execution time for cron schedules
|
|
1357
|
+
const nextExecutionTime = getNextCronTime(row.cron);
|
|
1358
|
+
const nextTimestamp = Math.floor(nextExecutionTime.getTime() / 1000);
|
|
1359
|
+
|
|
1360
|
+
this.sql`
|
|
890
1361
|
UPDATE cf_agents_schedules SET time = ${nextTimestamp} WHERE id = ${row.id}
|
|
891
1362
|
`;
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
1363
|
+
} else {
|
|
1364
|
+
// Delete one-time schedules after execution
|
|
1365
|
+
this.sql`
|
|
895
1366
|
DELETE FROM cf_agents_schedules WHERE id = ${row.id}
|
|
896
1367
|
`;
|
|
1368
|
+
}
|
|
897
1369
|
}
|
|
898
1370
|
}
|
|
899
1371
|
|
|
@@ -909,11 +1381,23 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
909
1381
|
this.sql`DROP TABLE IF EXISTS cf_agents_state`;
|
|
910
1382
|
this.sql`DROP TABLE IF EXISTS cf_agents_schedules`;
|
|
911
1383
|
this.sql`DROP TABLE IF EXISTS cf_agents_mcp_servers`;
|
|
1384
|
+
this.sql`DROP TABLE IF EXISTS cf_agents_queues`;
|
|
912
1385
|
|
|
913
1386
|
// delete all alarms
|
|
914
1387
|
await this.ctx.storage.deleteAlarm();
|
|
915
1388
|
await this.ctx.storage.deleteAll();
|
|
916
1389
|
this.ctx.abort("destroyed"); // enforce that the agent is evicted
|
|
1390
|
+
|
|
1391
|
+
this.observability?.emit(
|
|
1392
|
+
{
|
|
1393
|
+
displayMessage: "Agent destroyed",
|
|
1394
|
+
id: nanoid(),
|
|
1395
|
+
payload: {},
|
|
1396
|
+
timestamp: Date.now(),
|
|
1397
|
+
type: "destroy"
|
|
1398
|
+
},
|
|
1399
|
+
this.ctx
|
|
1400
|
+
);
|
|
917
1401
|
}
|
|
918
1402
|
|
|
919
1403
|
/**
|
|
@@ -927,8 +1411,9 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
927
1411
|
/**
|
|
928
1412
|
* Connect to a new MCP Server
|
|
929
1413
|
*
|
|
1414
|
+
* @param serverName Name of the MCP server
|
|
930
1415
|
* @param url MCP Server SSE URL
|
|
931
|
-
* @param callbackHost Base host for the agent, used for the redirect URI.
|
|
1416
|
+
* @param callbackHost Base host for the agent, used for the redirect URI. If not provided, will be derived from the current request.
|
|
932
1417
|
* @param agentsPrefix agents routing prefix if not using `agents`
|
|
933
1418
|
* @param options MCP client and transport (header) options
|
|
934
1419
|
* @returns authUrl
|
|
@@ -936,7 +1421,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
936
1421
|
async addMcpServer(
|
|
937
1422
|
serverName: string,
|
|
938
1423
|
url: string,
|
|
939
|
-
callbackHost
|
|
1424
|
+
callbackHost?: string,
|
|
940
1425
|
agentsPrefix = "agents",
|
|
941
1426
|
options?: {
|
|
942
1427
|
client?: ConstructorParameters<typeof Client>[1];
|
|
@@ -945,7 +1430,22 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
945
1430
|
};
|
|
946
1431
|
}
|
|
947
1432
|
): Promise<{ id: string; authUrl: string | undefined }> {
|
|
948
|
-
|
|
1433
|
+
// If callbackHost is not provided, derive it from the current request
|
|
1434
|
+
let resolvedCallbackHost = callbackHost;
|
|
1435
|
+
if (!resolvedCallbackHost) {
|
|
1436
|
+
const { request } = getCurrentAgent();
|
|
1437
|
+
if (!request) {
|
|
1438
|
+
throw new Error(
|
|
1439
|
+
"callbackHost is required when not called within a request context"
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
// Extract the origin from the request
|
|
1444
|
+
const requestUrl = new URL(request.url);
|
|
1445
|
+
resolvedCallbackHost = `${requestUrl.protocol}//${requestUrl.host}`;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
const callbackUrl = `${resolvedCallbackHost}/${agentsPrefix}/${camelCaseToKebabCase(this._ParentClass.name)}/${this.name}/callback`;
|
|
949
1449
|
|
|
950
1450
|
const result = await this._connectToMcpServerInternal(
|
|
951
1451
|
serverName,
|
|
@@ -970,7 +1470,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
970
1470
|
this.broadcast(
|
|
971
1471
|
JSON.stringify({
|
|
972
1472
|
mcp: this.getMcpServers(),
|
|
973
|
-
type:
|
|
1473
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
974
1474
|
})
|
|
975
1475
|
);
|
|
976
1476
|
|
|
@@ -993,6 +1493,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
993
1493
|
*/
|
|
994
1494
|
transport?: {
|
|
995
1495
|
headers?: HeadersInit;
|
|
1496
|
+
type?: TransportType;
|
|
996
1497
|
};
|
|
997
1498
|
},
|
|
998
1499
|
reconnect?: {
|
|
@@ -1026,40 +1527,45 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1026
1527
|
fetch: (url, init) =>
|
|
1027
1528
|
fetch(url, {
|
|
1028
1529
|
...init,
|
|
1029
|
-
headers: options?.transport?.headers
|
|
1030
|
-
})
|
|
1530
|
+
headers: options?.transport?.headers
|
|
1531
|
+
})
|
|
1031
1532
|
},
|
|
1032
1533
|
requestInit: {
|
|
1033
|
-
headers: options?.transport?.headers
|
|
1034
|
-
}
|
|
1534
|
+
headers: options?.transport?.headers
|
|
1535
|
+
}
|
|
1035
1536
|
};
|
|
1036
1537
|
}
|
|
1037
1538
|
|
|
1539
|
+
// Use the transport type specified in options, or default to "auto"
|
|
1540
|
+
const transportType = options?.transport?.type || "auto";
|
|
1541
|
+
|
|
1038
1542
|
const { id, authUrl, clientId } = await this.mcp.connect(url, {
|
|
1039
1543
|
client: options?.client,
|
|
1040
1544
|
reconnect,
|
|
1041
1545
|
transport: {
|
|
1042
1546
|
...headerTransportOpts,
|
|
1043
1547
|
authProvider,
|
|
1044
|
-
|
|
1548
|
+
type: transportType
|
|
1549
|
+
}
|
|
1045
1550
|
});
|
|
1046
1551
|
|
|
1047
1552
|
return {
|
|
1048
1553
|
authUrl,
|
|
1049
1554
|
clientId,
|
|
1050
|
-
id
|
|
1555
|
+
id
|
|
1051
1556
|
};
|
|
1052
1557
|
}
|
|
1053
1558
|
|
|
1054
1559
|
async removeMcpServer(id: string) {
|
|
1055
1560
|
this.mcp.closeConnection(id);
|
|
1561
|
+
this.mcp.unregisterCallbackUrl(id);
|
|
1056
1562
|
this.sql`
|
|
1057
1563
|
DELETE FROM cf_agents_mcp_servers WHERE id = ${id};
|
|
1058
1564
|
`;
|
|
1059
1565
|
this.broadcast(
|
|
1060
1566
|
JSON.stringify({
|
|
1061
1567
|
mcp: this.getMcpServers(),
|
|
1062
|
-
type:
|
|
1568
|
+
type: MessageType.CF_AGENT_MCP_SERVERS
|
|
1063
1569
|
})
|
|
1064
1570
|
);
|
|
1065
1571
|
}
|
|
@@ -1069,30 +1575,35 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1069
1575
|
prompts: this.mcp.listPrompts(),
|
|
1070
1576
|
resources: this.mcp.listResources(),
|
|
1071
1577
|
servers: {},
|
|
1072
|
-
tools: this.mcp.listTools()
|
|
1578
|
+
tools: this.mcp.listTools()
|
|
1073
1579
|
};
|
|
1074
1580
|
|
|
1075
1581
|
const servers = this.sql<MCPServerRow>`
|
|
1076
1582
|
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
|
|
1077
1583
|
`;
|
|
1078
1584
|
|
|
1079
|
-
|
|
1080
|
-
const
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1585
|
+
if (servers && Array.isArray(servers) && servers.length > 0) {
|
|
1586
|
+
for (const server of servers) {
|
|
1587
|
+
const serverConn = this.mcp.mcpConnections[server.id];
|
|
1588
|
+
mcpState.servers[server.id] = {
|
|
1589
|
+
auth_url: server.auth_url,
|
|
1590
|
+
capabilities: serverConn?.serverCapabilities ?? null,
|
|
1591
|
+
instructions: serverConn?.instructions ?? null,
|
|
1592
|
+
name: server.name,
|
|
1593
|
+
server_url: server.server_url,
|
|
1594
|
+
// mark as "authenticating" because the server isn't automatically connected, so it's pending authenticating
|
|
1595
|
+
state: serverConn?.connectionState ?? "authenticating"
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1090
1598
|
}
|
|
1091
1599
|
|
|
1092
1600
|
return mcpState;
|
|
1093
1601
|
}
|
|
1094
1602
|
}
|
|
1095
1603
|
|
|
1604
|
+
// A set of classes that have been wrapped with agent context
|
|
1605
|
+
const wrappedClasses = new Set<typeof Agent.prototype.constructor>();
|
|
1606
|
+
|
|
1096
1607
|
/**
|
|
1097
1608
|
* Namespace for creating Agent instances
|
|
1098
1609
|
* @template Agentic Type of the Agent class
|
|
@@ -1133,14 +1644,14 @@ export async function routeAgentRequest<Env>(
|
|
|
1133
1644
|
"Access-Control-Allow-Credentials": "true",
|
|
1134
1645
|
"Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS",
|
|
1135
1646
|
"Access-Control-Allow-Origin": "*",
|
|
1136
|
-
"Access-Control-Max-Age": "86400"
|
|
1647
|
+
"Access-Control-Max-Age": "86400"
|
|
1137
1648
|
}
|
|
1138
1649
|
: options?.cors;
|
|
1139
1650
|
|
|
1140
1651
|
if (request.method === "OPTIONS") {
|
|
1141
1652
|
if (corsHeaders) {
|
|
1142
1653
|
return new Response(null, {
|
|
1143
|
-
headers: corsHeaders
|
|
1654
|
+
headers: corsHeaders
|
|
1144
1655
|
});
|
|
1145
1656
|
}
|
|
1146
1657
|
console.warn(
|
|
@@ -1153,7 +1664,7 @@ export async function routeAgentRequest<Env>(
|
|
|
1153
1664
|
env as Record<string, unknown>,
|
|
1154
1665
|
{
|
|
1155
1666
|
prefix: "agents",
|
|
1156
|
-
...(options as PartyServerOptions<Record<string, unknown>>)
|
|
1667
|
+
...(options as PartyServerOptions<Record<string, unknown>>)
|
|
1157
1668
|
}
|
|
1158
1669
|
);
|
|
1159
1670
|
|
|
@@ -1166,24 +1677,238 @@ export async function routeAgentRequest<Env>(
|
|
|
1166
1677
|
response = new Response(response.body, {
|
|
1167
1678
|
headers: {
|
|
1168
1679
|
...response.headers,
|
|
1169
|
-
...corsHeaders
|
|
1170
|
-
}
|
|
1680
|
+
...corsHeaders
|
|
1681
|
+
}
|
|
1171
1682
|
});
|
|
1172
1683
|
}
|
|
1173
1684
|
return response;
|
|
1174
1685
|
}
|
|
1175
1686
|
|
|
1687
|
+
export type EmailResolver<Env> = (
|
|
1688
|
+
email: ForwardableEmailMessage,
|
|
1689
|
+
env: Env
|
|
1690
|
+
) => Promise<{
|
|
1691
|
+
agentName: string;
|
|
1692
|
+
agentId: string;
|
|
1693
|
+
} | null>;
|
|
1694
|
+
|
|
1695
|
+
/**
|
|
1696
|
+
* Create a resolver that uses the message-id header to determine the agent to route the email to
|
|
1697
|
+
* @returns A function that resolves the agent to route the email to
|
|
1698
|
+
*/
|
|
1699
|
+
export function createHeaderBasedEmailResolver<Env>(): EmailResolver<Env> {
|
|
1700
|
+
return async (email: ForwardableEmailMessage, _env: Env) => {
|
|
1701
|
+
const messageId = email.headers.get("message-id");
|
|
1702
|
+
if (messageId) {
|
|
1703
|
+
const messageIdMatch = messageId.match(/<([^@]+)@([^>]+)>/);
|
|
1704
|
+
if (messageIdMatch) {
|
|
1705
|
+
const [, agentId, domain] = messageIdMatch;
|
|
1706
|
+
const agentName = domain.split(".")[0];
|
|
1707
|
+
return { agentName, agentId };
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
const references = email.headers.get("references");
|
|
1712
|
+
if (references) {
|
|
1713
|
+
const referencesMatch = references.match(
|
|
1714
|
+
/<([A-Za-z0-9+/]{43}=)@([^>]+)>/
|
|
1715
|
+
);
|
|
1716
|
+
if (referencesMatch) {
|
|
1717
|
+
const [, base64Id, domain] = referencesMatch;
|
|
1718
|
+
const agentId = Buffer.from(base64Id, "base64").toString("hex");
|
|
1719
|
+
const agentName = domain.split(".")[0];
|
|
1720
|
+
return { agentName, agentId };
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
const agentName = email.headers.get("x-agent-name");
|
|
1725
|
+
const agentId = email.headers.get("x-agent-id");
|
|
1726
|
+
if (agentName && agentId) {
|
|
1727
|
+
return { agentName, agentId };
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
return null;
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
/**
|
|
1735
|
+
* Create a resolver that uses the email address to determine the agent to route the email to
|
|
1736
|
+
* @param defaultAgentName The default agent name to use if the email address does not contain a sub-address
|
|
1737
|
+
* @returns A function that resolves the agent to route the email to
|
|
1738
|
+
*/
|
|
1739
|
+
export function createAddressBasedEmailResolver<Env>(
|
|
1740
|
+
defaultAgentName: string
|
|
1741
|
+
): EmailResolver<Env> {
|
|
1742
|
+
return async (email: ForwardableEmailMessage, _env: Env) => {
|
|
1743
|
+
const emailMatch = email.to.match(/^([^+@]+)(?:\+([^@]+))?@(.+)$/);
|
|
1744
|
+
if (!emailMatch) {
|
|
1745
|
+
return null;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
const [, localPart, subAddress] = emailMatch;
|
|
1749
|
+
|
|
1750
|
+
if (subAddress) {
|
|
1751
|
+
return {
|
|
1752
|
+
agentName: localPart,
|
|
1753
|
+
agentId: subAddress
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
// Option 2: Use defaultAgentName namespace, localPart as agentId
|
|
1758
|
+
// Common for catch-all email routing to a single EmailAgent namespace
|
|
1759
|
+
return {
|
|
1760
|
+
agentName: defaultAgentName,
|
|
1761
|
+
agentId: localPart
|
|
1762
|
+
};
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
/**
|
|
1767
|
+
* Create a resolver that uses the agentName and agentId to determine the agent to route the email to
|
|
1768
|
+
* @param agentName The name of the agent to route the email to
|
|
1769
|
+
* @param agentId The id of the agent to route the email to
|
|
1770
|
+
* @returns A function that resolves the agent to route the email to
|
|
1771
|
+
*/
|
|
1772
|
+
export function createCatchAllEmailResolver<Env>(
|
|
1773
|
+
agentName: string,
|
|
1774
|
+
agentId: string
|
|
1775
|
+
): EmailResolver<Env> {
|
|
1776
|
+
return async () => ({ agentName, agentId });
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
export type EmailRoutingOptions<Env> = AgentOptions<Env> & {
|
|
1780
|
+
resolver: EmailResolver<Env>;
|
|
1781
|
+
};
|
|
1782
|
+
|
|
1783
|
+
// Cache the agent namespace map for email routing
|
|
1784
|
+
// This maps both kebab-case and original names to namespaces
|
|
1785
|
+
const agentMapCache = new WeakMap<
|
|
1786
|
+
Record<string, unknown>,
|
|
1787
|
+
Record<string, unknown>
|
|
1788
|
+
>();
|
|
1789
|
+
|
|
1176
1790
|
/**
|
|
1177
1791
|
* Route an email to the appropriate Agent
|
|
1178
|
-
* @param email
|
|
1179
|
-
* @param env
|
|
1180
|
-
* @param options
|
|
1792
|
+
* @param email The email to route
|
|
1793
|
+
* @param env The environment containing the Agent bindings
|
|
1794
|
+
* @param options The options for routing the email
|
|
1795
|
+
* @returns A promise that resolves when the email has been routed
|
|
1181
1796
|
*/
|
|
1182
1797
|
export async function routeAgentEmail<Env>(
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
): Promise<void> {
|
|
1798
|
+
email: ForwardableEmailMessage,
|
|
1799
|
+
env: Env,
|
|
1800
|
+
options: EmailRoutingOptions<Env>
|
|
1801
|
+
): Promise<void> {
|
|
1802
|
+
const routingInfo = await options.resolver(email, env);
|
|
1803
|
+
|
|
1804
|
+
if (!routingInfo) {
|
|
1805
|
+
console.warn("No routing information found for email, dropping message");
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
// Build a map that includes both original names and kebab-case versions
|
|
1810
|
+
if (!agentMapCache.has(env as Record<string, unknown>)) {
|
|
1811
|
+
const map: Record<string, unknown> = {};
|
|
1812
|
+
for (const [key, value] of Object.entries(env as Record<string, unknown>)) {
|
|
1813
|
+
if (
|
|
1814
|
+
value &&
|
|
1815
|
+
typeof value === "object" &&
|
|
1816
|
+
"idFromName" in value &&
|
|
1817
|
+
typeof value.idFromName === "function"
|
|
1818
|
+
) {
|
|
1819
|
+
// Add both the original name and kebab-case version
|
|
1820
|
+
map[key] = value;
|
|
1821
|
+
map[camelCaseToKebabCase(key)] = value;
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
agentMapCache.set(env as Record<string, unknown>, map);
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
const agentMap = agentMapCache.get(env as Record<string, unknown>)!;
|
|
1828
|
+
const namespace = agentMap[routingInfo.agentName];
|
|
1829
|
+
|
|
1830
|
+
if (!namespace) {
|
|
1831
|
+
// Provide helpful error message listing available agents
|
|
1832
|
+
const availableAgents = Object.keys(agentMap)
|
|
1833
|
+
.filter((key) => !key.includes("-")) // Show only original names, not kebab-case duplicates
|
|
1834
|
+
.join(", ");
|
|
1835
|
+
throw new Error(
|
|
1836
|
+
`Agent namespace '${routingInfo.agentName}' not found in environment. Available agents: ${availableAgents}`
|
|
1837
|
+
);
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
const agent = await getAgentByName(
|
|
1841
|
+
namespace as unknown as AgentNamespace<Agent<Env>>,
|
|
1842
|
+
routingInfo.agentId
|
|
1843
|
+
);
|
|
1844
|
+
|
|
1845
|
+
// let's make a serialisable version of the email
|
|
1846
|
+
const serialisableEmail: AgentEmail = {
|
|
1847
|
+
getRaw: async () => {
|
|
1848
|
+
const reader = email.raw.getReader();
|
|
1849
|
+
const chunks: Uint8Array[] = [];
|
|
1850
|
+
|
|
1851
|
+
let done = false;
|
|
1852
|
+
while (!done) {
|
|
1853
|
+
const { value, done: readerDone } = await reader.read();
|
|
1854
|
+
done = readerDone;
|
|
1855
|
+
if (value) {
|
|
1856
|
+
chunks.push(value);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
1861
|
+
const combined = new Uint8Array(totalLength);
|
|
1862
|
+
let offset = 0;
|
|
1863
|
+
for (const chunk of chunks) {
|
|
1864
|
+
combined.set(chunk, offset);
|
|
1865
|
+
offset += chunk.length;
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
return combined;
|
|
1869
|
+
},
|
|
1870
|
+
headers: email.headers,
|
|
1871
|
+
rawSize: email.rawSize,
|
|
1872
|
+
setReject: (reason: string) => {
|
|
1873
|
+
email.setReject(reason);
|
|
1874
|
+
},
|
|
1875
|
+
forward: (rcptTo: string, headers?: Headers) => {
|
|
1876
|
+
return email.forward(rcptTo, headers);
|
|
1877
|
+
},
|
|
1878
|
+
reply: (options: { from: string; to: string; raw: string }) => {
|
|
1879
|
+
return email.reply(
|
|
1880
|
+
new EmailMessage(options.from, options.to, options.raw)
|
|
1881
|
+
);
|
|
1882
|
+
},
|
|
1883
|
+
from: email.from,
|
|
1884
|
+
to: email.to
|
|
1885
|
+
};
|
|
1886
|
+
|
|
1887
|
+
await agent._onEmail(serialisableEmail);
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
export type AgentEmail = {
|
|
1891
|
+
from: string;
|
|
1892
|
+
to: string;
|
|
1893
|
+
getRaw: () => Promise<Uint8Array>;
|
|
1894
|
+
headers: Headers;
|
|
1895
|
+
rawSize: number;
|
|
1896
|
+
setReject: (reason: string) => void;
|
|
1897
|
+
forward: (rcptTo: string, headers?: Headers) => Promise<void>;
|
|
1898
|
+
reply: (options: { from: string; to: string; raw: string }) => Promise<void>;
|
|
1899
|
+
};
|
|
1900
|
+
|
|
1901
|
+
export type EmailSendOptions = {
|
|
1902
|
+
to: string;
|
|
1903
|
+
subject: string;
|
|
1904
|
+
body: string;
|
|
1905
|
+
contentType?: string;
|
|
1906
|
+
headers?: Record<string, string>;
|
|
1907
|
+
includeRoutingHeaders?: boolean;
|
|
1908
|
+
agentName?: string;
|
|
1909
|
+
agentId?: string;
|
|
1910
|
+
domain?: string;
|
|
1911
|
+
};
|
|
1187
1912
|
|
|
1188
1913
|
/**
|
|
1189
1914
|
* Get or create an Agent by name
|
|
@@ -1194,12 +1919,17 @@ export async function routeAgentEmail<Env>(
|
|
|
1194
1919
|
* @param options Options for Agent creation
|
|
1195
1920
|
* @returns Promise resolving to an Agent instance stub
|
|
1196
1921
|
*/
|
|
1197
|
-
export async function getAgentByName<
|
|
1922
|
+
export async function getAgentByName<
|
|
1923
|
+
Env,
|
|
1924
|
+
T extends Agent<Env>,
|
|
1925
|
+
Props extends Record<string, unknown> = Record<string, unknown>
|
|
1926
|
+
>(
|
|
1198
1927
|
namespace: AgentNamespace<T>,
|
|
1199
1928
|
name: string,
|
|
1200
1929
|
options?: {
|
|
1201
1930
|
jurisdiction?: DurableObjectJurisdiction;
|
|
1202
1931
|
locationHint?: DurableObjectLocationHint;
|
|
1932
|
+
props?: Props;
|
|
1203
1933
|
}
|
|
1204
1934
|
) {
|
|
1205
1935
|
return getServerByName<Env, T>(namespace, name, options);
|
|
@@ -1231,7 +1961,7 @@ export class StreamingResponse {
|
|
|
1231
1961
|
id: this._id,
|
|
1232
1962
|
result: chunk,
|
|
1233
1963
|
success: true,
|
|
1234
|
-
type:
|
|
1964
|
+
type: MessageType.RPC
|
|
1235
1965
|
};
|
|
1236
1966
|
this._connection.send(JSON.stringify(response));
|
|
1237
1967
|
}
|
|
@@ -1250,7 +1980,7 @@ export class StreamingResponse {
|
|
|
1250
1980
|
id: this._id,
|
|
1251
1981
|
result: finalChunk,
|
|
1252
1982
|
success: true,
|
|
1253
|
-
type:
|
|
1983
|
+
type: MessageType.RPC
|
|
1254
1984
|
};
|
|
1255
1985
|
this._connection.send(JSON.stringify(response));
|
|
1256
1986
|
}
|