agents 0.0.0-0ac89c6 → 0.0.0-0bb74b8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -22
- package/dist/ai-chat-agent.d.ts +5 -4
- package/dist/ai-chat-agent.js +64 -26
- package/dist/ai-chat-agent.js.map +1 -1
- package/dist/ai-react.d.ts +10 -9
- package/dist/ai-react.js +27 -27
- package/dist/ai-react.js.map +1 -1
- package/dist/{chunk-QSGN3REV.js → chunk-KUH345EY.js} +8 -15
- package/dist/chunk-KUH345EY.js.map +1 -0
- package/dist/{chunk-4ARKO5R4.js → chunk-MGHXAF5T.js} +390 -89
- package/dist/chunk-MGHXAF5T.js.map +1 -0
- package/dist/{chunk-Y67CHZBI.js → chunk-MW5BQ2FW.js} +23 -18
- package/dist/chunk-MW5BQ2FW.js.map +1 -0
- package/dist/{chunk-BZXOAZUX.js → chunk-PVQZBKN7.js} +5 -5
- package/dist/chunk-PVQZBKN7.js.map +1 -0
- package/dist/client.d.ts +8 -2
- package/dist/client.js +1 -1
- package/dist/index-CEVsIbwa.d.ts +565 -0
- package/dist/index.d.ts +32 -398
- package/dist/index.js +10 -4
- package/dist/mcp/client.d.ts +281 -9
- package/dist/mcp/client.js +1 -1
- package/dist/mcp/do-oauth-client-provider.js +1 -1
- package/dist/mcp/index.d.ts +9 -9
- package/dist/mcp/index.js +55 -51
- package/dist/mcp/index.js.map +1 -1
- package/dist/observability/index.d.ts +12 -0
- package/dist/observability/index.js +10 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/react.d.ts +56 -38
- package/dist/react.js +16 -6
- package/dist/react.js.map +1 -1
- package/dist/schedule.d.ts +6 -6
- package/dist/schedule.js +4 -4
- package/dist/schedule.js.map +1 -1
- package/dist/serializable.d.ts +32 -0
- package/dist/serializable.js +1 -0
- package/dist/serializable.js.map +1 -0
- package/package.json +76 -72
- package/src/index.ts +517 -126
- package/dist/chunk-4ARKO5R4.js.map +0 -1
- package/dist/chunk-BZXOAZUX.js.map +0 -1
- package/dist/chunk-QSGN3REV.js.map +0 -1
- package/dist/chunk-Y67CHZBI.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -1,34 +1,32 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
|
+
import type { SSEClientTransportOptions } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
Prompt,
|
|
7
|
+
Resource,
|
|
8
|
+
ServerCapabilities,
|
|
9
|
+
Tool
|
|
10
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
11
|
+
import { parseCronExpression } from "cron-schedule";
|
|
12
|
+
import { nanoid } from "nanoid";
|
|
13
|
+
import { EmailMessage } from "cloudflare:email";
|
|
1
14
|
import {
|
|
2
|
-
Server,
|
|
3
|
-
routePartykitRequest,
|
|
4
|
-
type PartyServerOptions,
|
|
5
|
-
getServerByName,
|
|
6
15
|
type Connection,
|
|
7
16
|
type ConnectionContext,
|
|
17
|
+
type PartyServerOptions,
|
|
18
|
+
Server,
|
|
8
19
|
type WSMessage,
|
|
20
|
+
getServerByName,
|
|
21
|
+
routePartykitRequest
|
|
9
22
|
} from "partyserver";
|
|
10
|
-
|
|
11
|
-
import { parseCronExpression } from "cron-schedule";
|
|
12
|
-
import { nanoid } from "nanoid";
|
|
13
|
-
|
|
14
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
15
|
-
import { MCPClientManager } from "./mcp/client";
|
|
16
|
-
import {
|
|
17
|
-
DurableObjectOAuthClientProvider,
|
|
18
|
-
type AgentsOAuthProvider,
|
|
19
|
-
} from "./mcp/do-oauth-client-provider";
|
|
20
|
-
import type {
|
|
21
|
-
Tool,
|
|
22
|
-
Resource,
|
|
23
|
-
Prompt,
|
|
24
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
25
|
-
|
|
26
|
-
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
27
|
-
import type { SSEClientTransportOptions } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
28
|
-
|
|
29
23
|
import { camelCaseToKebabCase } from "./client";
|
|
24
|
+
import { MCPClientManager } from "./mcp/client";
|
|
25
|
+
// import type { MCPClientConnection } from "./mcp/client-connection";
|
|
26
|
+
import { DurableObjectOAuthClientProvider } from "./mcp/do-oauth-client-provider";
|
|
27
|
+
import { genericObservability, type Observability } from "./observability";
|
|
30
28
|
|
|
31
|
-
export type { Connection,
|
|
29
|
+
export type { Connection, ConnectionContext, WSMessage } from "partyserver";
|
|
32
30
|
|
|
33
31
|
/**
|
|
34
32
|
* RPC request message from client
|
|
@@ -121,6 +119,7 @@ const callableMetadata = new Map<Function, CallableMetadata>();
|
|
|
121
119
|
export function unstable_callable(metadata: CallableMetadata = {}) {
|
|
122
120
|
return function callableDecorator<This, Args extends unknown[], Return>(
|
|
123
121
|
target: (this: This, ...args: Args) => Return,
|
|
122
|
+
// biome-ignore lint/correctness/noUnusedFunctionParameters: later
|
|
124
123
|
context: ClassMethodDecoratorContext
|
|
125
124
|
) {
|
|
126
125
|
if (!callableMetadata.has(target)) {
|
|
@@ -193,7 +192,12 @@ export type MCPServer = {
|
|
|
193
192
|
name: string;
|
|
194
193
|
server_url: string;
|
|
195
194
|
auth_url: string | null;
|
|
195
|
+
// This state is specifically about the temporary process of getting a token (if needed).
|
|
196
|
+
// Scope outside of that can't be relied upon because when the DO sleeps, there's no way
|
|
197
|
+
// to communicate a change to a non-ready state.
|
|
196
198
|
state: "authenticating" | "connecting" | "ready" | "discovering" | "failed";
|
|
199
|
+
instructions: string | null;
|
|
200
|
+
capabilities: ServerCapabilities | null;
|
|
197
201
|
};
|
|
198
202
|
|
|
199
203
|
/**
|
|
@@ -218,10 +222,11 @@ const agentContext = new AsyncLocalStorage<{
|
|
|
218
222
|
agent: Agent<unknown>;
|
|
219
223
|
connection: Connection | undefined;
|
|
220
224
|
request: Request | undefined;
|
|
225
|
+
email: AgentEmail | undefined;
|
|
221
226
|
}>();
|
|
222
227
|
|
|
223
228
|
export function getCurrentAgent<
|
|
224
|
-
T extends Agent<unknown, unknown> = Agent<unknown, unknown
|
|
229
|
+
T extends Agent<unknown, unknown> = Agent<unknown, unknown>
|
|
225
230
|
>(): {
|
|
226
231
|
agent: T | undefined;
|
|
227
232
|
connection: Connection | undefined;
|
|
@@ -238,7 +243,7 @@ export function getCurrentAgent<
|
|
|
238
243
|
return {
|
|
239
244
|
agent: undefined,
|
|
240
245
|
connection: undefined,
|
|
241
|
-
request: undefined
|
|
246
|
+
request: undefined
|
|
242
247
|
};
|
|
243
248
|
}
|
|
244
249
|
return store;
|
|
@@ -311,9 +316,14 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
311
316
|
*/
|
|
312
317
|
static options = {
|
|
313
318
|
/** Whether the Agent should hibernate when inactive */
|
|
314
|
-
hibernate: true
|
|
319
|
+
hibernate: true // default to hibernate
|
|
315
320
|
};
|
|
316
321
|
|
|
322
|
+
/**
|
|
323
|
+
* The observability implementation to use for the Agent
|
|
324
|
+
*/
|
|
325
|
+
observability?: Observability = genericObservability;
|
|
326
|
+
|
|
317
327
|
/**
|
|
318
328
|
* Execute SQL queries against the Agent's database
|
|
319
329
|
* @template T Type of the returned rows
|
|
@@ -386,7 +396,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
386
396
|
const _onRequest = this.onRequest.bind(this);
|
|
387
397
|
this.onRequest = (request: Request) => {
|
|
388
398
|
return agentContext.run(
|
|
389
|
-
{ agent: this, connection: undefined, request },
|
|
399
|
+
{ agent: this, connection: undefined, request, email: undefined },
|
|
390
400
|
async () => {
|
|
391
401
|
if (this.mcp.isCallbackRequest(request)) {
|
|
392
402
|
await this.mcp.handleCallbackRequest(request);
|
|
@@ -394,15 +404,15 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
394
404
|
// after the MCP connection handshake, we can send updated mcp state
|
|
395
405
|
this.broadcast(
|
|
396
406
|
JSON.stringify({
|
|
397
|
-
|
|
398
|
-
|
|
407
|
+
mcp: this.getMcpServers(),
|
|
408
|
+
type: "cf_agent_mcp_servers"
|
|
399
409
|
})
|
|
400
410
|
);
|
|
401
411
|
|
|
402
412
|
// We probably should let the user configure this response/redirect, but this is fine for now.
|
|
403
413
|
return new Response("<script>window.close();</script>", {
|
|
404
|
-
status: 200,
|
|
405
414
|
headers: { "content-type": "text/html" },
|
|
415
|
+
status: 200
|
|
406
416
|
});
|
|
407
417
|
}
|
|
408
418
|
|
|
@@ -414,7 +424,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
414
424
|
const _onMessage = this.onMessage.bind(this);
|
|
415
425
|
this.onMessage = async (connection: Connection, message: WSMessage) => {
|
|
416
426
|
return agentContext.run(
|
|
417
|
-
{ agent: this, connection, request: undefined },
|
|
427
|
+
{ agent: this, connection, request: undefined, email: undefined },
|
|
418
428
|
async () => {
|
|
419
429
|
if (typeof message !== "string") {
|
|
420
430
|
return this._tryCatch(() => _onMessage(connection, message));
|
|
@@ -423,7 +433,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
423
433
|
let parsed: unknown;
|
|
424
434
|
try {
|
|
425
435
|
parsed = JSON.parse(message);
|
|
426
|
-
} catch (
|
|
436
|
+
} catch (_e) {
|
|
427
437
|
// silently fail and let the onMessage handler handle it
|
|
428
438
|
return this._tryCatch(() => _onMessage(connection, message));
|
|
429
439
|
}
|
|
@@ -458,22 +468,39 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
458
468
|
|
|
459
469
|
// For regular methods, execute and send response
|
|
460
470
|
const result = await methodFn.apply(this, args);
|
|
471
|
+
|
|
472
|
+
this.observability?.emit(
|
|
473
|
+
{
|
|
474
|
+
displayMessage: `RPC call to ${method}`,
|
|
475
|
+
id: nanoid(),
|
|
476
|
+
payload: {
|
|
477
|
+
args,
|
|
478
|
+
method,
|
|
479
|
+
streaming: metadata?.streaming,
|
|
480
|
+
success: true
|
|
481
|
+
},
|
|
482
|
+
timestamp: Date.now(),
|
|
483
|
+
type: "rpc"
|
|
484
|
+
},
|
|
485
|
+
this.ctx
|
|
486
|
+
);
|
|
487
|
+
|
|
461
488
|
const response: RPCResponse = {
|
|
462
|
-
|
|
489
|
+
done: true,
|
|
463
490
|
id,
|
|
464
|
-
success: true,
|
|
465
491
|
result,
|
|
466
|
-
|
|
492
|
+
success: true,
|
|
493
|
+
type: "rpc"
|
|
467
494
|
};
|
|
468
495
|
connection.send(JSON.stringify(response));
|
|
469
496
|
} catch (e) {
|
|
470
497
|
// Send error response
|
|
471
498
|
const response: RPCResponse = {
|
|
472
|
-
type: "rpc",
|
|
473
|
-
id: parsed.id,
|
|
474
|
-
success: false,
|
|
475
499
|
error:
|
|
476
500
|
e instanceof Error ? e.message : "Unknown error occurred",
|
|
501
|
+
id: parsed.id,
|
|
502
|
+
success: false,
|
|
503
|
+
type: "rpc"
|
|
477
504
|
};
|
|
478
505
|
connection.send(JSON.stringify(response));
|
|
479
506
|
console.error("RPC error:", e);
|
|
@@ -491,25 +518,37 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
491
518
|
// TODO: This is a hack to ensure the state is sent after the connection is established
|
|
492
519
|
// must fix this
|
|
493
520
|
return agentContext.run(
|
|
494
|
-
{ agent: this, connection, request: ctx.request },
|
|
521
|
+
{ agent: this, connection, request: ctx.request, email: undefined },
|
|
495
522
|
async () => {
|
|
496
523
|
setTimeout(() => {
|
|
497
524
|
if (this.state) {
|
|
498
525
|
connection.send(
|
|
499
526
|
JSON.stringify({
|
|
500
|
-
type: "cf_agent_state",
|
|
501
527
|
state: this.state,
|
|
528
|
+
type: "cf_agent_state"
|
|
502
529
|
})
|
|
503
530
|
);
|
|
504
531
|
}
|
|
505
532
|
|
|
506
533
|
connection.send(
|
|
507
534
|
JSON.stringify({
|
|
508
|
-
|
|
509
|
-
|
|
535
|
+
mcp: this.getMcpServers(),
|
|
536
|
+
type: "cf_agent_mcp_servers"
|
|
510
537
|
})
|
|
511
538
|
);
|
|
512
539
|
|
|
540
|
+
this.observability?.emit(
|
|
541
|
+
{
|
|
542
|
+
displayMessage: "Connection established",
|
|
543
|
+
id: nanoid(),
|
|
544
|
+
payload: {
|
|
545
|
+
connectionId: connection.id
|
|
546
|
+
},
|
|
547
|
+
timestamp: Date.now(),
|
|
548
|
+
type: "connect"
|
|
549
|
+
},
|
|
550
|
+
this.ctx
|
|
551
|
+
);
|
|
513
552
|
return this._tryCatch(() => _onConnect(connection, ctx));
|
|
514
553
|
}, 20);
|
|
515
554
|
}
|
|
@@ -519,14 +558,19 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
519
558
|
const _onStart = this.onStart.bind(this);
|
|
520
559
|
this.onStart = async () => {
|
|
521
560
|
return agentContext.run(
|
|
522
|
-
{
|
|
561
|
+
{
|
|
562
|
+
agent: this,
|
|
563
|
+
connection: undefined,
|
|
564
|
+
request: undefined,
|
|
565
|
+
email: undefined
|
|
566
|
+
},
|
|
523
567
|
async () => {
|
|
524
568
|
const servers = this.sql<MCPServerRow>`
|
|
525
569
|
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
|
|
526
570
|
`;
|
|
527
571
|
|
|
528
|
-
// from DO storage, reconnect to all servers using our saved auth information
|
|
529
|
-
|
|
572
|
+
// from DO storage, reconnect to all servers not currently in the oauth flow using our saved auth information
|
|
573
|
+
Promise.allSettled(
|
|
530
574
|
servers.map((server) => {
|
|
531
575
|
return this._connectToMcpServerInternal(
|
|
532
576
|
server.name,
|
|
@@ -537,19 +581,18 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
537
581
|
: undefined,
|
|
538
582
|
{
|
|
539
583
|
id: server.id,
|
|
540
|
-
oauthClientId: server.client_id ?? undefined
|
|
584
|
+
oauthClientId: server.client_id ?? undefined
|
|
541
585
|
}
|
|
542
586
|
);
|
|
543
587
|
})
|
|
544
|
-
)
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
);
|
|
552
|
-
|
|
588
|
+
).then((_results) => {
|
|
589
|
+
this.broadcast(
|
|
590
|
+
JSON.stringify({
|
|
591
|
+
mcp: this.getMcpServers(),
|
|
592
|
+
type: "cf_agent_mcp_servers"
|
|
593
|
+
})
|
|
594
|
+
);
|
|
595
|
+
});
|
|
553
596
|
await this._tryCatch(() => _onStart());
|
|
554
597
|
}
|
|
555
598
|
);
|
|
@@ -560,6 +603,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
560
603
|
state: State,
|
|
561
604
|
source: Connection | "server" = "server"
|
|
562
605
|
) {
|
|
606
|
+
const previousState = this._state;
|
|
563
607
|
this._state = state;
|
|
564
608
|
this.sql`
|
|
565
609
|
INSERT OR REPLACE INTO cf_agents_state (id, state)
|
|
@@ -571,16 +615,29 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
571
615
|
`;
|
|
572
616
|
this.broadcast(
|
|
573
617
|
JSON.stringify({
|
|
574
|
-
type: "cf_agent_state",
|
|
575
618
|
state: state,
|
|
619
|
+
type: "cf_agent_state"
|
|
576
620
|
}),
|
|
577
621
|
source !== "server" ? [source.id] : []
|
|
578
622
|
);
|
|
579
623
|
return this._tryCatch(() => {
|
|
580
|
-
const { connection, request } = agentContext.getStore() || {};
|
|
624
|
+
const { connection, request, email } = agentContext.getStore() || {};
|
|
581
625
|
return agentContext.run(
|
|
582
|
-
{ agent: this, connection, request },
|
|
626
|
+
{ agent: this, connection, request, email },
|
|
583
627
|
async () => {
|
|
628
|
+
this.observability?.emit(
|
|
629
|
+
{
|
|
630
|
+
displayMessage: "State updated",
|
|
631
|
+
id: nanoid(),
|
|
632
|
+
payload: {
|
|
633
|
+
previousState,
|
|
634
|
+
state
|
|
635
|
+
},
|
|
636
|
+
timestamp: Date.now(),
|
|
637
|
+
type: "state:update"
|
|
638
|
+
},
|
|
639
|
+
this.ctx
|
|
640
|
+
);
|
|
584
641
|
return this.onStateUpdate(state, source);
|
|
585
642
|
}
|
|
586
643
|
);
|
|
@@ -600,23 +657,89 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
600
657
|
* @param state Updated state
|
|
601
658
|
* @param source Source of the state update ("server" or a client connection)
|
|
602
659
|
*/
|
|
660
|
+
// biome-ignore lint/correctness/noUnusedFunctionParameters: overridden later
|
|
603
661
|
onStateUpdate(state: State | undefined, source: Connection | "server") {
|
|
604
662
|
// override this to handle state updates
|
|
605
663
|
}
|
|
606
664
|
|
|
607
665
|
/**
|
|
608
|
-
* Called when the Agent receives an email
|
|
666
|
+
* Called when the Agent receives an email via routeAgentEmail()
|
|
667
|
+
* Override this method to handle incoming emails
|
|
609
668
|
* @param email Email message to process
|
|
610
669
|
*/
|
|
611
|
-
|
|
670
|
+
async _onEmail(email: AgentEmail) {
|
|
671
|
+
// nb: we use this roundabout way of getting to onEmail
|
|
672
|
+
// because of https://github.com/cloudflare/workerd/issues/4499
|
|
612
673
|
return agentContext.run(
|
|
613
|
-
{ agent: this, connection: undefined, request: undefined },
|
|
674
|
+
{ agent: this, connection: undefined, request: undefined, email: email },
|
|
614
675
|
async () => {
|
|
615
|
-
|
|
676
|
+
if ("onEmail" in this && typeof this.onEmail === "function") {
|
|
677
|
+
return this._tryCatch(() =>
|
|
678
|
+
(this.onEmail as (email: AgentEmail) => Promise<void>)(email)
|
|
679
|
+
);
|
|
680
|
+
} else {
|
|
681
|
+
console.log("Received email from:", email.from, "to:", email.to);
|
|
682
|
+
console.log("Subject:", email.headers.get("subject"));
|
|
683
|
+
console.log(
|
|
684
|
+
"Implement onEmail(email: AgentEmail): Promise<void> in your agent to process emails"
|
|
685
|
+
);
|
|
686
|
+
}
|
|
616
687
|
}
|
|
617
688
|
);
|
|
618
689
|
}
|
|
619
690
|
|
|
691
|
+
/**
|
|
692
|
+
* Reply to an email
|
|
693
|
+
* @param email The email to reply to
|
|
694
|
+
* @param options Options for the reply
|
|
695
|
+
* @returns void
|
|
696
|
+
*/
|
|
697
|
+
async replyToEmail(
|
|
698
|
+
email: AgentEmail,
|
|
699
|
+
options: {
|
|
700
|
+
fromName: string;
|
|
701
|
+
subject?: string | undefined;
|
|
702
|
+
body: string;
|
|
703
|
+
contentType?: string;
|
|
704
|
+
headers?: Record<string, string>;
|
|
705
|
+
}
|
|
706
|
+
): Promise<void> {
|
|
707
|
+
return this._tryCatch(async () => {
|
|
708
|
+
const agentName = camelCaseToKebabCase(this._ParentClass.name);
|
|
709
|
+
const agentId = this.name;
|
|
710
|
+
|
|
711
|
+
const { createMimeMessage } = await import("mimetext");
|
|
712
|
+
const msg = createMimeMessage();
|
|
713
|
+
msg.setSender({ addr: email.to, name: options.fromName });
|
|
714
|
+
msg.setRecipient(email.from);
|
|
715
|
+
msg.setSubject(
|
|
716
|
+
options.subject || `Re: ${email.headers.get("subject")}` || "No subject"
|
|
717
|
+
);
|
|
718
|
+
msg.addMessage({
|
|
719
|
+
contentType: options.contentType || "text/plain",
|
|
720
|
+
data: options.body
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
const domain = email.from.split("@")[1];
|
|
724
|
+
const messageId = `<${agentId}@${domain}>`;
|
|
725
|
+
msg.setHeader("In-Reply-To", email.headers.get("Message-ID")!);
|
|
726
|
+
msg.setHeader("Message-ID", messageId);
|
|
727
|
+
msg.setHeader("X-Agent-Name", agentName);
|
|
728
|
+
msg.setHeader("X-Agent-ID", agentId);
|
|
729
|
+
|
|
730
|
+
if (options.headers) {
|
|
731
|
+
for (const [key, value] of Object.entries(options.headers)) {
|
|
732
|
+
msg.setHeader(key, value);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
await email.reply({
|
|
736
|
+
from: email.to,
|
|
737
|
+
raw: msg.asRaw(),
|
|
738
|
+
to: email.from
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
|
|
620
743
|
private async _tryCatch<T>(fn: () => T | Promise<T>) {
|
|
621
744
|
try {
|
|
622
745
|
return await fn();
|
|
@@ -674,6 +797,18 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
674
797
|
): Promise<Schedule<T>> {
|
|
675
798
|
const id = nanoid(9);
|
|
676
799
|
|
|
800
|
+
const emitScheduleCreate = (schedule: Schedule<T>) =>
|
|
801
|
+
this.observability?.emit(
|
|
802
|
+
{
|
|
803
|
+
displayMessage: `Schedule ${schedule.id} created`,
|
|
804
|
+
id: nanoid(),
|
|
805
|
+
payload: schedule,
|
|
806
|
+
timestamp: Date.now(),
|
|
807
|
+
type: "schedule:create"
|
|
808
|
+
},
|
|
809
|
+
this.ctx
|
|
810
|
+
);
|
|
811
|
+
|
|
677
812
|
if (typeof callback !== "string") {
|
|
678
813
|
throw new Error("Callback must be a string");
|
|
679
814
|
}
|
|
@@ -693,13 +828,17 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
693
828
|
|
|
694
829
|
await this._scheduleNextAlarm();
|
|
695
830
|
|
|
696
|
-
|
|
697
|
-
id,
|
|
831
|
+
const schedule: Schedule<T> = {
|
|
698
832
|
callback: callback,
|
|
833
|
+
id,
|
|
699
834
|
payload: payload as T,
|
|
700
835
|
time: timestamp,
|
|
701
|
-
type: "scheduled"
|
|
836
|
+
type: "scheduled"
|
|
702
837
|
};
|
|
838
|
+
|
|
839
|
+
emitScheduleCreate(schedule);
|
|
840
|
+
|
|
841
|
+
return schedule;
|
|
703
842
|
}
|
|
704
843
|
if (typeof when === "number") {
|
|
705
844
|
const time = new Date(Date.now() + when * 1000);
|
|
@@ -714,14 +853,18 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
714
853
|
|
|
715
854
|
await this._scheduleNextAlarm();
|
|
716
855
|
|
|
717
|
-
|
|
718
|
-
id,
|
|
856
|
+
const schedule: Schedule<T> = {
|
|
719
857
|
callback: callback,
|
|
720
|
-
payload: payload as T,
|
|
721
858
|
delayInSeconds: when,
|
|
859
|
+
id,
|
|
860
|
+
payload: payload as T,
|
|
722
861
|
time: timestamp,
|
|
723
|
-
type: "delayed"
|
|
862
|
+
type: "delayed"
|
|
724
863
|
};
|
|
864
|
+
|
|
865
|
+
emitScheduleCreate(schedule);
|
|
866
|
+
|
|
867
|
+
return schedule;
|
|
725
868
|
}
|
|
726
869
|
if (typeof when === "string") {
|
|
727
870
|
const nextExecutionTime = getNextCronTime(when);
|
|
@@ -736,14 +879,18 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
736
879
|
|
|
737
880
|
await this._scheduleNextAlarm();
|
|
738
881
|
|
|
739
|
-
|
|
740
|
-
id,
|
|
882
|
+
const schedule: Schedule<T> = {
|
|
741
883
|
callback: callback,
|
|
742
|
-
payload: payload as T,
|
|
743
884
|
cron: when,
|
|
885
|
+
id,
|
|
886
|
+
payload: payload as T,
|
|
744
887
|
time: timestamp,
|
|
745
|
-
type: "cron"
|
|
888
|
+
type: "cron"
|
|
746
889
|
};
|
|
890
|
+
|
|
891
|
+
emitScheduleCreate(schedule);
|
|
892
|
+
|
|
893
|
+
return schedule;
|
|
747
894
|
}
|
|
748
895
|
throw new Error("Invalid schedule type");
|
|
749
896
|
}
|
|
@@ -807,7 +954,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
807
954
|
.toArray()
|
|
808
955
|
.map((row) => ({
|
|
809
956
|
...row,
|
|
810
|
-
payload: JSON.parse(row.payload as string) as T
|
|
957
|
+
payload: JSON.parse(row.payload as string) as T
|
|
811
958
|
})) as Schedule<T>[];
|
|
812
959
|
|
|
813
960
|
return result;
|
|
@@ -819,6 +966,19 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
819
966
|
* @returns true if the task was cancelled, false otherwise
|
|
820
967
|
*/
|
|
821
968
|
async cancelSchedule(id: string): Promise<boolean> {
|
|
969
|
+
const schedule = await this.getSchedule(id);
|
|
970
|
+
if (schedule) {
|
|
971
|
+
this.observability?.emit(
|
|
972
|
+
{
|
|
973
|
+
displayMessage: `Schedule ${id} cancelled`,
|
|
974
|
+
id: nanoid(),
|
|
975
|
+
payload: schedule,
|
|
976
|
+
timestamp: Date.now(),
|
|
977
|
+
type: "schedule:cancel"
|
|
978
|
+
},
|
|
979
|
+
this.ctx
|
|
980
|
+
);
|
|
981
|
+
}
|
|
822
982
|
this.sql`DELETE FROM cf_agents_schedules WHERE id = ${id}`;
|
|
823
983
|
|
|
824
984
|
await this._scheduleNextAlarm();
|
|
@@ -864,9 +1024,25 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
864
1024
|
continue;
|
|
865
1025
|
}
|
|
866
1026
|
await agentContext.run(
|
|
867
|
-
{
|
|
1027
|
+
{
|
|
1028
|
+
agent: this,
|
|
1029
|
+
connection: undefined,
|
|
1030
|
+
request: undefined,
|
|
1031
|
+
email: undefined
|
|
1032
|
+
},
|
|
868
1033
|
async () => {
|
|
869
1034
|
try {
|
|
1035
|
+
this.observability?.emit(
|
|
1036
|
+
{
|
|
1037
|
+
displayMessage: `Schedule ${row.id} executed`,
|
|
1038
|
+
id: nanoid(),
|
|
1039
|
+
payload: row,
|
|
1040
|
+
timestamp: Date.now(),
|
|
1041
|
+
type: "schedule:execute"
|
|
1042
|
+
},
|
|
1043
|
+
this.ctx
|
|
1044
|
+
);
|
|
1045
|
+
|
|
870
1046
|
await (
|
|
871
1047
|
callback as (
|
|
872
1048
|
payload: unknown,
|
|
@@ -910,6 +1086,18 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
910
1086
|
// delete all alarms
|
|
911
1087
|
await this.ctx.storage.deleteAlarm();
|
|
912
1088
|
await this.ctx.storage.deleteAll();
|
|
1089
|
+
this.ctx.abort("destroyed"); // enforce that the agent is evicted
|
|
1090
|
+
|
|
1091
|
+
this.observability?.emit(
|
|
1092
|
+
{
|
|
1093
|
+
displayMessage: "Agent destroyed",
|
|
1094
|
+
id: nanoid(),
|
|
1095
|
+
payload: {},
|
|
1096
|
+
timestamp: Date.now(),
|
|
1097
|
+
type: "destroy"
|
|
1098
|
+
},
|
|
1099
|
+
this.ctx
|
|
1100
|
+
);
|
|
913
1101
|
}
|
|
914
1102
|
|
|
915
1103
|
/**
|
|
@@ -949,11 +1137,24 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
949
1137
|
callbackUrl,
|
|
950
1138
|
options
|
|
951
1139
|
);
|
|
1140
|
+
this.sql`
|
|
1141
|
+
INSERT
|
|
1142
|
+
OR REPLACE INTO cf_agents_mcp_servers (id, name, server_url, client_id, auth_url, callback_url, server_options)
|
|
1143
|
+
VALUES (
|
|
1144
|
+
${result.id},
|
|
1145
|
+
${serverName},
|
|
1146
|
+
${url},
|
|
1147
|
+
${result.clientId ?? null},
|
|
1148
|
+
${result.authUrl ?? null},
|
|
1149
|
+
${callbackUrl},
|
|
1150
|
+
${options ? JSON.stringify(options) : null}
|
|
1151
|
+
);
|
|
1152
|
+
`;
|
|
952
1153
|
|
|
953
1154
|
this.broadcast(
|
|
954
1155
|
JSON.stringify({
|
|
955
|
-
|
|
956
|
-
|
|
1156
|
+
mcp: this.getMcpServers(),
|
|
1157
|
+
type: "cf_agent_mcp_servers"
|
|
957
1158
|
})
|
|
958
1159
|
);
|
|
959
1160
|
|
|
@@ -961,7 +1162,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
961
1162
|
}
|
|
962
1163
|
|
|
963
1164
|
async _connectToMcpServerInternal(
|
|
964
|
-
|
|
1165
|
+
_serverName: string,
|
|
965
1166
|
url: string,
|
|
966
1167
|
callbackUrl: string,
|
|
967
1168
|
// it's important that any options here are serializable because we put them into our sqlite DB for reconnection purposes
|
|
@@ -982,7 +1183,11 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
982
1183
|
id: string;
|
|
983
1184
|
oauthClientId?: string;
|
|
984
1185
|
}
|
|
985
|
-
): Promise<{
|
|
1186
|
+
): Promise<{
|
|
1187
|
+
id: string;
|
|
1188
|
+
authUrl: string | undefined;
|
|
1189
|
+
clientId: string | undefined;
|
|
1190
|
+
}> {
|
|
986
1191
|
const authProvider = new DurableObjectOAuthClientProvider(
|
|
987
1192
|
this.ctx.storage,
|
|
988
1193
|
this.name,
|
|
@@ -1005,40 +1210,28 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1005
1210
|
fetch: (url, init) =>
|
|
1006
1211
|
fetch(url, {
|
|
1007
1212
|
...init,
|
|
1008
|
-
headers: options?.transport?.headers
|
|
1009
|
-
})
|
|
1213
|
+
headers: options?.transport?.headers
|
|
1214
|
+
})
|
|
1010
1215
|
},
|
|
1011
1216
|
requestInit: {
|
|
1012
|
-
headers: options?.transport?.headers
|
|
1013
|
-
}
|
|
1217
|
+
headers: options?.transport?.headers
|
|
1218
|
+
}
|
|
1014
1219
|
};
|
|
1015
1220
|
}
|
|
1016
1221
|
|
|
1017
1222
|
const { id, authUrl, clientId } = await this.mcp.connect(url, {
|
|
1223
|
+
client: options?.client,
|
|
1018
1224
|
reconnect,
|
|
1019
1225
|
transport: {
|
|
1020
1226
|
...headerTransportOpts,
|
|
1021
|
-
authProvider
|
|
1022
|
-
}
|
|
1023
|
-
client: options?.client,
|
|
1227
|
+
authProvider
|
|
1228
|
+
}
|
|
1024
1229
|
});
|
|
1025
1230
|
|
|
1026
|
-
this.sql`
|
|
1027
|
-
INSERT OR REPLACE INTO cf_agents_mcp_servers (id, name, server_url, client_id, auth_url, callback_url, server_options)
|
|
1028
|
-
VALUES (
|
|
1029
|
-
${id},
|
|
1030
|
-
${serverName},
|
|
1031
|
-
${url},
|
|
1032
|
-
${clientId ?? null},
|
|
1033
|
-
${authUrl ?? null},
|
|
1034
|
-
${callbackUrl},
|
|
1035
|
-
${options ? JSON.stringify(options) : null}
|
|
1036
|
-
);
|
|
1037
|
-
`;
|
|
1038
|
-
|
|
1039
1231
|
return {
|
|
1040
|
-
id,
|
|
1041
1232
|
authUrl,
|
|
1233
|
+
clientId,
|
|
1234
|
+
id
|
|
1042
1235
|
};
|
|
1043
1236
|
}
|
|
1044
1237
|
|
|
@@ -1049,18 +1242,18 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1049
1242
|
`;
|
|
1050
1243
|
this.broadcast(
|
|
1051
1244
|
JSON.stringify({
|
|
1052
|
-
|
|
1053
|
-
|
|
1245
|
+
mcp: this.getMcpServers(),
|
|
1246
|
+
type: "cf_agent_mcp_servers"
|
|
1054
1247
|
})
|
|
1055
1248
|
);
|
|
1056
1249
|
}
|
|
1057
1250
|
|
|
1058
|
-
|
|
1251
|
+
getMcpServers(): MCPServersState {
|
|
1059
1252
|
const mcpState: MCPServersState = {
|
|
1060
|
-
servers: {},
|
|
1061
|
-
tools: this.mcp.listTools(),
|
|
1062
1253
|
prompts: this.mcp.listPrompts(),
|
|
1063
1254
|
resources: this.mcp.listResources(),
|
|
1255
|
+
servers: {},
|
|
1256
|
+
tools: this.mcp.listTools()
|
|
1064
1257
|
};
|
|
1065
1258
|
|
|
1066
1259
|
const servers = this.sql<MCPServerRow>`
|
|
@@ -1068,11 +1261,15 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
1068
1261
|
`;
|
|
1069
1262
|
|
|
1070
1263
|
for (const server of servers) {
|
|
1264
|
+
const serverConn = this.mcp.mcpConnections[server.id];
|
|
1071
1265
|
mcpState.servers[server.id] = {
|
|
1266
|
+
auth_url: server.auth_url,
|
|
1267
|
+
capabilities: serverConn?.serverCapabilities ?? null,
|
|
1268
|
+
instructions: serverConn?.instructions ?? null,
|
|
1072
1269
|
name: server.name,
|
|
1073
1270
|
server_url: server.server_url,
|
|
1074
|
-
|
|
1075
|
-
state:
|
|
1271
|
+
// mark as "authenticating" because the server isn't automatically connected, so it's pending authenticating
|
|
1272
|
+
state: serverConn?.connectionState ?? "authenticating"
|
|
1076
1273
|
};
|
|
1077
1274
|
}
|
|
1078
1275
|
|
|
@@ -1117,17 +1314,17 @@ export async function routeAgentRequest<Env>(
|
|
|
1117
1314
|
const corsHeaders =
|
|
1118
1315
|
options?.cors === true
|
|
1119
1316
|
? {
|
|
1120
|
-
"Access-Control-Allow-Origin": "*",
|
|
1121
|
-
"Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS",
|
|
1122
1317
|
"Access-Control-Allow-Credentials": "true",
|
|
1123
|
-
"Access-Control-
|
|
1318
|
+
"Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS",
|
|
1319
|
+
"Access-Control-Allow-Origin": "*",
|
|
1320
|
+
"Access-Control-Max-Age": "86400"
|
|
1124
1321
|
}
|
|
1125
1322
|
: options?.cors;
|
|
1126
1323
|
|
|
1127
1324
|
if (request.method === "OPTIONS") {
|
|
1128
1325
|
if (corsHeaders) {
|
|
1129
1326
|
return new Response(null, {
|
|
1130
|
-
headers: corsHeaders
|
|
1327
|
+
headers: corsHeaders
|
|
1131
1328
|
});
|
|
1132
1329
|
}
|
|
1133
1330
|
console.warn(
|
|
@@ -1140,7 +1337,7 @@ export async function routeAgentRequest<Env>(
|
|
|
1140
1337
|
env as Record<string, unknown>,
|
|
1141
1338
|
{
|
|
1142
1339
|
prefix: "agents",
|
|
1143
|
-
...(options as PartyServerOptions<Record<string, unknown>>)
|
|
1340
|
+
...(options as PartyServerOptions<Record<string, unknown>>)
|
|
1144
1341
|
}
|
|
1145
1342
|
);
|
|
1146
1343
|
|
|
@@ -1153,24 +1350,218 @@ export async function routeAgentRequest<Env>(
|
|
|
1153
1350
|
response = new Response(response.body, {
|
|
1154
1351
|
headers: {
|
|
1155
1352
|
...response.headers,
|
|
1156
|
-
...corsHeaders
|
|
1157
|
-
}
|
|
1353
|
+
...corsHeaders
|
|
1354
|
+
}
|
|
1158
1355
|
});
|
|
1159
1356
|
}
|
|
1160
1357
|
return response;
|
|
1161
1358
|
}
|
|
1162
1359
|
|
|
1360
|
+
export type EmailResolver<Env> = (
|
|
1361
|
+
email: ForwardableEmailMessage,
|
|
1362
|
+
env: Env
|
|
1363
|
+
) => Promise<{
|
|
1364
|
+
agentName: string;
|
|
1365
|
+
agentId: string;
|
|
1366
|
+
} | null>;
|
|
1367
|
+
|
|
1368
|
+
/**
|
|
1369
|
+
* Create a resolver that uses the message-id header to determine the agent to route the email to
|
|
1370
|
+
* @returns A function that resolves the agent to route the email to
|
|
1371
|
+
*/
|
|
1372
|
+
export function createHeaderBasedEmailResolver<Env>(): EmailResolver<Env> {
|
|
1373
|
+
return async (email: ForwardableEmailMessage, _env: Env) => {
|
|
1374
|
+
const messageId = email.headers.get("message-id");
|
|
1375
|
+
if (messageId) {
|
|
1376
|
+
const messageIdMatch = messageId.match(/<([^@]+)@([^>]+)>/);
|
|
1377
|
+
if (messageIdMatch) {
|
|
1378
|
+
const [, agentId, domain] = messageIdMatch;
|
|
1379
|
+
const agentName = domain.split(".")[0];
|
|
1380
|
+
return { agentName, agentId };
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
const references = email.headers.get("references");
|
|
1385
|
+
if (references) {
|
|
1386
|
+
const referencesMatch = references.match(
|
|
1387
|
+
/<([A-Za-z0-9+/]{43}=)@([^>]+)>/
|
|
1388
|
+
);
|
|
1389
|
+
if (referencesMatch) {
|
|
1390
|
+
const [, base64Id, domain] = referencesMatch;
|
|
1391
|
+
const agentId = Buffer.from(base64Id, "base64").toString("hex");
|
|
1392
|
+
const agentName = domain.split(".")[0];
|
|
1393
|
+
return { agentName, agentId };
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
const agentName = email.headers.get("x-agent-name");
|
|
1398
|
+
const agentId = email.headers.get("x-agent-id");
|
|
1399
|
+
if (agentName && agentId) {
|
|
1400
|
+
return { agentName, agentId };
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
return null;
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
/**
|
|
1408
|
+
* Create a resolver that uses the email address to determine the agent to route the email to
|
|
1409
|
+
* @param defaultAgentName The default agent name to use if the email address does not contain a sub-address
|
|
1410
|
+
* @returns A function that resolves the agent to route the email to
|
|
1411
|
+
*/
|
|
1412
|
+
export function createAddressBasedEmailResolver<Env>(
|
|
1413
|
+
defaultAgentName: string
|
|
1414
|
+
): EmailResolver<Env> {
|
|
1415
|
+
return async (email: ForwardableEmailMessage, _env: Env) => {
|
|
1416
|
+
const emailMatch = email.to.match(/^([^+@]+)(?:\+([^@]+))?@(.+)$/);
|
|
1417
|
+
if (!emailMatch) {
|
|
1418
|
+
return null;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
const [, localPart, subAddress] = emailMatch;
|
|
1422
|
+
|
|
1423
|
+
if (subAddress) {
|
|
1424
|
+
return {
|
|
1425
|
+
agentName: localPart,
|
|
1426
|
+
agentId: subAddress
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
// Option 2: Use defaultAgentName namespace, localPart as agentId
|
|
1431
|
+
// Common for catch-all email routing to a single EmailAgent namespace
|
|
1432
|
+
return {
|
|
1433
|
+
agentName: defaultAgentName,
|
|
1434
|
+
agentId: localPart
|
|
1435
|
+
};
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
/**
|
|
1440
|
+
* Create a resolver that uses the agentName and agentId to determine the agent to route the email to
|
|
1441
|
+
* @param agentName The name of the agent to route the email to
|
|
1442
|
+
* @param agentId The id of the agent to route the email to
|
|
1443
|
+
* @returns A function that resolves the agent to route the email to
|
|
1444
|
+
*/
|
|
1445
|
+
export function createCatchAllEmailResolver<Env>(
|
|
1446
|
+
agentName: string,
|
|
1447
|
+
agentId: string
|
|
1448
|
+
): EmailResolver<Env> {
|
|
1449
|
+
return async () => ({ agentName, agentId });
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
export type EmailRoutingOptions<Env> = AgentOptions<Env> & {
|
|
1453
|
+
resolver: EmailResolver<Env>;
|
|
1454
|
+
};
|
|
1455
|
+
|
|
1163
1456
|
/**
|
|
1164
1457
|
* Route an email to the appropriate Agent
|
|
1165
|
-
* @param email
|
|
1166
|
-
* @param env
|
|
1167
|
-
* @param options
|
|
1458
|
+
* @param email The email to route
|
|
1459
|
+
* @param env The environment containing the Agent bindings
|
|
1460
|
+
* @param options The options for routing the email
|
|
1461
|
+
* @returns A promise that resolves when the email has been routed
|
|
1168
1462
|
*/
|
|
1169
1463
|
export async function routeAgentEmail<Env>(
|
|
1170
1464
|
email: ForwardableEmailMessage,
|
|
1171
1465
|
env: Env,
|
|
1172
|
-
options
|
|
1173
|
-
): Promise<void> {
|
|
1466
|
+
options: EmailRoutingOptions<Env>
|
|
1467
|
+
): Promise<void> {
|
|
1468
|
+
const routingInfo = await options.resolver(email, env);
|
|
1469
|
+
|
|
1470
|
+
if (!routingInfo) {
|
|
1471
|
+
console.warn("No routing information found for email, dropping message");
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
const namespaceBinding = env[routingInfo.agentName as keyof Env];
|
|
1476
|
+
if (!namespaceBinding) {
|
|
1477
|
+
throw new Error(
|
|
1478
|
+
`Agent namespace '${routingInfo.agentName}' not found in environment`
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
// Type guard to check if this is actually a DurableObjectNamespace (AgentNamespace)
|
|
1483
|
+
if (
|
|
1484
|
+
typeof namespaceBinding !== "object" ||
|
|
1485
|
+
!("idFromName" in namespaceBinding) ||
|
|
1486
|
+
typeof namespaceBinding.idFromName !== "function"
|
|
1487
|
+
) {
|
|
1488
|
+
throw new Error(
|
|
1489
|
+
`Environment binding '${routingInfo.agentName}' is not an AgentNamespace (found: ${typeof namespaceBinding})`
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// Safe cast after runtime validation
|
|
1494
|
+
const namespace = namespaceBinding as unknown as AgentNamespace<Agent<Env>>;
|
|
1495
|
+
|
|
1496
|
+
const agent = await getAgentByName(namespace, routingInfo.agentId);
|
|
1497
|
+
|
|
1498
|
+
// let's make a serialisable version of the email
|
|
1499
|
+
const serialisableEmail: AgentEmail = {
|
|
1500
|
+
getRaw: async () => {
|
|
1501
|
+
const reader = email.raw.getReader();
|
|
1502
|
+
const chunks: Uint8Array[] = [];
|
|
1503
|
+
|
|
1504
|
+
let done = false;
|
|
1505
|
+
while (!done) {
|
|
1506
|
+
const { value, done: readerDone } = await reader.read();
|
|
1507
|
+
done = readerDone;
|
|
1508
|
+
if (value) {
|
|
1509
|
+
chunks.push(value);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
1514
|
+
const combined = new Uint8Array(totalLength);
|
|
1515
|
+
let offset = 0;
|
|
1516
|
+
for (const chunk of chunks) {
|
|
1517
|
+
combined.set(chunk, offset);
|
|
1518
|
+
offset += chunk.length;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
return combined;
|
|
1522
|
+
},
|
|
1523
|
+
headers: email.headers,
|
|
1524
|
+
rawSize: email.rawSize,
|
|
1525
|
+
setReject: (reason: string) => {
|
|
1526
|
+
email.setReject(reason);
|
|
1527
|
+
},
|
|
1528
|
+
forward: (rcptTo: string, headers?: Headers) => {
|
|
1529
|
+
return email.forward(rcptTo, headers);
|
|
1530
|
+
},
|
|
1531
|
+
reply: (options: { from: string; to: string; raw: string }) => {
|
|
1532
|
+
return email.reply(
|
|
1533
|
+
new EmailMessage(options.from, options.to, options.raw)
|
|
1534
|
+
);
|
|
1535
|
+
},
|
|
1536
|
+
from: email.from,
|
|
1537
|
+
to: email.to
|
|
1538
|
+
};
|
|
1539
|
+
|
|
1540
|
+
await agent._onEmail(serialisableEmail);
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
export type AgentEmail = {
|
|
1544
|
+
from: string;
|
|
1545
|
+
to: string;
|
|
1546
|
+
getRaw: () => Promise<Uint8Array>;
|
|
1547
|
+
headers: Headers;
|
|
1548
|
+
rawSize: number;
|
|
1549
|
+
setReject: (reason: string) => void;
|
|
1550
|
+
forward: (rcptTo: string, headers?: Headers) => Promise<void>;
|
|
1551
|
+
reply: (options: { from: string; to: string; raw: string }) => Promise<void>;
|
|
1552
|
+
};
|
|
1553
|
+
|
|
1554
|
+
export type EmailSendOptions = {
|
|
1555
|
+
to: string;
|
|
1556
|
+
subject: string;
|
|
1557
|
+
body: string;
|
|
1558
|
+
contentType?: string;
|
|
1559
|
+
headers?: Record<string, string>;
|
|
1560
|
+
includeRoutingHeaders?: boolean;
|
|
1561
|
+
agentName?: string;
|
|
1562
|
+
agentId?: string;
|
|
1563
|
+
domain?: string;
|
|
1564
|
+
};
|
|
1174
1565
|
|
|
1175
1566
|
/**
|
|
1176
1567
|
* Get or create an Agent by name
|
|
@@ -1214,11 +1605,11 @@ export class StreamingResponse {
|
|
|
1214
1605
|
throw new Error("StreamingResponse is already closed");
|
|
1215
1606
|
}
|
|
1216
1607
|
const response: RPCResponse = {
|
|
1217
|
-
|
|
1608
|
+
done: false,
|
|
1218
1609
|
id: this._id,
|
|
1219
|
-
success: true,
|
|
1220
1610
|
result: chunk,
|
|
1221
|
-
|
|
1611
|
+
success: true,
|
|
1612
|
+
type: "rpc"
|
|
1222
1613
|
};
|
|
1223
1614
|
this._connection.send(JSON.stringify(response));
|
|
1224
1615
|
}
|
|
@@ -1233,11 +1624,11 @@ export class StreamingResponse {
|
|
|
1233
1624
|
}
|
|
1234
1625
|
this._closed = true;
|
|
1235
1626
|
const response: RPCResponse = {
|
|
1236
|
-
|
|
1627
|
+
done: true,
|
|
1237
1628
|
id: this._id,
|
|
1238
|
-
success: true,
|
|
1239
1629
|
result: finalChunk,
|
|
1240
|
-
|
|
1630
|
+
success: true,
|
|
1631
|
+
type: "rpc"
|
|
1241
1632
|
};
|
|
1242
1633
|
this._connection.send(JSON.stringify(response));
|
|
1243
1634
|
}
|