agents 0.0.0-db5b372 → 0.0.0-dc7a99c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-chat-agent.d.ts +32 -6
- package/dist/ai-chat-agent.js +149 -115
- package/dist/ai-chat-agent.js.map +1 -1
- package/dist/ai-react.d.ts +17 -4
- package/dist/ai-react.js +29 -29
- package/dist/ai-react.js.map +1 -1
- package/dist/chunk-767EASBA.js +106 -0
- package/dist/chunk-767EASBA.js.map +1 -0
- package/dist/{chunk-YZNSS675.js → chunk-E3LCYPCB.js} +71 -37
- package/dist/chunk-E3LCYPCB.js.map +1 -0
- package/dist/chunk-JFRK72K3.js +910 -0
- package/dist/chunk-JFRK72K3.js.map +1 -0
- package/dist/chunk-NKZZ66QY.js +116 -0
- package/dist/chunk-NKZZ66QY.js.map +1 -0
- package/dist/client.d.ts +15 -1
- package/dist/client.js +6 -126
- package/dist/client.js.map +1 -1
- package/dist/index-CITGJflw.d.ts +486 -0
- package/dist/index.d.ts +25 -307
- package/dist/index.js +8 -7
- package/dist/mcp/client.d.ts +310 -23
- package/dist/mcp/client.js +1 -2
- package/dist/mcp/do-oauth-client-provider.d.ts +3 -3
- package/dist/mcp/do-oauth-client-provider.js +3 -103
- package/dist/mcp/do-oauth-client-provider.js.map +1 -1
- package/dist/mcp/index.d.ts +22 -11
- package/dist/mcp/index.js +172 -175
- 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/react.d.ts +85 -5
- package/dist/react.js +20 -8
- package/dist/react.js.map +1 -1
- package/dist/schedule.d.ts +6 -6
- package/dist/schedule.js +4 -6
- 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 +75 -68
- package/src/index.ts +538 -91
- package/dist/chunk-AV3OMRR4.js +0 -597
- package/dist/chunk-AV3OMRR4.js.map +0 -1
- package/dist/chunk-HMLY7DHA.js +0 -16
- package/dist/chunk-YZNSS675.js.map +0 -1
- /package/dist/{chunk-HMLY7DHA.js.map → observability/index.js.map} +0 -0
package/src/index.ts
CHANGED
|
@@ -1,20 +1,31 @@
|
|
|
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";
|
|
1
13
|
import {
|
|
2
|
-
Server,
|
|
3
|
-
routePartykitRequest,
|
|
4
|
-
type PartyServerOptions,
|
|
5
|
-
getServerByName,
|
|
6
14
|
type Connection,
|
|
7
15
|
type ConnectionContext,
|
|
16
|
+
getServerByName,
|
|
17
|
+
type PartyServerOptions,
|
|
18
|
+
routePartykitRequest,
|
|
19
|
+
Server,
|
|
8
20
|
type WSMessage,
|
|
9
21
|
} from "partyserver";
|
|
10
|
-
|
|
11
|
-
import { parseCronExpression } from "cron-schedule";
|
|
12
|
-
import { nanoid } from "nanoid";
|
|
13
|
-
|
|
14
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
22
|
+
import { camelCaseToKebabCase } from "./client";
|
|
15
23
|
import { MCPClientManager } from "./mcp/client";
|
|
24
|
+
// import type { MCPClientConnection } from "./mcp/client-connection";
|
|
25
|
+
import { DurableObjectOAuthClientProvider } from "./mcp/do-oauth-client-provider";
|
|
26
|
+
import { genericObservability, type Observability } from "./observability";
|
|
16
27
|
|
|
17
|
-
export type { Connection,
|
|
28
|
+
export type { Connection, ConnectionContext, WSMessage } from "partyserver";
|
|
18
29
|
|
|
19
30
|
/**
|
|
20
31
|
* RPC request message from client
|
|
@@ -98,7 +109,6 @@ export type CallableMetadata = {
|
|
|
98
109
|
streaming?: boolean;
|
|
99
110
|
};
|
|
100
111
|
|
|
101
|
-
// biome-ignore lint/complexity/noBannedTypes: <explanation>
|
|
102
112
|
const callableMetadata = new Map<Function, CallableMetadata>();
|
|
103
113
|
|
|
104
114
|
/**
|
|
@@ -108,6 +118,7 @@ const callableMetadata = new Map<Function, CallableMetadata>();
|
|
|
108
118
|
export function unstable_callable(metadata: CallableMetadata = {}) {
|
|
109
119
|
return function callableDecorator<This, Args extends unknown[], Return>(
|
|
110
120
|
target: (this: This, ...args: Args) => Return,
|
|
121
|
+
// biome-ignore lint/correctness/noUnusedFunctionParameters: later
|
|
111
122
|
context: ClassMethodDecoratorContext
|
|
112
123
|
) {
|
|
113
124
|
if (!callableMetadata.has(target)) {
|
|
@@ -159,29 +170,95 @@ function getNextCronTime(cron: string) {
|
|
|
159
170
|
return interval.getNextDate();
|
|
160
171
|
}
|
|
161
172
|
|
|
173
|
+
/**
|
|
174
|
+
* MCP Server state update message from server -> Client
|
|
175
|
+
*/
|
|
176
|
+
export type MCPServerMessage = {
|
|
177
|
+
type: "cf_agent_mcp_servers";
|
|
178
|
+
mcp: MCPServersState;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export type MCPServersState = {
|
|
182
|
+
servers: {
|
|
183
|
+
[id: string]: MCPServer;
|
|
184
|
+
};
|
|
185
|
+
tools: Tool[];
|
|
186
|
+
prompts: Prompt[];
|
|
187
|
+
resources: Resource[];
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export type MCPServer = {
|
|
191
|
+
name: string;
|
|
192
|
+
server_url: string;
|
|
193
|
+
auth_url: string | null;
|
|
194
|
+
// This state is specifically about the temporary process of getting a token (if needed).
|
|
195
|
+
// Scope outside of that can't be relied upon because when the DO sleeps, there's no way
|
|
196
|
+
// to communicate a change to a non-ready state.
|
|
197
|
+
state: "authenticating" | "connecting" | "ready" | "discovering" | "failed";
|
|
198
|
+
instructions: string | null;
|
|
199
|
+
capabilities: ServerCapabilities | null;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* MCP Server data stored in DO SQL for resuming MCP Server connections
|
|
204
|
+
*/
|
|
205
|
+
type MCPServerRow = {
|
|
206
|
+
id: string;
|
|
207
|
+
name: string;
|
|
208
|
+
server_url: string;
|
|
209
|
+
client_id: string | null;
|
|
210
|
+
auth_url: string | null;
|
|
211
|
+
callback_url: string;
|
|
212
|
+
server_options: string;
|
|
213
|
+
};
|
|
214
|
+
|
|
162
215
|
const STATE_ROW_ID = "cf_state_row_id";
|
|
163
216
|
const STATE_WAS_CHANGED = "cf_state_was_changed";
|
|
164
217
|
|
|
165
218
|
const DEFAULT_STATE = {} as unknown;
|
|
166
219
|
|
|
167
|
-
|
|
220
|
+
const agentContext = new AsyncLocalStorage<{
|
|
168
221
|
agent: Agent<unknown>;
|
|
169
222
|
connection: Connection | undefined;
|
|
170
223
|
request: Request | undefined;
|
|
171
224
|
}>();
|
|
172
225
|
|
|
226
|
+
export function getCurrentAgent<
|
|
227
|
+
T extends Agent<unknown, unknown> = Agent<unknown, unknown>,
|
|
228
|
+
>(): {
|
|
229
|
+
agent: T | undefined;
|
|
230
|
+
connection: Connection | undefined;
|
|
231
|
+
request: Request<unknown, CfProperties<unknown>> | undefined;
|
|
232
|
+
} {
|
|
233
|
+
const store = agentContext.getStore() as
|
|
234
|
+
| {
|
|
235
|
+
agent: T;
|
|
236
|
+
connection: Connection | undefined;
|
|
237
|
+
request: Request<unknown, CfProperties<unknown>> | undefined;
|
|
238
|
+
}
|
|
239
|
+
| undefined;
|
|
240
|
+
if (!store) {
|
|
241
|
+
return {
|
|
242
|
+
agent: undefined,
|
|
243
|
+
connection: undefined,
|
|
244
|
+
request: undefined,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
return store;
|
|
248
|
+
}
|
|
249
|
+
|
|
173
250
|
/**
|
|
174
251
|
* Base class for creating Agent implementations
|
|
175
252
|
* @template Env Environment type containing bindings
|
|
176
253
|
* @template State State type to store within the Agent
|
|
177
254
|
*/
|
|
178
255
|
export class Agent<Env, State = unknown> extends Server<Env> {
|
|
179
|
-
|
|
256
|
+
private _state = DEFAULT_STATE as State;
|
|
180
257
|
|
|
181
|
-
|
|
258
|
+
private _ParentClass: typeof Agent<Env, State> =
|
|
182
259
|
Object.getPrototypeOf(this).constructor;
|
|
183
260
|
|
|
184
|
-
mcp: MCPClientManager = new MCPClientManager(this
|
|
261
|
+
mcp: MCPClientManager = new MCPClientManager(this._ParentClass.name, "0.0.1");
|
|
185
262
|
|
|
186
263
|
/**
|
|
187
264
|
* Initial state for the Agent
|
|
@@ -193,9 +270,9 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
193
270
|
* Current state of the Agent
|
|
194
271
|
*/
|
|
195
272
|
get state(): State {
|
|
196
|
-
if (this
|
|
273
|
+
if (this._state !== DEFAULT_STATE) {
|
|
197
274
|
// state was previously set, and populated internal state
|
|
198
|
-
return this
|
|
275
|
+
return this._state;
|
|
199
276
|
}
|
|
200
277
|
// looks like this is the first time the state is being accessed
|
|
201
278
|
// check if the state was set in a previous life
|
|
@@ -215,8 +292,8 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
215
292
|
) {
|
|
216
293
|
const state = result[0]?.state as string; // could be null?
|
|
217
294
|
|
|
218
|
-
this
|
|
219
|
-
return this
|
|
295
|
+
this._state = JSON.parse(state);
|
|
296
|
+
return this._state;
|
|
220
297
|
}
|
|
221
298
|
|
|
222
299
|
// ok, this is the first time the state is being accessed
|
|
@@ -240,6 +317,11 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
240
317
|
hibernate: true, // default to hibernate
|
|
241
318
|
};
|
|
242
319
|
|
|
320
|
+
/**
|
|
321
|
+
* The observability implementation to use for the Agent
|
|
322
|
+
*/
|
|
323
|
+
observability?: Observability = genericObservability;
|
|
324
|
+
|
|
243
325
|
/**
|
|
244
326
|
* Execute SQL queries against the Agent's database
|
|
245
327
|
* @template T Type of the returned rows
|
|
@@ -277,7 +359,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
277
359
|
`;
|
|
278
360
|
|
|
279
361
|
void this.ctx.blockConcurrencyWhile(async () => {
|
|
280
|
-
return this
|
|
362
|
+
return this._tryCatch(async () => {
|
|
281
363
|
// Create alarms table if it doesn't exist
|
|
282
364
|
this.sql`
|
|
283
365
|
CREATE TABLE IF NOT EXISTS cf_agents_schedules (
|
|
@@ -297,25 +379,65 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
297
379
|
});
|
|
298
380
|
});
|
|
299
381
|
|
|
382
|
+
this.sql`
|
|
383
|
+
CREATE TABLE IF NOT EXISTS cf_agents_mcp_servers (
|
|
384
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
385
|
+
name TEXT NOT NULL,
|
|
386
|
+
server_url TEXT NOT NULL,
|
|
387
|
+
callback_url TEXT NOT NULL,
|
|
388
|
+
client_id TEXT,
|
|
389
|
+
auth_url TEXT,
|
|
390
|
+
server_options TEXT
|
|
391
|
+
)
|
|
392
|
+
`;
|
|
393
|
+
|
|
394
|
+
const _onRequest = this.onRequest.bind(this);
|
|
395
|
+
this.onRequest = (request: Request) => {
|
|
396
|
+
return agentContext.run(
|
|
397
|
+
{ agent: this, connection: undefined, request },
|
|
398
|
+
async () => {
|
|
399
|
+
if (this.mcp.isCallbackRequest(request)) {
|
|
400
|
+
await this.mcp.handleCallbackRequest(request);
|
|
401
|
+
|
|
402
|
+
// after the MCP connection handshake, we can send updated mcp state
|
|
403
|
+
this.broadcast(
|
|
404
|
+
JSON.stringify({
|
|
405
|
+
mcp: this.getMcpServers(),
|
|
406
|
+
type: "cf_agent_mcp_servers",
|
|
407
|
+
})
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
// We probably should let the user configure this response/redirect, but this is fine for now.
|
|
411
|
+
return new Response("<script>window.close();</script>", {
|
|
412
|
+
headers: { "content-type": "text/html" },
|
|
413
|
+
status: 200,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return this._tryCatch(() => _onRequest(request));
|
|
418
|
+
}
|
|
419
|
+
);
|
|
420
|
+
};
|
|
421
|
+
|
|
300
422
|
const _onMessage = this.onMessage.bind(this);
|
|
301
423
|
this.onMessage = async (connection: Connection, message: WSMessage) => {
|
|
302
|
-
return
|
|
424
|
+
return agentContext.run(
|
|
303
425
|
{ agent: this, connection, request: undefined },
|
|
304
426
|
async () => {
|
|
305
427
|
if (typeof message !== "string") {
|
|
306
|
-
return this
|
|
428
|
+
return this._tryCatch(() => _onMessage(connection, message));
|
|
307
429
|
}
|
|
308
430
|
|
|
309
431
|
let parsed: unknown;
|
|
310
432
|
try {
|
|
311
433
|
parsed = JSON.parse(message);
|
|
312
|
-
} catch (
|
|
434
|
+
} catch (_e) {
|
|
313
435
|
// silently fail and let the onMessage handler handle it
|
|
314
|
-
return this
|
|
436
|
+
return this._tryCatch(() => _onMessage(connection, message));
|
|
315
437
|
}
|
|
316
438
|
|
|
317
439
|
if (isStateUpdateMessage(parsed)) {
|
|
318
|
-
this
|
|
440
|
+
this._setStateInternal(parsed.state as State, connection);
|
|
319
441
|
return;
|
|
320
442
|
}
|
|
321
443
|
|
|
@@ -329,11 +451,10 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
329
451
|
throw new Error(`Method ${method} does not exist`);
|
|
330
452
|
}
|
|
331
453
|
|
|
332
|
-
if (!this
|
|
454
|
+
if (!this._isCallable(method)) {
|
|
333
455
|
throw new Error(`Method ${method} is not callable`);
|
|
334
456
|
}
|
|
335
457
|
|
|
336
|
-
// biome-ignore lint/complexity/noBannedTypes: <explanation>
|
|
337
458
|
const metadata = callableMetadata.get(methodFn as Function);
|
|
338
459
|
|
|
339
460
|
// For streaming methods, pass a StreamingResponse object
|
|
@@ -345,22 +466,39 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
345
466
|
|
|
346
467
|
// For regular methods, execute and send response
|
|
347
468
|
const result = await methodFn.apply(this, args);
|
|
469
|
+
|
|
470
|
+
this.observability?.emit(
|
|
471
|
+
{
|
|
472
|
+
displayMessage: `RPC call to ${method}`,
|
|
473
|
+
id: nanoid(),
|
|
474
|
+
payload: {
|
|
475
|
+
args,
|
|
476
|
+
method,
|
|
477
|
+
streaming: metadata?.streaming,
|
|
478
|
+
success: true,
|
|
479
|
+
},
|
|
480
|
+
timestamp: Date.now(),
|
|
481
|
+
type: "rpc",
|
|
482
|
+
},
|
|
483
|
+
this.ctx
|
|
484
|
+
);
|
|
485
|
+
|
|
348
486
|
const response: RPCResponse = {
|
|
349
|
-
|
|
487
|
+
done: true,
|
|
350
488
|
id,
|
|
351
|
-
success: true,
|
|
352
489
|
result,
|
|
353
|
-
|
|
490
|
+
success: true,
|
|
491
|
+
type: "rpc",
|
|
354
492
|
};
|
|
355
493
|
connection.send(JSON.stringify(response));
|
|
356
494
|
} catch (e) {
|
|
357
495
|
// Send error response
|
|
358
496
|
const response: RPCResponse = {
|
|
359
|
-
type: "rpc",
|
|
360
|
-
id: parsed.id,
|
|
361
|
-
success: false,
|
|
362
497
|
error:
|
|
363
498
|
e instanceof Error ? e.message : "Unknown error occurred",
|
|
499
|
+
id: parsed.id,
|
|
500
|
+
success: false,
|
|
501
|
+
type: "rpc",
|
|
364
502
|
};
|
|
365
503
|
connection.send(JSON.stringify(response));
|
|
366
504
|
console.error("RPC error:", e);
|
|
@@ -368,7 +506,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
368
506
|
return;
|
|
369
507
|
}
|
|
370
508
|
|
|
371
|
-
return this
|
|
509
|
+
return this._tryCatch(() => _onMessage(connection, message));
|
|
372
510
|
}
|
|
373
511
|
);
|
|
374
512
|
};
|
|
@@ -377,27 +515,89 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
377
515
|
this.onConnect = (connection: Connection, ctx: ConnectionContext) => {
|
|
378
516
|
// TODO: This is a hack to ensure the state is sent after the connection is established
|
|
379
517
|
// must fix this
|
|
380
|
-
return
|
|
518
|
+
return agentContext.run(
|
|
381
519
|
{ agent: this, connection, request: ctx.request },
|
|
382
520
|
async () => {
|
|
383
521
|
setTimeout(() => {
|
|
384
522
|
if (this.state) {
|
|
385
523
|
connection.send(
|
|
386
524
|
JSON.stringify({
|
|
387
|
-
type: "cf_agent_state",
|
|
388
525
|
state: this.state,
|
|
526
|
+
type: "cf_agent_state",
|
|
389
527
|
})
|
|
390
528
|
);
|
|
391
529
|
}
|
|
392
|
-
|
|
530
|
+
|
|
531
|
+
connection.send(
|
|
532
|
+
JSON.stringify({
|
|
533
|
+
mcp: this.getMcpServers(),
|
|
534
|
+
type: "cf_agent_mcp_servers",
|
|
535
|
+
})
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
this.observability?.emit(
|
|
539
|
+
{
|
|
540
|
+
displayMessage: "Connection established",
|
|
541
|
+
id: nanoid(),
|
|
542
|
+
payload: {
|
|
543
|
+
connectionId: connection.id,
|
|
544
|
+
},
|
|
545
|
+
timestamp: Date.now(),
|
|
546
|
+
type: "connect",
|
|
547
|
+
},
|
|
548
|
+
this.ctx
|
|
549
|
+
);
|
|
550
|
+
return this._tryCatch(() => _onConnect(connection, ctx));
|
|
393
551
|
}, 20);
|
|
394
552
|
}
|
|
395
553
|
);
|
|
396
554
|
};
|
|
555
|
+
|
|
556
|
+
const _onStart = this.onStart.bind(this);
|
|
557
|
+
this.onStart = async () => {
|
|
558
|
+
return agentContext.run(
|
|
559
|
+
{ agent: this, connection: undefined, request: undefined },
|
|
560
|
+
async () => {
|
|
561
|
+
const servers = this.sql<MCPServerRow>`
|
|
562
|
+
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
|
|
563
|
+
`;
|
|
564
|
+
|
|
565
|
+
// from DO storage, reconnect to all servers not currently in the oauth flow using our saved auth information
|
|
566
|
+
Promise.allSettled(
|
|
567
|
+
servers.map((server) => {
|
|
568
|
+
return this._connectToMcpServerInternal(
|
|
569
|
+
server.name,
|
|
570
|
+
server.server_url,
|
|
571
|
+
server.callback_url,
|
|
572
|
+
server.server_options
|
|
573
|
+
? JSON.parse(server.server_options)
|
|
574
|
+
: undefined,
|
|
575
|
+
{
|
|
576
|
+
id: server.id,
|
|
577
|
+
oauthClientId: server.client_id ?? undefined,
|
|
578
|
+
}
|
|
579
|
+
);
|
|
580
|
+
})
|
|
581
|
+
).then((_results) => {
|
|
582
|
+
this.broadcast(
|
|
583
|
+
JSON.stringify({
|
|
584
|
+
mcp: this.getMcpServers(),
|
|
585
|
+
type: "cf_agent_mcp_servers",
|
|
586
|
+
})
|
|
587
|
+
);
|
|
588
|
+
});
|
|
589
|
+
await this._tryCatch(() => _onStart());
|
|
590
|
+
}
|
|
591
|
+
);
|
|
592
|
+
};
|
|
397
593
|
}
|
|
398
594
|
|
|
399
|
-
|
|
400
|
-
|
|
595
|
+
private _setStateInternal(
|
|
596
|
+
state: State,
|
|
597
|
+
source: Connection | "server" = "server"
|
|
598
|
+
) {
|
|
599
|
+
const previousState = this._state;
|
|
600
|
+
this._state = state;
|
|
401
601
|
this.sql`
|
|
402
602
|
INSERT OR REPLACE INTO cf_agents_state (id, state)
|
|
403
603
|
VALUES (${STATE_ROW_ID}, ${JSON.stringify(state)})
|
|
@@ -408,16 +608,29 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
408
608
|
`;
|
|
409
609
|
this.broadcast(
|
|
410
610
|
JSON.stringify({
|
|
411
|
-
type: "cf_agent_state",
|
|
412
611
|
state: state,
|
|
612
|
+
type: "cf_agent_state",
|
|
413
613
|
}),
|
|
414
614
|
source !== "server" ? [source.id] : []
|
|
415
615
|
);
|
|
416
|
-
return this
|
|
417
|
-
const { connection, request } =
|
|
418
|
-
return
|
|
616
|
+
return this._tryCatch(() => {
|
|
617
|
+
const { connection, request } = agentContext.getStore() || {};
|
|
618
|
+
return agentContext.run(
|
|
419
619
|
{ agent: this, connection, request },
|
|
420
620
|
async () => {
|
|
621
|
+
this.observability?.emit(
|
|
622
|
+
{
|
|
623
|
+
displayMessage: "State updated",
|
|
624
|
+
id: nanoid(),
|
|
625
|
+
payload: {
|
|
626
|
+
previousState,
|
|
627
|
+
state,
|
|
628
|
+
},
|
|
629
|
+
timestamp: Date.now(),
|
|
630
|
+
type: "state:update",
|
|
631
|
+
},
|
|
632
|
+
this.ctx
|
|
633
|
+
);
|
|
421
634
|
return this.onStateUpdate(state, source);
|
|
422
635
|
}
|
|
423
636
|
);
|
|
@@ -429,7 +642,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
429
642
|
* @param state New state to set
|
|
430
643
|
*/
|
|
431
644
|
setState(state: State) {
|
|
432
|
-
this
|
|
645
|
+
this._setStateInternal(state, "server");
|
|
433
646
|
}
|
|
434
647
|
|
|
435
648
|
/**
|
|
@@ -437,6 +650,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
437
650
|
* @param state Updated state
|
|
438
651
|
* @param source Source of the state update ("server" or a client connection)
|
|
439
652
|
*/
|
|
653
|
+
// biome-ignore lint/correctness/noUnusedFunctionParameters: overridden later
|
|
440
654
|
onStateUpdate(state: State | undefined, source: Connection | "server") {
|
|
441
655
|
// override this to handle state updates
|
|
442
656
|
}
|
|
@@ -445,8 +659,9 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
445
659
|
* Called when the Agent receives an email
|
|
446
660
|
* @param email Email message to process
|
|
447
661
|
*/
|
|
662
|
+
// biome-ignore lint/correctness/noUnusedFunctionParameters: overridden later
|
|
448
663
|
onEmail(email: ForwardableEmailMessage) {
|
|
449
|
-
return
|
|
664
|
+
return agentContext.run(
|
|
450
665
|
{ agent: this, connection: undefined, request: undefined },
|
|
451
666
|
async () => {
|
|
452
667
|
console.error("onEmail not implemented");
|
|
@@ -454,7 +669,7 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
454
669
|
);
|
|
455
670
|
}
|
|
456
671
|
|
|
457
|
-
async
|
|
672
|
+
private async _tryCatch<T>(fn: () => T | Promise<T>) {
|
|
458
673
|
try {
|
|
459
674
|
return await fn();
|
|
460
675
|
} catch (e) {
|
|
@@ -511,6 +726,18 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
511
726
|
): Promise<Schedule<T>> {
|
|
512
727
|
const id = nanoid(9);
|
|
513
728
|
|
|
729
|
+
const emitScheduleCreate = (schedule: Schedule<T>) =>
|
|
730
|
+
this.observability?.emit(
|
|
731
|
+
{
|
|
732
|
+
displayMessage: `Schedule ${schedule.id} created`,
|
|
733
|
+
id: nanoid(),
|
|
734
|
+
payload: schedule,
|
|
735
|
+
timestamp: Date.now(),
|
|
736
|
+
type: "schedule:create",
|
|
737
|
+
},
|
|
738
|
+
this.ctx
|
|
739
|
+
);
|
|
740
|
+
|
|
514
741
|
if (typeof callback !== "string") {
|
|
515
742
|
throw new Error("Callback must be a string");
|
|
516
743
|
}
|
|
@@ -528,15 +755,19 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
528
755
|
)}, 'scheduled', ${timestamp})
|
|
529
756
|
`;
|
|
530
757
|
|
|
531
|
-
await this
|
|
758
|
+
await this._scheduleNextAlarm();
|
|
532
759
|
|
|
533
|
-
|
|
534
|
-
id,
|
|
760
|
+
const schedule: Schedule<T> = {
|
|
535
761
|
callback: callback,
|
|
762
|
+
id,
|
|
536
763
|
payload: payload as T,
|
|
537
764
|
time: timestamp,
|
|
538
765
|
type: "scheduled",
|
|
539
766
|
};
|
|
767
|
+
|
|
768
|
+
emitScheduleCreate(schedule);
|
|
769
|
+
|
|
770
|
+
return schedule;
|
|
540
771
|
}
|
|
541
772
|
if (typeof when === "number") {
|
|
542
773
|
const time = new Date(Date.now() + when * 1000);
|
|
@@ -549,16 +780,20 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
549
780
|
)}, 'delayed', ${when}, ${timestamp})
|
|
550
781
|
`;
|
|
551
782
|
|
|
552
|
-
await this
|
|
783
|
+
await this._scheduleNextAlarm();
|
|
553
784
|
|
|
554
|
-
|
|
555
|
-
id,
|
|
785
|
+
const schedule: Schedule<T> = {
|
|
556
786
|
callback: callback,
|
|
557
|
-
payload: payload as T,
|
|
558
787
|
delayInSeconds: when,
|
|
788
|
+
id,
|
|
789
|
+
payload: payload as T,
|
|
559
790
|
time: timestamp,
|
|
560
791
|
type: "delayed",
|
|
561
792
|
};
|
|
793
|
+
|
|
794
|
+
emitScheduleCreate(schedule);
|
|
795
|
+
|
|
796
|
+
return schedule;
|
|
562
797
|
}
|
|
563
798
|
if (typeof when === "string") {
|
|
564
799
|
const nextExecutionTime = getNextCronTime(when);
|
|
@@ -571,16 +806,20 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
571
806
|
)}, 'cron', ${when}, ${timestamp})
|
|
572
807
|
`;
|
|
573
808
|
|
|
574
|
-
await this
|
|
809
|
+
await this._scheduleNextAlarm();
|
|
575
810
|
|
|
576
|
-
|
|
577
|
-
id,
|
|
811
|
+
const schedule: Schedule<T> = {
|
|
578
812
|
callback: callback,
|
|
579
|
-
payload: payload as T,
|
|
580
813
|
cron: when,
|
|
814
|
+
id,
|
|
815
|
+
payload: payload as T,
|
|
581
816
|
time: timestamp,
|
|
582
817
|
type: "cron",
|
|
583
818
|
};
|
|
819
|
+
|
|
820
|
+
emitScheduleCreate(schedule);
|
|
821
|
+
|
|
822
|
+
return schedule;
|
|
584
823
|
}
|
|
585
824
|
throw new Error("Invalid schedule type");
|
|
586
825
|
}
|
|
@@ -656,13 +895,26 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
656
895
|
* @returns true if the task was cancelled, false otherwise
|
|
657
896
|
*/
|
|
658
897
|
async cancelSchedule(id: string): Promise<boolean> {
|
|
898
|
+
const schedule = await this.getSchedule(id);
|
|
899
|
+
if (schedule) {
|
|
900
|
+
this.observability?.emit(
|
|
901
|
+
{
|
|
902
|
+
displayMessage: `Schedule ${id} cancelled`,
|
|
903
|
+
id: nanoid(),
|
|
904
|
+
payload: schedule,
|
|
905
|
+
timestamp: Date.now(),
|
|
906
|
+
type: "schedule:cancel",
|
|
907
|
+
},
|
|
908
|
+
this.ctx
|
|
909
|
+
);
|
|
910
|
+
}
|
|
659
911
|
this.sql`DELETE FROM cf_agents_schedules WHERE id = ${id}`;
|
|
660
912
|
|
|
661
|
-
await this
|
|
913
|
+
await this._scheduleNextAlarm();
|
|
662
914
|
return true;
|
|
663
915
|
}
|
|
664
916
|
|
|
665
|
-
async
|
|
917
|
+
private async _scheduleNextAlarm() {
|
|
666
918
|
// Find the next schedule that needs to be executed
|
|
667
919
|
const result = this.sql`
|
|
668
920
|
SELECT time FROM cf_agents_schedules
|
|
@@ -679,10 +931,14 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
679
931
|
}
|
|
680
932
|
|
|
681
933
|
/**
|
|
682
|
-
* Method called when an alarm fires
|
|
683
|
-
* Executes any scheduled tasks that are due
|
|
934
|
+
* Method called when an alarm fires.
|
|
935
|
+
* Executes any scheduled tasks that are due.
|
|
936
|
+
*
|
|
937
|
+
* @remarks
|
|
938
|
+
* To schedule a task, please use the `this.schedule` method instead.
|
|
939
|
+
* See {@link https://developers.cloudflare.com/agents/api-reference/schedule-tasks/}
|
|
684
940
|
*/
|
|
685
|
-
async
|
|
941
|
+
public readonly alarm = async () => {
|
|
686
942
|
const now = Math.floor(Date.now() / 1000);
|
|
687
943
|
|
|
688
944
|
// Get all schedules that should be executed now
|
|
@@ -696,10 +952,21 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
696
952
|
console.error(`callback ${row.callback} not found`);
|
|
697
953
|
continue;
|
|
698
954
|
}
|
|
699
|
-
await
|
|
955
|
+
await agentContext.run(
|
|
700
956
|
{ agent: this, connection: undefined, request: undefined },
|
|
701
957
|
async () => {
|
|
702
958
|
try {
|
|
959
|
+
this.observability?.emit(
|
|
960
|
+
{
|
|
961
|
+
displayMessage: `Schedule ${row.id} executed`,
|
|
962
|
+
id: nanoid(),
|
|
963
|
+
payload: row,
|
|
964
|
+
timestamp: Date.now(),
|
|
965
|
+
type: "schedule:execute",
|
|
966
|
+
},
|
|
967
|
+
this.ctx
|
|
968
|
+
);
|
|
969
|
+
|
|
703
970
|
await (
|
|
704
971
|
callback as (
|
|
705
972
|
payload: unknown,
|
|
@@ -728,8 +995,8 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
728
995
|
}
|
|
729
996
|
|
|
730
997
|
// Schedule the next alarm
|
|
731
|
-
await this
|
|
732
|
-
}
|
|
998
|
+
await this._scheduleNextAlarm();
|
|
999
|
+
};
|
|
733
1000
|
|
|
734
1001
|
/**
|
|
735
1002
|
* Destroy the Agent, removing all state and scheduled tasks
|
|
@@ -738,20 +1005,200 @@ export class Agent<Env, State = unknown> extends Server<Env> {
|
|
|
738
1005
|
// drop all tables
|
|
739
1006
|
this.sql`DROP TABLE IF EXISTS cf_agents_state`;
|
|
740
1007
|
this.sql`DROP TABLE IF EXISTS cf_agents_schedules`;
|
|
1008
|
+
this.sql`DROP TABLE IF EXISTS cf_agents_mcp_servers`;
|
|
741
1009
|
|
|
742
1010
|
// delete all alarms
|
|
743
1011
|
await this.ctx.storage.deleteAlarm();
|
|
744
1012
|
await this.ctx.storage.deleteAll();
|
|
1013
|
+
this.ctx.abort("destroyed"); // enforce that the agent is evicted
|
|
1014
|
+
|
|
1015
|
+
this.observability?.emit(
|
|
1016
|
+
{
|
|
1017
|
+
displayMessage: "Agent destroyed",
|
|
1018
|
+
id: nanoid(),
|
|
1019
|
+
payload: {},
|
|
1020
|
+
timestamp: Date.now(),
|
|
1021
|
+
type: "destroy",
|
|
1022
|
+
},
|
|
1023
|
+
this.ctx
|
|
1024
|
+
);
|
|
745
1025
|
}
|
|
746
1026
|
|
|
747
1027
|
/**
|
|
748
1028
|
* Get all methods marked as callable on this Agent
|
|
749
1029
|
* @returns A map of method names to their metadata
|
|
750
1030
|
*/
|
|
751
|
-
|
|
752
|
-
// biome-ignore lint/complexity/noBannedTypes: <explanation>
|
|
1031
|
+
private _isCallable(method: string): boolean {
|
|
753
1032
|
return callableMetadata.has(this[method as keyof this] as Function);
|
|
754
1033
|
}
|
|
1034
|
+
|
|
1035
|
+
/**
|
|
1036
|
+
* Connect to a new MCP Server
|
|
1037
|
+
*
|
|
1038
|
+
* @param url MCP Server SSE URL
|
|
1039
|
+
* @param callbackHost Base host for the agent, used for the redirect URI.
|
|
1040
|
+
* @param agentsPrefix agents routing prefix if not using `agents`
|
|
1041
|
+
* @param options MCP client and transport (header) options
|
|
1042
|
+
* @returns authUrl
|
|
1043
|
+
*/
|
|
1044
|
+
async addMcpServer(
|
|
1045
|
+
serverName: string,
|
|
1046
|
+
url: string,
|
|
1047
|
+
callbackHost: string,
|
|
1048
|
+
agentsPrefix = "agents",
|
|
1049
|
+
options?: {
|
|
1050
|
+
client?: ConstructorParameters<typeof Client>[1];
|
|
1051
|
+
transport?: {
|
|
1052
|
+
headers: HeadersInit;
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
): Promise<{ id: string; authUrl: string | undefined }> {
|
|
1056
|
+
const callbackUrl = `${callbackHost}/${agentsPrefix}/${camelCaseToKebabCase(this._ParentClass.name)}/${this.name}/callback`;
|
|
1057
|
+
|
|
1058
|
+
const result = await this._connectToMcpServerInternal(
|
|
1059
|
+
serverName,
|
|
1060
|
+
url,
|
|
1061
|
+
callbackUrl,
|
|
1062
|
+
options
|
|
1063
|
+
);
|
|
1064
|
+
this.sql`
|
|
1065
|
+
INSERT
|
|
1066
|
+
OR REPLACE INTO cf_agents_mcp_servers (id, name, server_url, client_id, auth_url, callback_url, server_options)
|
|
1067
|
+
VALUES (
|
|
1068
|
+
${result.id},
|
|
1069
|
+
${serverName},
|
|
1070
|
+
${url},
|
|
1071
|
+
${result.clientId ?? null},
|
|
1072
|
+
${result.authUrl ?? null},
|
|
1073
|
+
${callbackUrl},
|
|
1074
|
+
${options ? JSON.stringify(options) : null}
|
|
1075
|
+
);
|
|
1076
|
+
`;
|
|
1077
|
+
|
|
1078
|
+
this.broadcast(
|
|
1079
|
+
JSON.stringify({
|
|
1080
|
+
mcp: this.getMcpServers(),
|
|
1081
|
+
type: "cf_agent_mcp_servers",
|
|
1082
|
+
})
|
|
1083
|
+
);
|
|
1084
|
+
|
|
1085
|
+
return result;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
async _connectToMcpServerInternal(
|
|
1089
|
+
_serverName: string,
|
|
1090
|
+
url: string,
|
|
1091
|
+
callbackUrl: string,
|
|
1092
|
+
// it's important that any options here are serializable because we put them into our sqlite DB for reconnection purposes
|
|
1093
|
+
options?: {
|
|
1094
|
+
client?: ConstructorParameters<typeof Client>[1];
|
|
1095
|
+
/**
|
|
1096
|
+
* We don't expose the normal set of transport options because:
|
|
1097
|
+
* 1) we can't serialize things like the auth provider or a fetch function into the DB for reconnection purposes
|
|
1098
|
+
* 2) We probably want these options to be agnostic to the transport type (SSE vs Streamable)
|
|
1099
|
+
*
|
|
1100
|
+
* This has the limitation that you can't override fetch, but I think headers should handle nearly all cases needed (i.e. non-standard bearer auth).
|
|
1101
|
+
*/
|
|
1102
|
+
transport?: {
|
|
1103
|
+
headers?: HeadersInit;
|
|
1104
|
+
};
|
|
1105
|
+
},
|
|
1106
|
+
reconnect?: {
|
|
1107
|
+
id: string;
|
|
1108
|
+
oauthClientId?: string;
|
|
1109
|
+
}
|
|
1110
|
+
): Promise<{
|
|
1111
|
+
id: string;
|
|
1112
|
+
authUrl: string | undefined;
|
|
1113
|
+
clientId: string | undefined;
|
|
1114
|
+
}> {
|
|
1115
|
+
const authProvider = new DurableObjectOAuthClientProvider(
|
|
1116
|
+
this.ctx.storage,
|
|
1117
|
+
this.name,
|
|
1118
|
+
callbackUrl
|
|
1119
|
+
);
|
|
1120
|
+
|
|
1121
|
+
if (reconnect) {
|
|
1122
|
+
authProvider.serverId = reconnect.id;
|
|
1123
|
+
if (reconnect.oauthClientId) {
|
|
1124
|
+
authProvider.clientId = reconnect.oauthClientId;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// allows passing through transport headers if necessary
|
|
1129
|
+
// this handles some non-standard bearer auth setups (i.e. MCP server behind CF access instead of OAuth)
|
|
1130
|
+
let headerTransportOpts: SSEClientTransportOptions = {};
|
|
1131
|
+
if (options?.transport?.headers) {
|
|
1132
|
+
headerTransportOpts = {
|
|
1133
|
+
eventSourceInit: {
|
|
1134
|
+
fetch: (url, init) =>
|
|
1135
|
+
fetch(url, {
|
|
1136
|
+
...init,
|
|
1137
|
+
headers: options?.transport?.headers,
|
|
1138
|
+
}),
|
|
1139
|
+
},
|
|
1140
|
+
requestInit: {
|
|
1141
|
+
headers: options?.transport?.headers,
|
|
1142
|
+
},
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
const { id, authUrl, clientId } = await this.mcp.connect(url, {
|
|
1147
|
+
client: options?.client,
|
|
1148
|
+
reconnect,
|
|
1149
|
+
transport: {
|
|
1150
|
+
...headerTransportOpts,
|
|
1151
|
+
authProvider,
|
|
1152
|
+
},
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
return {
|
|
1156
|
+
authUrl,
|
|
1157
|
+
clientId,
|
|
1158
|
+
id,
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
async removeMcpServer(id: string) {
|
|
1163
|
+
this.mcp.closeConnection(id);
|
|
1164
|
+
this.sql`
|
|
1165
|
+
DELETE FROM cf_agents_mcp_servers WHERE id = ${id};
|
|
1166
|
+
`;
|
|
1167
|
+
this.broadcast(
|
|
1168
|
+
JSON.stringify({
|
|
1169
|
+
mcp: this.getMcpServers(),
|
|
1170
|
+
type: "cf_agent_mcp_servers",
|
|
1171
|
+
})
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
getMcpServers(): MCPServersState {
|
|
1176
|
+
const mcpState: MCPServersState = {
|
|
1177
|
+
prompts: this.mcp.listPrompts(),
|
|
1178
|
+
resources: this.mcp.listResources(),
|
|
1179
|
+
servers: {},
|
|
1180
|
+
tools: this.mcp.listTools(),
|
|
1181
|
+
};
|
|
1182
|
+
|
|
1183
|
+
const servers = this.sql<MCPServerRow>`
|
|
1184
|
+
SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers;
|
|
1185
|
+
`;
|
|
1186
|
+
|
|
1187
|
+
for (const server of servers) {
|
|
1188
|
+
const serverConn = this.mcp.mcpConnections[server.id];
|
|
1189
|
+
mcpState.servers[server.id] = {
|
|
1190
|
+
auth_url: server.auth_url,
|
|
1191
|
+
capabilities: serverConn?.serverCapabilities ?? null,
|
|
1192
|
+
instructions: serverConn?.instructions ?? null,
|
|
1193
|
+
name: server.name,
|
|
1194
|
+
server_url: server.server_url,
|
|
1195
|
+
// mark as "authenticating" because the server isn't automatically connected, so it's pending authenticating
|
|
1196
|
+
state: serverConn?.connectionState ?? "authenticating",
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
return mcpState;
|
|
1201
|
+
}
|
|
755
1202
|
}
|
|
756
1203
|
|
|
757
1204
|
/**
|
|
@@ -791,9 +1238,9 @@ export async function routeAgentRequest<Env>(
|
|
|
791
1238
|
const corsHeaders =
|
|
792
1239
|
options?.cors === true
|
|
793
1240
|
? {
|
|
794
|
-
"Access-Control-Allow-Origin": "*",
|
|
795
|
-
"Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS",
|
|
796
1241
|
"Access-Control-Allow-Credentials": "true",
|
|
1242
|
+
"Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS",
|
|
1243
|
+
"Access-Control-Allow-Origin": "*",
|
|
797
1244
|
"Access-Control-Max-Age": "86400",
|
|
798
1245
|
}
|
|
799
1246
|
: options?.cors;
|
|
@@ -841,9 +1288,9 @@ export async function routeAgentRequest<Env>(
|
|
|
841
1288
|
* @param options Routing options
|
|
842
1289
|
*/
|
|
843
1290
|
export async function routeAgentEmail<Env>(
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1291
|
+
_email: ForwardableEmailMessage,
|
|
1292
|
+
_env: Env,
|
|
1293
|
+
_options?: AgentOptions<Env>
|
|
847
1294
|
): Promise<void> {}
|
|
848
1295
|
|
|
849
1296
|
/**
|
|
@@ -855,7 +1302,7 @@ export async function routeAgentEmail<Env>(
|
|
|
855
1302
|
* @param options Options for Agent creation
|
|
856
1303
|
* @returns Promise resolving to an Agent instance stub
|
|
857
1304
|
*/
|
|
858
|
-
export function getAgentByName<Env, T extends Agent<Env>>(
|
|
1305
|
+
export async function getAgentByName<Env, T extends Agent<Env>>(
|
|
859
1306
|
namespace: AgentNamespace<T>,
|
|
860
1307
|
name: string,
|
|
861
1308
|
options?: {
|
|
@@ -870,13 +1317,13 @@ export function getAgentByName<Env, T extends Agent<Env>>(
|
|
|
870
1317
|
* A wrapper for streaming responses in callable methods
|
|
871
1318
|
*/
|
|
872
1319
|
export class StreamingResponse {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1320
|
+
private _connection: Connection;
|
|
1321
|
+
private _id: string;
|
|
1322
|
+
private _closed = false;
|
|
876
1323
|
|
|
877
1324
|
constructor(connection: Connection, id: string) {
|
|
878
|
-
this
|
|
879
|
-
this
|
|
1325
|
+
this._connection = connection;
|
|
1326
|
+
this._id = id;
|
|
880
1327
|
}
|
|
881
1328
|
|
|
882
1329
|
/**
|
|
@@ -884,17 +1331,17 @@ export class StreamingResponse {
|
|
|
884
1331
|
* @param chunk The data to send
|
|
885
1332
|
*/
|
|
886
1333
|
send(chunk: unknown) {
|
|
887
|
-
if (this
|
|
1334
|
+
if (this._closed) {
|
|
888
1335
|
throw new Error("StreamingResponse is already closed");
|
|
889
1336
|
}
|
|
890
1337
|
const response: RPCResponse = {
|
|
891
|
-
type: "rpc",
|
|
892
|
-
id: this.#id,
|
|
893
|
-
success: true,
|
|
894
|
-
result: chunk,
|
|
895
1338
|
done: false,
|
|
1339
|
+
id: this._id,
|
|
1340
|
+
result: chunk,
|
|
1341
|
+
success: true,
|
|
1342
|
+
type: "rpc",
|
|
896
1343
|
};
|
|
897
|
-
this
|
|
1344
|
+
this._connection.send(JSON.stringify(response));
|
|
898
1345
|
}
|
|
899
1346
|
|
|
900
1347
|
/**
|
|
@@ -902,17 +1349,17 @@ export class StreamingResponse {
|
|
|
902
1349
|
* @param finalChunk Optional final chunk of data to send
|
|
903
1350
|
*/
|
|
904
1351
|
end(finalChunk?: unknown) {
|
|
905
|
-
if (this
|
|
1352
|
+
if (this._closed) {
|
|
906
1353
|
throw new Error("StreamingResponse is already closed");
|
|
907
1354
|
}
|
|
908
|
-
this
|
|
1355
|
+
this._closed = true;
|
|
909
1356
|
const response: RPCResponse = {
|
|
910
|
-
type: "rpc",
|
|
911
|
-
id: this.#id,
|
|
912
|
-
success: true,
|
|
913
|
-
result: finalChunk,
|
|
914
1357
|
done: true,
|
|
1358
|
+
id: this._id,
|
|
1359
|
+
result: finalChunk,
|
|
1360
|
+
success: true,
|
|
1361
|
+
type: "rpc",
|
|
915
1362
|
};
|
|
916
|
-
this
|
|
1363
|
+
this._connection.send(JSON.stringify(response));
|
|
917
1364
|
}
|
|
918
1365
|
}
|