agents 0.0.0-54e1986 → 0.0.0-569680f
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/dist/ai-chat-agent.d.ts +5 -4
- package/dist/ai-chat-agent.js +64 -26
- package/dist/ai-chat-agent.js.map +1 -1
- package/dist/ai-react.d.ts +9 -8
- package/dist/ai-react.js +27 -27
- package/dist/ai-react.js.map +1 -1
- package/dist/{chunk-BZXOAZUX.js → chunk-767EASBA.js} +5 -5
- package/dist/{chunk-BZXOAZUX.js.map → chunk-767EASBA.js.map} +1 -1
- package/dist/{chunk-QSGN3REV.js → chunk-NKZZ66QY.js} +8 -15
- package/dist/chunk-NKZZ66QY.js.map +1 -0
- package/dist/{chunk-RIYR6FR6.js → chunk-RX76B6DL.js} +328 -80
- package/dist/chunk-RX76B6DL.js.map +1 -0
- package/dist/{chunk-Y67CHZBI.js → chunk-TN7QOY4S.js} +23 -18
- package/dist/chunk-TN7QOY4S.js.map +1 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.js +1 -1
- package/dist/index-aBwVVXj7.d.ts +529 -0
- package/dist/index.d.ts +32 -394
- package/dist/index.js +12 -4
- package/dist/mcp/client.d.ts +281 -9
- package/dist/mcp/client.js +1 -1
- package/dist/mcp/do-oauth-client-provider.js +1 -1
- package/dist/mcp/index.d.ts +6 -6
- package/dist/mcp/index.js +55 -51
- package/dist/mcp/index.js.map +1 -1
- package/dist/observability/index.d.ts +12 -0
- package/dist/observability/index.js +10 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/react.d.ts +76 -10
- package/dist/react.js +16 -6
- package/dist/react.js.map +1 -1
- package/dist/schedule.d.ts +6 -6
- package/dist/schedule.js +4 -4
- package/dist/schedule.js.map +1 -1
- package/dist/serializable.d.ts +32 -0
- package/dist/serializable.js +1 -0
- package/dist/serializable.js.map +1 -0
- package/package.json +76 -71
- package/src/index.ts +395 -100
- package/dist/chunk-QSGN3REV.js.map +0 -1
- package/dist/chunk-RIYR6FR6.js.map +0 -1
- package/dist/chunk-Y67CHZBI.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -1,34 +1,32 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
|
+
import type { SSEClientTransportOptions } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
Prompt,
|
|
7
|
+
Resource,
|
|
8
|
+
ServerCapabilities,
|
|
9
|
+
Tool,
|
|
10
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
11
|
+
import { parseCronExpression } from "cron-schedule";
|
|
12
|
+
import { nanoid } from "nanoid";
|
|
13
|
+
import { EmailMessage } from "cloudflare:email";
|
|
1
14
|
import {
|
|
2
|
-
Server,
|
|
3
|
-
routePartykitRequest,
|
|
4
|
-
type PartyServerOptions,
|
|
5
|
-
getServerByName,
|
|
6
15
|
type Connection,
|
|
7
16
|
type ConnectionContext,
|
|
17
|
+
type PartyServerOptions,
|
|
18
|
+
Server,
|
|
8
19
|
type WSMessage,
|
|
20
|
+
getServerByName,
|
|
21
|
+
routePartykitRequest,
|
|
9
22
|
} from "partyserver";
|
|
10
|
-
|
|
11
|
-
import { parseCronExpression } from "cron-schedule";
|
|
12
|
-
import { nanoid } from "nanoid";
|
|
13
|
-
|
|
14
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
15
|
-
import { MCPClientManager } from "./mcp/client";
|
|
16
|
-
import {
|
|
17
|
-
DurableObjectOAuthClientProvider,
|
|
18
|
-
type AgentsOAuthProvider,
|
|
19
|
-
} from "./mcp/do-oauth-client-provider";
|
|
20
|
-
import type {
|
|
21
|
-
Tool,
|
|
22
|
-
Resource,
|
|
23
|
-
Prompt,
|
|
24
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
25
|
-
|
|
26
|
-
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
27
|
-
import type { SSEClientTransportOptions } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
28
|
-
|
|
29
23
|
import { camelCaseToKebabCase } from "./client";
|
|
24
|
+
import { MCPClientManager } from "./mcp/client";
|
|
25
|
+
// import type { MCPClientConnection } from "./mcp/client-connection";
|
|
26
|
+
import { DurableObjectOAuthClientProvider } from "./mcp/do-oauth-client-provider";
|
|
27
|
+
import { genericObservability, type Observability } from "./observability";
|
|
30
28
|
|
|
31
|
-
export type { Connection,
|
|
29
|
+
export type { Connection, ConnectionContext, WSMessage } from "partyserver";
|
|
32
30
|
|
|
33
31
|
/**
|
|
34
32
|
* RPC request message from client
|
|
@@ -112,7 +110,6 @@ export type CallableMetadata = {
|
|
|
112
110
|
streaming?: boolean;
|
|
113
111
|
};
|
|
114
112
|
|
|
115
|
-
// biome-ignore lint/complexity/noBannedTypes: <explanation>
|
|
116
113
|
const callableMetadata = new Map<Function, CallableMetadata>();
|
|
117
114
|
|
|
118
115
|
/**
|
|
@@ -122,6 +119,7 @@ const callableMetadata = new Map<Function, CallableMetadata>();
|
|
|
122
119
|
export function unstable_callable(metadata: CallableMetadata = {}) {
|
|
123
120
|
return function callableDecorator<This, Args extends unknown[], Return>(
|
|
124
121
|
target: (this: This, ...args: Args) => Return,
|
|
122
|
+
// biome-ignore lint/correctness/noUnusedFunctionParameters: later
|
|
125
123
|
context: ClassMethodDecoratorContext
|
|
126
124
|
) {
|
|
127
125
|
if (!callableMetadata.has(target)) {
|
|
@@ -194,7 +192,12 @@ export type MCPServer = {
|
|
|
194
192
|
name: string;
|
|
195
193
|
server_url: string;
|
|
196
194
|
auth_url: string | null;
|
|
195
|
+
// This state is specifically about the temporary process of getting a token (if needed).
|
|
196
|
+
// Scope outside of that can't be relied upon because when the DO sleeps, there's no way
|
|
197
|
+
// to communicate a change to a non-ready state.
|
|
197
198
|
state: "authenticating" | "connecting" | "ready" | "discovering" | "failed";
|
|
199
|
+
instructions: string | null;
|
|
200
|
+
capabilities: ServerCapabilities | null;
|
|
198
201
|
};
|
|
199
202
|
|
|
200
203
|
/**
|
|
@@ -315,6 +318,11 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
315
318
|
hibernate: true, // default to hibernate
|
|
316
319
|
};
|
|
317
320
|
|
|
321
|
+
/**
|
|
322
|
+
* The observability implementation to use for the Agent
|
|
323
|
+
*/
|
|
324
|
+
observability?: Observability = genericObservability;
|
|
325
|
+
|
|
318
326
|
/**
|
|
319
327
|
* Execute SQL queries against the Agent's database
|
|
320
328
|
* @template T Type of the returned rows
|
|
@@ -395,15 +403,15 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
395
403
|
// after the MCP connection handshake, we can send updated mcp state
|
|
396
404
|
this.broadcast(
|
|
397
405
|
JSON.stringify({
|
|
406
|
+
mcp: this.getMcpServers(),
|
|
398
407
|
type: "cf_agent_mcp_servers",
|
|
399
|
-
mcp: this._getMcpServerStateInternal(),
|
|
400
408
|
})
|
|
401
409
|
);
|
|
402
410
|
|
|
403
411
|
// We probably should let the user configure this response/redirect, but this is fine for now.
|
|
404
412
|
return new Response("<script>window.close();</script>", {
|
|
405
|
-
status: 200,
|
|
406
413
|
headers: { "content-type": "text/html" },
|
|
414
|
+
status: 200,
|
|
407
415
|
});
|
|
408
416
|
}
|
|
409
417
|
|
|
@@ -424,7 +432,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
424
432
|
let parsed: unknown;
|
|
425
433
|
try {
|
|
426
434
|
parsed = JSON.parse(message);
|
|
427
|
-
} catch (
|
|
435
|
+
} catch (_e) {
|
|
428
436
|
// silently fail and let the onMessage handler handle it
|
|
429
437
|
return this._tryCatch(() => _onMessage(connection, message));
|
|
430
438
|
}
|
|
@@ -448,7 +456,6 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
448
456
|
throw new Error(`Method ${method} is not callable`);
|
|
449
457
|
}
|
|
450
458
|
|
|
451
|
-
// biome-ignore lint/complexity/noBannedTypes: <explanation>
|
|
452
459
|
const metadata = callableMetadata.get(methodFn as Function);
|
|
453
460
|
|
|
454
461
|
// For streaming methods, pass a StreamingResponse object
|
|
@@ -460,22 +467,39 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
460
467
|
|
|
461
468
|
// For regular methods, execute and send response
|
|
462
469
|
const result = await methodFn.apply(this, args);
|
|
470
|
+
|
|
471
|
+
this.observability?.emit(
|
|
472
|
+
{
|
|
473
|
+
displayMessage: `RPC call to ${method}`,
|
|
474
|
+
id: nanoid(),
|
|
475
|
+
payload: {
|
|
476
|
+
args,
|
|
477
|
+
method,
|
|
478
|
+
streaming: metadata?.streaming,
|
|
479
|
+
success: true,
|
|
480
|
+
},
|
|
481
|
+
timestamp: Date.now(),
|
|
482
|
+
type: "rpc",
|
|
483
|
+
},
|
|
484
|
+
this.ctx
|
|
485
|
+
);
|
|
486
|
+
|
|
463
487
|
const response: RPCResponse = {
|
|
464
|
-
|
|
488
|
+
done: true,
|
|
465
489
|
id,
|
|
466
|
-
success: true,
|
|
467
490
|
result,
|
|
468
|
-
|
|
491
|
+
success: true,
|
|
492
|
+
type: "rpc",
|
|
469
493
|
};
|
|
470
494
|
connection.send(JSON.stringify(response));
|
|
471
495
|
} catch (e) {
|
|
472
496
|
// Send error response
|
|
473
497
|
const response: RPCResponse = {
|
|
474
|
-
type: "rpc",
|
|
475
|
-
id: parsed.id,
|
|
476
|
-
success: false,
|
|
477
498
|
error:
|
|
478
499
|
e instanceof Error ? e.message : "Unknown error occurred",
|
|
500
|
+
id: parsed.id,
|
|
501
|
+
success: false,
|
|
502
|
+
type: "rpc",
|
|
479
503
|
};
|
|
480
504
|
connection.send(JSON.stringify(response));
|
|
481
505
|
console.error("RPC error:", e);
|
|
@@ -499,19 +523,31 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
499
523
|
if (this.state) {
|
|
500
524
|
connection.send(
|
|
501
525
|
JSON.stringify({
|
|
502
|
-
type: "cf_agent_state",
|
|
503
526
|
state: this.state,
|
|
527
|
+
type: "cf_agent_state",
|
|
504
528
|
})
|
|
505
529
|
);
|
|
506
530
|
}
|
|
507
531
|
|
|
508
532
|
connection.send(
|
|
509
533
|
JSON.stringify({
|
|
534
|
+
mcp: this.getMcpServers(),
|
|
510
535
|
type: "cf_agent_mcp_servers",
|
|
511
|
-
mcp: this._getMcpServerStateInternal(),
|
|
512
536
|
})
|
|
513
537
|
);
|
|
514
538
|
|
|
539
|
+
this.observability?.emit(
|
|
540
|
+
{
|
|
541
|
+
displayMessage: "Connection established",
|
|
542
|
+
id: nanoid(),
|
|
543
|
+
payload: {
|
|
544
|
+
connectionId: connection.id,
|
|
545
|
+
},
|
|
546
|
+
timestamp: Date.now(),
|
|
547
|
+
type: "connect",
|
|
548
|
+
},
|
|
549
|
+
this.ctx
|
|
550
|
+
);
|
|
515
551
|
return this._tryCatch(() => _onConnect(connection, ctx));
|
|
516
552
|
}, 20);
|
|
517
553
|
}
|
|
@@ -527,8 +563,8 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
527
563
|
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
|
|
528
564
|
`;
|
|
529
565
|
|
|
530
|
-
// from DO storage, reconnect to all servers using our saved auth information
|
|
531
|
-
|
|
566
|
+
// from DO storage, reconnect to all servers not currently in the oauth flow using our saved auth information
|
|
567
|
+
Promise.allSettled(
|
|
532
568
|
servers.map((server) => {
|
|
533
569
|
return this._connectToMcpServerInternal(
|
|
534
570
|
server.name,
|
|
@@ -543,15 +579,14 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
543
579
|
}
|
|
544
580
|
);
|
|
545
581
|
})
|
|
546
|
-
)
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
);
|
|
554
|
-
|
|
582
|
+
).then((_results) => {
|
|
583
|
+
this.broadcast(
|
|
584
|
+
JSON.stringify({
|
|
585
|
+
mcp: this.getMcpServers(),
|
|
586
|
+
type: "cf_agent_mcp_servers",
|
|
587
|
+
})
|
|
588
|
+
);
|
|
589
|
+
});
|
|
555
590
|
await this._tryCatch(() => _onStart());
|
|
556
591
|
}
|
|
557
592
|
);
|
|
@@ -562,6 +597,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
562
597
|
state: State,
|
|
563
598
|
source: Connection | "server" = "server"
|
|
564
599
|
) {
|
|
600
|
+
const previousState = this._state;
|
|
565
601
|
this._state = state;
|
|
566
602
|
this.sql`
|
|
567
603
|
INSERT OR REPLACE INTO cf_agents_state (id, state)
|
|
@@ -573,8 +609,8 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
573
609
|
`;
|
|
574
610
|
this.broadcast(
|
|
575
611
|
JSON.stringify({
|
|
576
|
-
type: "cf_agent_state",
|
|
577
612
|
state: state,
|
|
613
|
+
type: "cf_agent_state",
|
|
578
614
|
}),
|
|
579
615
|
source !== "server" ? [source.id] : []
|
|
580
616
|
);
|
|
@@ -583,6 +619,19 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
583
619
|
return agentContext.run(
|
|
584
620
|
{ agent: this, connection, request },
|
|
585
621
|
async () => {
|
|
622
|
+
this.observability?.emit(
|
|
623
|
+
{
|
|
624
|
+
displayMessage: "State updated",
|
|
625
|
+
id: nanoid(),
|
|
626
|
+
payload: {
|
|
627
|
+
previousState,
|
|
628
|
+
state,
|
|
629
|
+
},
|
|
630
|
+
timestamp: Date.now(),
|
|
631
|
+
type: "state:update",
|
|
632
|
+
},
|
|
633
|
+
this.ctx
|
|
634
|
+
);
|
|
586
635
|
return this.onStateUpdate(state, source);
|
|
587
636
|
}
|
|
588
637
|
);
|
|
@@ -602,23 +651,44 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
602
651
|
* @param state Updated state
|
|
603
652
|
* @param source Source of the state update ("server" or a client connection)
|
|
604
653
|
*/
|
|
654
|
+
// biome-ignore lint/correctness/noUnusedFunctionParameters: overridden later
|
|
605
655
|
onStateUpdate(state: State | undefined, source: Connection | "server") {
|
|
606
656
|
// override this to handle state updates
|
|
607
657
|
}
|
|
608
658
|
|
|
609
659
|
/**
|
|
610
660
|
* Called when the Agent receives an email
|
|
661
|
+
* Override this method to handle incoming emails
|
|
611
662
|
* @param email Email message to process
|
|
612
663
|
*/
|
|
613
|
-
onEmail(email: ForwardableEmailMessage) {
|
|
664
|
+
async onEmail(email: ForwardableEmailMessage) {
|
|
614
665
|
return agentContext.run(
|
|
615
666
|
{ agent: this, connection: undefined, request: undefined },
|
|
616
667
|
async () => {
|
|
617
|
-
console.
|
|
668
|
+
console.log("Received email from:", email.from, "to:", email.to);
|
|
669
|
+
console.log("Subject:", email.headers.get("subject"));
|
|
670
|
+
console.log("Override onEmail() in your agent to process emails");
|
|
618
671
|
}
|
|
619
672
|
);
|
|
620
673
|
}
|
|
621
674
|
|
|
675
|
+
async sendEmail(
|
|
676
|
+
emailBinding: SendEmail,
|
|
677
|
+
from: string,
|
|
678
|
+
fromName: string,
|
|
679
|
+
options: Omit<EmailSendOptions, "agentName" | "agentId">
|
|
680
|
+
): Promise<void> {
|
|
681
|
+
const agentName = camelCaseToKebabCase(this._ParentClass.name);
|
|
682
|
+
const agentId = this.name;
|
|
683
|
+
|
|
684
|
+
return sendEmailWithRouting(emailBinding, from, fromName, {
|
|
685
|
+
...options,
|
|
686
|
+
agentName,
|
|
687
|
+
agentId,
|
|
688
|
+
includeRoutingHeaders: true,
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
622
692
|
private async _tryCatch<T>(fn: () => T | Promise<T>) {
|
|
623
693
|
try {
|
|
624
694
|
return await fn();
|
|
@@ -676,6 +746,18 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
676
746
|
): Promise<Schedule<T>> {
|
|
677
747
|
const id = nanoid(9);
|
|
678
748
|
|
|
749
|
+
const emitScheduleCreate = (schedule: Schedule<T>) =>
|
|
750
|
+
this.observability?.emit(
|
|
751
|
+
{
|
|
752
|
+
displayMessage: `Schedule ${schedule.id} created`,
|
|
753
|
+
id: nanoid(),
|
|
754
|
+
payload: schedule,
|
|
755
|
+
timestamp: Date.now(),
|
|
756
|
+
type: "schedule:create",
|
|
757
|
+
},
|
|
758
|
+
this.ctx
|
|
759
|
+
);
|
|
760
|
+
|
|
679
761
|
if (typeof callback !== "string") {
|
|
680
762
|
throw new Error("Callback must be a string");
|
|
681
763
|
}
|
|
@@ -695,13 +777,17 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
695
777
|
|
|
696
778
|
await this._scheduleNextAlarm();
|
|
697
779
|
|
|
698
|
-
|
|
699
|
-
id,
|
|
780
|
+
const schedule: Schedule<T> = {
|
|
700
781
|
callback: callback,
|
|
782
|
+
id,
|
|
701
783
|
payload: payload as T,
|
|
702
784
|
time: timestamp,
|
|
703
785
|
type: "scheduled",
|
|
704
786
|
};
|
|
787
|
+
|
|
788
|
+
emitScheduleCreate(schedule);
|
|
789
|
+
|
|
790
|
+
return schedule;
|
|
705
791
|
}
|
|
706
792
|
if (typeof when === "number") {
|
|
707
793
|
const time = new Date(Date.now() + when * 1000);
|
|
@@ -716,14 +802,18 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
716
802
|
|
|
717
803
|
await this._scheduleNextAlarm();
|
|
718
804
|
|
|
719
|
-
|
|
720
|
-
id,
|
|
805
|
+
const schedule: Schedule<T> = {
|
|
721
806
|
callback: callback,
|
|
722
|
-
payload: payload as T,
|
|
723
807
|
delayInSeconds: when,
|
|
808
|
+
id,
|
|
809
|
+
payload: payload as T,
|
|
724
810
|
time: timestamp,
|
|
725
811
|
type: "delayed",
|
|
726
812
|
};
|
|
813
|
+
|
|
814
|
+
emitScheduleCreate(schedule);
|
|
815
|
+
|
|
816
|
+
return schedule;
|
|
727
817
|
}
|
|
728
818
|
if (typeof when === "string") {
|
|
729
819
|
const nextExecutionTime = getNextCronTime(when);
|
|
@@ -738,14 +828,18 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
738
828
|
|
|
739
829
|
await this._scheduleNextAlarm();
|
|
740
830
|
|
|
741
|
-
|
|
742
|
-
id,
|
|
831
|
+
const schedule: Schedule<T> = {
|
|
743
832
|
callback: callback,
|
|
744
|
-
payload: payload as T,
|
|
745
833
|
cron: when,
|
|
834
|
+
id,
|
|
835
|
+
payload: payload as T,
|
|
746
836
|
time: timestamp,
|
|
747
837
|
type: "cron",
|
|
748
838
|
};
|
|
839
|
+
|
|
840
|
+
emitScheduleCreate(schedule);
|
|
841
|
+
|
|
842
|
+
return schedule;
|
|
749
843
|
}
|
|
750
844
|
throw new Error("Invalid schedule type");
|
|
751
845
|
}
|
|
@@ -821,6 +915,19 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
821
915
|
* @returns true if the task was cancelled, false otherwise
|
|
822
916
|
*/
|
|
823
917
|
async cancelSchedule(id: string): Promise<boolean> {
|
|
918
|
+
const schedule = await this.getSchedule(id);
|
|
919
|
+
if (schedule) {
|
|
920
|
+
this.observability?.emit(
|
|
921
|
+
{
|
|
922
|
+
displayMessage: `Schedule ${id} cancelled`,
|
|
923
|
+
id: nanoid(),
|
|
924
|
+
payload: schedule,
|
|
925
|
+
timestamp: Date.now(),
|
|
926
|
+
type: "schedule:cancel",
|
|
927
|
+
},
|
|
928
|
+
this.ctx
|
|
929
|
+
);
|
|
930
|
+
}
|
|
824
931
|
this.sql`DELETE FROM cf_agents_schedules WHERE id = ${id}`;
|
|
825
932
|
|
|
826
933
|
await this._scheduleNextAlarm();
|
|
@@ -869,6 +976,17 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
869
976
|
{ agent: this, connection: undefined, request: undefined },
|
|
870
977
|
async () => {
|
|
871
978
|
try {
|
|
979
|
+
this.observability?.emit(
|
|
980
|
+
{
|
|
981
|
+
displayMessage: `Schedule ${row.id} executed`,
|
|
982
|
+
id: nanoid(),
|
|
983
|
+
payload: row,
|
|
984
|
+
timestamp: Date.now(),
|
|
985
|
+
type: "schedule:execute",
|
|
986
|
+
},
|
|
987
|
+
this.ctx
|
|
988
|
+
);
|
|
989
|
+
|
|
872
990
|
await (
|
|
873
991
|
callback as (
|
|
874
992
|
payload: unknown,
|
|
@@ -912,10 +1030,25 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
912
1030
|
// delete all alarms
|
|
913
1031
|
await this.ctx.storage.deleteAlarm();
|
|
914
1032
|
await this.ctx.storage.deleteAll();
|
|
1033
|
+
this.ctx.abort("destroyed"); // enforce that the agent is evicted
|
|
1034
|
+
|
|
1035
|
+
this.observability?.emit(
|
|
1036
|
+
{
|
|
1037
|
+
displayMessage: "Agent destroyed",
|
|
1038
|
+
id: nanoid(),
|
|
1039
|
+
payload: {},
|
|
1040
|
+
timestamp: Date.now(),
|
|
1041
|
+
type: "destroy",
|
|
1042
|
+
},
|
|
1043
|
+
this.ctx
|
|
1044
|
+
);
|
|
915
1045
|
}
|
|
916
1046
|
|
|
1047
|
+
/**
|
|
1048
|
+
* Get all methods marked as callable on this Agent
|
|
1049
|
+
* @returns A map of method names to their metadata
|
|
1050
|
+
*/
|
|
917
1051
|
private _isCallable(method: string): boolean {
|
|
918
|
-
// biome-ignore lint/complexity/noBannedTypes: <explanation>
|
|
919
1052
|
return callableMetadata.has(this[method as keyof this] as Function);
|
|
920
1053
|
}
|
|
921
1054
|
|
|
@@ -948,11 +1081,24 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
948
1081
|
callbackUrl,
|
|
949
1082
|
options
|
|
950
1083
|
);
|
|
1084
|
+
this.sql`
|
|
1085
|
+
INSERT
|
|
1086
|
+
OR REPLACE INTO cf_agents_mcp_servers (id, name, server_url, client_id, auth_url, callback_url, server_options)
|
|
1087
|
+
VALUES (
|
|
1088
|
+
${result.id},
|
|
1089
|
+
${serverName},
|
|
1090
|
+
${url},
|
|
1091
|
+
${result.clientId ?? null},
|
|
1092
|
+
${result.authUrl ?? null},
|
|
1093
|
+
${callbackUrl},
|
|
1094
|
+
${options ? JSON.stringify(options) : null}
|
|
1095
|
+
);
|
|
1096
|
+
`;
|
|
951
1097
|
|
|
952
1098
|
this.broadcast(
|
|
953
1099
|
JSON.stringify({
|
|
1100
|
+
mcp: this.getMcpServers(),
|
|
954
1101
|
type: "cf_agent_mcp_servers",
|
|
955
|
-
mcp: this._getMcpServerStateInternal(),
|
|
956
1102
|
})
|
|
957
1103
|
);
|
|
958
1104
|
|
|
@@ -960,7 +1106,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
960
1106
|
}
|
|
961
1107
|
|
|
962
1108
|
async _connectToMcpServerInternal(
|
|
963
|
-
|
|
1109
|
+
_serverName: string,
|
|
964
1110
|
url: string,
|
|
965
1111
|
callbackUrl: string,
|
|
966
1112
|
// it's important that any options here are serializable because we put them into our sqlite DB for reconnection purposes
|
|
@@ -981,7 +1127,11 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
981
1127
|
id: string;
|
|
982
1128
|
oauthClientId?: string;
|
|
983
1129
|
}
|
|
984
|
-
): Promise<{
|
|
1130
|
+
): Promise<{
|
|
1131
|
+
id: string;
|
|
1132
|
+
authUrl: string | undefined;
|
|
1133
|
+
clientId: string | undefined;
|
|
1134
|
+
}> {
|
|
985
1135
|
const authProvider = new DurableObjectOAuthClientProvider(
|
|
986
1136
|
this.ctx.storage,
|
|
987
1137
|
this.name,
|
|
@@ -1014,30 +1164,18 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1014
1164
|
}
|
|
1015
1165
|
|
|
1016
1166
|
const { id, authUrl, clientId } = await this.mcp.connect(url, {
|
|
1167
|
+
client: options?.client,
|
|
1017
1168
|
reconnect,
|
|
1018
1169
|
transport: {
|
|
1019
1170
|
...headerTransportOpts,
|
|
1020
1171
|
authProvider,
|
|
1021
1172
|
},
|
|
1022
|
-
client: options?.client,
|
|
1023
1173
|
});
|
|
1024
1174
|
|
|
1025
|
-
this.sql`
|
|
1026
|
-
INSERT OR REPLACE INTO cf_agents_mcp_servers (id, name, server_url, client_id, auth_url, callback_url, server_options)
|
|
1027
|
-
VALUES (
|
|
1028
|
-
${id},
|
|
1029
|
-
${serverName},
|
|
1030
|
-
${url},
|
|
1031
|
-
${clientId ?? null},
|
|
1032
|
-
${authUrl ?? null},
|
|
1033
|
-
${callbackUrl},
|
|
1034
|
-
${options ? JSON.stringify(options) : null}
|
|
1035
|
-
);
|
|
1036
|
-
`;
|
|
1037
|
-
|
|
1038
1175
|
return {
|
|
1039
|
-
id,
|
|
1040
1176
|
authUrl,
|
|
1177
|
+
clientId,
|
|
1178
|
+
id,
|
|
1041
1179
|
};
|
|
1042
1180
|
}
|
|
1043
1181
|
|
|
@@ -1048,18 +1186,18 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1048
1186
|
`;
|
|
1049
1187
|
this.broadcast(
|
|
1050
1188
|
JSON.stringify({
|
|
1189
|
+
mcp: this.getMcpServers(),
|
|
1051
1190
|
type: "cf_agent_mcp_servers",
|
|
1052
|
-
mcp: this._getMcpServerStateInternal(),
|
|
1053
1191
|
})
|
|
1054
1192
|
);
|
|
1055
1193
|
}
|
|
1056
1194
|
|
|
1057
|
-
|
|
1195
|
+
getMcpServers(): MCPServersState {
|
|
1058
1196
|
const mcpState: MCPServersState = {
|
|
1059
|
-
servers: {},
|
|
1060
|
-
tools: this.mcp.listTools(),
|
|
1061
1197
|
prompts: this.mcp.listPrompts(),
|
|
1062
1198
|
resources: this.mcp.listResources(),
|
|
1199
|
+
servers: {},
|
|
1200
|
+
tools: this.mcp.listTools(),
|
|
1063
1201
|
};
|
|
1064
1202
|
|
|
1065
1203
|
const servers = this.sql<MCPServerRow>`
|
|
@@ -1067,11 +1205,15 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1067
1205
|
`;
|
|
1068
1206
|
|
|
1069
1207
|
for (const server of servers) {
|
|
1208
|
+
const serverConn = this.mcp.mcpConnections[server.id];
|
|
1070
1209
|
mcpState.servers[server.id] = {
|
|
1210
|
+
auth_url: server.auth_url,
|
|
1211
|
+
capabilities: serverConn?.serverCapabilities ?? null,
|
|
1212
|
+
instructions: serverConn?.instructions ?? null,
|
|
1071
1213
|
name: server.name,
|
|
1072
1214
|
server_url: server.server_url,
|
|
1073
|
-
|
|
1074
|
-
state:
|
|
1215
|
+
// mark as "authenticating" because the server isn't automatically connected, so it's pending authenticating
|
|
1216
|
+
state: serverConn?.connectionState ?? "authenticating",
|
|
1075
1217
|
};
|
|
1076
1218
|
}
|
|
1077
1219
|
|
|
@@ -1116,9 +1258,9 @@ export async function routeAgentRequest<Env>(
|
|
|
1116
1258
|
const corsHeaders =
|
|
1117
1259
|
options?.cors === true
|
|
1118
1260
|
? {
|
|
1119
|
-
"Access-Control-Allow-Origin": "*",
|
|
1120
|
-
"Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS",
|
|
1121
1261
|
"Access-Control-Allow-Credentials": "true",
|
|
1262
|
+
"Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS",
|
|
1263
|
+
"Access-Control-Allow-Origin": "*",
|
|
1122
1264
|
"Access-Control-Max-Age": "86400",
|
|
1123
1265
|
}
|
|
1124
1266
|
: options?.cors;
|
|
@@ -1159,17 +1301,170 @@ export async function routeAgentRequest<Env>(
|
|
|
1159
1301
|
return response;
|
|
1160
1302
|
}
|
|
1161
1303
|
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1304
|
+
export type EmailResolver<Env> = (
|
|
1305
|
+
email: ForwardableEmailMessage,
|
|
1306
|
+
env: Env
|
|
1307
|
+
) => Promise<{
|
|
1308
|
+
agentName: string;
|
|
1309
|
+
agentId: string;
|
|
1310
|
+
} | null>;
|
|
1311
|
+
|
|
1312
|
+
export function createHeaderBasedResolver<Env>(): EmailResolver<Env> {
|
|
1313
|
+
return async (email: ForwardableEmailMessage, _env: Env) => {
|
|
1314
|
+
const messageId = email.headers.get("message-id");
|
|
1315
|
+
if (messageId) {
|
|
1316
|
+
const messageIdMatch = messageId.match(/<([^@]+)@([^>]+)>/);
|
|
1317
|
+
if (messageIdMatch) {
|
|
1318
|
+
const [, agentId, domain] = messageIdMatch;
|
|
1319
|
+
const agentName = domain.split(".")[0];
|
|
1320
|
+
return { agentName, agentId };
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
const references = email.headers.get("references");
|
|
1325
|
+
if (references) {
|
|
1326
|
+
const referencesMatch = references.match(
|
|
1327
|
+
/<([A-Za-z0-9+/]{43}=)@([^>]+)>/
|
|
1328
|
+
);
|
|
1329
|
+
if (referencesMatch) {
|
|
1330
|
+
const [, base64Id, domain] = referencesMatch;
|
|
1331
|
+
const agentId = Buffer.from(base64Id, "base64").toString("hex");
|
|
1332
|
+
const agentName = domain.split(".")[0];
|
|
1333
|
+
return { agentName, agentId };
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
const agentName = email.headers.get("x-agent-name");
|
|
1338
|
+
const agentId = email.headers.get("x-agent-id");
|
|
1339
|
+
if (agentName && agentId) {
|
|
1340
|
+
return { agentName, agentId };
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
return null;
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
export function createAddressBasedResolver<Env>(
|
|
1348
|
+
defaultAgentName: string
|
|
1349
|
+
): EmailResolver<Env> {
|
|
1350
|
+
return async (email: ForwardableEmailMessage, _env: Env) => {
|
|
1351
|
+
const emailMatch = email.to.match(/^([^+@]+)(?:\+([^@]+))?@(.+)$/);
|
|
1352
|
+
if (!emailMatch) {
|
|
1353
|
+
return null;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
const [, localPart, subAddress] = emailMatch;
|
|
1357
|
+
|
|
1358
|
+
if (subAddress) {
|
|
1359
|
+
return {
|
|
1360
|
+
agentName: localPart,
|
|
1361
|
+
agentId: subAddress,
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
// Option 2: Use defaultAgentName namespace, localPart as agentId
|
|
1366
|
+
// Common for catch-all email routing to a single EmailAgent namespace
|
|
1367
|
+
return {
|
|
1368
|
+
agentName: defaultAgentName,
|
|
1369
|
+
agentId: localPart,
|
|
1370
|
+
};
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
export function createCatchAllResolver<Env>(
|
|
1375
|
+
agentName: string,
|
|
1376
|
+
agentId: string
|
|
1377
|
+
): EmailResolver<Env> {
|
|
1378
|
+
return async () => ({ agentName, agentId });
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
export type EmailRoutingOptions<Env> = AgentOptions<Env> & {
|
|
1382
|
+
resolver: EmailResolver<Env>;
|
|
1383
|
+
};
|
|
1384
|
+
|
|
1168
1385
|
export async function routeAgentEmail<Env>(
|
|
1169
1386
|
email: ForwardableEmailMessage,
|
|
1170
1387
|
env: Env,
|
|
1171
|
-
options
|
|
1172
|
-
): Promise<void> {
|
|
1388
|
+
options: EmailRoutingOptions<Env>
|
|
1389
|
+
): Promise<void> {
|
|
1390
|
+
const routingInfo = await options.resolver(email, env);
|
|
1391
|
+
|
|
1392
|
+
if (!routingInfo) {
|
|
1393
|
+
console.warn("No routing information found for email, dropping message");
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
const namespaceBinding = env[routingInfo.agentName as keyof Env];
|
|
1398
|
+
if (!namespaceBinding) {
|
|
1399
|
+
console.error(
|
|
1400
|
+
`Agent namespace '${routingInfo.agentName}' not found in environment`
|
|
1401
|
+
);
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
// Type guard to check if this is actually a DurableObjectNamespace (AgentNamespace)
|
|
1406
|
+
if (
|
|
1407
|
+
typeof namespaceBinding !== "object" ||
|
|
1408
|
+
!("idFromName" in namespaceBinding) ||
|
|
1409
|
+
typeof namespaceBinding.idFromName !== "function"
|
|
1410
|
+
) {
|
|
1411
|
+
console.error(
|
|
1412
|
+
`Environment binding '${routingInfo.agentName}' is not an AgentNamespace (found: ${typeof namespaceBinding})`
|
|
1413
|
+
);
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
// Safe cast after runtime validation
|
|
1418
|
+
const namespace = namespaceBinding as unknown as AgentNamespace<Agent<Env>>;
|
|
1419
|
+
|
|
1420
|
+
const agent = await getAgentByName(namespace, routingInfo.agentId);
|
|
1421
|
+
await agent.onEmail(email);
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
export type EmailSendOptions = {
|
|
1425
|
+
to: string;
|
|
1426
|
+
subject: string;
|
|
1427
|
+
body: string;
|
|
1428
|
+
contentType?: string;
|
|
1429
|
+
headers?: Record<string, string>;
|
|
1430
|
+
includeRoutingHeaders?: boolean;
|
|
1431
|
+
agentName?: string;
|
|
1432
|
+
agentId?: string;
|
|
1433
|
+
domain?: string;
|
|
1434
|
+
};
|
|
1435
|
+
|
|
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
|
+
}
|
|
1173
1468
|
|
|
1174
1469
|
/**
|
|
1175
1470
|
* Get or create an Agent by name
|
|
@@ -1213,11 +1508,11 @@ export class StreamingResponse {
|
|
|
1213
1508
|
throw new Error("StreamingResponse is already closed");
|
|
1214
1509
|
}
|
|
1215
1510
|
const response: RPCResponse = {
|
|
1216
|
-
|
|
1511
|
+
done: false,
|
|
1217
1512
|
id: this._id,
|
|
1218
|
-
success: true,
|
|
1219
1513
|
result: chunk,
|
|
1220
|
-
|
|
1514
|
+
success: true,
|
|
1515
|
+
type: "rpc",
|
|
1221
1516
|
};
|
|
1222
1517
|
this._connection.send(JSON.stringify(response));
|
|
1223
1518
|
}
|
|
@@ -1232,11 +1527,11 @@ export class StreamingResponse {
|
|
|
1232
1527
|
}
|
|
1233
1528
|
this._closed = true;
|
|
1234
1529
|
const response: RPCResponse = {
|
|
1235
|
-
|
|
1530
|
+
done: true,
|
|
1236
1531
|
id: this._id,
|
|
1237
|
-
success: true,
|
|
1238
1532
|
result: finalChunk,
|
|
1239
|
-
|
|
1533
|
+
success: true,
|
|
1534
|
+
type: "rpc",
|
|
1240
1535
|
};
|
|
1241
1536
|
this._connection.send(JSON.stringify(response));
|
|
1242
1537
|
}
|