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.
Files changed (42) hide show
  1. package/dist/ai-chat-agent.d.ts +5 -4
  2. package/dist/ai-chat-agent.js +64 -26
  3. package/dist/ai-chat-agent.js.map +1 -1
  4. package/dist/ai-react.d.ts +9 -8
  5. package/dist/ai-react.js +27 -27
  6. package/dist/ai-react.js.map +1 -1
  7. package/dist/{chunk-BZXOAZUX.js → chunk-767EASBA.js} +5 -5
  8. package/dist/{chunk-BZXOAZUX.js.map → chunk-767EASBA.js.map} +1 -1
  9. package/dist/{chunk-QSGN3REV.js → chunk-NKZZ66QY.js} +8 -15
  10. package/dist/chunk-NKZZ66QY.js.map +1 -0
  11. package/dist/{chunk-RIYR6FR6.js → chunk-RX76B6DL.js} +328 -80
  12. package/dist/chunk-RX76B6DL.js.map +1 -0
  13. package/dist/{chunk-Y67CHZBI.js → chunk-TN7QOY4S.js} +23 -18
  14. package/dist/chunk-TN7QOY4S.js.map +1 -0
  15. package/dist/client.d.ts +6 -0
  16. package/dist/client.js +1 -1
  17. package/dist/index-aBwVVXj7.d.ts +529 -0
  18. package/dist/index.d.ts +32 -394
  19. package/dist/index.js +12 -4
  20. package/dist/mcp/client.d.ts +281 -9
  21. package/dist/mcp/client.js +1 -1
  22. package/dist/mcp/do-oauth-client-provider.js +1 -1
  23. package/dist/mcp/index.d.ts +6 -6
  24. package/dist/mcp/index.js +55 -51
  25. package/dist/mcp/index.js.map +1 -1
  26. package/dist/observability/index.d.ts +12 -0
  27. package/dist/observability/index.js +10 -0
  28. package/dist/observability/index.js.map +1 -0
  29. package/dist/react.d.ts +76 -10
  30. package/dist/react.js +16 -6
  31. package/dist/react.js.map +1 -1
  32. package/dist/schedule.d.ts +6 -6
  33. package/dist/schedule.js +4 -4
  34. package/dist/schedule.js.map +1 -1
  35. package/dist/serializable.d.ts +32 -0
  36. package/dist/serializable.js +1 -0
  37. package/dist/serializable.js.map +1 -0
  38. package/package.json +76 -71
  39. package/src/index.ts +395 -100
  40. package/dist/chunk-QSGN3REV.js.map +0 -1
  41. package/dist/chunk-RIYR6FR6.js.map +0 -1
  42. 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, WSMessage, ConnectionContext } from "partyserver";
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 (e) {
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
- type: "rpc",
488
+ done: true,
465
489
  id,
466
- success: true,
467
490
  result,
468
- done: true,
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
- await Promise.allSettled(
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
- this.broadcast(
549
- JSON.stringify({
550
- type: "cf_agent_mcp_servers",
551
- mcp: this._getMcpServerStateInternal(),
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.error("onEmail not implemented");
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
- return {
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
- return {
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
- return {
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
- serverName: string,
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<{ id: string; authUrl: string | undefined }> {
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
- private _getMcpServerStateInternal(): MCPServersState {
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
- auth_url: server.auth_url,
1074
- state: this.mcp.mcpConnections[server.id].connectionState,
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
- * Route an email to the appropriate Agent
1164
- * @param email Email message to route
1165
- * @param env Environment containing Agent bindings
1166
- * @param options Routing options
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?: AgentOptions<Env>
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
- type: "rpc",
1511
+ done: false,
1217
1512
  id: this._id,
1218
- success: true,
1219
1513
  result: chunk,
1220
- done: false,
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
- type: "rpc",
1530
+ done: true,
1236
1531
  id: this._id,
1237
- success: true,
1238
1532
  result: finalChunk,
1239
- done: true,
1533
+ success: true,
1534
+ type: "rpc",
1240
1535
  };
1241
1536
  this._connection.send(JSON.stringify(response));
1242
1537
  }