agents 0.0.0-c5e3a32 → 0.0.0-c6d9bf1

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 (72) hide show
  1. package/README.md +234 -6
  2. package/dist/_esm-LV5FJ3HK.js +3922 -0
  3. package/dist/_esm-LV5FJ3HK.js.map +1 -0
  4. package/dist/ai-chat-agent.d.ts +13 -9
  5. package/dist/ai-chat-agent.js +444 -60
  6. package/dist/ai-chat-agent.js.map +1 -1
  7. package/dist/ai-chat-v5-migration.d.ts +152 -0
  8. package/dist/ai-chat-v5-migration.js +20 -0
  9. package/dist/ai-chat-v5-migration.js.map +1 -0
  10. package/dist/ai-react.d.ts +69 -71
  11. package/dist/ai-react.js +252 -99
  12. package/dist/ai-react.js.map +1 -1
  13. package/dist/ai-types.d.ts +37 -19
  14. package/dist/ai-types.js +7 -0
  15. package/dist/ccip-CMBYN64O.js +15 -0
  16. package/dist/ccip-CMBYN64O.js.map +1 -0
  17. package/dist/{chunk-EDUDXISR.js → chunk-254F4GDT.js} +321 -126
  18. package/dist/chunk-254F4GDT.js.map +1 -0
  19. package/dist/chunk-3OT2NNEW.js +941 -0
  20. package/dist/chunk-3OT2NNEW.js.map +1 -0
  21. package/dist/chunk-5Y6BEZDY.js +276 -0
  22. package/dist/chunk-5Y6BEZDY.js.map +1 -0
  23. package/dist/chunk-BER7KXUJ.js +18 -0
  24. package/dist/chunk-BER7KXUJ.js.map +1 -0
  25. package/dist/chunk-JJBFIGUC.js +5202 -0
  26. package/dist/chunk-JJBFIGUC.js.map +1 -0
  27. package/dist/chunk-PR4QN5HX.js +43 -0
  28. package/dist/chunk-PR4QN5HX.js.map +1 -0
  29. package/dist/{chunk-KUH345EY.js → chunk-QEVM4BVL.js} +5 -5
  30. package/dist/chunk-QEVM4BVL.js.map +1 -0
  31. package/dist/chunk-TYAY6AU6.js +159 -0
  32. package/dist/chunk-TYAY6AU6.js.map +1 -0
  33. package/dist/chunk-UJVEAURM.js +150 -0
  34. package/dist/chunk-UJVEAURM.js.map +1 -0
  35. package/dist/{chunk-PVQZBKN7.js → chunk-Z44WASMA.js} +11 -3
  36. package/dist/chunk-Z44WASMA.js.map +1 -0
  37. package/dist/client-DVoPb3-C.d.ts +5120 -0
  38. package/dist/client.js +3 -1
  39. package/dist/codemode/ai.d.ts +25 -0
  40. package/dist/codemode/ai.js +5112 -0
  41. package/dist/codemode/ai.js.map +1 -0
  42. package/dist/index.d.ts +550 -31
  43. package/dist/index.js +8 -4
  44. package/dist/mcp/client.d.ts +10 -1053
  45. package/dist/mcp/client.js +2 -1
  46. package/dist/mcp/do-oauth-client-provider.d.ts +1 -0
  47. package/dist/mcp/do-oauth-client-provider.js +2 -1
  48. package/dist/mcp/index.d.ts +58 -63
  49. package/dist/mcp/index.js +954 -638
  50. package/dist/mcp/index.js.map +1 -1
  51. package/dist/mcp/x402.d.ts +39 -0
  52. package/dist/mcp/x402.js +3195 -0
  53. package/dist/mcp/x402.js.map +1 -0
  54. package/dist/mcp-BH1fJeiU.d.ts +58 -0
  55. package/dist/observability/index.d.ts +34 -12
  56. package/dist/observability/index.js +6 -4
  57. package/dist/react.d.ts +16 -8
  58. package/dist/react.js +107 -7
  59. package/dist/react.js.map +1 -1
  60. package/dist/schedule.d.ts +83 -9
  61. package/dist/schedule.js +17 -2
  62. package/dist/schedule.js.map +1 -1
  63. package/dist/secp256k1-M22GZP2U.js +2193 -0
  64. package/dist/secp256k1-M22GZP2U.js.map +1 -0
  65. package/package.json +32 -9
  66. package/src/index.ts +453 -154
  67. package/dist/chunk-EDUDXISR.js.map +0 -1
  68. package/dist/chunk-KUH345EY.js.map +0 -1
  69. package/dist/chunk-MW5BQ2FW.js +0 -469
  70. package/dist/chunk-MW5BQ2FW.js.map +0 -1
  71. package/dist/chunk-PVQZBKN7.js.map +0 -1
  72. package/dist/index-DukU3sIa.d.ts +0 -571
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { env } from "cloudflare:workers";
1
2
  import { AsyncLocalStorage } from "node:async_hooks";
2
3
  import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
4
  import type { SSEClientTransportOptions } from "@modelcontextprotocol/sdk/client/sse.js";
@@ -21,10 +22,13 @@ import {
21
22
  routePartykitRequest
22
23
  } from "partyserver";
23
24
  import { camelCaseToKebabCase } from "./client";
24
- import { MCPClientManager } from "./mcp/client";
25
- // import type { MCPClientConnection } from "./mcp/client-connection";
25
+ import { MCPClientManager, type MCPClientOAuthResult } from "./mcp/client";
26
+ import type { MCPConnectionState } from "./mcp/client-connection";
26
27
  import { DurableObjectOAuthClientProvider } from "./mcp/do-oauth-client-provider";
