agents 0.0.0-569680f → 0.0.0-579b228
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 +22 -22
- package/dist/ai-chat-agent.d.ts +1 -1
- package/dist/ai-chat-agent.js +4 -4
- package/dist/ai-chat-agent.js.map +1 -1
- package/dist/ai-react.d.ts +2 -2
- package/dist/ai-react.js.map +1 -1
- package/dist/{chunk-RX76B6DL.js → chunk-CKHRUXM5.js} +380 -135
- package/dist/chunk-CKHRUXM5.js.map +1 -0
- package/dist/{chunk-NKZZ66QY.js → chunk-KUH345EY.js} +1 -1
- package/dist/chunk-KUH345EY.js.map +1 -0
- package/dist/{chunk-TN7QOY4S.js → chunk-MW5BQ2FW.js} +1 -1
- package/dist/chunk-MW5BQ2FW.js.map +1 -0
- package/dist/{chunk-767EASBA.js → chunk-PVQZBKN7.js} +1 -1
- package/dist/chunk-PVQZBKN7.js.map +1 -0
- package/dist/client.d.ts +2 -2
- package/dist/client.js +1 -1
- package/dist/{index-aBwVVXj7.d.ts → index-BIJvkfYt.d.ts} +112 -27
- package/dist/index.d.ts +9 -8
- package/dist/index.js +10 -12
- package/dist/mcp/client.js +1 -1
- package/dist/mcp/do-oauth-client-provider.js +1 -1
- package/dist/mcp/index.d.ts +3 -3
- package/dist/mcp/index.js +4 -4
- package/dist/mcp/index.js.map +1 -1
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +4 -4
- package/dist/react.d.ts +4 -4
- package/dist/react.js.map +1 -1
- package/dist/schedule.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +555 -199
- package/dist/chunk-767EASBA.js.map +0 -1
- package/dist/chunk-NKZZ66QY.js.map +0 -1
- package/dist/chunk-RX76B6DL.js.map +0 -1
- package/dist/chunk-TN7QOY4S.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
Prompt,
|
|
7
7
|
Resource,
|
|
8
8
|
ServerCapabilities,
|
|
9
|
-
Tool
|
|
9
|
+
Tool
|
|
10
10
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
11
11
|
import { parseCronExpression } from "cron-schedule";
|
|
12
12
|
import { nanoid } from "nanoid";
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
Server,
|
|
19
19
|
type WSMessage,
|
|
20
20
|
getServerByName,
|
|
21
|
-
routePartykitRequest
|
|
21
|
+
routePartykitRequest
|
|
22
22
|
} from "partyserver";
|
|
23
23
|
import { camelCaseToKebabCase } from "./client";
|
|
24
24
|
import { MCPClientManager } from "./mcp/client";
|
|
@@ -130,6 +130,13 @@ export function unstable_callable(metadata: CallableMetadata = {}) {
|
|
|
130
130
|
};
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
export type QueueItem<T = string> = {
|
|
134
|
+
id: string;
|
|
135
|
+
payload: T;
|
|
136
|
+
callback: keyof Agent<unknown>;
|
|
137
|
+
created_at: number;
|
|
138
|
+
};
|
|
139
|
+
|
|
133
140
|
/**
|
|
134
141
|
* Represents a scheduled task within an Agent
|
|
135
142
|
* @template T Type of the payload data
|
|
@@ -219,23 +226,26 @@ const STATE_WAS_CHANGED = "cf_state_was_changed";
|
|
|
219
226
|
const DEFAULT_STATE = {} as unknown;
|
|
220
227
|
|
|
221
228
|
const agentContext = new AsyncLocalStorage<{
|
|
222
|
-
agent: Agent<unknown>;
|
|
229
|
+
agent: Agent<unknown, unknown>;
|
|
223
230
|
connection: Connection | undefined;
|
|
224
231
|
request: Request | undefined;
|
|
232
|
+
email: AgentEmail | undefined;
|
|
225
233
|
}>();
|
|
226
234
|
|
|
227
235
|
export function getCurrentAgent<
|
|
228
|
-
T extends Agent<unknown, unknown> = Agent<unknown, unknown
|
|
236
|
+
T extends Agent<unknown, unknown> = Agent<unknown, unknown>
|
|
229
237
|
>(): {
|
|
230
238
|
agent: T | undefined;
|
|
231
239
|
connection: Connection | undefined;
|
|
232
|
-
request: Request
|
|
240
|
+
request: Request | undefined;
|
|
241
|
+
email: AgentEmail | undefined;
|
|
233
242
|
} {
|
|
234
243
|
const store = agentContext.getStore() as
|
|
235
244
|
| {
|
|
236
245
|
agent: T;
|
|
237
246
|
connection: Connection | undefined;
|
|
238
|
-
request: Request
|
|
247
|
+
request: Request | undefined;
|
|
248
|
+
email: AgentEmail | undefined;
|
|
239
249
|
}
|
|
240
250
|
| undefined;
|
|
241
251
|
if (!store) {
|
|
@@ -243,11 +253,31 @@ export function getCurrentAgent<
|
|
|
243
253
|
agent: undefined,
|
|
244
254
|
connection: undefined,
|
|
245
255
|
request: undefined,
|
|
256
|
+
email: undefined
|
|
246
257
|
};
|
|
247
258
|
}
|
|
248
259
|
return store;
|
|
249
260
|
}
|
|
250
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Wraps a method to run within the agent context, ensuring getCurrentAgent() works properly
|
|
264
|
+
* @param agent The agent instance
|
|
265
|
+
* @param method The method to wrap
|
|
266
|
+
* @returns A wrapped method that runs within the agent context
|
|
267
|
+
*/
|
|
268
|
+
|
|
269
|
+
// biome-ignore lint/suspicious/noExplicitAny: I can't typescript
|
|
270
|
+
function withAgentContext<T extends (...args: any[]) => any>(
|
|
271
|
+
method: T
|
|
272
|
+
): (this: Agent<unknown, unknown>, ...args: Parameters<T>) => ReturnType<T> {
|
|
273
|
+
return function (...args: Parameters<T>): ReturnType<T> {
|
|
274
|
+
const { connection, request, email } = getCurrentAgent();
|
|
275
|
+
return agentContext.run({ agent: this, connection, request, email }, () => {
|
|
276
|
+
return method.apply(this, args);
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
251
281
|
/**
|
|
252
282
|
* Base class for creating Agent implementations
|
|
253
283
|
* @template Env Environment type containing bindings
|
|
@@ -315,7 +345,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
315
345
|
*/
|
|
316
346
|
static options = {
|
|
317
347
|
/** Whether the Agent should hibernate when inactive */
|
|
318
|
-
hibernate: true
|
|
348
|
+
hibernate: true // default to hibernate
|
|
319
349
|
};
|
|
320
350
|
|
|
321
351
|
/**
|
|
@@ -352,6 +382,9 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
352
382
|
constructor(ctx: AgentContext, env: Env) {
|
|
353
383
|
super(ctx, env);
|
|
354
384
|
|
|
385
|
+
// Auto-wrap custom methods with agent context
|
|
386
|
+
this._autoWrapCustomMethods();
|
|
387
|
+
|
|
355
388
|
this.sql`
|
|
356
389
|
CREATE TABLE IF NOT EXISTS cf_agents_state (
|
|
357
390
|
id TEXT PRIMARY KEY NOT NULL,
|
|
@@ -359,6 +392,15 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
359
392
|
)
|
|
360
393
|
`;
|
|
361
394
|
|
|
395
|
+
this.sql`
|
|
396
|
+
CREATE TABLE IF NOT EXISTS cf_agents_queues (
|
|
397
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
398
|
+
payload TEXT,
|
|
399
|
+
callback TEXT,
|
|
400
|
+
created_at INTEGER DEFAULT (unixepoch())
|
|
401
|
+
)
|
|
402
|
+
`;
|
|
403
|
+
|
|
362
404
|
void this.ctx.blockConcurrencyWhile(async () => {
|
|
363
405
|
return this._tryCatch(async () => {
|
|
364
406
|
// Create alarms table if it doesn't exist
|
|
@@ -395,7 +437,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
395
437
|
const _onRequest = this.onRequest.bind(this);
|
|
396
438
|
this.onRequest = (request: Request) => {
|
|
397
439
|
return agentContext.run(
|
|
398
|
-
{ agent: this, connection: undefined, request },
|
|
440
|
+
{ agent: this, connection: undefined, request, email: undefined },
|
|
399
441
|
async () => {
|
|
400
442
|
if (this.mcp.isCallbackRequest(request)) {
|
|
401
443
|
await this.mcp.handleCallbackRequest(request);
|
|
@@ -404,14 +446,14 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
404
446
|
this.broadcast(
|
|
405
447
|
JSON.stringify({
|
|
406
448
|
mcp: this.getMcpServers(),
|
|
407
|
-
type: "cf_agent_mcp_servers"
|
|
449
|
+
type: "cf_agent_mcp_servers"
|
|
408
450
|
})
|
|
409
451
|
);
|
|
410
452
|
|
|
411
453
|
// We probably should let the user configure this response/redirect, but this is fine for now.
|
|
412
454
|
return new Response("<script>window.close();</script>", {
|
|
413
455
|
headers: { "content-type": "text/html" },
|
|
414
|
-
status: 200
|
|
456
|
+
status: 200
|
|
415
457
|
});
|
|
416
458
|
}
|
|
417
459
|
|
|
@@ -423,7 +465,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
423
465
|
const _onMessage = this.onMessage.bind(this);
|
|
424
466
|
this.onMessage = async (connection: Connection, message: WSMessage) => {
|
|
425
467
|
return agentContext.run(
|
|
426
|
-
{ agent: this, connection, request: undefined },
|
|
468
|
+
{ agent: this, connection, request: undefined, email: undefined },
|
|
427
469
|
async () => {
|
|
428
470
|
if (typeof message !== "string") {
|
|
429
471
|
return this._tryCatch(() => _onMessage(connection, message));
|
|
@@ -476,10 +518,10 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
476
518
|
args,
|
|
477
519
|
method,
|
|
478
520
|
streaming: metadata?.streaming,
|
|
479
|
-
success: true
|
|
521
|
+
success: true
|
|
480
522
|
},
|
|
481
523
|
timestamp: Date.now(),
|
|
482
|
-
type: "rpc"
|
|
524
|
+
type: "rpc"
|
|
483
525
|
},
|
|
484
526
|
this.ctx
|
|
485
527
|
);
|
|
@@ -489,7 +531,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
489
531
|
id,
|
|
490
532
|
result,
|
|
491
533
|
success: true,
|
|
492
|
-
type: "rpc"
|
|
534
|
+
type: "rpc"
|
|
493
535
|
};
|
|
494
536
|
connection.send(JSON.stringify(response));
|
|
495
537
|
} catch (e) {
|
|
@@ -499,7 +541,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
499
541
|
e instanceof Error ? e.message : "Unknown error occurred",
|
|
500
542
|
id: parsed.id,
|
|
501
543
|
success: false,
|
|
502
|
-
type: "rpc"
|
|
544
|
+
type: "rpc"
|
|
503
545
|
};
|
|
504
546
|
connection.send(JSON.stringify(response));
|
|
505
547
|
console.error("RPC error:", e);
|
|
@@ -517,14 +559,14 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
517
559
|
// TODO: This is a hack to ensure the state is sent after the connection is established
|
|
518
560
|
// must fix this
|
|
519
561
|
return agentContext.run(
|
|
520
|
-
{ agent: this, connection, request: ctx.request },
|
|
562
|
+
{ agent: this, connection, request: ctx.request, email: undefined },
|
|
521
563
|
async () => {
|
|
522
564
|
setTimeout(() => {
|
|
523
565
|
if (this.state) {
|
|
524
566
|
connection.send(
|
|
525
567
|
JSON.stringify({
|
|
526
568
|
state: this.state,
|
|
527
|
-
type: "cf_agent_state"
|
|
569
|
+
type: "cf_agent_state"
|
|
528
570
|
})
|
|
529
571
|
);
|
|
530
572
|
}
|
|
@@ -532,7 +574,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
532
574
|
connection.send(
|
|
533
575
|
JSON.stringify({
|
|
534
576
|
mcp: this.getMcpServers(),
|
|
535
|
-
type: "cf_agent_mcp_servers"
|
|
577
|
+
type: "cf_agent_mcp_servers"
|
|
536
578
|
})
|
|
537
579
|
);
|
|
538
580
|
|
|
@@ -541,10 +583,10 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
541
583
|
displayMessage: "Connection established",
|
|
542
584
|
id: nanoid(),
|
|
543
585
|
payload: {
|
|
544
|
-
connectionId: connection.id
|
|
586
|
+
connectionId: connection.id
|
|
545
587
|
},
|
|
546
588
|
timestamp: Date.now(),
|
|
547
|
-
type: "connect"
|
|
589
|
+
type: "connect"
|
|
548
590
|
},
|
|
549
591
|
this.ctx
|
|
550
592
|
);
|
|
@@ -557,36 +599,43 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
557
599
|
const _onStart = this.onStart.bind(this);
|
|
558
600
|
this.onStart = async () => {
|
|
559
601
|
return agentContext.run(
|
|
560
|
-
{
|
|
602
|
+
{
|
|
603
|
+
agent: this,
|
|
604
|
+
connection: undefined,
|
|
605
|
+
request: undefined,
|
|
606
|
+
email: undefined
|
|
607
|
+
},
|
|
561
608
|
async () => {
|
|
562
609
|
const servers = this.sql<MCPServerRow>`
|
|
563
610
|
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
|
|
564
611
|
`;
|
|
565
612
|
|
|
566
613
|
// from DO storage, reconnect to all servers not currently in the oauth flow using our saved auth information
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
).then((_results) => {
|
|
583
|
-
this.broadcast(
|
|
584
|
-
JSON.stringify({
|
|
585
|
-
mcp: this.getMcpServers(),
|
|
586
|
-
type: "cf_agent_mcp_servers",
|
|
614
|
+
if (servers && Array.isArray(servers) && servers.length > 0) {
|
|
615
|
+
Promise.allSettled(
|
|
616
|
+
servers.map((server) => {
|
|
617
|
+
return this._connectToMcpServerInternal(
|
|
618
|
+
server.name,
|
|
619
|
+
server.server_url,
|
|
620
|
+
server.callback_url,
|
|
621
|
+
server.server_options
|
|
622
|
+
? JSON.parse(server.server_options)
|
|
623
|
+
: undefined,
|
|
624
|
+
{
|
|
625
|
+
id: server.id,
|
|
626
|
+
oauthClientId: server.client_id ?? undefined
|
|
627
|
+
}
|
|
628
|
+
);
|
|
587
629
|
})
|
|
588
|
-
)
|
|
589
|
-
|
|
630
|
+
).then((_results) => {
|
|
631
|
+
this.broadcast(
|
|
632
|
+
JSON.stringify({
|
|
633
|
+
mcp: this.getMcpServers(),
|
|
634
|
+
type: "cf_agent_mcp_servers"
|
|
635
|
+
})
|
|
636
|
+
);
|
|
637
|
+
});
|
|
638
|
+
}
|
|
590
639
|
await this._tryCatch(() => _onStart());
|
|
591
640
|
}
|
|
592
641
|
);
|
|
@@ -610,14 +659,14 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
610
659
|
this.broadcast(
|
|
611
660
|
JSON.stringify({
|
|
612
661
|
state: state,
|
|
613
|
-
type: "cf_agent_state"
|
|
662
|
+
type: "cf_agent_state"
|
|
614
663
|
}),
|
|
615
664
|
source !== "server" ? [source.id] : []
|
|
616
665
|
);
|
|
617
666
|
return this._tryCatch(() => {
|
|
618
|
-
const { connection, request } = agentContext.getStore() || {};
|
|
667
|
+
const { connection, request, email } = agentContext.getStore() || {};
|
|
619
668
|
return agentContext.run(
|
|
620
|
-
{ agent: this, connection, request },
|
|
669
|
+
{ agent: this, connection, request, email },
|
|
621
670
|
async () => {
|
|
622
671
|
this.observability?.emit(
|
|
623
672
|
{
|
|
@@ -625,10 +674,10 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
625
674
|
id: nanoid(),
|
|
626
675
|
payload: {
|
|
627
676
|
previousState,
|
|
628
|
-
state
|
|
677
|
+
state
|
|
629
678
|
},
|
|
630
679
|
timestamp: Date.now(),
|
|
631
|
-
type: "state:update"
|
|
680
|
+
type: "state:update"
|
|
632
681
|
},
|
|
633
682
|
this.ctx
|
|
634
683
|
);
|
|
@@ -657,35 +706,80 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
657
706
|
}
|
|
658
707
|
|
|
659
708
|
/**
|
|
660
|
-
* Called when the Agent receives an email
|
|
709
|
+
* Called when the Agent receives an email via routeAgentEmail()
|
|
661
710
|
* Override this method to handle incoming emails
|
|
662
711
|
* @param email Email message to process
|
|
663
712
|
*/
|
|
664
|
-
async
|
|
713
|
+
async _onEmail(email: AgentEmail) {
|
|
714
|
+
// nb: we use this roundabout way of getting to onEmail
|
|
715
|
+
// because of https://github.com/cloudflare/workerd/issues/4499
|
|
665
716
|
return agentContext.run(
|
|
666
|
-
{ agent: this, connection: undefined, request: undefined },
|
|
717
|
+
{ agent: this, connection: undefined, request: undefined, email: email },
|
|
667
718
|
async () => {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
719
|
+
if ("onEmail" in this && typeof this.onEmail === "function") {
|
|
720
|
+
return this._tryCatch(() =>
|
|
721
|
+
(this.onEmail as (email: AgentEmail) => Promise<void>)(email)
|
|
722
|
+
);
|
|
723
|
+
} else {
|
|
724
|
+
console.log("Received email from:", email.from, "to:", email.to);
|
|
725
|
+
console.log("Subject:", email.headers.get("subject"));
|
|
726
|
+
console.log(
|
|
727
|
+
"Implement onEmail(email: AgentEmail): Promise<void> in your agent to process emails"
|
|
728
|
+
);
|
|
729
|
+
}
|
|
671
730
|
}
|
|
672
731
|
);
|
|
673
732
|
}
|
|
674
733
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
734
|
+
/**
|
|
735
|
+
* Reply to an email
|
|
736
|
+
* @param email The email to reply to
|
|
737
|
+
* @param options Options for the reply
|
|
738
|
+
* @returns void
|
|
739
|
+
*/
|
|
740
|
+
async replyToEmail(
|
|
741
|
+
email: AgentEmail,
|
|
742
|
+
options: {
|
|
743
|
+
fromName: string;
|
|
744
|
+
subject?: string | undefined;
|
|
745
|
+
body: string;
|
|
746
|
+
contentType?: string;
|
|
747
|
+
headers?: Record<string, string>;
|
|
748
|
+
}
|
|
680
749
|
): Promise<void> {
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
750
|
+
return this._tryCatch(async () => {
|
|
751
|
+
const agentName = camelCaseToKebabCase(this._ParentClass.name);
|
|
752
|
+
const agentId = this.name;
|
|
753
|
+
|
|
754
|
+
const { createMimeMessage } = await import("mimetext");
|
|
755
|
+
const msg = createMimeMessage();
|
|
756
|
+
msg.setSender({ addr: email.to, name: options.fromName });
|
|
757
|
+
msg.setRecipient(email.from);
|
|
758
|
+
msg.setSubject(
|
|
759
|
+
options.subject || `Re: ${email.headers.get("subject")}` || "No subject"
|
|
760
|
+
);
|
|
761
|
+
msg.addMessage({
|
|
762
|
+
contentType: options.contentType || "text/plain",
|
|
763
|
+
data: options.body
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
const domain = email.from.split("@")[1];
|
|
767
|
+
const messageId = `<${agentId}@${domain}>`;
|
|
768
|
+
msg.setHeader("In-Reply-To", email.headers.get("Message-ID")!);
|
|
769
|
+
msg.setHeader("Message-ID", messageId);
|
|
770
|
+
msg.setHeader("X-Agent-Name", agentName);
|
|
771
|
+
msg.setHeader("X-Agent-ID", agentId);
|
|
772
|
+
|
|
773
|
+
if (options.headers) {
|
|
774
|
+
for (const [key, value] of Object.entries(options.headers)) {
|
|
775
|
+
msg.setHeader(key, value);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
await email.reply({
|
|
779
|
+
from: email.to,
|
|
780
|
+
raw: msg.asRaw(),
|
|
781
|
+
to: email.from
|
|
782
|
+
});
|
|
689
783
|
});
|
|
690
784
|
}
|
|
691
785
|
|
|
@@ -697,6 +791,72 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
697
791
|
}
|
|
698
792
|
}
|
|
699
793
|
|
|
794
|
+
/**
|
|
795
|
+
* Automatically wrap custom methods with agent context
|
|
796
|
+
* This ensures getCurrentAgent() works in all custom methods without decorators
|
|
797
|
+
*/
|
|
798
|
+
private _autoWrapCustomMethods() {
|
|
799
|
+
// Collect all methods from base prototypes (Agent and Server)
|
|
800
|
+
const basePrototypes = [Agent.prototype, Server.prototype];
|
|
801
|
+
const baseMethods = new Set<string>();
|
|
802
|
+
for (const baseProto of basePrototypes) {
|
|
803
|
+
let proto = baseProto;
|
|
804
|
+
while (proto && proto !== Object.prototype) {
|
|
805
|
+
const methodNames = Object.getOwnPropertyNames(proto);
|
|
806
|
+
for (const methodName of methodNames) {
|
|
807
|
+
baseMethods.add(methodName);
|
|
808
|
+
}
|
|
809
|
+
proto = Object.getPrototypeOf(proto);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
// Get all methods from the current instance's prototype chain
|
|
813
|
+
let proto = Object.getPrototypeOf(this);
|
|
814
|
+
let depth = 0;
|
|
815
|
+
while (proto && proto !== Object.prototype && depth < 10) {
|
|
816
|
+
const methodNames = Object.getOwnPropertyNames(proto);
|
|
817
|
+
for (const methodName of methodNames) {
|
|
818
|
+
// Skip if it's a private method or not a function
|
|
819
|
+
if (
|
|
820
|
+
baseMethods.has(methodName) ||
|
|
821
|
+
methodName.startsWith("_") ||
|
|
822
|
+
typeof this[methodName as keyof this] !== "function"
|
|
823
|
+
) {
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
// If the method doesn't exist in base prototypes, it's a custom method
|
|
827
|
+
if (!baseMethods.has(methodName)) {
|
|
828
|
+
const descriptor = Object.getOwnPropertyDescriptor(proto, methodName);
|
|
829
|
+
if (descriptor && typeof descriptor.value === "function") {
|
|
830
|
+
// Wrap the custom method with context
|
|
831
|
+
|
|
832
|
+
const wrappedFunction = withAgentContext(
|
|
833
|
+
// biome-ignore lint/suspicious/noExplicitAny: I can't typescript
|
|
834
|
+
this[methodName as keyof this] as (...args: any[]) => any
|
|
835
|
+
// biome-ignore lint/suspicious/noExplicitAny: I can't typescript
|
|
836
|
+
) as any;
|
|
837
|
+
|
|
838
|
+
// if the method is callable, copy the metadata from the original method
|
|
839
|
+
if (this._isCallable(methodName)) {
|
|
840
|
+
callableMetadata.set(
|
|
841
|
+
wrappedFunction,
|
|
842
|
+
callableMetadata.get(
|
|
843
|
+
this[methodName as keyof this] as Function
|
|
844
|
+
)!
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// set the wrapped function on the prototype
|
|
849
|
+
this.constructor.prototype[methodName as keyof this] =
|
|
850
|
+
wrappedFunction;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
proto = Object.getPrototypeOf(proto);
|
|
856
|
+
depth++;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
700
860
|
override onError(
|
|
701
861
|
connection: Connection,
|
|
702
862
|
error: unknown
|
|
@@ -731,6 +891,131 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
731
891
|
throw new Error("Not implemented");
|
|
732
892
|
}
|
|
733
893
|
|
|
894
|
+
/**
|
|
895
|
+
* Queue a task to be executed in the future
|
|
896
|
+
* @param payload Payload to pass to the callback
|
|
897
|
+
* @param callback Name of the method to call
|
|
898
|
+
* @returns The ID of the queued task
|
|
899
|
+
*/
|
|
900
|
+
async queue<T = unknown>(callback: keyof this, payload: T): Promise<string> {
|
|
901
|
+
const id = nanoid(9);
|
|
902
|
+
if (typeof callback !== "string") {
|
|
903
|
+
throw new Error("Callback must be a string");
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
if (typeof this[callback] !== "function") {
|
|
907
|
+
throw new Error(`this.${callback} is not a function`);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
this.sql`
|
|
911
|
+
INSERT OR REPLACE INTO cf_agents_queues (id, payload, callback)
|
|
912
|
+
VALUES (${id}, ${JSON.stringify(payload)}, ${callback})
|
|
913
|
+
`;
|
|
914
|
+
|
|
915
|
+
void this._flushQueue().catch((e) => {
|
|
916
|
+
console.error("Error flushing queue:", e);
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
return id;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
private _flushingQueue = false;
|
|
923
|
+
|
|
924
|
+
private async _flushQueue() {
|
|
925
|
+
if (this._flushingQueue) {
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
this._flushingQueue = true;
|
|
929
|
+
while (true) {
|
|
930
|
+
const result = this.sql<QueueItem<string>>`
|
|
931
|
+
SELECT * FROM cf_agents_queues
|
|
932
|
+
ORDER BY created_at ASC
|
|
933
|
+
`;
|
|
934
|
+
|
|
935
|
+
if (!result || result.length === 0) {
|
|
936
|
+
break;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
for (const row of result || []) {
|
|
940
|
+
const callback = this[row.callback as keyof Agent<Env>];
|
|
941
|
+
if (!callback) {
|
|
942
|
+
console.error(`callback ${row.callback} not found`);
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
const { connection, request, email } = agentContext.getStore() || {};
|
|
946
|
+
await agentContext.run(
|
|
947
|
+
{
|
|
948
|
+
agent: this,
|
|
949
|
+
connection,
|
|
950
|
+
request,
|
|
951
|
+
email
|
|
952
|
+
},
|
|
953
|
+
async () => {
|
|
954
|
+
// TODO: add retries and backoff
|
|
955
|
+
await (
|
|
956
|
+
callback as (
|
|
957
|
+
payload: unknown,
|
|
958
|
+
queueItem: QueueItem<string>
|
|
959
|
+
) => Promise<void>
|
|
960
|
+
).bind(this)(JSON.parse(row.payload as string), row);
|
|
961
|
+
await this.dequeue(row.id);
|
|
962
|
+
}
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
this._flushingQueue = false;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Dequeue a task by ID
|
|
971
|
+
* @param id ID of the task to dequeue
|
|
972
|
+
*/
|
|
973
|
+
async dequeue(id: string) {
|
|
974
|
+
this.sql`DELETE FROM cf_agents_queues WHERE id = ${id}`;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* Dequeue all tasks
|
|
979
|
+
*/
|
|
980
|
+
async dequeueAll() {
|
|
981
|
+
this.sql`DELETE FROM cf_agents_queues`;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* Dequeue all tasks by callback
|
|
986
|
+
* @param callback Name of the callback to dequeue
|
|
987
|
+
*/
|
|
988
|
+
async dequeueAllByCallback(callback: string) {
|
|
989
|
+
this.sql`DELETE FROM cf_agents_queues WHERE callback = ${callback}`;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* Get a queued task by ID
|
|
994
|
+
* @param id ID of the task to get
|
|
995
|
+
* @returns The task or undefined if not found
|
|
996
|
+
*/
|
|
997
|
+
async getQueue(id: string): Promise<QueueItem<string> | undefined> {
|
|
998
|
+
const result = this.sql<QueueItem<string>>`
|
|
999
|
+
SELECT * FROM cf_agents_queues WHERE id = ${id}
|
|
1000
|
+
`;
|
|
1001
|
+
return result
|
|
1002
|
+
? { ...result[0], payload: JSON.parse(result[0].payload) }
|
|
1003
|
+
: undefined;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* Get all queues by key and value
|
|
1008
|
+
* @param key Key to filter by
|
|
1009
|
+
* @param value Value to filter by
|
|
1010
|
+
* @returns Array of matching QueueItem objects
|
|
1011
|
+
*/
|
|
1012
|
+
async getQueues(key: string, value: string): Promise<QueueItem<string>[]> {
|
|
1013
|
+
const result = this.sql<QueueItem<string>>`
|
|
1014
|
+
SELECT * FROM cf_agents_queues
|
|
1015
|
+
`;
|
|
1016
|
+
return result.filter((row) => JSON.parse(row.payload)[key] === value);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
734
1019
|
/**
|
|
735
1020
|
* Schedule a task to be executed in the future
|
|
736
1021
|
* @template T Type of the payload data
|
|
@@ -753,7 +1038,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
753
1038
|
id: nanoid(),
|
|
754
1039
|
payload: schedule,
|
|
755
1040
|
timestamp: Date.now(),
|
|
756
|
-
type: "schedule:create"
|
|
1041
|
+
type: "schedule:create"
|
|
757
1042
|
},
|
|
758
1043
|
this.ctx
|
|
759
1044
|
);
|
|
@@ -782,7 +1067,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
782
1067
|
id,
|
|
783
1068
|
payload: payload as T,
|
|
784
1069
|
time: timestamp,
|
|
785
|
-
type: "scheduled"
|
|
1070
|
+
type: "scheduled"
|
|
786
1071
|
};
|
|
787
1072
|
|
|
788
1073
|
emitScheduleCreate(schedule);
|
|
@@ -808,7 +1093,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
808
1093
|
id,
|
|
809
1094
|
payload: payload as T,
|
|
810
1095
|
time: timestamp,
|
|
811
|
-
type: "delayed"
|
|
1096
|
+
type: "delayed"
|
|
812
1097
|
};
|
|
813
1098
|
|
|
814
1099
|
emitScheduleCreate(schedule);
|
|
@@ -834,7 +1119,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
834
1119
|
id,
|
|
835
1120
|
payload: payload as T,
|
|
836
1121
|
time: timestamp,
|
|
837
|
-
type: "cron"
|
|
1122
|
+
type: "cron"
|
|
838
1123
|
};
|
|
839
1124
|
|
|
840
1125
|
emitScheduleCreate(schedule);
|
|
@@ -903,7 +1188,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
903
1188
|
.toArray()
|
|
904
1189
|
.map((row) => ({
|
|
905
1190
|
...row,
|
|
906
|
-
payload: JSON.parse(row.payload as string) as T
|
|
1191
|
+
payload: JSON.parse(row.payload as string) as T
|
|
907
1192
|
})) as Schedule<T>[];
|
|
908
1193
|
|
|
909
1194
|
return result;
|
|
@@ -923,7 +1208,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
923
1208
|
id: nanoid(),
|
|
924
1209
|
payload: schedule,
|
|
925
1210
|
timestamp: Date.now(),
|
|
926
|
-
type: "schedule:cancel"
|
|
1211
|
+
type: "schedule:cancel"
|
|
927
1212
|
},
|
|
928
1213
|
this.ctx
|
|
929
1214
|
);
|
|
@@ -937,9 +1222,9 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
937
1222
|
private async _scheduleNextAlarm() {
|
|
938
1223
|
// Find the next schedule that needs to be executed
|
|
939
1224
|
const result = this.sql`
|
|
940
|
-
SELECT time FROM cf_agents_schedules
|
|
1225
|
+
SELECT time FROM cf_agents_schedules
|
|
941
1226
|
WHERE time > ${Math.floor(Date.now() / 1000)}
|
|
942
|
-
ORDER BY time ASC
|
|
1227
|
+
ORDER BY time ASC
|
|
943
1228
|
LIMIT 1
|
|
944
1229
|
`;
|
|
945
1230
|
if (!result) return;
|
|
@@ -966,51 +1251,58 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
966
1251
|
SELECT * FROM cf_agents_schedules WHERE time <= ${now}
|
|
967
1252
|
`;
|
|
968
1253
|
|
|
969
|
-
|
|
970
|
-
const
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1254
|
+
if (result && Array.isArray(result)) {
|
|
1255
|
+
for (const row of result) {
|
|
1256
|
+
const callback = this[row.callback as keyof Agent<Env>];
|
|
1257
|
+
if (!callback) {
|
|
1258
|
+
console.error(`callback ${row.callback} not found`);
|
|
1259
|
+
continue;
|
|
1260
|
+
}
|
|
1261
|
+
await agentContext.run(
|
|
1262
|
+
{
|
|
1263
|
+
agent: this,
|
|
1264
|
+
connection: undefined,
|
|
1265
|
+
request: undefined,
|
|
1266
|
+
email: undefined
|
|
1267
|
+
},
|
|
1268
|
+
async () => {
|
|
1269
|
+
try {
|
|
1270
|
+
this.observability?.emit(
|
|
1271
|
+
{
|
|
1272
|
+
displayMessage: `Schedule ${row.id} executed`,
|
|
1273
|
+
id: nanoid(),
|
|
1274
|
+
payload: row,
|
|
1275
|
+
timestamp: Date.now(),
|
|
1276
|
+
type: "schedule:execute"
|
|
1277
|
+
},
|
|
1278
|
+
this.ctx
|
|
1279
|
+
);
|
|
989
1280
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1281
|
+
await (
|
|
1282
|
+
callback as (
|
|
1283
|
+
payload: unknown,
|
|
1284
|
+
schedule: Schedule<unknown>
|
|
1285
|
+
) => Promise<void>
|
|
1286
|
+
).bind(this)(JSON.parse(row.payload as string), row);
|
|
1287
|
+
} catch (e) {
|
|
1288
|
+
console.error(`error executing callback "${row.callback}"`, e);
|
|
1289
|
+
}
|
|
998
1290
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
const nextTimestamp = Math.floor(nextExecutionTime.getTime() / 1000);
|
|
1291
|
+
);
|
|
1292
|
+
if (row.type === "cron") {
|
|
1293
|
+
// Update next execution time for cron schedules
|
|
1294
|
+
const nextExecutionTime = getNextCronTime(row.cron);
|
|
1295
|
+
const nextTimestamp = Math.floor(nextExecutionTime.getTime() / 1000);
|
|
1005
1296
|
|
|
1006
|
-
|
|
1297
|
+
this.sql`
|
|
1007
1298
|
UPDATE cf_agents_schedules SET time = ${nextTimestamp} WHERE id = ${row.id}
|
|
1008
1299
|
`;
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1300
|
+
} else {
|
|
1301
|
+
// Delete one-time schedules after execution
|
|
1302
|
+
this.sql`
|
|
1012
1303
|
DELETE FROM cf_agents_schedules WHERE id = ${row.id}
|
|
1013
1304
|
`;
|
|
1305
|
+
}
|
|
1014
1306
|
}
|
|
1015
1307
|
}
|
|
1016
1308
|
|
|
@@ -1026,6 +1318,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1026
1318
|
this.sql`DROP TABLE IF EXISTS cf_agents_state`;
|
|
1027
1319
|
this.sql`DROP TABLE IF EXISTS cf_agents_schedules`;
|
|
1028
1320
|
this.sql`DROP TABLE IF EXISTS cf_agents_mcp_servers`;
|
|
1321
|
+
this.sql`DROP TABLE IF EXISTS cf_agents_queues`;
|
|
1029
1322
|
|
|
1030
1323
|
// delete all alarms
|
|
1031
1324
|
await this.ctx.storage.deleteAlarm();
|
|
@@ -1038,7 +1331,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1038
1331
|
id: nanoid(),
|
|
1039
1332
|
payload: {},
|
|
1040
1333
|
timestamp: Date.now(),
|
|
1041
|
-
type: "destroy"
|
|
1334
|
+
type: "destroy"
|
|
1042
1335
|
},
|
|
1043
1336
|
this.ctx
|
|
1044
1337
|
);
|
|
@@ -1098,7 +1391,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1098
1391
|
this.broadcast(
|
|
1099
1392
|
JSON.stringify({
|
|
1100
1393
|
mcp: this.getMcpServers(),
|
|
1101
|
-
type: "cf_agent_mcp_servers"
|
|
1394
|
+
type: "cf_agent_mcp_servers"
|
|
1102
1395
|
})
|
|
1103
1396
|
);
|
|
1104
1397
|
|
|
@@ -1154,12 +1447,12 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1154
1447
|
fetch: (url, init) =>
|
|
1155
1448
|
fetch(url, {
|
|
1156
1449
|
...init,
|
|
1157
|
-
headers: options?.transport?.headers
|
|
1158
|
-
})
|
|
1450
|
+
headers: options?.transport?.headers
|
|
1451
|
+
})
|
|
1159
1452
|
},
|
|
1160
1453
|
requestInit: {
|
|
1161
|
-
headers: options?.transport?.headers
|
|
1162
|
-
}
|
|
1454
|
+
headers: options?.transport?.headers
|
|
1455
|
+
}
|
|
1163
1456
|
};
|
|
1164
1457
|
}
|
|
1165
1458
|
|
|
@@ -1168,14 +1461,14 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1168
1461
|
reconnect,
|
|
1169
1462
|
transport: {
|
|
1170
1463
|
...headerTransportOpts,
|
|
1171
|
-
authProvider
|
|
1172
|
-
}
|
|
1464
|
+
authProvider
|
|
1465
|
+
}
|
|
1173
1466
|
});
|
|
1174
1467
|
|
|
1175
1468
|
return {
|
|
1176
1469
|
authUrl,
|
|
1177
1470
|
clientId,
|
|
1178
|
-
id
|
|
1471
|
+
id
|
|
1179
1472
|
};
|
|
1180
1473
|
}
|
|
1181
1474
|
|
|
@@ -1187,7 +1480,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1187
1480
|
this.broadcast(
|
|
1188
1481
|
JSON.stringify({
|
|
1189
1482
|
mcp: this.getMcpServers(),
|
|
1190
|
-
type: "cf_agent_mcp_servers"
|
|
1483
|
+
type: "cf_agent_mcp_servers"
|
|
1191
1484
|
})
|
|
1192
1485
|
);
|
|
1193
1486
|
}
|
|
@@ -1197,24 +1490,26 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1197
1490
|
prompts: this.mcp.listPrompts(),
|
|
1198
1491
|
resources: this.mcp.listResources(),
|
|
1199
1492
|
servers: {},
|
|
1200
|
-
tools: this.mcp.listTools()
|
|
1493
|
+
tools: this.mcp.listTools()
|
|
1201
1494
|
};
|
|
1202
1495
|
|
|
1203
1496
|
const servers = this.sql<MCPServerRow>`
|
|
1204
1497
|
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
|
|
1205
1498
|
`;
|
|
1206
1499
|
|
|
1207
|
-
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1500
|
+
if (servers && Array.isArray(servers) && servers.length > 0) {
|
|
1501
|
+
for (const server of servers) {
|
|
1502
|
+
const serverConn = this.mcp.mcpConnections[server.id];
|
|
1503
|
+
mcpState.servers[server.id] = {
|
|
1504
|
+
auth_url: server.auth_url,
|
|
1505
|
+
capabilities: serverConn?.serverCapabilities ?? null,
|
|
1506
|
+
instructions: serverConn?.instructions ?? null,
|
|
1507
|
+
name: server.name,
|
|
1508
|
+
server_url: server.server_url,
|
|
1509
|
+
// mark as "authenticating" because the server isn't automatically connected, so it's pending authenticating
|
|
1510
|
+
state: serverConn?.connectionState ?? "authenticating"
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1218
1513
|
}
|
|
1219
1514
|
|
|
1220
1515
|
return mcpState;
|
|
@@ -1261,14 +1556,14 @@ export async function routeAgentRequest<Env>(
|
|
|
1261
1556
|
"Access-Control-Allow-Credentials": "true",
|
|
1262
1557
|
"Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS",
|
|
1263
1558
|
"Access-Control-Allow-Origin": "*",
|
|
1264
|
-
"Access-Control-Max-Age": "86400"
|
|
1559
|
+
"Access-Control-Max-Age": "86400"
|
|
1265
1560
|
}
|
|
1266
1561
|
: options?.cors;
|
|
1267
1562
|
|
|
1268
1563
|
if (request.method === "OPTIONS") {
|
|
1269
1564
|
if (corsHeaders) {
|
|
1270
1565
|
return new Response(null, {
|
|
1271
|
-
headers: corsHeaders
|
|
1566
|
+
headers: corsHeaders
|
|
1272
1567
|
});
|
|
1273
1568
|
}
|
|
1274
1569
|
console.warn(
|
|
@@ -1281,7 +1576,7 @@ export async function routeAgentRequest<Env>(
|
|
|
1281
1576
|
env as Record<string, unknown>,
|
|
1282
1577
|
{
|
|
1283
1578
|
prefix: "agents",
|
|
1284
|
-
...(options as PartyServerOptions<Record<string, unknown>>)
|
|
1579
|
+
...(options as PartyServerOptions<Record<string, unknown>>)
|
|
1285
1580
|
}
|
|
1286
1581
|
);
|
|
1287
1582
|
|
|
@@ -1294,8 +1589,8 @@ export async function routeAgentRequest<Env>(
|
|
|
1294
1589
|
response = new Response(response.body, {
|
|
1295
1590
|
headers: {
|
|
1296
1591
|
...response.headers,
|
|
1297
|
-
...corsHeaders
|
|
1298
|
-
}
|
|
1592
|
+
...corsHeaders
|
|
1593
|
+
}
|
|
1299
1594
|
});
|
|
1300
1595
|
}
|
|
1301
1596
|
return response;
|
|
@@ -1309,7 +1604,11 @@ export type EmailResolver<Env> = (
|
|
|
1309
1604
|
agentId: string;
|
|
1310
1605
|
} | null>;
|
|
1311
1606
|
|
|
1312
|
-
|
|
1607
|
+
/**
|
|
1608
|
+
* Create a resolver that uses the message-id header to determine the agent to route the email to
|
|
1609
|
+
* @returns A function that resolves the agent to route the email to
|
|
1610
|
+
*/
|
|
1611
|
+
export function createHeaderBasedEmailResolver<Env>(): EmailResolver<Env> {
|
|
1313
1612
|
return async (email: ForwardableEmailMessage, _env: Env) => {
|
|
1314
1613
|
const messageId = email.headers.get("message-id");
|
|
1315
1614
|
if (messageId) {
|
|
@@ -1344,7 +1643,12 @@ export function createHeaderBasedResolver<Env>(): EmailResolver<Env> {
|
|
|
1344
1643
|
};
|
|
1345
1644
|
}
|
|
1346
1645
|
|
|
1347
|
-
|
|
1646
|
+
/**
|
|
1647
|
+
* Create a resolver that uses the email address to determine the agent to route the email to
|
|
1648
|
+
* @param defaultAgentName The default agent name to use if the email address does not contain a sub-address
|
|
1649
|
+
* @returns A function that resolves the agent to route the email to
|
|
1650
|
+
*/
|
|
1651
|
+
export function createAddressBasedEmailResolver<Env>(
|
|
1348
1652
|
defaultAgentName: string
|
|
1349
1653
|
): EmailResolver<Env> {
|
|
1350
1654
|
return async (email: ForwardableEmailMessage, _env: Env) => {
|
|
@@ -1358,7 +1662,7 @@ export function createAddressBasedResolver<Env>(
|
|
|
1358
1662
|
if (subAddress) {
|
|
1359
1663
|
return {
|
|
1360
1664
|
agentName: localPart,
|
|
1361
|
-
agentId: subAddress
|
|
1665
|
+
agentId: subAddress
|
|
1362
1666
|
};
|
|
1363
1667
|
}
|
|
1364
1668
|
|
|
@@ -1366,12 +1670,18 @@ export function createAddressBasedResolver<Env>(
|
|
|
1366
1670
|
// Common for catch-all email routing to a single EmailAgent namespace
|
|
1367
1671
|
return {
|
|
1368
1672
|
agentName: defaultAgentName,
|
|
1369
|
-
agentId: localPart
|
|
1673
|
+
agentId: localPart
|
|
1370
1674
|
};
|
|
1371
1675
|
};
|
|
1372
1676
|
}
|
|
1373
1677
|
|
|
1374
|
-
|
|
1678
|
+
/**
|
|
1679
|
+
* Create a resolver that uses the agentName and agentId to determine the agent to route the email to
|
|
1680
|
+
* @param agentName The name of the agent to route the email to
|
|
1681
|
+
* @param agentId The id of the agent to route the email to
|
|
1682
|
+
* @returns A function that resolves the agent to route the email to
|
|
1683
|
+
*/
|
|
1684
|
+
export function createCatchAllEmailResolver<Env>(
|
|
1375
1685
|
agentName: string,
|
|
1376
1686
|
agentId: string
|
|
1377
1687
|
): EmailResolver<Env> {
|
|
@@ -1382,6 +1692,20 @@ export type EmailRoutingOptions<Env> = AgentOptions<Env> & {
|
|
|
1382
1692
|
resolver: EmailResolver<Env>;
|
|
1383
1693
|
};
|
|
1384
1694
|
|
|
1695
|
+
// Cache the agent namespace map for email routing
|
|
1696
|
+
// This maps both kebab-case and original names to namespaces
|
|
1697
|
+
const agentMapCache = new WeakMap<
|
|
1698
|
+
Record<string, unknown>,
|
|
1699
|
+
Record<string, unknown>
|
|
1700
|
+
>();
|
|
1701
|
+
|
|
1702
|
+
/**
|
|
1703
|
+
* Route an email to the appropriate Agent
|
|
1704
|
+
* @param email The email to route
|
|
1705
|
+
* @param env The environment containing the Agent bindings
|
|
1706
|
+
* @param options The options for routing the email
|
|
1707
|
+
* @returns A promise that resolves when the email has been routed
|
|
1708
|
+
*/
|
|
1385
1709
|
export async function routeAgentEmail<Env>(
|
|
1386
1710
|
email: ForwardableEmailMessage,
|
|
1387
1711
|
env: Env,
|
|
@@ -1394,33 +1718,98 @@ export async function routeAgentEmail<Env>(
|
|
|
1394
1718
|
return;
|
|
1395
1719
|
}
|
|
1396
1720
|
|
|
1397
|
-
|
|
1398
|
-
if (!
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1721
|
+
// Build a map that includes both original names and kebab-case versions
|
|
1722
|
+
if (!agentMapCache.has(env as Record<string, unknown>)) {
|
|
1723
|
+
const map: Record<string, unknown> = {};
|
|
1724
|
+
for (const [key, value] of Object.entries(env as Record<string, unknown>)) {
|
|
1725
|
+
if (
|
|
1726
|
+
value &&
|
|
1727
|
+
typeof value === "object" &&
|
|
1728
|
+
"idFromName" in value &&
|
|
1729
|
+
typeof value.idFromName === "function"
|
|
1730
|
+
) {
|
|
1731
|
+
// Add both the original name and kebab-case version
|
|
1732
|
+
map[key] = value;
|
|
1733
|
+
map[camelCaseToKebabCase(key)] = value;
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
agentMapCache.set(env as Record<string, unknown>, map);
|
|
1403
1737
|
}
|
|
1404
1738
|
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1739
|
+
const agentMap = agentMapCache.get(env as Record<string, unknown>)!;
|
|
1740
|
+
const namespace = agentMap[routingInfo.agentName];
|
|
1741
|
+
|
|
1742
|
+
if (!namespace) {
|
|
1743
|
+
// Provide helpful error message listing available agents
|
|
1744
|
+
const availableAgents = Object.keys(agentMap)
|
|
1745
|
+
.filter((key) => !key.includes("-")) // Show only original names, not kebab-case duplicates
|
|
1746
|
+
.join(", ");
|
|
1747
|
+
throw new Error(
|
|
1748
|
+
`Agent namespace '${routingInfo.agentName}' not found in environment. Available agents: ${availableAgents}`
|
|
1413
1749
|
);
|
|
1414
|
-
return;
|
|
1415
1750
|
}
|
|
1416
1751
|
|
|
1417
|
-
|
|
1418
|
-
|
|
1752
|
+
const agent = await getAgentByName(
|
|
1753
|
+
namespace as unknown as AgentNamespace<Agent<Env>>,
|
|
1754
|
+
routingInfo.agentId
|
|
1755
|
+
);
|
|
1756
|
+
|
|
1757
|
+
// let's make a serialisable version of the email
|
|
1758
|
+
const serialisableEmail: AgentEmail = {
|
|
1759
|
+
getRaw: async () => {
|
|
1760
|
+
const reader = email.raw.getReader();
|
|
1761
|
+
const chunks: Uint8Array[] = [];
|
|
1762
|
+
|
|
1763
|
+
let done = false;
|
|
1764
|
+
while (!done) {
|
|
1765
|
+
const { value, done: readerDone } = await reader.read();
|
|
1766
|
+
done = readerDone;
|
|
1767
|
+
if (value) {
|
|
1768
|
+
chunks.push(value);
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
1773
|
+
const combined = new Uint8Array(totalLength);
|
|
1774
|
+
let offset = 0;
|
|
1775
|
+
for (const chunk of chunks) {
|
|
1776
|
+
combined.set(chunk, offset);
|
|
1777
|
+
offset += chunk.length;
|
|
1778
|
+
}
|
|
1419
1779
|
|
|
1420
|
-
|
|
1421
|
-
|
|
1780
|
+
return combined;
|
|
1781
|
+
},
|
|
1782
|
+
headers: email.headers,
|
|
1783
|
+
rawSize: email.rawSize,
|
|
1784
|
+
setReject: (reason: string) => {
|
|
1785
|
+
email.setReject(reason);
|
|
1786
|
+
},
|
|
1787
|
+
forward: (rcptTo: string, headers?: Headers) => {
|
|
1788
|
+
return email.forward(rcptTo, headers);
|
|
1789
|
+
},
|
|
1790
|
+
reply: (options: { from: string; to: string; raw: string }) => {
|
|
1791
|
+
return email.reply(
|
|
1792
|
+
new EmailMessage(options.from, options.to, options.raw)
|
|
1793
|
+
);
|
|
1794
|
+
},
|
|
1795
|
+
from: email.from,
|
|
1796
|
+
to: email.to
|
|
1797
|
+
};
|
|
1798
|
+
|
|
1799
|
+
await agent._onEmail(serialisableEmail);
|
|
1422
1800
|
}
|
|
1423
1801
|
|
|
1802
|
+
export type AgentEmail = {
|
|
1803
|
+
from: string;
|
|
1804
|
+
to: string;
|
|
1805
|
+
getRaw: () => Promise<Uint8Array>;
|
|
1806
|
+
headers: Headers;
|
|
1807
|
+
rawSize: number;
|
|
1808
|
+
setReject: (reason: string) => void;
|
|
1809
|
+
forward: (rcptTo: string, headers?: Headers) => Promise<void>;
|
|
1810
|
+
reply: (options: { from: string; to: string; raw: string }) => Promise<void>;
|
|
1811
|
+
};
|
|
1812
|
+
|
|
1424
1813
|
export type EmailSendOptions = {
|
|
1425
1814
|
to: string;
|
|
1426
1815
|
subject: string;
|
|
@@ -1433,39 +1822,6 @@ export type EmailSendOptions = {
|
|
|
1433
1822
|
domain?: string;
|
|
1434
1823
|
};
|
|
1435
1824
|
|
|
1436
|
-
export async function sendEmailWithRouting(
|
|
1437
|
-
emailBinding: SendEmail,
|
|
1438
|
-
from: string,
|
|
1439
|
-
fromName: string,
|
|
1440
|
-
options: EmailSendOptions
|
|
1441
|
-
): Promise<void> {
|
|
1442
|
-
const { createMimeMessage } = await import("mimetext");
|
|
1443
|
-
const msg = createMimeMessage();
|
|
1444
|
-
msg.setSender({ addr: from, name: fromName });
|
|
1445
|
-
msg.setRecipient(options.to);
|
|
1446
|
-
msg.setSubject(options.subject);
|
|
1447
|
-
msg.addMessage({
|
|
1448
|
-
contentType: options.contentType || "text/plain",
|
|
1449
|
-
data: options.body,
|
|
1450
|
-
});
|
|
1451
|
-
|
|
1452
|
-
if (options.includeRoutingHeaders && options.agentName && options.agentId) {
|
|
1453
|
-
const domain = options.domain || from.split("@")[1];
|
|
1454
|
-
const messageId = `<${options.agentId}@${domain}>`;
|
|
1455
|
-
msg.setHeader("Message-ID", messageId);
|
|
1456
|
-
msg.setHeader("X-Agent-Name", options.agentName);
|
|
1457
|
-
msg.setHeader("X-Agent-ID", options.agentId);
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
if (options.headers) {
|
|
1461
|
-
for (const [key, value] of Object.entries(options.headers)) {
|
|
1462
|
-
msg.setHeader(key, value);
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
await emailBinding.send(new EmailMessage(from, options.to, msg.asRaw()));
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
1825
|
/**
|
|
1470
1826
|
* Get or create an Agent by name
|
|
1471
1827
|
* @template Env Environment type containing bindings
|
|
@@ -1512,7 +1868,7 @@ export class StreamingResponse {
|
|
|
1512
1868
|
id: this._id,
|
|
1513
1869
|
result: chunk,
|
|
1514
1870
|
success: true,
|
|
1515
|
-
type: "rpc"
|
|
1871
|
+
type: "rpc"
|
|
1516
1872
|
};
|
|
1517
1873
|
this._connection.send(JSON.stringify(response));
|
|
1518
1874
|
}
|
|
@@ -1531,7 +1887,7 @@ export class StreamingResponse {
|
|
|
1531
1887
|
id: this._id,
|
|
1532
1888
|
result: finalChunk,
|
|
1533
1889
|
success: true,
|
|
1534
|
-
type: "rpc"
|
|
1890
|
+
type: "rpc"
|
|
1535
1891
|
};
|
|
1536
1892
|
this._connection.send(JSON.stringify(response));
|
|
1537
1893
|
}
|