agents 0.0.0-d40512c → 0.0.0-d4257c1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +234 -6
- package/dist/ai-chat-agent.d.ts +20 -18
- package/dist/ai-chat-agent.js +532 -260
- package/dist/ai-chat-agent.js.map +1 -1
- package/dist/ai-chat-v5-migration-gdyLiTd8.js +155 -0
- package/dist/ai-chat-v5-migration-gdyLiTd8.js.map +1 -0
- package/dist/ai-chat-v5-migration.d.ts +155 -0
- package/dist/ai-chat-v5-migration.js +3 -0
- package/dist/ai-react.d.ts +73 -85
- package/dist/ai-react.js +260 -199
- package/dist/ai-react.js.map +1 -1
- package/dist/ai-types-BWW4umHY.d.ts +95 -0
- package/dist/ai-types-UZlfLOYP.js +20 -0
- package/dist/ai-types-UZlfLOYP.js.map +1 -0
- package/dist/ai-types.d.ts +6 -74
- package/dist/ai-types.js +3 -1
- package/dist/client-C-nwz-3N.d.ts +5313 -0
- package/dist/client-CZBVDDoO.js +786 -0
- package/dist/client-CZBVDDoO.js.map +1 -0
- package/dist/client-CmMi85Sj.d.ts +104 -0
- package/dist/client-DjR-lC16.js +117 -0
- package/dist/client-DjR-lC16.js.map +1 -0
- package/dist/client.d.ts +11 -92
- package/dist/client.js +4 -11
- package/dist/codemode/ai.d.ts +27 -0
- package/dist/codemode/ai.js +151 -0
- package/dist/codemode/ai.js.map +1 -0
- package/dist/do-oauth-client-provider-B2jr6UNq.js +93 -0
- package/dist/do-oauth-client-provider-B2jr6UNq.js.map +1 -0
- package/dist/do-oauth-client-provider-CCwGwnrA.d.ts +55 -0
- package/dist/{index-CITGJflw.d.ts → index-CkQU40oY.d.ts} +219 -147
- package/dist/index-W4JUkafc.d.ts +54 -0
- package/dist/index.d.ts +69 -30
- package/dist/index.js +7 -22
- package/dist/mcp/client.d.ts +4 -1055
- package/dist/mcp/client.js +3 -9
- package/dist/mcp/do-oauth-client-provider.d.ts +2 -41
- package/dist/mcp/do-oauth-client-provider.js +3 -7
- package/dist/mcp/index.d.ts +73 -82
- package/dist/mcp/index.js +836 -772
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/x402.d.ts +34 -0
- package/dist/mcp/x402.js +194 -0
- package/dist/mcp/x402.js.map +1 -0
- package/dist/mcp-BEwaCsxO.d.ts +61 -0
- package/dist/observability/index.d.ts +3 -12
- package/dist/observability/index.js +7 -10
- package/dist/react-B4e1rDid.d.ts +113 -0
- package/dist/react.d.ts +10 -119
- package/dist/react.js +183 -110
- package/dist/react.js.map +1 -1
- package/dist/schedule.d.ts +91 -14
- package/dist/schedule.js +46 -21
- package/dist/schedule.js.map +1 -1
- package/dist/serializable-gtr9YMhp.d.ts +34 -0
- package/dist/serializable.d.ts +7 -32
- package/dist/serializable.js +1 -1
- package/dist/src-COfG--3R.js +1179 -0
- package/dist/src-COfG--3R.js.map +1 -0
- package/package.json +45 -10
- package/src/index.ts +853 -187
- package/dist/ai-types.js.map +0 -1
- package/dist/chunk-4RBEYCWK.js +0 -469
- package/dist/chunk-4RBEYCWK.js.map +0 -1
- package/dist/chunk-KUH345EY.js +0 -116
- package/dist/chunk-KUH345EY.js.map +0 -1
- package/dist/chunk-LU2RSO54.js +0 -910
- package/dist/chunk-LU2RSO54.js.map +0 -1
- package/dist/chunk-PVQZBKN7.js +0 -106
- package/dist/chunk-PVQZBKN7.js.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/mcp/client.js.map +0 -1
- package/dist/mcp/do-oauth-client-provider.js.map +0 -1
- package/dist/observability/index.js.map +0 -1
- package/dist/serializable.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { env } from "cloudflare:workers";
|
|
1
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
3
|
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
4
|
import type { SSEClientTransportOptions } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
@@ -10,20 +11,24 @@ import type {
|
|
|
10
11
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
11
12
|
import { parseCronExpression } from "cron-schedule";
|
|
12
13
|
import { nanoid } from "nanoid";
|
|
14
|
+
import { EmailMessage } from "cloudflare:email";
|
|
13
15
|
import {
|
|
14
16
|
type Connection,
|
|
15
17
|
type ConnectionContext,
|
|
16
|
-
getServerByName,
|
|
17
18
|
type PartyServerOptions,
|
|
18
|
-
routePartykitRequest,
|
|
19
19
|
Server,
|
|
20
|
-
type WSMessage
|
|
20
|
+
type WSMessage,
|
|
21
|
+
getServerByName,
|
|
22
|
+
routePartykitRequest
|
|
21
23
|
} from "partyserver";
|
|
22
24
|
import { camelCaseToKebabCase } from "./client";
|
|
23
|
-
import { MCPClientManager } from "./mcp/client";
|
|
24
|
-
|
|
25
|
+
import { MCPClientManager, type MCPClientOAuthResult } from "./mcp/client";
|
|
26
|
+
import type { MCPConnectionState } from "./mcp/client-connection";
|
|
25
27
|
import { DurableObjectOAuthClientProvider } from "./mcp/do-oauth-client-provider";
|
|
28
|
+
import type { TransportType } from "./mcp/types";
|
|
26
29
|
import { genericObservability, type Observability } from "./observability";
|
|
30
|
+
import { DisposableStore } from "./core/events";
|
|
31
|
+
import { MessageType } from "./ai-types";
|
|
27
32
|
|
|
28
33
|
export type { Connection, ConnectionContext, WSMessage } from "partyserver";
|
|
29
34
|
|
|
@@ -41,7 +46,7 @@ export type RPCRequest = {
|
|
|
41
46
|
* State update message from client
|
|
42
47
|
*/
|
|
43
48
|
export type StateUpdateMessage = {
|
|
44
|
-
type:
|
|
49
|
+
type: MessageType.CF_AGENT_STATE;
|
|
45
50
|
state: unknown;
|
|
46
51
|
};
|
|
47
52
|
|
|
@@ -49,7 +54,7 @@ export type StateUpdateMessage = {
|
|
|
49
54
|
* RPC response message to client
|
|
50
55
|
*/
|
|
51
56
|
export type RPCResponse = {
|
|
52
|
-
type:
|
|
57
|
+
type: MessageType.RPC;
|
|
53
58
|
id: string;
|
|
54
59
|
} & (
|
|
55
60
|
| {
|
|
@@ -76,7 +81,7 @@ function isRPCRequest(msg: unknown): msg is RPCRequest {
|
|
|
76
81
|
typeof msg === "object" &&
|
|
77
82
|
msg !== null &&
|
|
78
83
|
"type" in msg &&
|
|
79
|
-
msg.type ===
|
|
84
|
+
msg.type === MessageType.RPC &&
|
|
80
85
|
"id" in msg &&
|
|
81
86
|
typeof msg.id === "string" &&
|
|
82
87
|
"method" in msg &&
|
|
@@ -94,7 +99,7 @@ function isStateUpdateMessage(msg: unknown): msg is StateUpdateMessage {
|
|
|
94
99
|
typeof msg === "object" &&
|
|
95
100
|
msg !== null &&
|
|
96
101
|
"type" in msg &&
|
|
97
|
-
msg.type ===
|
|
102
|
+
msg.type === MessageType.CF_AGENT_STATE &&
|
|
98
103
|
"state" in msg
|
|
99
104
|
);
|
|
100
105
|
}
|
|
@@ -115,7 +120,7 @@ const callableMetadata = new Map<Function, CallableMetadata>();
|
|
|
115
120
|
* Decorator that marks a method as callable by clients
|
|
116
121
|
* @param metadata Optional metadata about the callable method
|
|
117
122
|
*/
|
|
118
|
-
export function
|
|
123
|
+
export function callable(metadata: CallableMetadata = {}) {
|
|
119
124
|
return function callableDecorator<This, Args extends unknown[], Return>(
|
|
120
125
|
target: (this: This, ...args: Args) => Return,
|
|
121
126
|
// biome-ignore lint/correctness/noUnusedFunctionParameters: later
|
|
@@ -129,6 +134,30 @@ export function unstable_callable(metadata: CallableMetadata = {}) {
|
|
|
129
134
|
};
|
|
130
135
|
}
|
|
131
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
|
+
|
|
132
161
|
/**
|
|
133
162
|
* Represents a scheduled task within an Agent
|
|
134
163
|
* @template T Type of the payload data
|
|
@@ -170,11 +199,13 @@ function getNextCronTime(cron: string) {
|
|
|
170
199
|
return interval.getNextDate();
|
|
171
200
|
}
|
|
172
201
|
|
|
202
|
+
export type { TransportType } from "./mcp/types";
|
|
203
|
+
|
|
173
204
|
/**
|
|
174
205
|
* MCP Server state update message from server -> Client
|
|
175
206
|
*/
|
|
176
207
|
export type MCPServerMessage = {
|
|
177
|
-
type:
|
|
208
|
+
type: MessageType.CF_AGENT_MCP_SERVERS;
|
|
178
209
|
mcp: MCPServersState;
|
|
179
210
|
};
|
|
180
211
|
|
|
@@ -194,7 +225,7 @@ export type MCPServer = {
|
|
|
194
225
|
// This state is specifically about the temporary process of getting a token (if needed).
|
|
195
226
|
// Scope outside of that can't be relied upon because when the DO sleeps, there's no way
|
|
196
227
|
// to communicate a change to a non-ready state.
|
|
197
|
-
state:
|
|
228
|
+
state: MCPConnectionState;
|
|
198
229
|
instructions: string | null;
|
|
199
230
|
capabilities: ServerCapabilities | null;
|
|
200
231
|
};
|
|
@@ -218,9 +249,10 @@ const STATE_WAS_CHANGED = "cf_state_was_changed";
|
|
|
218
249
|
const DEFAULT_STATE = {} as unknown;
|
|
219
250
|
|
|
220
251
|
const agentContext = new AsyncLocalStorage<{
|
|
221
|
-
agent: Agent<unknown>;
|
|
252
|
+
agent: Agent<unknown, unknown>;
|
|
222
253
|
connection: Connection | undefined;
|
|
223
254
|
request: Request | undefined;
|
|
255
|
+
email: AgentEmail | undefined;
|
|
224
256
|
}>();
|
|
225
257
|
|
|
226
258
|
export function getCurrentAgent<
|
|
@@ -228,37 +260,73 @@ export function getCurrentAgent<
|
|
|
228
260
|
>(): {
|
|
229
261
|
agent: T | undefined;
|
|
230
262
|
connection: Connection | undefined;
|
|
231
|
-
request: Request
|
|
263
|
+
request: Request | undefined;
|
|
264
|
+
email: AgentEmail | undefined;
|
|
232
265
|
} {
|
|
233
266
|
const store = agentContext.getStore() as
|
|
234
267
|
| {
|
|
235
268
|
agent: T;
|
|
236
269
|
connection: Connection | undefined;
|
|
237
|
-
request: Request
|
|
270
|
+
request: Request | undefined;
|
|
271
|
+
email: AgentEmail | undefined;
|
|
238
272
|
}
|
|
239
273
|
| undefined;
|
|
240
274
|
if (!store) {
|
|
241
275
|
return {
|
|
242
276
|
agent: undefined,
|
|
243
277
|
connection: undefined,
|
|
244
|
-
request: undefined
|
|
278
|
+
request: undefined,
|
|
279
|
+
email: undefined
|
|
245
280
|
};
|
|
246
281
|
}
|
|
247
282
|
return store;
|
|
248
283
|
}
|
|
249
284
|
|
|
285
|
+
/**
|
|
286
|
+
* Wraps a method to run within the agent context, ensuring getCurrentAgent() works properly
|
|
287
|
+
* @param agent The agent instance
|
|
288
|
+
* @param method The method to wrap
|
|
289
|
+
* @returns A wrapped method that runs within the agent context
|
|
290
|
+
*/
|
|
291
|
+
|
|
292
|
+
// biome-ignore lint/suspicious/noExplicitAny: I can't typescript
|
|
293
|
+
function withAgentContext<T extends (...args: any[]) => any>(
|
|
294
|
+
method: T
|
|
295
|
+
): (this: Agent<unknown, unknown>, ...args: Parameters<T>) => ReturnType<T> {
|
|
296
|
+
return function (...args: Parameters<T>): ReturnType<T> {
|
|
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
|
|
304
|
+
return agentContext.run({ agent: this, connection, request, email }, () => {
|
|
305
|
+
return method.apply(this, args);
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
250
310
|
/**
|
|
251
311
|
* Base class for creating Agent implementations
|
|
252
312
|
* @template Env Environment type containing bindings
|
|
253
313
|
* @template State State type to store within the Agent
|
|
254
314
|
*/
|
|
255
|
-
export class Agent<
|
|
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> {
|
|
256
320
|
private _state = DEFAULT_STATE as State;
|
|
321
|
+
private _disposables = new DisposableStore();
|
|
257
322
|
|
|
258
323
|
private _ParentClass: typeof Agent<Env, State> =
|
|
259
324
|
Object.getPrototypeOf(this).constructor;
|
|
260
325
|
|
|
261
|
-
mcp: MCPClientManager = new MCPClientManager(
|
|
326
|
+
readonly mcp: MCPClientManager = new MCPClientManager(
|
|
327
|
+
this._ParentClass.name,
|
|
328
|
+
"0.0.1"
|
|
329
|
+
);
|
|
262
330
|
|
|
263
331
|
/**
|
|
264
332
|
* Initial state for the Agent
|
|
@@ -351,6 +419,26 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
351
419
|
constructor(ctx: AgentContext, env: Env) {
|
|
352
420
|
super(ctx, env);
|
|
353
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
|
+
|
|
354
442
|
this.sql`
|
|
355
443
|
CREATE TABLE IF NOT EXISTS cf_agents_state (
|
|
356
444
|
id TEXT PRIMARY KEY NOT NULL,
|
|
@@ -358,6 +446,15 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
358
446
|
)
|
|
359
447
|
`;
|
|
360
448
|
|
|
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
|
+
`;
|
|
457
|
+
|
|
361
458
|
void this.ctx.blockConcurrencyWhile(async () => {
|
|
362
459
|
return this._tryCatch(async () => {
|
|
363
460
|
// Create alarms table if it doesn't exist
|
|
@@ -394,24 +491,27 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
394
491
|
const _onRequest = this.onRequest.bind(this);
|
|
395
492
|
this.onRequest = (request: Request) => {
|
|
396
493
|
return agentContext.run(
|
|
397
|
-
{ agent: this, connection: undefined, request },
|
|
494
|
+
{ agent: this, connection: undefined, request, email: undefined },
|
|
398
495
|
async () => {
|
|
399
496
|
if (this.mcp.isCallbackRequest(request)) {
|
|
400
|
-
await this.mcp.handleCallbackRequest(request);
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
+
}
|
|
409
512
|
|
|
410
|
-
//
|
|
411
|
-
return
|
|
412
|
-
headers: { "content-type": "text/html" },
|
|
413
|
-
status: 200
|
|
414
|
-
});
|
|
513
|
+
// Handle OAuth callback response using MCPClientManager configuration
|
|
514
|
+
return this.handleOAuthCallbackResponse(result, request);
|
|
415
515
|
}
|
|
416
516
|
|
|
417
517
|
return this._tryCatch(() => _onRequest(request));
|
|
@@ -422,7 +522,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
422
522
|
const _onMessage = this.onMessage.bind(this);
|
|
423
523
|
this.onMessage = async (connection: Connection, message: WSMessage) => {
|
|
424
524
|
return agentContext.run(
|
|
425
|
-
{ agent: this, connection, request: undefined },
|
|
525
|
+
{ agent: this, connection, request: undefined, email: undefined },
|
|
426
526
|
async () => {
|
|
427
527
|
if (typeof message !== "string") {
|
|
428
528
|
return this._tryCatch(() => _onMessage(connection, message));
|
|
@@ -472,10 +572,8 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
472
572
|
displayMessage: `RPC call to ${method}`,
|
|
473
573
|
id: nanoid(),
|
|
474
574
|
payload: {
|
|
475
|
-
args,
|
|
476
575
|
method,
|
|
477
|
-
streaming: metadata?.streaming
|
|
478
|
-
success: true
|
|
576
|
+
streaming: metadata?.streaming
|
|
479
577
|
},
|
|
480
578
|
timestamp: Date.now(),
|
|
481
579
|
type: "rpc"
|
|
@@ -488,7 +586,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
488
586
|
id,
|
|
489
587
|
result,
|
|
490
588
|
success: true,
|
|
491
|
-
type:
|
|
589
|
+
type: MessageType.RPC
|
|
492
590
|
};
|
|
493
591
|
connection.send(JSON.stringify(response));
|
|
494
592
|
} catch (e) {
|
|
@@ -498,7 +596,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
498
596
|
e instanceof Error ? e.message : "Unknown error occurred",
|
|
499
597
|
id: parsed.id,
|
|
500
598
|
success: false,
|
|
501
|
-
type:
|
|
599
|
+
type: MessageType.RPC
|
|
502
600
|
};
|
|
503
601
|
connection.send(JSON.stringify(response));
|
|
504
602
|
console.error("RPC error:", e);
|
|
@@ -516,77 +614,99 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
516
614
|
// TODO: This is a hack to ensure the state is sent after the connection is established
|
|
517
615
|
// must fix this
|
|
518
616
|
return agentContext.run(
|
|
519
|
-
{ agent: this, connection, request: ctx.request },
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
if (this.state) {
|
|
523
|
-
connection.send(
|
|
524
|
-
JSON.stringify({
|
|
525
|
-
state: this.state,
|
|
526
|
-
type: "cf_agent_state"
|
|
527
|
-
})
|
|
528
|
-
);
|
|
529
|
-
}
|
|
530
|
-
|
|
617
|
+
{ agent: this, connection, request: ctx.request, email: undefined },
|
|
618
|
+
() => {
|
|
619
|
+
if (this.state) {
|
|
531
620
|
connection.send(
|
|
532
621
|
JSON.stringify({
|
|
533
|
-
|
|
534
|
-
type:
|
|
622
|
+
state: this.state,
|
|
623
|
+
type: MessageType.CF_AGENT_STATE
|
|
535
624
|
})
|
|
536
625
|
);
|
|
626
|
+
}
|
|
537
627
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
|
547
641
|
},
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
642
|
+
timestamp: Date.now(),
|
|
643
|
+
type: "connect"
|
|
644
|
+
},
|
|
645
|
+
this.ctx
|
|
646
|
+
);
|
|
647
|
+
return this._tryCatch(() => _onConnect(connection, ctx));
|
|
552
648
|
}
|
|
553
649
|
);
|
|
554
650
|
};
|
|
555
651
|
|
|
556
652
|
const _onStart = this.onStart.bind(this);
|
|
557
|
-
this.onStart = async () => {
|
|
653
|
+
this.onStart = async (props?: Props) => {
|
|
558
654
|
return agentContext.run(
|
|
559
|
-
{
|
|
655
|
+
{
|
|
656
|
+
agent: this,
|
|
657
|
+
connection: undefined,
|
|
658
|
+
request: undefined,
|
|
659
|
+
email: undefined
|
|
660
|
+
},
|
|
560
661
|
async () => {
|
|
561
|
-
|
|
662
|
+
await this._tryCatch(() => {
|
|
663
|
+
const servers = this.sql<MCPServerRow>`
|
|
562
664
|
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
|
|
563
665
|
`;
|
|
564
666
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
servers
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
server.callback_url
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
id: server.id,
|
|
577
|
-
oauthClientId: server.client_id ?? undefined
|
|
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
|
+
);
|
|
578
678
|
}
|
|
579
|
-
);
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
servers.forEach((server) => {
|
|
682
|
+
this._connectToMcpServerInternal(
|
|
683
|
+
server.name,
|
|
684
|
+
server.server_url,
|
|
685
|
+
server.callback_url,
|
|
686
|
+
server.server_options
|
|
687
|
+
? JSON.parse(server.server_options)
|
|
688
|
+
: undefined,
|
|
689
|
+
{
|
|
690
|
+
id: server.id,
|
|
691
|
+
oauthClientId: server.client_id ?? undefined
|
|
692
|
+
}
|
|
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);
|
|
588
709
|
});
|
|
589
|
-
await this._tryCatch(() => _onStart());
|
|
590
710
|
}
|
|
591
711
|
);
|
|
592
712
|
};
|
|
@@ -596,7 +716,6 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
596
716
|
state: State,
|
|
597
717
|
source: Connection | "server" = "server"
|
|
598
718
|
) {
|
|
599
|
-
const previousState = this._state;
|
|
600
719
|
this._state = state;
|
|
601
720
|
this.sql`
|
|
602
721
|
INSERT OR REPLACE INTO cf_agents_state (id, state)
|
|
@@ -609,23 +728,20 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
609
728
|
this.broadcast(
|
|
610
729
|
JSON.stringify({
|
|
611
730
|
state: state,
|
|
612
|
-
type:
|
|
731
|
+
type: MessageType.CF_AGENT_STATE
|
|
613
732
|
}),
|
|
614
733
|
source !== "server" ? [source.id] : []
|
|
615
734
|
);
|
|
616
735
|
return this._tryCatch(() => {
|
|
617
|
-
const { connection, request } = agentContext.getStore() || {};
|
|
736
|
+
const { connection, request, email } = agentContext.getStore() || {};
|
|
618
737
|
return agentContext.run(
|
|
619
|
-
{ agent: this, connection, request },
|
|
738
|
+
{ agent: this, connection, request, email },
|
|
620
739
|
async () => {
|
|
621
740
|
this.observability?.emit(
|
|
622
741
|
{
|
|
623
742
|
displayMessage: "State updated",
|
|
624
743
|
id: nanoid(),
|
|
625
|
-
payload: {
|
|
626
|
-
previousState,
|
|
627
|
-
state
|
|
628
|
-
},
|
|
744
|
+
payload: {},
|
|
629
745
|
timestamp: Date.now(),
|
|
630
746
|
type: "state:update"
|
|
631
747
|
},
|
|
@@ -656,19 +772,83 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
656
772
|
}
|
|
657
773
|
|
|
658
774
|
/**
|
|
659
|
-
* Called when the Agent receives an email
|
|
775
|
+
* Called when the Agent receives an email via routeAgentEmail()
|
|
776
|
+
* Override this method to handle incoming emails
|
|
660
777
|
* @param email Email message to process
|
|
661
778
|
*/
|
|
662
|
-
|
|
663
|
-
|
|
779
|
+
async _onEmail(email: AgentEmail) {
|
|
780
|
+
// nb: we use this roundabout way of getting to onEmail
|
|
781
|
+
// because of https://github.com/cloudflare/workerd/issues/4499
|
|
664
782
|
return agentContext.run(
|
|
665
|
-
{ agent: this, connection: undefined, request: undefined },
|
|
783
|
+
{ agent: this, connection: undefined, request: undefined, email: email },
|
|
666
784
|
async () => {
|
|
667
|
-
|
|
785
|
+
if ("onEmail" in this && typeof this.onEmail === "function") {
|
|
786
|
+
return this._tryCatch(() =>
|
|
787
|
+
(this.onEmail as (email: AgentEmail) => Promise<void>)(email)
|
|
788
|
+
);
|
|
789
|
+
} else {
|
|
790
|
+
console.log("Received email from:", email.from, "to:", email.to);
|
|
791
|
+
console.log("Subject:", email.headers.get("subject"));
|
|
792
|
+
console.log(
|
|
793
|
+
"Implement onEmail(email: AgentEmail): Promise<void> in your agent to process emails"
|
|
794
|
+
);
|
|
795
|
+
}
|
|
668
796
|
}
|
|
669
797
|
);
|
|
670
798
|
}
|
|
671
799
|
|
|
800
|
+
/**
|
|
801
|
+
* Reply to an email
|
|
802
|
+
* @param email The email to reply to
|
|
803
|
+
* @param options Options for the reply
|
|
804
|
+
* @returns void
|
|
805
|
+
*/
|
|
806
|
+
async replyToEmail(
|
|
807
|
+
email: AgentEmail,
|
|
808
|
+
options: {
|
|
809
|
+
fromName: string;
|
|
810
|
+
subject?: string | undefined;
|
|
811
|
+
body: string;
|
|
812
|
+
contentType?: string;
|
|
813
|
+
headers?: Record<string, string>;
|
|
814
|
+
}
|
|
815
|
+
): Promise<void> {
|
|
816
|
+
return this._tryCatch(async () => {
|
|
817
|
+
const agentName = camelCaseToKebabCase(this._ParentClass.name);
|
|
818
|
+
const agentId = this.name;
|
|
819
|
+
|
|
820
|
+
const { createMimeMessage } = await import("mimetext");
|
|
821
|
+
const msg = createMimeMessage();
|
|
822
|
+
msg.setSender({ addr: email.to, name: options.fromName });
|
|
823
|
+
msg.setRecipient(email.from);
|
|
824
|
+
msg.setSubject(
|
|
825
|
+
options.subject || `Re: ${email.headers.get("subject")}` || "No subject"
|
|
826
|
+
);
|
|
827
|
+
msg.addMessage({
|
|
828
|
+
contentType: options.contentType || "text/plain",
|
|
829
|
+
data: options.body
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
const domain = email.from.split("@")[1];
|
|
833
|
+
const messageId = `<${agentId}@${domain}>`;
|
|
834
|
+
msg.setHeader("In-Reply-To", email.headers.get("Message-ID")!);
|
|
835
|
+
msg.setHeader("Message-ID", messageId);
|
|
836
|
+
msg.setHeader("X-Agent-Name", agentName);
|
|
837
|
+
msg.setHeader("X-Agent-ID", agentId);
|
|
838
|
+
|
|
839
|
+
if (options.headers) {
|
|
840
|
+
for (const [key, value] of Object.entries(options.headers)) {
|
|
841
|
+
msg.setHeader(key, value);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
await email.reply({
|
|
845
|
+
from: email.to,
|
|
846
|
+
raw: msg.asRaw(),
|
|
847
|
+
to: email.from
|
|
848
|
+
});
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
|
|
672
852
|
private async _tryCatch<T>(fn: () => T | Promise<T>) {
|
|
673
853
|
try {
|
|
674
854
|
return await fn();
|
|
@@ -677,6 +857,68 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
677
857
|
}
|
|
678
858
|
}
|
|
679
859
|
|
|
860
|
+
/**
|
|
861
|
+
* Automatically wrap custom methods with agent context
|
|
862
|
+
* This ensures getCurrentAgent() works in all custom methods without decorators
|
|
863
|
+
*/
|
|
864
|
+
private _autoWrapCustomMethods() {
|
|
865
|
+
// Collect all methods from base prototypes (Agent and Server)
|
|
866
|
+
const basePrototypes = [Agent.prototype, Server.prototype];
|
|
867
|
+
const baseMethods = new Set<string>();
|
|
868
|
+
for (const baseProto of basePrototypes) {
|
|
869
|
+
let proto = baseProto;
|
|
870
|
+
while (proto && proto !== Object.prototype) {
|
|
871
|
+
const methodNames = Object.getOwnPropertyNames(proto);
|
|
872
|
+
for (const methodName of methodNames) {
|
|
873
|
+
baseMethods.add(methodName);
|
|
874
|
+
}
|
|
875
|
+
proto = Object.getPrototypeOf(proto);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
// Get all methods from the current instance's prototype chain
|
|
879
|
+
let proto = Object.getPrototypeOf(this);
|
|
880
|
+
let depth = 0;
|
|
881
|
+
while (proto && proto !== Object.prototype && depth < 10) {
|
|
882
|
+
const methodNames = Object.getOwnPropertyNames(proto);
|
|
883
|
+
for (const methodName of methodNames) {
|
|
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,
|
|
887
|
+
if (
|
|
888
|
+
baseMethods.has(methodName) ||
|
|
889
|
+
methodName.startsWith("_") ||
|
|
890
|
+
!descriptor ||
|
|
891
|
+
!!descriptor.get ||
|
|
892
|
+
typeof descriptor.value !== "function"
|
|
893
|
+
) {
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
|
|
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
|
+
);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// set the wrapped function on the prototype
|
|
914
|
+
this.constructor.prototype[methodName as keyof this] = wrappedFunction;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
proto = Object.getPrototypeOf(proto);
|
|
918
|
+
depth++;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
680
922
|
override onError(
|
|
681
923
|
connection: Connection,
|
|
682
924
|
error: unknown
|
|
@@ -711,6 +953,131 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
711
953
|
throw new Error("Not implemented");
|
|
712
954
|
}
|
|
713
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
|
+
|
|
714
1081
|
/**
|
|
715
1082
|
* Schedule a task to be executed in the future
|
|
716
1083
|
* @template T Type of the payload data
|
|
@@ -731,7 +1098,10 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
731
1098
|
{
|
|
732
1099
|
displayMessage: `Schedule ${schedule.id} created`,
|
|
733
1100
|
id: nanoid(),
|
|
734
|
-
payload:
|
|
1101
|
+
payload: {
|
|
1102
|
+
callback: callback as string,
|
|
1103
|
+
id: id
|
|
1104
|
+
},
|
|
735
1105
|
timestamp: Date.now(),
|
|
736
1106
|
type: "schedule:create"
|
|
737
1107
|
},
|
|
@@ -901,7 +1271,10 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
901
1271
|
{
|
|
902
1272
|
displayMessage: `Schedule ${id} cancelled`,
|
|
903
1273
|
id: nanoid(),
|
|
904
|
-
payload:
|
|
1274
|
+
payload: {
|
|
1275
|
+
callback: schedule.callback,
|
|
1276
|
+
id: schedule.id
|
|
1277
|
+
},
|
|
905
1278
|
timestamp: Date.now(),
|
|
906
1279
|
type: "schedule:cancel"
|
|
907
1280
|
},
|
|
@@ -917,9 +1290,9 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
917
1290
|
private async _scheduleNextAlarm() {
|
|
918
1291
|
// Find the next schedule that needs to be executed
|
|
919
1292
|
const result = this.sql`
|
|
920
|
-
SELECT time FROM cf_agents_schedules
|
|
1293
|
+
SELECT time FROM cf_agents_schedules
|
|
921
1294
|
WHERE time > ${Math.floor(Date.now() / 1000)}
|
|
922
|
-
ORDER BY time ASC
|
|
1295
|
+
ORDER BY time ASC
|
|
923
1296
|
LIMIT 1
|
|
924
1297
|
`;
|
|
925
1298
|
if (!result) return;
|
|
@@ -946,51 +1319,61 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
946
1319
|
SELECT * FROM cf_agents_schedules WHERE time <= ${now}
|
|
947
1320
|
`;
|
|
948
1321
|
|
|
949
|
-
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1322
|
+
if (result && Array.isArray(result)) {
|
|
1323
|
+
for (const row of result) {
|
|
1324
|
+
const callback = this[row.callback as keyof Agent<Env>];
|
|
1325
|
+
if (!callback) {
|
|
1326
|
+
console.error(`callback ${row.callback} not found`);
|
|
1327
|
+
continue;
|
|
1328
|
+
}
|
|
1329
|
+
await agentContext.run(
|
|
1330
|
+
{
|
|
1331
|
+
agent: this,
|
|
1332
|
+
connection: undefined,
|
|
1333
|
+
request: undefined,
|
|
1334
|
+
email: undefined
|
|
1335
|
+
},
|
|
1336
|
+
async () => {
|
|
1337
|
+
try {
|
|
1338
|
+
this.observability?.emit(
|
|
1339
|
+
{
|
|
1340
|
+
displayMessage: `Schedule ${row.id} executed`,
|
|
1341
|
+
id: nanoid(),
|
|
1342
|
+
payload: {
|
|
1343
|
+
callback: row.callback,
|
|
1344
|
+
id: row.id
|
|
1345
|
+
},
|
|
1346
|
+
timestamp: Date.now(),
|
|
1347
|
+
type: "schedule:execute"
|
|
1348
|
+
},
|
|
1349
|
+
this.ctx
|
|
1350
|
+
);
|
|
969
1351
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1352
|
+
await (
|
|
1353
|
+
callback as (
|
|
1354
|
+
payload: unknown,
|
|
1355
|
+
schedule: Schedule<unknown>
|
|
1356
|
+
) => Promise<void>
|
|
1357
|
+
).bind(this)(JSON.parse(row.payload as string), row);
|
|
1358
|
+
} catch (e) {
|
|
1359
|
+
console.error(`error executing callback "${row.callback}"`, e);
|
|
1360
|
+
}
|
|
978
1361
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
const nextTimestamp = Math.floor(nextExecutionTime.getTime() / 1000);
|
|
1362
|
+
);
|
|
1363
|
+
if (row.type === "cron") {
|
|
1364
|
+
// Update next execution time for cron schedules
|
|
1365
|
+
const nextExecutionTime = getNextCronTime(row.cron);
|
|
1366
|
+
const nextTimestamp = Math.floor(nextExecutionTime.getTime() / 1000);
|
|
985
1367
|
|
|
986
|
-
|
|
1368
|
+
this.sql`
|
|
987
1369
|
UPDATE cf_agents_schedules SET time = ${nextTimestamp} WHERE id = ${row.id}
|
|
988
1370
|
`;
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1371
|
+
} else {
|
|
1372
|
+
// Delete one-time schedules after execution
|
|
1373
|
+
this.sql`
|
|
992
1374
|
DELETE FROM cf_agents_schedules WHERE id = ${row.id}
|
|
993
1375
|
`;
|
|
1376
|
+
}
|
|
994
1377
|
}
|
|
995
1378
|
}
|
|
996
1379
|
|
|
@@ -1006,10 +1389,13 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1006
1389
|
this.sql`DROP TABLE IF EXISTS cf_agents_state`;
|
|
1007
1390
|
this.sql`DROP TABLE IF EXISTS cf_agents_schedules`;
|
|
1008
1391
|
this.sql`DROP TABLE IF EXISTS cf_agents_mcp_servers`;
|
|
1392
|
+
this.sql`DROP TABLE IF EXISTS cf_agents_queues`;
|
|
1009
1393
|
|
|
1010
1394
|
// delete all alarms
|
|
1011
1395
|
await this.ctx.storage.deleteAlarm();
|
|
1012
1396
|
await this.ctx.storage.deleteAll();
|
|
1397
|
+
this._disposables.dispose();
|
|
1398
|
+
await this.mcp.dispose?.();
|
|
1013
1399
|
this.ctx.abort("destroyed"); // enforce that the agent is evicted
|
|
1014
1400
|
|
|
1015
1401
|
this.observability?.emit(
|
|
@@ -1035,25 +1421,42 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1035
1421
|
/**
|
|
1036
1422
|
* Connect to a new MCP Server
|
|
1037
1423
|
*
|
|
1424
|
+
* @param serverName Name of the MCP server
|
|
1038
1425
|
* @param url MCP Server SSE URL
|
|
1039
|
-
* @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.
|
|
1040
1427
|
* @param agentsPrefix agents routing prefix if not using `agents`
|
|
1041
|
-
* @param options MCP client and transport
|
|
1428
|
+
* @param options MCP client and transport options
|
|
1042
1429
|
* @returns authUrl
|
|
1043
1430
|
*/
|
|
1044
1431
|
async addMcpServer(
|
|
1045
1432
|
serverName: string,
|
|
1046
1433
|
url: string,
|
|
1047
|
-
callbackHost
|
|
1434
|
+
callbackHost?: string,
|
|
1048
1435
|
agentsPrefix = "agents",
|
|
1049
1436
|
options?: {
|
|
1050
1437
|
client?: ConstructorParameters<typeof Client>[1];
|
|
1051
1438
|
transport?: {
|
|
1052
|
-
headers
|
|
1439
|
+
headers?: HeadersInit;
|
|
1440
|
+
type?: TransportType;
|
|
1053
1441
|
};
|
|
1054
1442
|
}
|
|
1055
1443
|
): Promise<{ id: string; authUrl: string | undefined }> {
|
|
1056
|
-
|
|
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`;
|
|
1057
1460
|
|
|
1058
1461
|
const result = await this._connectToMcpServerInternal(
|
|
1059
1462
|
serverName,
|
|
@@ -1061,6 +1464,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1061
1464
|
callbackUrl,
|
|
1062
1465
|
options
|
|
1063
1466
|
);
|
|
1467
|
+
|
|
1064
1468
|
this.sql`
|
|
1065
1469
|
INSERT
|
|
1066
1470
|
OR REPLACE INTO cf_agents_mcp_servers (id, name, server_url, client_id, auth_url, callback_url, server_options)
|
|
@@ -1075,17 +1479,12 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1075
1479
|
);
|
|
1076
1480
|
`;
|
|
1077
1481
|
|
|
1078
|
-
this.
|
|
1079
|
-
JSON.stringify({
|
|
1080
|
-
mcp: this.getMcpServers(),
|
|
1081
|
-
type: "cf_agent_mcp_servers"
|
|
1082
|
-
})
|
|
1083
|
-
);
|
|
1482
|
+
this.broadcastMcpServers();
|
|
1084
1483
|
|
|
1085
1484
|
return result;
|
|
1086
1485
|
}
|
|
1087
1486
|
|
|
1088
|
-
async _connectToMcpServerInternal(
|
|
1487
|
+
private async _connectToMcpServerInternal(
|
|
1089
1488
|
_serverName: string,
|
|
1090
1489
|
url: string,
|
|
1091
1490
|
callbackUrl: string,
|
|
@@ -1101,6 +1500,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1101
1500
|
*/
|
|
1102
1501
|
transport?: {
|
|
1103
1502
|
headers?: HeadersInit;
|
|
1503
|
+
type?: TransportType;
|
|
1104
1504
|
};
|
|
1105
1505
|
},
|
|
1106
1506
|
reconnect?: {
|
|
@@ -1125,6 +1525,9 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1125
1525
|
}
|
|
1126
1526
|
}
|
|
1127
1527
|
|
|
1528
|
+
// Use the transport type specified in options, or default to "auto"
|
|
1529
|
+
const transportType: TransportType = options?.transport?.type ?? "auto";
|
|
1530
|
+
|
|
1128
1531
|
// allows passing through transport headers if necessary
|
|
1129
1532
|
// this handles some non-standard bearer auth setups (i.e. MCP server behind CF access instead of OAuth)
|
|
1130
1533
|
let headerTransportOpts: SSEClientTransportOptions = {};
|
|
@@ -1148,7 +1551,8 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1148
1551
|
reconnect,
|
|
1149
1552
|
transport: {
|
|
1150
1553
|
...headerTransportOpts,
|
|
1151
|
-
authProvider
|
|
1554
|
+
authProvider,
|
|
1555
|
+
type: transportType
|
|
1152
1556
|
}
|
|
1153
1557
|
});
|
|
1154
1558
|
|
|
@@ -1161,15 +1565,11 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1161
1565
|
|
|
1162
1566
|
async removeMcpServer(id: string) {
|
|
1163
1567
|
this.mcp.closeConnection(id);
|
|
1568
|
+
this.mcp.unregisterCallbackUrl(id);
|
|
1164
1569
|
this.sql`
|
|
1165
1570
|
DELETE FROM cf_agents_mcp_servers WHERE id = ${id};
|
|
1166
1571
|
`;
|
|
1167
|
-
this.
|
|
1168
|
-
JSON.stringify({
|
|
1169
|
-
mcp: this.getMcpServers(),
|
|
1170
|
-
type: "cf_agent_mcp_servers"
|
|
1171
|
-
})
|
|
1172
|
-
);
|
|
1572
|
+
this.broadcastMcpServers();
|
|
1173
1573
|
}
|
|
1174
1574
|
|
|
1175
1575
|
getMcpServers(): MCPServersState {
|
|
@@ -1184,23 +1584,70 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1184
1584
|
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
|
|
1185
1585
|
`;
|
|
1186
1586
|
|
|
1187
|
-
|
|
1188
|
-
const
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1587
|
+
if (servers && Array.isArray(servers) && servers.length > 0) {
|
|
1588
|
+
for (const server of servers) {
|
|
1589
|
+
const serverConn = this.mcp.mcpConnections[server.id];
|
|
1590
|
+
mcpState.servers[server.id] = {
|
|
1591
|
+
auth_url: server.auth_url,
|
|
1592
|
+
capabilities: serverConn?.serverCapabilities ?? null,
|
|
1593
|
+
instructions: serverConn?.instructions ?? null,
|
|
1594
|
+
name: server.name,
|
|
1595
|
+
server_url: server.server_url,
|
|
1596
|
+
// mark as "authenticating" because the server isn't automatically connected, so it's pending authenticating
|
|
1597
|
+
state: serverConn?.connectionState ?? "authenticating"
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1198
1600
|
}
|
|
1199
1601
|
|
|
1200
1602
|
return mcpState;
|
|
1201
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
|
+
}
|
|
1202
1646
|
}
|
|
1203
1647
|
|
|
1648
|
+
// A set of classes that have been wrapped with agent context
|
|
1649
|
+
const wrappedClasses = new Set<typeof Agent.prototype.constructor>();
|
|
1650
|
+
|
|
1204
1651
|
/**
|
|
1205
1652
|
* Namespace for creating Agent instances
|
|
1206
1653
|
* @template Agentic Type of the Agent class
|
|
@@ -1281,17 +1728,231 @@ export async function routeAgentRequest<Env>(
|
|
|
1281
1728
|
return response;
|
|
1282
1729
|
}
|
|
1283
1730
|
|
|
1731
|
+
export type EmailResolver<Env> = (
|
|
1732
|
+
email: ForwardableEmailMessage,
|
|
1733
|
+
env: Env
|
|
1734
|
+
) => Promise<{
|
|
1735
|
+
agentName: string;
|
|
1736
|
+
agentId: string;
|
|
1737
|
+
} | null>;
|
|
1738
|
+
|
|
1739
|
+
/**
|
|
1740
|
+
* Create a resolver that uses the message-id header to determine the agent to route the email to
|
|
1741
|
+
* @returns A function that resolves the agent to route the email to
|
|
1742
|
+
*/
|
|
1743
|
+
export function createHeaderBasedEmailResolver<Env>(): EmailResolver<Env> {
|
|
1744
|
+
return async (email: ForwardableEmailMessage, _env: Env) => {
|
|
1745
|
+
const messageId = email.headers.get("message-id");
|
|
1746
|
+
if (messageId) {
|
|
1747
|
+
const messageIdMatch = messageId.match(/<([^@]+)@([^>]+)>/);
|
|
1748
|
+
if (messageIdMatch) {
|
|
1749
|
+
const [, agentId, domain] = messageIdMatch;
|
|
1750
|
+
const agentName = domain.split(".")[0];
|
|
1751
|
+
return { agentName, agentId };
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
const references = email.headers.get("references");
|
|
1756
|
+
if (references) {
|
|
1757
|
+
const referencesMatch = references.match(
|
|
1758
|
+
/<([A-Za-z0-9+/]{43}=)@([^>]+)>/
|
|
1759
|
+
);
|
|
1760
|
+
if (referencesMatch) {
|
|
1761
|
+
const [, base64Id, domain] = referencesMatch;
|
|
1762
|
+
const agentId = Buffer.from(base64Id, "base64").toString("hex");
|
|
1763
|
+
const agentName = domain.split(".")[0];
|
|
1764
|
+
return { agentName, agentId };
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
const agentName = email.headers.get("x-agent-name");
|
|
1769
|
+
const agentId = email.headers.get("x-agent-id");
|
|
1770
|
+
if (agentName && agentId) {
|
|
1771
|
+
return { agentName, agentId };
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
return null;
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
/**
|
|
1779
|
+
* Create a resolver that uses the email address to determine the agent to route the email to
|
|
1780
|
+
* @param defaultAgentName The default agent name to use if the email address does not contain a sub-address
|
|
1781
|
+
* @returns A function that resolves the agent to route the email to
|
|
1782
|
+
*/
|
|
1783
|
+
export function createAddressBasedEmailResolver<Env>(
|
|
1784
|
+
defaultAgentName: string
|
|
1785
|
+
): EmailResolver<Env> {
|
|
1786
|
+
return async (email: ForwardableEmailMessage, _env: Env) => {
|
|
1787
|
+
const emailMatch = email.to.match(/^([^+@]+)(?:\+([^@]+))?@(.+)$/);
|
|
1788
|
+
if (!emailMatch) {
|
|
1789
|
+
return null;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
const [, localPart, subAddress] = emailMatch;
|
|
1793
|
+
|
|
1794
|
+
if (subAddress) {
|
|
1795
|
+
return {
|
|
1796
|
+
agentName: localPart,
|
|
1797
|
+
agentId: subAddress
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
// Option 2: Use defaultAgentName namespace, localPart as agentId
|
|
1802
|
+
// Common for catch-all email routing to a single EmailAgent namespace
|
|
1803
|
+
return {
|
|
1804
|
+
agentName: defaultAgentName,
|
|
1805
|
+
agentId: localPart
|
|
1806
|
+
};
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
/**
|
|
1811
|
+
* Create a resolver that uses the agentName and agentId to determine the agent to route the email to
|
|
1812
|
+
* @param agentName The name of the agent to route the email to
|
|
1813
|
+
* @param agentId The id of the agent to route the email to
|
|
1814
|
+
* @returns A function that resolves the agent to route the email to
|
|
1815
|
+
*/
|
|
1816
|
+
export function createCatchAllEmailResolver<Env>(
|
|
1817
|
+
agentName: string,
|
|
1818
|
+
agentId: string
|
|
1819
|
+
): EmailResolver<Env> {
|
|
1820
|
+
return async () => ({ agentName, agentId });
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
export type EmailRoutingOptions<Env> = AgentOptions<Env> & {
|
|
1824
|
+
resolver: EmailResolver<Env>;
|
|
1825
|
+
};
|
|
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
|
+
|
|
1284
1834
|
/**
|
|
1285
1835
|
* Route an email to the appropriate Agent
|
|
1286
|
-
* @param email
|
|
1287
|
-
* @param env
|
|
1288
|
-
* @param options
|
|
1836
|
+
* @param email The email to route
|
|
1837
|
+
* @param env The environment containing the Agent bindings
|
|
1838
|
+
* @param options The options for routing the email
|
|
1839
|
+
* @returns A promise that resolves when the email has been routed
|
|
1289
1840
|
*/
|
|
1290
1841
|
export async function routeAgentEmail<Env>(
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
): Promise<void> {
|
|
1842
|
+
email: ForwardableEmailMessage,
|
|
1843
|
+
env: Env,
|
|
1844
|
+
options: EmailRoutingOptions<Env>
|
|
1845
|
+
): Promise<void> {
|
|
1846
|
+
const routingInfo = await options.resolver(email, env);
|
|
1847
|
+
|
|
1848
|
+
if (!routingInfo) {
|
|
1849
|
+
console.warn("No routing information found for email, dropping message");
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
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);
|
|
1869
|
+
}
|
|
1870
|
+
|
|
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(", ");
|
|
1879
|
+
throw new Error(
|
|
1880
|
+
`Agent namespace '${routingInfo.agentName}' not found in environment. Available agents: ${availableAgents}`
|
|
1881
|
+
);
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
const agent = await getAgentByName(
|
|
1885
|
+
namespace as unknown as AgentNamespace<Agent<Env>>,
|
|
1886
|
+
routingInfo.agentId
|
|
1887
|
+
);
|
|
1888
|
+
|
|
1889
|
+
// let's make a serialisable version of the email
|
|
1890
|
+
const serialisableEmail: AgentEmail = {
|
|
1891
|
+
getRaw: async () => {
|
|
1892
|
+
const reader = email.raw.getReader();
|
|
1893
|
+
const chunks: Uint8Array[] = [];
|
|
1894
|
+
|
|
1895
|
+
let done = false;
|
|
1896
|
+
while (!done) {
|
|
1897
|
+
const { value, done: readerDone } = await reader.read();
|
|
1898
|
+
done = readerDone;
|
|
1899
|
+
if (value) {
|
|
1900
|
+
chunks.push(value);
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
1905
|
+
const combined = new Uint8Array(totalLength);
|
|
1906
|
+
let offset = 0;
|
|
1907
|
+
for (const chunk of chunks) {
|
|
1908
|
+
combined.set(chunk, offset);
|
|
1909
|
+
offset += chunk.length;
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
return combined;
|
|
1913
|
+
},
|
|
1914
|
+
headers: email.headers,
|
|
1915
|
+
rawSize: email.rawSize,
|
|
1916
|
+
setReject: (reason: string) => {
|
|
1917
|
+
email.setReject(reason);
|
|
1918
|
+
},
|
|
1919
|
+
forward: (rcptTo: string, headers?: Headers) => {
|
|
1920
|
+
return email.forward(rcptTo, headers);
|
|
1921
|
+
},
|
|
1922
|
+
reply: (options: { from: string; to: string; raw: string }) => {
|
|
1923
|
+
return email.reply(
|
|
1924
|
+
new EmailMessage(options.from, options.to, options.raw)
|
|
1925
|
+
);
|
|
1926
|
+
},
|
|
1927
|
+
from: email.from,
|
|
1928
|
+
to: email.to
|
|
1929
|
+
};
|
|
1930
|
+
|
|
1931
|
+
await agent._onEmail(serialisableEmail);
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
export type AgentEmail = {
|
|
1935
|
+
from: string;
|
|
1936
|
+
to: string;
|
|
1937
|
+
getRaw: () => Promise<Uint8Array>;
|
|
1938
|
+
headers: Headers;
|
|
1939
|
+
rawSize: number;
|
|
1940
|
+
setReject: (reason: string) => void;
|
|
1941
|
+
forward: (rcptTo: string, headers?: Headers) => Promise<void>;
|
|
1942
|
+
reply: (options: { from: string; to: string; raw: string }) => Promise<void>;
|
|
1943
|
+
};
|
|
1944
|
+
|
|
1945
|
+
export type EmailSendOptions = {
|
|
1946
|
+
to: string;
|
|
1947
|
+
subject: string;
|
|
1948
|
+
body: string;
|
|
1949
|
+
contentType?: string;
|
|
1950
|
+
headers?: Record<string, string>;
|
|
1951
|
+
includeRoutingHeaders?: boolean;
|
|
1952
|
+
agentName?: string;
|
|
1953
|
+
agentId?: string;
|
|
1954
|
+
domain?: string;
|
|
1955
|
+
};
|
|
1295
1956
|
|
|
1296
1957
|
/**
|
|
1297
1958
|
* Get or create an Agent by name
|
|
@@ -1302,12 +1963,17 @@ export async function routeAgentEmail<Env>(
|
|
|
1302
1963
|
* @param options Options for Agent creation
|
|
1303
1964
|
* @returns Promise resolving to an Agent instance stub
|
|
1304
1965
|
*/
|
|
1305
|
-
export async function getAgentByName<
|
|
1966
|
+
export async function getAgentByName<
|
|
1967
|
+
Env,
|
|
1968
|
+
T extends Agent<Env>,
|
|
1969
|
+
Props extends Record<string, unknown> = Record<string, unknown>
|
|
1970
|
+
>(
|
|
1306
1971
|
namespace: AgentNamespace<T>,
|
|
1307
1972
|
name: string,
|
|
1308
1973
|
options?: {
|
|
1309
1974
|
jurisdiction?: DurableObjectJurisdiction;
|
|
1310
1975
|
locationHint?: DurableObjectLocationHint;
|
|
1976
|
+
props?: Props;
|
|
1311
1977
|
}
|
|
1312
1978
|
) {
|
|
1313
1979
|
return getServerByName<Env, T>(namespace, name, options);
|
|
@@ -1339,7 +2005,7 @@ export class StreamingResponse {
|
|
|
1339
2005
|
id: this._id,
|
|
1340
2006
|
result: chunk,
|
|
1341
2007
|
success: true,
|
|
1342
|
-
type:
|
|
2008
|
+
type: MessageType.RPC
|
|
1343
2009
|
};
|
|
1344
2010
|
this._connection.send(JSON.stringify(response));
|
|
1345
2011
|
}
|
|
@@ -1358,7 +2024,7 @@ export class StreamingResponse {
|
|
|
1358
2024
|
id: this._id,
|
|
1359
2025
|
result: finalChunk,
|
|
1360
2026
|
success: true,
|
|
1361
|
-
type:
|
|
2027
|
+
type: MessageType.RPC
|
|
1362
2028
|
};
|
|
1363
2029
|
this._connection.send(JSON.stringify(response));
|
|
1364
2030
|
}
|