28
+ import type { TransportType } from "./mcp/types";
27
29
  import { genericObservability, type Observability } from "./observability";
30
+ import { DisposableStore } from "./core/events";
31
+ import { MessageType } from "./ai-types";
28
32
 
29
33
  export type { Connection, ConnectionContext, WSMessage } from "partyserver";
30
34
 
@@ -42,7 +46,7 @@ export type RPCRequest = {
42
46
  * State update message from client
43
47
  */
44
48
  export type StateUpdateMessage = {
45
- type: "cf_agent_state";
49
+ type: MessageType.CF_AGENT_STATE;
46
50
  state: unknown;
47
51
  };
48
52
 
@@ -50,7 +54,7 @@ export type StateUpdateMessage = {
50
54
  * RPC response message to client
51
55
  */
52
56
  export type RPCResponse = {
53
- type: "rpc";
57
+ type: MessageType.RPC;
54
58
  id: string;
55
59
  } & (
56
60
  | {
@@ -77,7 +81,7 @@ function isRPCRequest(msg: unknown): msg is RPCRequest {
77
81
  typeof msg === "object" &&
78
82
  msg !== null &&
79
83
  "type" in msg &&
80
- msg.type === "rpc" &&
84
+ msg.type === MessageType.RPC &&
81
85
  "id" in msg &&
82
86
  typeof msg.id === "string" &&
83
87
  "method" in msg &&
@@ -95,7 +99,7 @@ function isStateUpdateMessage(msg: unknown): msg is StateUpdateMessage {
95
99
  typeof msg === "object" &&
96
100
  msg !== null &&
97
101
  "type" in msg &&
98
- msg.type === "cf_agent_state" &&
102
+ msg.type === MessageType.CF_AGENT_STATE &&
99
103
  "state" in msg
100
104
  );
101
105
  }
@@ -116,7 +120,7 @@ const callableMetadata = new Map<Function, CallableMetadata>();
116
120
  * Decorator that marks a method as callable by clients
117
121
  * @param metadata Optional metadata about the callable method
118
122
  */
119
- export function unstable_callable(metadata: CallableMetadata = {}) {
123
+ export function callable(metadata: CallableMetadata = {}) {
120
124
  return function callableDecorator<This, Args extends unknown[], Return>(
121
125
  target: (this: This, ...args: Args) => Return,
122
126
  // biome-ignore lint/correctness/noUnusedFunctionParameters: later
@@ -130,6 +134,30 @@ export function unstable_callable(metadata: CallableMetadata = {}) {
130
134
  };
131
135
  }
132
136
 
137
+ let didWarnAboutUnstableCallable = false;
138
+
139
+ /**
140
+ * Decorator that marks a method as callable by clients
141
+ * @deprecated this has been renamed to callable, and unstable_callable will be removed in the next major version
142
+ * @param metadata Optional metadata about the callable method
143
+ */
144
+ export const unstable_callable = (metadata: CallableMetadata = {}) => {
145
+ if (!didWarnAboutUnstableCallable) {
146
+ didWarnAboutUnstableCallable = true;
147
+ console.warn(
148
+ "unstable_callable is deprecated, use callable instead. unstable_callable will be removed in the next major version."
149
+ );
150
+ }
151
+ callable(metadata);
152
+ };
153
+
154
+ export type QueueItem<T = string> = {
155
+ id: string;
156
+ payload: T;
157
+ callback: keyof Agent<unknown>;
158
+ created_at: number;
159
+ };
160
+
133
161
  /**
134
162
  * Represents a scheduled task within an Agent
135
163
  * @template T Type of the payload data
@@ -171,11 +199,13 @@ function getNextCronTime(cron: string) {
171
199
  return interval.getNextDate();
172
200
  }
173
201
 
202
+ export type { TransportType } from "./mcp/types";
203
+
174
204
  /**
175
205
  * MCP Server state update message from server -> Client
176
206
  */
177
207
  export type MCPServerMessage = {
178
- type: "cf_agent_mcp_servers";
208
+ type: MessageType.CF_AGENT_MCP_SERVERS;
179
209
  mcp: MCPServersState;
180
210
  };
181
211
 
@@ -195,7 +225,7 @@ export type MCPServer = {
195
225
  // This state is specifically about the temporary process of getting a token (if needed).
196
226
  // Scope outside of that can't be relied upon because when the DO sleeps, there's no way
197
227
  // to communicate a change to a non-ready state.
198
- state: "authenticating" | "connecting" | "ready" | "discovering" | "failed";
228
+ state: MCPConnectionState;
199
229
  instructions: string | null;
200
230
  capabilities: ServerCapabilities | null;
201
231
  };
@@ -264,7 +294,13 @@ function withAgentContext<T extends (...args: any[]) => any>(
264
294
  method: T
265
295
  ): (this: Agent<unknown, unknown>, ...args: Parameters<T>) => ReturnType<T> {
266
296
  return function (...args: Parameters<T>): ReturnType<T> {
267
- const { connection, request, email } = getCurrentAgent();
297
+ const { connection, request, email, agent } = getCurrentAgent();
298
+
299
+ if (agent === this) {
300
+ // already wrapped, so we can just call the method
301
+ return method.apply(this, args);
302
+ }
303
+ // not wrapped, so we need to wrap it
268
304
  return agentContext.run({ agent: this, connection, request, email }, () => {
269
305
  return method.apply(this, args);
270
306
  });
@@ -276,13 +312,21 @@ function withAgentContext<T extends (...args: any[]) => any>(
276
312
  * @template Env Environment type containing bindings
277
313
  * @template State State type to store within the Agent
278
314
  */
279
- export class Agent<Env, State = unknown> extends Server<Env> {
315
+ export class Agent<
316
+ Env = typeof env,
317
+ State = unknown,
318
+ Props extends Record<string, unknown> = Record<string, unknown>
319
+ > extends Server<Env, Props> {
280
320
  private _state = DEFAULT_STATE as State;
321
+ private _disposables = new DisposableStore();
281
322
 
282
323
  private _ParentClass: typeof Agent<Env, State> =
283
324
  Object.getPrototypeOf(this).constructor;
284
325
 
285
- mcp: MCPClientManager = new MCPClientManager(this._ParentClass.name, "0.0.1");
326
+ readonly mcp: MCPClientManager = new MCPClientManager(
327
+ this._ParentClass.name,
328
+ "0.0.1"
329
+ );
286
330
 
287
331
  /**
288
332
  * Initial state for the Agent
@@ -375,6 +419,26 @@ export class Agent<Env, State = unknown> extends Server<Env> {
375
419
  constructor(ctx: AgentContext, env: Env) {
376
420
  super(ctx, env);
377
421
 
422
+ if (!wrappedClasses.has(this.constructor)) {
423
+ // Auto-wrap custom methods with agent context
424
+ this._autoWrapCustomMethods();
425
+ wrappedClasses.add(this.constructor);
426
+ }
427
+
428
+ // Broadcast server state after background connects (for OAuth servers)
429
+ this._disposables.add(
430
+ this.mcp.onConnected(async () => {
431
+ this.broadcastMcpServers();
432
+ })
433
+ );
434
+
435
+ // Emit MCP observability events
436
+ this._disposables.add(
437
+ this.mcp.onObservabilityEvent((event) => {
438
+ this.observability?.emit(event);
439
+ })
440
+ );
441
+
378
442
  this.sql`
379
443
  CREATE TABLE IF NOT EXISTS cf_agents_state (
380
444
  id TEXT PRIMARY KEY NOT NULL,
@@ -382,8 +446,14 @@ export class Agent<Env, State = unknown> extends Server<Env> {
382
446
  )
383
447
  `;
384
448
 
385
- // Auto-wrap custom methods with agent context
386
- this._autoWrapCustomMethods();
449
+ this.sql`
450
+ CREATE TABLE IF NOT EXISTS cf_agents_queues (
451
+ id TEXT PRIMARY KEY NOT NULL,
452
+ payload TEXT,
453
+ callback TEXT,
454
+ created_at INTEGER DEFAULT (unixepoch())
455
+ )
456
+ `;
387
457
 
388
458
  void this.ctx.blockConcurrencyWhile(async () => {
389
459
  return this._tryCatch(async () => {
@@ -424,21 +494,24 @@ export class Agent<Env, State = unknown> extends Server<Env> {
424
494
  { agent: this, connection: undefined, request, email: undefined },
425
495
  async () => {
426
496
  if (this.mcp.isCallbackRequest(request)) {
427
- await this.mcp.handleCallbackRequest(request);
428
-
429
- // after the MCP connection handshake, we can send updated mcp state
430
- this.broadcast(
431
- JSON.stringify({
432
- mcp: this.getMcpServers(),
433
- type: "cf_agent_mcp_servers"
434
- })
435
- );
497
+ const result = await this.mcp.handleCallbackRequest(request);
498
+ this.broadcastMcpServers();
499
+
500
+ if (result.authSuccess) {
501
+ // Start background connection if auth was successful
502
+ this.mcp
503
+ .establishConnection(result.serverId)
504
+ .catch((error) => {
505
+ console.error("Background connection failed:", error);
506
+ })
507
+ .finally(() => {
508
+ // Broadcast after background connection resolves (success/failure)
509
+ this.broadcastMcpServers();
510
+ });
511
+ }
436
512
 
437
- // We probably should let the user configure this response/redirect, but this is fine for now.
438
- return new Response("<script>window.close();</script>", {
439
- headers: { "content-type": "text/html" },
440
- status: 200
441
- });
513
+ // Handle OAuth callback response using MCPClientManager configuration
514
+ return this.handleOAuthCallbackResponse(result, request);
442
515
  }
443
516
 
444
517
  return this._tryCatch(() => _onRequest(request));
@@ -499,10 +572,8 @@ export class Agent<Env, State = unknown> extends Server<Env> {
499
572
  displayMessage: `RPC call to ${method}`,
500
573
  id: nanoid(),
501
574
  payload: {
502
- args,
503
575
  method,
504
- streaming: metadata?.streaming,
505
- success: true
576
+ streaming: metadata?.streaming
506
577
  },
507
578
  timestamp: Date.now(),
508
579
  type: "rpc"
@@ -515,7 +586,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
515
586
  id,
516
587
  result,
517
588
  success: true,
518
- type: "rpc"
589
+ type: MessageType.RPC
519
590
  };
520
591
  connection.send(JSON.stringify(response));
521
592
  } catch (e) {
@@ -525,7 +596,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
525
596
  e instanceof Error ? e.message : "Unknown error occurred",
526
597
  id: parsed.id,
527
598
  success: false,
528
- type: "rpc"
599
+ type: MessageType.RPC
529
600
  };
530
601
  connection.send(JSON.stringify(response));
531
602
  console.error("RPC error:", e);
@@ -544,44 +615,42 @@ export class Agent<Env, State = unknown> extends Server<Env> {
544
615
  // must fix this
545
616
  return agentContext.run(
546
617
  { agent: this, connection, request: ctx.request, email: undefined },
547
- async () => {
548
- setTimeout(() => {
549
- if (this.state) {
550
- connection.send(
551
- JSON.stringify({
552
- state: this.state,
553
- type: "cf_agent_state"
554
- })
555
- );
556
- }
557
-
618
+ () => {
619
+ if (this.state) {
558
620
  connection.send(
559
621
  JSON.stringify({
560
- mcp: this.getMcpServers(),
561
- type: "cf_agent_mcp_servers"
622
+ state: this.state,
623
+ type: MessageType.CF_AGENT_STATE
562
624
  })
563
625
  );
626
+ }
564
627
 
565
- this.observability?.emit(
566
- {
567
- displayMessage: "Connection established",
568
- id: nanoid(),
569
- payload: {
570
- connectionId: connection.id
571
- },
572
- timestamp: Date.now(),
573
- type: "connect"
628
+ connection.send(
629
+ JSON.stringify({
630
+ mcp: this.getMcpServers(),
631
+ type: MessageType.CF_AGENT_MCP_SERVERS
632
+ })
633
+ );
634
+
635
+ this.observability?.emit(
636
+ {
637
+ displayMessage: "Connection established",
638
+ id: nanoid(),
639
+ payload: {
640
+ connectionId: connection.id
574
641
  },
575
- this.ctx
576
- );
577
- return this._tryCatch(() => _onConnect(connection, ctx));
578
- }, 20);
642
+ timestamp: Date.now(),
643
+ type: "connect"
644
+ },
645
+ this.ctx
646
+ );
647
+ return this._tryCatch(() => _onConnect(connection, ctx));
579
648
  }
580
649
  );
581
650
  };
582
651
 
583
652
  const _onStart = this.onStart.bind(this);
584
- this.onStart = async () => {
653
+ this.onStart = async (props?: Props) => {
585
654
  return agentContext.run(
586
655
  {
587
656
  agent: this,
@@ -590,15 +659,27 @@ export class Agent<Env, State = unknown> extends Server<Env> {
590
659
  email: undefined
591
660
  },
592
661
  async () => {
593
- const servers = this.sql<MCPServerRow>`
662
+ await this._tryCatch(() => {
663
+ const servers = this.sql<MCPServerRow>`
594
664
  SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
595
665
  `;
596
666
 
597
- // from DO storage, reconnect to all servers not currently in the oauth flow using our saved auth information
598
- if (servers && Array.isArray(servers) && servers.length > 0) {
599
- Promise.allSettled(
600
- servers.map((server) => {
601
- return this._connectToMcpServerInternal(
667
+ this.broadcastMcpServers();
668
+
669
+ // from DO storage, reconnect to all servers not currently in the oauth flow using our saved auth information
670
+ if (servers && Array.isArray(servers) && servers.length > 0) {
671
+ // Restore callback URLs for OAuth-enabled servers
672
+ servers.forEach((server) => {
673
+ if (server.callback_url) {
674
+ // Register the full redirect URL including serverId to avoid ambiguous matches
675
+ this.mcp.registerCallbackUrl(
676
+ `${server.callback_url}/${server.id}`
677
+ );
678
+ }
679
+ });
680
+
681
+ servers.forEach((server) => {
682
+ this._connectToMcpServerInternal(
602
683
  server.name,
603
684
  server.server_url,
604
685
  server.callback_url,
@@ -609,18 +690,23 @@ export class Agent<Env, State = unknown> extends Server<Env> {
609
690
  id: server.id,
610
691
  oauthClientId: server.client_id ?? undefined
611
692
  }
612
- );
613
- })
614
- ).then((_results) => {
615
- this.broadcast(
616
- JSON.stringify({
617
- mcp: this.getMcpServers(),
618
- type: "cf_agent_mcp_servers"
619
- })
620
- );
621
- });
622
- }
623
- await this._tryCatch(() => _onStart());
693
+ )
694
+ .then(() => {
695
+ // Broadcast updated MCP servers state after each server connects
696
+ this.broadcastMcpServers();
697
+ })
698
+ .catch((error) => {
699
+ console.error(
700
+ `Error connecting to MCP server: ${server.name} (${server.server_url})`,
701
+ error
702
+ );
703
+ // Still broadcast even if connection fails, so clients know about the failure
704
+ this.broadcastMcpServers();
705
+ });
706
+ });
707
+ }
708
+ return _onStart(props);
709
+ });
624
710
  }
625
711
  );
626
712
  };
@@ -630,7 +716,6 @@ export class Agent<Env, State = unknown> extends Server<Env> {
630
716
  state: State,
631
717
  source: Connection | "server" = "server"
632
718
  ) {
633
- const previousState = this._state;
634
719
  this._state = state;
635
720
  this.sql`
636
721
  INSERT OR REPLACE INTO cf_agents_state (id, state)
@@ -643,7 +728,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
643
728
  this.broadcast(
644
729
  JSON.stringify({
645
730
  state: state,
646
- type: "cf_agent_state"
731
+ type: MessageType.CF_AGENT_STATE
647
732
  }),
648
733
  source !== "server" ? [source.id] : []
649
734
  );
@@ -656,10 +741,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
656
741
  {
657
742
  displayMessage: "State updated",
658
743
  id: nanoid(),
659
- payload: {
660
- previousState,
661
- state
662
- },
744
+ payload: {},
663
745
  timestamp: Date.now(),
664
746
  type: "state:update"
665
747
  },
@@ -799,41 +881,37 @@ export class Agent<Env, State = unknown> extends Server<Env> {
799
881
  while (proto && proto !== Object.prototype && depth < 10) {
800
882
  const methodNames = Object.getOwnPropertyNames(proto);
801
883
  for (const methodName of methodNames) {
802
- // Skip if it's a private method or not a function
884
+ const descriptor = Object.getOwnPropertyDescriptor(proto, methodName);
885
+
886
+ // Skip if it's a private method, a base method, a getter, or not a function,
803
887
  if (
804
888
  baseMethods.has(methodName) ||
805
889
  methodName.startsWith("_") ||
806
- typeof this[methodName as keyof this] !== "function"
890
+ !descriptor ||
891
+ !!descriptor.get ||
892
+ typeof descriptor.value !== "function"
807
893
  ) {
808
894
  continue;
809
895
  }
810
- // If the method doesn't exist in base prototypes, it's a custom method
811
- if (!baseMethods.has(methodName)) {
812
- const descriptor = Object.getOwnPropertyDescriptor(proto, methodName);
813
- if (descriptor && typeof descriptor.value === "function") {
814
- // Wrap the custom method with context
815
-
816
- const wrappedFunction = withAgentContext(
817
- // biome-ignore lint/suspicious/noExplicitAny: I can't typescript
818
- this[methodName as keyof this] as (...args: any[]) => any
819
- // biome-ignore lint/suspicious/noExplicitAny: I can't typescript
820
- ) as any;
821
-
822
- // if the method is callable, copy the metadata from the original method
823
- if (this._isCallable(methodName)) {
824
- callableMetadata.set(
825
- wrappedFunction,
826
- callableMetadata.get(
827
- this[methodName as keyof this] as Function
828
- )!
829
- );
830
- }
831
896
 
832
- // set the wrapped function on the prototype
833
- this.constructor.prototype[methodName as keyof this] =
834
- wrappedFunction;
835
- }
897
+ // Now, methodName is confirmed to be a custom method/function
898
+ // Wrap the custom method with context
899
+ const wrappedFunction = withAgentContext(
900
+ // biome-ignore lint/suspicious/noExplicitAny: I can't typescript
901
+ this[methodName as keyof this] as (...args: any[]) => any
902
+ // biome-ignore lint/suspicious/noExplicitAny: I can't typescript
903
+ ) as any;
904
+
905
+ // if the method is callable, copy the metadata from the original method
906
+ if (this._isCallable(methodName)) {
907
+ callableMetadata.set(
908
+ wrappedFunction,
909
+ callableMetadata.get(this[methodName as keyof this] as Function)!
910
+ );
836
911
  }
912
+
913
+ // set the wrapped function on the prototype
914
+ this.constructor.prototype[methodName as keyof this] = wrappedFunction;
837
915
  }
838
916
 
839
917
  proto = Object.getPrototypeOf(proto);
@@ -875,6 +953,131 @@ export class Agent<Env, State = unknown> extends Server<Env> {
875
953
  throw new Error("Not implemented");
876
954
  }
877
955
 
956
+ /**
957
+ * Queue a task to be executed in the future
958
+ * @param payload Payload to pass to the callback
959
+ * @param callback Name of the method to call
960
+ * @returns The ID of the queued task
961
+ */
962
+ async queue<T = unknown>(callback: keyof this, payload: T): Promise<string> {
963
+ const id = nanoid(9);
964
+ if (typeof callback !== "string") {
965
+ throw new Error("Callback must be a string");
966
+ }
967
+
968
+ if (typeof this[callback] !== "function") {
969
+ throw new Error(`this.${callback} is not a function`);
970
+ }
971
+
972
+ this.sql`
973
+ INSERT OR REPLACE INTO cf_agents_queues (id, payload, callback)
974
+ VALUES (${id}, ${JSON.stringify(payload)}, ${callback})
975
+ `;
976
+
977
+ void this._flushQueue().catch((e) => {
978
+ console.error("Error flushing queue:", e);
979
+ });
980
+
981
+ return id;
982
+ }
983
+
984
+ private _flushingQueue = false;
985
+
986
+ private async _flushQueue() {
987
+ if (this._flushingQueue) {
988
+ return;
989
+ }
990
+ this._flushingQueue = true;
991
+ while (true) {
992
+ const result = this.sql<QueueItem<string>>`
993
+ SELECT * FROM cf_agents_queues
994
+ ORDER BY created_at ASC
995
+ `;
996
+
997
+ if (!result || result.length === 0) {
998
+ break;
999
+ }
1000
+
1001
+ for (const row of result || []) {
1002
+ const callback = this[row.callback as keyof Agent<Env>];
1003
+ if (!callback) {
1004
+ console.error(`callback ${row.callback} not found`);
1005
+ continue;
1006
+ }
1007
+ const { connection, request, email } = agentContext.getStore() || {};
1008
+ await agentContext.run(
1009
+ {
1010
+ agent: this,
1011
+ connection,
1012
+ request,
1013
+ email
1014
+ },
1015
+ async () => {
1016
+ // TODO: add retries and backoff
1017
+ await (
1018
+ callback as (
1019
+ payload: unknown,
1020
+ queueItem: QueueItem<string>
1021
+ ) => Promise<void>
1022
+ ).bind(this)(JSON.parse(row.payload as string), row);
1023
+ await this.dequeue(row.id);
1024
+ }
1025
+ );
1026
+ }
1027
+ }
1028
+ this._flushingQueue = false;
1029
+ }
1030
+
1031
+ /**
1032
+ * Dequeue a task by ID
1033
+ * @param id ID of the task to dequeue
1034
+ */
1035
+ async dequeue(id: string) {
1036
+ this.sql`DELETE FROM cf_agents_queues WHERE id = ${id}`;
1037
+ }
1038
+
1039
+ /**
1040
+ * Dequeue all tasks
1041
+ */
1042
+ async dequeueAll() {
1043
+ this.sql`DELETE FROM cf_agents_queues`;
1044
+ }
1045
+
1046
+ /**
1047
+ * Dequeue all tasks by callback
1048
+ * @param callback Name of the callback to dequeue
1049
+ */
1050
+ async dequeueAllByCallback(callback: string) {
1051
+ this.sql`DELETE FROM cf_agents_queues WHERE callback = ${callback}`;
1052
+ }
1053
+
1054
+ /**
1055
+ * Get a queued task by ID
1056
+ * @param id ID of the task to get
1057
+ * @returns The task or undefined if not found
1058
+ */
1059
+ async getQueue(id: string): Promise<QueueItem<string> | undefined> {
1060
+ const result = this.sql<QueueItem<string>>`
1061
+ SELECT * FROM cf_agents_queues WHERE id = ${id}
1062
+ `;
1063
+ return result
1064
+ ? { ...result[0], payload: JSON.parse(result[0].payload) }
1065
+ : undefined;
1066
+ }
1067
+
1068
+ /**
1069
+ * Get all queues by key and value
1070
+ * @param key Key to filter by
1071
+ * @param value Value to filter by
1072
+ * @returns Array of matching QueueItem objects
1073
+ */
1074
+ async getQueues(key: string, value: string): Promise<QueueItem<string>[]> {
1075
+ const result = this.sql<QueueItem<string>>`
1076
+ SELECT * FROM cf_agents_queues
1077
+ `;
1078
+ return result.filter((row) => JSON.parse(row.payload)[key] === value);
1079
+ }
1080
+
878
1081
  /**
879
1082
  * Schedule a task to be executed in the future
880
1083
  * @template T Type of the payload data
@@ -895,7 +1098,10 @@ export class Agent<Env, State = unknown> extends Server<Env> {
895
1098
  {
896
1099
  displayMessage: `Schedule ${schedule.id} created`,
897
1100
  id: nanoid(),
898
- payload: schedule,
1101
+ payload: {
1102
+ callback: callback as string,
1103
+ id: id
1104
+ },
899
1105
  timestamp: Date.now(),
900
1106
  type: "schedule:create"
901
1107
  },
@@ -1065,7 +1271,10 @@ export class Agent<Env, State = unknown> extends Server<Env> {
1065
1271
  {
1066
1272
  displayMessage: `Schedule ${id} cancelled`,
1067
1273
  id: nanoid(),
1068
- payload: schedule,
1274
+ payload: {
1275
+ callback: schedule.callback,
1276
+ id: schedule.id
1277
+ },
1069
1278
  timestamp: Date.now(),
1070
1279
  type: "schedule:cancel"
1071
1280
  },
@@ -1081,9 +1290,9 @@ export class Agent<Env, State = unknown> extends Server<Env> {
1081
1290
  private async _scheduleNextAlarm() {
1082
1291
  // Find the next schedule that needs to be executed
1083
1292
  const result = this.sql`
1084
- SELECT time FROM cf_agents_schedules
1293
+ SELECT time FROM cf_agents_schedules
1085
1294
  WHERE time > ${Math.floor(Date.now() / 1000)}
1086
- ORDER BY time ASC
1295
+ ORDER BY time ASC
1087
1296
  LIMIT 1
1088
1297
  `;
1089
1298
  if (!result) return;
@@ -1130,7 +1339,10 @@ export class Agent<Env, State = unknown> extends Server<Env> {
1130
1339
  {
1131
1340
  displayMessage: `Schedule ${row.id} executed`,
1132
1341
  id: nanoid(),
1133
- payload: row,
1342
+ payload: {
1343
+ callback: row.callback,
1344
+ id: row.id
1345
+ },
1134
1346
  timestamp: Date.now(),
1135
1347
  type: "schedule:execute"
1136
1348
  },
@@ -1177,10 +1389,13 @@ export class Agent<Env, State = unknown> extends Server<Env> {
1177
1389
  this.sql`DROP TABLE IF EXISTS cf_agents_state`;
1178
1390
  this.sql`DROP TABLE IF EXISTS cf_agents_schedules`;
1179
1391
  this.sql`DROP TABLE IF EXISTS cf_agents_mcp_servers`;
1392
+ this.sql`DROP TABLE IF EXISTS cf_agents_queues`;
1180
1393
 
1181
1394
  // delete all alarms
1182
1395
  await this.ctx.storage.deleteAlarm();
1183
1396
  await this.ctx.storage.deleteAll();
1397
+ this._disposables.dispose();
1398
+ await this.mcp.dispose?.();
1184
1399
  this.ctx.abort("destroyed"); // enforce that the agent is evicted
1185
1400
 
1186
1401
  this.observability?.emit(
@@ -1206,25 +1421,42 @@ export class Agent<Env, State = unknown> extends Server<Env> {
1206
1421
  /**
1207
1422
  * Connect to a new MCP Server
1208
1423
  *
1424
+ * @param serverName Name of the MCP server
1209
1425
  * @param url MCP Server SSE URL
1210
- * @param callbackHost Base host for the agent, used for the redirect URI.
1426
+ * @param callbackHost Base host for the agent, used for the redirect URI. If not provided, will be derived from the current request.
1211
1427
  * @param agentsPrefix agents routing prefix if not using `agents`
1212
- * @param options MCP client and transport (header) options
1428
+ * @param options MCP client and transport options
1213
1429
  * @returns authUrl
1214
1430
  */
1215
1431
  async addMcpServer(
1216
1432
  serverName: string,
1217
1433
  url: string,
1218
- callbackHost: string,
1434
+ callbackHost?: string,
1219
1435
  agentsPrefix = "agents",
1220
1436
  options?: {
1221
1437
  client?: ConstructorParameters<typeof Client>[1];
1222
1438
  transport?: {
1223
- headers: HeadersInit;
1439
+ headers?: HeadersInit;
1440
+ type?: TransportType;
1224
1441
  };
1225
1442
  }
1226
1443
  ): Promise<{ id: string; authUrl: string | undefined }> {
1227
- const callbackUrl = `${callbackHost}/${agentsPrefix}/${camelCaseToKebabCase(this._ParentClass.name)}/${this.name}/callback`;
1444
+ // If callbackHost is not provided, derive it from the current request
1445
+ let resolvedCallbackHost = callbackHost;
1446
+ if (!resolvedCallbackHost) {
1447
+ const { request } = getCurrentAgent();
1448
+ if (!request) {
1449
+ throw new Error(
1450
+ "callbackHost is required when not called within a request context"
1451
+ );
1452
+ }
1453
+
1454
+ // Extract the origin from the request
1455
+ const requestUrl = new URL(request.url);
1456
+ resolvedCallbackHost = `${requestUrl.protocol}//${requestUrl.host}`;
1457
+ }
1458
+
1459
+ const callbackUrl = `${resolvedCallbackHost}/${agentsPrefix}/${camelCaseToKebabCase(this._ParentClass.name)}/${this.name}/callback`;
1228
1460
 
1229
1461
  const result = await this._connectToMcpServerInternal(
1230
1462
  serverName,
@@ -1232,6 +1464,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
1232
1464
  callbackUrl,
1233
1465
  options
1234
1466
  );
1467
+
1235
1468
  this.sql`
1236
1469
  INSERT
1237
1470
  OR REPLACE INTO cf_agents_mcp_servers (id, name, server_url, client_id, auth_url, callback_url, server_options)
@@ -1246,17 +1479,12 @@ export class Agent<Env, State = unknown> extends Server<Env> {
1246
1479
  );
1247
1480
  `;
1248
1481
 
1249
- this.broadcast(
1250
- JSON.stringify({
1251
- mcp: this.getMcpServers(),
1252
- type: "cf_agent_mcp_servers"
1253
- })
1254
- );
1482
+ this.broadcastMcpServers();
1255
1483
 
1256
1484
  return result;
1257
1485
  }
1258
1486
 
1259
- async _connectToMcpServerInternal(
1487
+ private async _connectToMcpServerInternal(
1260
1488
  _serverName: string,
1261
1489
  url: string,
1262
1490
  callbackUrl: string,
@@ -1272,6 +1500,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
1272
1500
  */
1273
1501
  transport?: {
1274
1502
  headers?: HeadersInit;
1503
+ type?: TransportType;
1275
1504
  };
1276
1505
  },
1277
1506
  reconnect?: {
@@ -1296,6 +1525,9 @@ export class Agent<Env, State = unknown> extends Server<Env> {
1296
1525
  }
1297
1526
  }
1298
1527
 
1528
+ // Use the transport type specified in options, or default to "auto"
1529
+ const transportType: TransportType = options?.transport?.type ?? "auto";
1530
+
1299
1531
  // allows passing through transport headers if necessary
1300
1532
  // this handles some non-standard bearer auth setups (i.e. MCP server behind CF access instead of OAuth)
1301
1533
  let headerTransportOpts: SSEClientTransportOptions = {};
@@ -1319,7 +1551,8 @@ export class Agent<Env, State = unknown> extends Server<Env> {
1319
1551
  reconnect,
1320
1552
  transport: {
1321
1553
  ...headerTransportOpts,
1322
- authProvider
1554
+ authProvider,
1555
+ type: transportType
1323
1556
  }
1324
1557
  });
1325
1558
 
@@ -1332,15 +1565,11 @@ export class Agent<Env, State = unknown> extends Server<Env> {
1332
1565
 
1333
1566
  async removeMcpServer(id: string) {
1334
1567
  this.mcp.closeConnection(id);
1568
+ this.mcp.unregisterCallbackUrl(id);
1335
1569
  this.sql`
1336
1570
  DELETE FROM cf_agents_mcp_servers WHERE id = ${id};
1337
1571
  `;
1338
- this.broadcast(
1339
- JSON.stringify({
1340
- mcp: this.getMcpServers(),
1341
- type: "cf_agent_mcp_servers"
1342
- })
1343
- );
1572
+ this.broadcastMcpServers();
1344
1573
  }
1345
1574
 
1346
1575
  getMcpServers(): MCPServersState {
@@ -1372,8 +1601,53 @@ export class Agent<Env, State = unknown> extends Server<Env> {
1372
1601
 
1373
1602
  return mcpState;
1374
1603
  }
1604
+
1605
+ private broadcastMcpServers() {
1606
+ this.broadcast(
1607
+ JSON.stringify({
1608
+ mcp: this.getMcpServers(),
1609
+ type: MessageType.CF_AGENT_MCP_SERVERS
1610
+ })
1611
+ );
1612
+ }
1613
+
1614
+ /**
1615
+ * Handle OAuth callback response using MCPClientManager configuration
1616
+ * @param result OAuth callback result
1617
+ * @param request The original request (needed for base URL)
1618
+ * @returns Response for the OAuth callback
1619
+ */
1620
+ private handleOAuthCallbackResponse(
1621
+ result: MCPClientOAuthResult,
1622
+ request: Request
1623
+ ): Response {
1624
+ const config = this.mcp.getOAuthCallbackConfig();
1625
+
1626
+ // Use custom handler if configured
1627
+ if (config?.customHandler) {
1628
+ return config.customHandler(result);
1629
+ }
1630
+
1631
+ // Use redirect URLs if configured
1632
+ if (config?.successRedirect && result.authSuccess) {
1633
+ return Response.redirect(config.successRedirect);
1634
+ }
1635
+
1636
+ if (config?.errorRedirect && !result.authSuccess) {
1637
+ return Response.redirect(
1638
+ `${config.errorRedirect}?error=${encodeURIComponent(result.authError || "Unknown error")}`
1639
+ );
1640
+ }
1641
+
1642
+ // Default behavior - redirect to base URL
1643
+ const baseUrl = new URL(request.url).origin;
1644
+ return Response.redirect(baseUrl);
1645
+ }
1375
1646
  }
1376
1647
 
1648
+ // A set of classes that have been wrapped with agent context
1649
+ const wrappedClasses = new Set<typeof Agent.prototype.constructor>();
1650
+
1377
1651
  /**
1378
1652
  * Namespace for creating Agent instances
1379
1653
  * @template Agentic Type of the Agent class
@@ -1550,6 +1824,13 @@ export type EmailRoutingOptions<Env> = AgentOptions<Env> & {
1550
1824
  resolver: EmailResolver<Env>;
1551
1825
  };
1552
1826
 
1827
+ // Cache the agent namespace map for email routing
1828
+ // This maps both kebab-case and original names to namespaces
1829
+ const agentMapCache = new WeakMap<
1830
+ Record<string, unknown>,
1831
+ Record<string, unknown>
1832
+ >();
1833
+
1553
1834
  /**
1554
1835
  * Route an email to the appropriate Agent
1555
1836
  * @param email The email to route
@@ -1569,28 +1850,41 @@ export async function routeAgentEmail<Env>(
1569
1850
  return;
1570
1851
  }
1571
1852
 
1572
- const namespaceBinding = env[routingInfo.agentName as keyof Env];
1573
- if (!namespaceBinding) {
1574
- throw new Error(
1575
- `Agent namespace '${routingInfo.agentName}' not found in environment`
1576
- );
1853
+ // Build a map that includes both original names and kebab-case versions
1854
+ if (!agentMapCache.has(env as Record<string, unknown>)) {
1855
+ const map: Record<string, unknown> = {};
1856
+ for (const [key, value] of Object.entries(env as Record<string, unknown>)) {
1857
+ if (
1858
+ value &&
1859
+ typeof value === "object" &&
1860
+ "idFromName" in value &&
1861
+ typeof value.idFromName === "function"
1862
+ ) {
1863
+ // Add both the original name and kebab-case version
1864
+ map[key] = value;
1865
+ map[camelCaseToKebabCase(key)] = value;
1866
+ }
1867
+ }
1868
+ agentMapCache.set(env as Record<string, unknown>, map);
1577
1869
  }
1578
1870
 
1579
- // Type guard to check if this is actually a DurableObjectNamespace (AgentNamespace)
1580
- if (
1581
- typeof namespaceBinding !== "object" ||
1582
- !("idFromName" in namespaceBinding) ||
1583
- typeof namespaceBinding.idFromName !== "function"
1584
- ) {
1871
+ const agentMap = agentMapCache.get(env as Record<string, unknown>)!;
1872
+ const namespace = agentMap[routingInfo.agentName];
1873
+
1874
+ if (!namespace) {
1875
+ // Provide helpful error message listing available agents
1876
+ const availableAgents = Object.keys(agentMap)
1877
+ .filter((key) => !key.includes("-")) // Show only original names, not kebab-case duplicates
1878
+ .join(", ");
1585
1879
  throw new Error(
1586
- `Environment binding '${routingInfo.agentName}' is not an AgentNamespace (found: ${typeof namespaceBinding})`
1880
+ `Agent namespace '${routingInfo.agentName}' not found in environment. Available agents: ${availableAgents}`
1587
1881
  );
1588
1882
  }
1589
1883
 
1590
- // Safe cast after runtime validation
1591
- const namespace = namespaceBinding as unknown as AgentNamespace<Agent<Env>>;
1592
-
1593
- const agent = await getAgentByName(namespace, routingInfo.agentId);
1884
+ const agent = await getAgentByName(
1885
+ namespace as unknown as AgentNamespace<Agent<Env>>,
1886
+ routingInfo.agentId
1887
+ );
1594
1888
 
1595
1889
  // let's make a serialisable version of the email
1596
1890
  const serialisableEmail: AgentEmail = {
@@ -1669,12 +1963,17 @@ export type EmailSendOptions = {
1669
1963
  * @param options Options for Agent creation
1670
1964
  * @returns Promise resolving to an Agent instance stub
1671
1965
  */
1672
- export async function getAgentByName<Env, T extends Agent<Env>>(
1966
+ export async function getAgentByName<
1967
+ Env,
1968
+ T extends Agent<Env>,
1969
+ Props extends Record<string, unknown> = Record<string, unknown>
1970
+ >(
1673
1971
  namespace: AgentNamespace<T>,
1674
1972
  name: string,
1675
1973
  options?: {
1676
1974
  jurisdiction?: DurableObjectJurisdiction;
1677
1975
  locationHint?: DurableObjectLocationHint;
1976
+ props?: Props;
1678
1977
  }
1679
1978
  ) {
1680
1979
  return getServerByName<Env, T>(namespace, name, options);
@@ -1706,7 +2005,7 @@ export class StreamingResponse {
1706
2005
  id: this._id,
1707
2006
  result: chunk,
1708
2007
  success: true,
1709
- type: "rpc"
2008
+ type: MessageType.RPC
1710
2009
  };
1711
2010
  this._connection.send(JSON.stringify(response));
1712
2011
  }
@@ -1725,7 +2024,7 @@ export class StreamingResponse {
1725
2024
  id: this._id,
1726
2025
  result: finalChunk,
1727
2026
  success: true,
1728
- type: "rpc"
2027
+ type: MessageType.RPC
1729
2028
  };
1730
2029
  this._connection.send(JSON.stringify(response));
1731
2030
  }