@copilotkit/core 1.56.2 → 1.56.4-canary.1777529757
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/index.cjs +299 -126
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +99 -25
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +99 -25
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +299 -126
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +306 -131
- package/dist/index.umd.js.map +1 -1
- package/package.json +4 -3
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Throttler } from "@tanstack/pacer";
|
|
1
2
|
import { AbstractAgent, EventType, HttpAgent, randomUUID, runHttpRequest, structuredClone_, transformChunks, transformHttpEventStream } from "@ag-ui/client";
|
|
2
3
|
import { RUNTIME_MODE_INTELLIGENCE, RUNTIME_MODE_SSE, logger, partialJSONParse, phoenixExponentialBackoff, randomUUID as randomUUID$1, resolveDebugConfig, schemaToJsonSchema } from "@copilotkit/shared";
|
|
3
4
|
import { BehaviorSubject, EMPTY, NEVER, Notification, Observable, Subject, Subscription, asapScheduler, concat, defer, dematerialize, firstValueFrom, from, lastValueFrom, merge, of, switchMap, throwError } from "rxjs";
|
|
@@ -126,6 +127,8 @@ function ɵobservePhoenixSocketHealth$(socketSignals$, maxConsecutiveErrors) {
|
|
|
126
127
|
//#endregion
|
|
127
128
|
//#region src/intelligence-agent.ts
|
|
128
129
|
const CLIENT_AG_UI_EVENT = "ag_ui_event";
|
|
130
|
+
const REPLAY_COMPLETE_EVENT = "replay_complete";
|
|
131
|
+
const STREAM_IDLE_EVENT = "stream_idle";
|
|
129
132
|
const STOP_RUN_EVENT = "stop_run";
|
|
130
133
|
var AgentThreadLockedError = class extends Error {
|
|
131
134
|
constructor(threadId) {
|
|
@@ -137,7 +140,7 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
|
|
|
137
140
|
config;
|
|
138
141
|
socket = null;
|
|
139
142
|
activeChannel = null;
|
|
140
|
-
|
|
143
|
+
canonicalRunId = null;
|
|
141
144
|
sharedState;
|
|
142
145
|
constructor(config, sharedState = { lastSeenEventIds: /* @__PURE__ */ new Map() }) {
|
|
143
146
|
super();
|
|
@@ -175,7 +178,11 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
|
|
|
175
178
|
try {
|
|
176
179
|
this.isRunning = true;
|
|
177
180
|
this.agentId = this.agentId ?? randomUUID();
|
|
178
|
-
const
|
|
181
|
+
const effectiveParameters = parameters?.runId || !this.canonicalRunId ? parameters : {
|
|
182
|
+
...parameters,
|
|
183
|
+
runId: this.canonicalRunId
|
|
184
|
+
};
|
|
185
|
+
const input = this.prepareRunAgentInput(effectiveParameters);
|
|
179
186
|
let result;
|
|
180
187
|
const previousMessageIds = new Set(this.messages.map((m) => m.id));
|
|
181
188
|
const subscribers = [
|
|
@@ -214,14 +221,14 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
|
|
|
214
221
|
}
|
|
215
222
|
}
|
|
216
223
|
abortRun() {
|
|
217
|
-
if (this.activeChannel && this.
|
|
224
|
+
if (this.activeChannel && this.canonicalRunId) {
|
|
218
225
|
const fallback = setTimeout(() => clear(), 5e3);
|
|
219
226
|
const clear = () => {
|
|
220
227
|
clearTimeout(fallback);
|
|
221
228
|
this.detachActiveRun();
|
|
222
229
|
this.cleanup();
|
|
223
230
|
};
|
|
224
|
-
this.activeChannel.push(STOP_RUN_EVENT, { run_id: this.
|
|
231
|
+
this.activeChannel.push(STOP_RUN_EVENT, { run_id: this.canonicalRunId }).receive("ok", clear).receive("error", clear).receive("timeout", clear);
|
|
225
232
|
} else {
|
|
226
233
|
this.detachActiveRun();
|
|
227
234
|
this.cleanup();
|
|
@@ -233,11 +240,15 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
|
|
|
233
240
|
*/
|
|
234
241
|
run(input) {
|
|
235
242
|
this.threadId = input.threadId;
|
|
236
|
-
this.
|
|
237
|
-
return defer(() => this.requestJoinCredentials$("run", input)).pipe(switchMap((credentials) =>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
243
|
+
this.canonicalRunId = input.runId;
|
|
244
|
+
return defer(() => this.requestJoinCredentials$("run", input)).pipe(switchMap((credentials) => {
|
|
245
|
+
if (credentials === null) return throwError(() => /* @__PURE__ */ new Error("REST run request returned no credentials"));
|
|
246
|
+
const canonicalInput = this.applyCanonicalRunIdentity(input, credentials, { fallbackToInputRunId: true });
|
|
247
|
+
return this.observeThread$(canonicalInput, credentials, {
|
|
248
|
+
completeOnRunError: false,
|
|
249
|
+
streamMode: "run"
|
|
250
|
+
});
|
|
251
|
+
}));
|
|
241
252
|
}
|
|
242
253
|
/**
|
|
243
254
|
* Reconnect to an existing thread by fetching websocket credentials and
|
|
@@ -245,21 +256,15 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
|
|
|
245
256
|
*/
|
|
246
257
|
connect(input) {
|
|
247
258
|
this.threadId = input.threadId;
|
|
248
|
-
this.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if (
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
for (const event of plan.events) this.updateRunIdFromEvent(event);
|
|
258
|
-
return concat(from(plan.events), this.observeThread$(input, { joinToken: plan.joinToken }, {
|
|
259
|
-
completeOnRunError: true,
|
|
260
|
-
streamMode: "connect",
|
|
261
|
-
replayCursor: plan.joinFromEventId
|
|
262
|
-
}));
|
|
259
|
+
this.canonicalRunId = null;
|
|
260
|
+
this.clearReconnectCursor(input.threadId);
|
|
261
|
+
return defer(() => this.requestJoinCredentials$("connect", input)).pipe(switchMap((credentials) => {
|
|
262
|
+
if (credentials === null) return EMPTY;
|
|
263
|
+
const canonicalInput = this.applyCanonicalRunIdentity(input, credentials, { fallbackToInputRunId: false });
|
|
264
|
+
return this.observeThread$(canonicalInput, credentials, {
|
|
265
|
+
completeOnRunError: false,
|
|
266
|
+
streamMode: "connect"
|
|
267
|
+
});
|
|
263
268
|
}));
|
|
264
269
|
}
|
|
265
270
|
/**
|
|
@@ -276,8 +281,7 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
|
|
|
276
281
|
ownSocket.disconnect();
|
|
277
282
|
if (this.socket === ownSocket) this.socket = null;
|
|
278
283
|
}
|
|
279
|
-
|
|
280
|
-
this.runId = null;
|
|
284
|
+
this.canonicalRunId = null;
|
|
281
285
|
}
|
|
282
286
|
cleanup() {
|
|
283
287
|
this.cleanupOwned(this.activeChannel, this.socket);
|
|
@@ -298,80 +302,56 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
|
|
|
298
302
|
tools: input.tools,
|
|
299
303
|
context: input.context,
|
|
300
304
|
state: input.state,
|
|
301
|
-
forwardedProps: input.forwardedProps
|
|
305
|
+
forwardedProps: input.forwardedProps,
|
|
306
|
+
...mode === "connect" ? { lastSeenEventId: this.getReconnectCursor(input) } : {}
|
|
302
307
|
}),
|
|
303
308
|
...this.config.credentials ? { credentials: this.config.credentials } : {}
|
|
304
309
|
});
|
|
305
|
-
if (response.status ===
|
|
310
|
+
if (response.status === 204 && mode === "connect") return null;
|
|
311
|
+
if (response.status === 409 && mode === "run") throw new AgentThreadLockedError(input.threadId);
|
|
306
312
|
if (!response.ok) {
|
|
307
313
|
const text = await response.text().catch(() => "");
|
|
308
314
|
throw new Error(text || response.statusText || String(response.status));
|
|
309
315
|
}
|
|
310
|
-
|
|
311
|
-
if (!payload.joinToken) throw new Error("missing joinToken");
|
|
312
|
-
return { joinToken: payload.joinToken };
|
|
316
|
+
return this.normalizeJoinCredentials(await response.json(), input);
|
|
313
317
|
} catch (error) {
|
|
314
318
|
if (error instanceof AgentThreadLockedError) throw error;
|
|
315
319
|
throw new Error(`REST ${mode} request failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
316
320
|
}
|
|
317
321
|
});
|
|
318
322
|
}
|
|
319
|
-
|
|
320
|
-
return defer(async () => {
|
|
321
|
-
try {
|
|
322
|
-
const response = await fetch(this.buildRuntimeUrl("connect"), {
|
|
323
|
-
method: "POST",
|
|
324
|
-
headers: {
|
|
325
|
-
"Content-Type": "application/json",
|
|
326
|
-
...this.config.headers
|
|
327
|
-
},
|
|
328
|
-
body: JSON.stringify({
|
|
329
|
-
threadId: input.threadId,
|
|
330
|
-
runId: input.runId,
|
|
331
|
-
messages: input.messages,
|
|
332
|
-
tools: input.tools,
|
|
333
|
-
context: input.context,
|
|
334
|
-
state: input.state,
|
|
335
|
-
forwardedProps: input.forwardedProps,
|
|
336
|
-
lastSeenEventId: this.getReconnectCursor(input)
|
|
337
|
-
}),
|
|
338
|
-
...this.config.credentials ? { credentials: this.config.credentials } : {}
|
|
339
|
-
});
|
|
340
|
-
if (response.status === 204) return null;
|
|
341
|
-
if (!response.ok) {
|
|
342
|
-
const text = await response.text().catch(() => "");
|
|
343
|
-
throw new Error(text || response.statusText || String(response.status));
|
|
344
|
-
}
|
|
345
|
-
return this.normalizeConnectPlan(await response.json());
|
|
346
|
-
} catch (error) {
|
|
347
|
-
throw new Error(`REST connect request failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
normalizeConnectPlan(payload) {
|
|
323
|
+
normalizeJoinCredentials(payload, input) {
|
|
352
324
|
const envelope = payload && typeof payload === "object" ? payload : null;
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
325
|
+
const realtime = envelope?.realtime && typeof envelope.realtime === "object" ? envelope.realtime : null;
|
|
326
|
+
if (typeof envelope?.joinToken !== "string" || !envelope.joinToken) throw new Error("missing joinToken");
|
|
327
|
+
if (typeof realtime?.clientUrl !== "string" || !realtime.clientUrl) throw new Error("missing realtime.clientUrl");
|
|
328
|
+
if (typeof realtime.topic !== "string" || !realtime.topic) throw new Error("missing realtime.topic");
|
|
329
|
+
return {
|
|
330
|
+
threadId: typeof envelope.threadId === "string" && envelope.threadId ? envelope.threadId : input.threadId,
|
|
331
|
+
runId: typeof envelope.runId === "string" && envelope.runId ? envelope.runId : null,
|
|
332
|
+
joinToken: envelope.joinToken,
|
|
333
|
+
realtime: {
|
|
334
|
+
clientUrl: realtime.clientUrl,
|
|
335
|
+
topic: realtime.topic
|
|
336
|
+
}
|
|
357
337
|
};
|
|
358
|
-
if (envelope?.mode === "live") {
|
|
359
|
-
if (typeof envelope.joinToken !== "string" || envelope.joinToken.length === 0) throw new Error("missing joinToken");
|
|
360
|
-
return {
|
|
361
|
-
mode: "live",
|
|
362
|
-
joinToken: envelope.joinToken,
|
|
363
|
-
joinFromEventId: typeof envelope.joinFromEventId === "string" ? envelope.joinFromEventId : null,
|
|
364
|
-
events: Array.isArray(envelope.events) ? envelope.events : []
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
throw new Error("invalid connect plan");
|
|
368
338
|
}
|
|
369
339
|
observeThread$(input, credentials, options) {
|
|
340
|
+
return this.observeThreadSession$(input, credentials, options).pipe(catchError((error) => {
|
|
341
|
+
if (!this.isSocketReconnectExhaustedError(error)) return throwError(() => error);
|
|
342
|
+
return this.requestJoinCredentials$("connect", input).pipe(switchMap((refreshedCredentials) => refreshedCredentials === null ? EMPTY : this.observeThread$(this.applyCanonicalRunIdentity(input, refreshedCredentials, { fallbackToInputRunId: options.streamMode === "run" }), refreshedCredentials, {
|
|
343
|
+
...options,
|
|
344
|
+
channelMode: "connect",
|
|
345
|
+
replayCursor: this.getReconnectCursor(input)
|
|
346
|
+
})));
|
|
347
|
+
}));
|
|
348
|
+
}
|
|
349
|
+
observeThreadSession$(input, credentials, options) {
|
|
370
350
|
return defer(() => {
|
|
371
351
|
let ownSocket = null;
|
|
372
352
|
let ownChannel = null;
|
|
373
353
|
const socket$ = ɵphoenixSocket$({
|
|
374
|
-
url:
|
|
354
|
+
url: credentials.realtime.clientUrl,
|
|
375
355
|
options: {
|
|
376
356
|
params: {
|
|
377
357
|
...this.config.socketParams,
|
|
@@ -387,10 +367,10 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
|
|
|
387
367
|
bufferSize: 1,
|
|
388
368
|
refCount: true
|
|
389
369
|
}));
|
|
390
|
-
const
|
|
370
|
+
const params = this.createThreadChannelParams(input, options.channelMode ?? options.streamMode, options.replayCursor);
|
|
391
371
|
const channel$ = ɵphoenixChannel$({
|
|
392
372
|
socket$,
|
|
393
|
-
topic,
|
|
373
|
+
topic: credentials.realtime.topic,
|
|
394
374
|
params
|
|
395
375
|
}).pipe(tap(({ channel }) => {
|
|
396
376
|
ownChannel = channel;
|
|
@@ -400,8 +380,14 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
|
|
|
400
380
|
refCount: true
|
|
401
381
|
}));
|
|
402
382
|
const threadEvents$ = this.observeThreadEvents$(input.threadId, channel$, options).pipe(share());
|
|
403
|
-
const
|
|
404
|
-
|
|
383
|
+
const replayComplete$ = this.observeControlEvent$(input.threadId, channel$, REPLAY_COMPLETE_EVENT).pipe(ignoreElements(), share());
|
|
384
|
+
const streamIdle$ = this.observeControlEvent$(input.threadId, channel$, STREAM_IDLE_EVENT).pipe(shareReplay({
|
|
385
|
+
bufferSize: 1,
|
|
386
|
+
refCount: true
|
|
387
|
+
}));
|
|
388
|
+
const streamIdleCompletion$ = options.streamMode === "connect" ? streamIdle$.pipe(take(1)) : EMPTY;
|
|
389
|
+
const terminal$ = merge(threadEvents$.pipe(ignoreElements(), endWith(null), take(1)), streamIdleCompletion$);
|
|
390
|
+
return merge(this.joinThreadChannel$(channel$), this.observeSocketHealth$(socket$).pipe(takeUntil(terminal$)), threadEvents$.pipe(takeUntil(streamIdleCompletion$)), replayComplete$.pipe(takeUntil(terminal$)), streamIdleCompletion$.pipe(ignoreElements())).pipe(finalize(() => this.cleanupOwned(ownChannel, ownSocket)));
|
|
405
391
|
});
|
|
406
392
|
}
|
|
407
393
|
joinThreadChannel$(channel$) {
|
|
@@ -413,17 +399,23 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
|
|
|
413
399
|
observeThreadEvents$(threadId, channel$, options) {
|
|
414
400
|
return channel$.pipe(switchMap$1(({ channel }) => this.observeChannelEvent$(channel, CLIENT_AG_UI_EVENT)), tap((payload) => {
|
|
415
401
|
this.updateLastSeenEventId(threadId, payload);
|
|
416
|
-
|
|
417
|
-
|
|
402
|
+
}), mergeMap((payload) => this.createThreadNotifications(payload, {
|
|
403
|
+
completeOnRunError: options.completeOnRunError,
|
|
404
|
+
completeOnRunFinished: options.streamMode === "run",
|
|
405
|
+
errorOnRunError: options.streamMode === "run"
|
|
406
|
+
})), dematerialize());
|
|
407
|
+
}
|
|
408
|
+
observeControlEvent$(threadId, channel$, eventName) {
|
|
409
|
+
return channel$.pipe(switchMap$1(({ channel }) => this.observeChannelEvent$(channel, eventName)), tap((payload) => this.updateLastSeenEventIdFromControl(threadId, payload)));
|
|
418
410
|
}
|
|
419
411
|
observeChannelEvent$(channel, eventName) {
|
|
420
412
|
return ɵobservePhoenixEvent$(channel, eventName);
|
|
421
413
|
}
|
|
422
|
-
createThreadNotifications(payload,
|
|
423
|
-
if (payload.type === EventType.RUN_FINISHED) return [Notification.createNext(payload), Notification.createComplete()];
|
|
414
|
+
createThreadNotifications(payload, options) {
|
|
415
|
+
if (payload.type === EventType.RUN_FINISHED) return options.completeOnRunFinished ? [Notification.createNext(payload), Notification.createComplete()] : [Notification.createNext(payload)];
|
|
424
416
|
if (payload.type === EventType.RUN_ERROR) {
|
|
425
417
|
const errorMessage = payload.message ?? "Run error";
|
|
426
|
-
return completeOnRunError ? [Notification.createNext(payload), Notification.createComplete()] : [Notification.createNext(payload), Notification.createError(new Error(errorMessage))];
|
|
418
|
+
return options.completeOnRunError ? [Notification.createNext(payload), Notification.createComplete()] : options.errorOnRunError ? [Notification.createNext(payload), Notification.createError(new Error(errorMessage))] : [Notification.createNext(payload)];
|
|
427
419
|
}
|
|
428
420
|
return [Notification.createNext(payload)];
|
|
429
421
|
}
|
|
@@ -432,60 +424,58 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
|
|
|
432
424
|
const origin = typeof window !== "undefined" && window.location ? window.location.origin : "http://localhost";
|
|
433
425
|
return new URL(path, new URL(this.config.runtimeUrl, origin)).toString();
|
|
434
426
|
}
|
|
435
|
-
|
|
436
|
-
|
|
427
|
+
createThreadChannelParams(input, streamMode, replayCursor) {
|
|
428
|
+
return streamMode === "run" ? {
|
|
437
429
|
stream_mode: "run",
|
|
438
430
|
run_id: input.runId
|
|
439
431
|
} : {
|
|
440
432
|
stream_mode: "connect",
|
|
441
433
|
last_seen_event_id: replayCursor === void 0 ? this.getReconnectCursor(input) : replayCursor
|
|
442
434
|
};
|
|
443
|
-
return {
|
|
444
|
-
topic: `thread:${input.threadId}`,
|
|
445
|
-
params
|
|
446
|
-
};
|
|
447
435
|
}
|
|
448
436
|
getLastSeenEventId(threadId) {
|
|
449
437
|
return this.sharedState.lastSeenEventIds.get(threadId) ?? null;
|
|
450
438
|
}
|
|
451
439
|
getReconnectCursor(input) {
|
|
452
|
-
return this.
|
|
440
|
+
return this.getLastSeenEventId(input.threadId);
|
|
453
441
|
}
|
|
454
|
-
|
|
455
|
-
|
|
442
|
+
clearReconnectCursor(threadId) {
|
|
443
|
+
this.sharedState.lastSeenEventIds.delete(threadId);
|
|
456
444
|
}
|
|
457
445
|
updateLastSeenEventId(threadId, payload) {
|
|
458
446
|
const eventId = this.readEventId(payload);
|
|
459
447
|
if (!eventId) return;
|
|
460
448
|
this.sharedState.lastSeenEventIds.set(threadId, eventId);
|
|
461
449
|
}
|
|
462
|
-
|
|
450
|
+
updateLastSeenEventIdFromControl(threadId, payload) {
|
|
451
|
+
const eventId = this.readControlEventId(payload);
|
|
463
452
|
if (!eventId) return;
|
|
464
453
|
this.sharedState.lastSeenEventIds.set(threadId, eventId);
|
|
465
454
|
}
|
|
466
|
-
/**
|
|
467
|
-
* Keep `this.runId` in sync with the backend's actual run ID.
|
|
468
|
-
*
|
|
469
|
-
* During a `connect` (resume) flow the client generates a fresh `runId`
|
|
470
|
-
* via `prepareRunAgentInput`, but the backend is running under its own
|
|
471
|
-
* run ID. If the client later sends `STOP_RUN_EVENT` with the wrong
|
|
472
|
-
* `runId`, the gateway's runner channel will not match it and the agent
|
|
473
|
-
* keeps running. Extracting the run ID from live events fixes this.
|
|
474
|
-
*
|
|
475
|
-
* The runner normalises events to `run_id` (snake_case) before pushing
|
|
476
|
-
* to the gateway, so we check both `runId` and `run_id`.
|
|
477
|
-
*/
|
|
478
|
-
updateRunIdFromEvent(payload) {
|
|
479
|
-
const record = payload;
|
|
480
|
-
const eventRunId = record.runId ?? record.run_id;
|
|
481
|
-
if (typeof eventRunId === "string" && eventRunId.length > 0) this.runId = eventRunId;
|
|
482
|
-
}
|
|
483
455
|
readEventId(payload) {
|
|
484
456
|
const metadata = payload.metadata;
|
|
485
457
|
if (!metadata || typeof metadata !== "object") return null;
|
|
486
458
|
const runnerEventId = metadata.cpki_event_id;
|
|
487
459
|
return typeof runnerEventId === "string" ? runnerEventId : null;
|
|
488
460
|
}
|
|
461
|
+
readControlEventId(payload) {
|
|
462
|
+
if (!payload || typeof payload !== "object") return null;
|
|
463
|
+
const latestEventId = payload.latestEventId;
|
|
464
|
+
return typeof latestEventId === "string" ? latestEventId : null;
|
|
465
|
+
}
|
|
466
|
+
applyCanonicalRunIdentity(input, credentials, options) {
|
|
467
|
+
this.threadId = credentials.threadId;
|
|
468
|
+
const runId = credentials.runId ?? (options.fallbackToInputRunId ? input.runId : null);
|
|
469
|
+
this.canonicalRunId = runId;
|
|
470
|
+
return {
|
|
471
|
+
...input,
|
|
472
|
+
threadId: credentials.threadId,
|
|
473
|
+
...runId === null ? {} : { runId }
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
isSocketReconnectExhaustedError(error) {
|
|
477
|
+
return error instanceof Error && error.message.includes("WebSocket connection failed after");
|
|
478
|
+
}
|
|
489
479
|
};
|
|
490
480
|
|
|
491
481
|
//#endregion
|
|
@@ -1234,6 +1224,19 @@ var SuggestionEngine = class {
|
|
|
1234
1224
|
this.notifySuggestionsChanged(agentId, []);
|
|
1235
1225
|
}
|
|
1236
1226
|
/**
|
|
1227
|
+
* Clear suggestions at the start of a run, but immediately restore static
|
|
1228
|
+
* suggestions that have `available: "always"` so they remain visible
|
|
1229
|
+
* throughout the run.
|
|
1230
|
+
*/
|
|
1231
|
+
clearSuggestionsForRun(agentId) {
|
|
1232
|
+
this.clearSuggestions(agentId);
|
|
1233
|
+
for (const config of Object.values(this._suggestionsConfig)) {
|
|
1234
|
+
if (config.consumerAgentId !== void 0 && config.consumerAgentId !== "*" && config.consumerAgentId !== agentId) continue;
|
|
1235
|
+
if (config.available !== "always") continue;
|
|
1236
|
+
if (isStaticSuggestionsConfig(config)) this.addStaticSuggestions(randomUUID$1(), config, agentId);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1237
1240
|
* Get current suggestions for an agent
|
|
1238
1241
|
*/
|
|
1239
1242
|
getSuggestions(agentId) {
|
|
@@ -1586,7 +1589,7 @@ var RunHandler = class {
|
|
|
1586
1589
|
* Run an agent
|
|
1587
1590
|
*/
|
|
1588
1591
|
async runAgent({ agent, forwardedProps }) {
|
|
1589
|
-
if (agent.agentId) this._internal.suggestionEngine.
|
|
1592
|
+
if (agent.agentId) this._internal.suggestionEngine.clearSuggestionsForRun(agent.agentId);
|
|
1590
1593
|
if (agent instanceof HttpAgent) agent.headers = { ...this._internal.headers };
|
|
1591
1594
|
if (agent.detachActiveRun) await agent.detachActiveRun();
|
|
1592
1595
|
this._internal.subscribeAgentToStateManager(agent);
|
|
@@ -2308,8 +2311,21 @@ let CopilotKitCoreErrorCode = /* @__PURE__ */ function(CopilotKitCoreErrorCode)
|
|
|
2308
2311
|
CopilotKitCoreErrorCode["TRANSCRIPTION_RATE_LIMITED"] = "transcription_rate_limited";
|
|
2309
2312
|
CopilotKitCoreErrorCode["TRANSCRIPTION_AUTH_FAILED"] = "transcription_auth_failed";
|
|
2310
2313
|
CopilotKitCoreErrorCode["TRANSCRIPTION_NETWORK_ERROR"] = "transcription_network_error";
|
|
2314
|
+
CopilotKitCoreErrorCode["SUBSCRIBER_CALLBACK_FAILED"] = "subscriber_callback_failed";
|
|
2311
2315
|
return CopilotKitCoreErrorCode;
|
|
2312
2316
|
}({});
|
|
2317
|
+
/**
|
|
2318
|
+
* Runtime allowlist derived from {@link SUBSCRIBE_TO_AGENT_KEYS}. Hoisted
|
|
2319
|
+
* to module scope so the Set is allocated once, not per-subscription.
|
|
2320
|
+
*/
|
|
2321
|
+
const ALLOWED_KEYS = new Set([
|
|
2322
|
+
"onMessagesChanged",
|
|
2323
|
+
"onStateChanged",
|
|
2324
|
+
"onRunInitialized",
|
|
2325
|
+
"onRunFinalized",
|
|
2326
|
+
"onRunFailed",
|
|
2327
|
+
"onRunErrorEvent"
|
|
2328
|
+
]);
|
|
2313
2329
|
let CopilotKitCoreRuntimeConnectionStatus = /* @__PURE__ */ function(CopilotKitCoreRuntimeConnectionStatus) {
|
|
2314
2330
|
CopilotKitCoreRuntimeConnectionStatus["Disconnected"] = "disconnected";
|
|
2315
2331
|
CopilotKitCoreRuntimeConnectionStatus["Connected"] = "connected";
|
|
@@ -2375,6 +2391,16 @@ var CopilotKitCore = class {
|
|
|
2375
2391
|
}), "Subscriber onError error:");
|
|
2376
2392
|
}
|
|
2377
2393
|
/**
|
|
2394
|
+
* Log a message to the console and emit an error to subscribers.
|
|
2395
|
+
* Catches failures from `emitError` itself to prevent unhandled rejections.
|
|
2396
|
+
*/
|
|
2397
|
+
logAndEmitError(message, params, logLevel = "error") {
|
|
2398
|
+
console[logLevel](message, params.error);
|
|
2399
|
+
this.emitError(params).catch((emitErr) => {
|
|
2400
|
+
console.error(message + " — emitError itself failed:", emitErr);
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
/**
|
|
2378
2404
|
* Snapshot accessors
|
|
2379
2405
|
*/
|
|
2380
2406
|
get context() {
|
|
@@ -2411,17 +2437,30 @@ var CopilotKitCore = class {
|
|
|
2411
2437
|
return this._properties;
|
|
2412
2438
|
}
|
|
2413
2439
|
/**
|
|
2414
|
-
* Default throttle interval (ms)
|
|
2415
|
-
*
|
|
2416
|
-
* `
|
|
2417
|
-
* or `<CopilotChat>` overrides this default and disables throttling.
|
|
2440
|
+
* Default throttle interval (ms) used by `subscribeToAgentWithOptions()`
|
|
2441
|
+
* when the caller does not specify an explicit `throttleMs`.
|
|
2442
|
+
* `undefined` means no default is configured; `0` means no throttling.
|
|
2418
2443
|
*/
|
|
2419
2444
|
get defaultThrottleMs() {
|
|
2420
2445
|
return this._defaultThrottleMs;
|
|
2421
2446
|
}
|
|
2447
|
+
/**
|
|
2448
|
+
* Set the default throttle interval (ms) for `subscribeToAgentWithOptions()`.
|
|
2449
|
+
*
|
|
2450
|
+
* Accepts a non-negative finite number or `undefined` (to clear the
|
|
2451
|
+
* default). Invalid values (NaN, Infinity, negative) are logged as
|
|
2452
|
+
* errors and ignored — the previous valid value is preserved.
|
|
2453
|
+
*/
|
|
2422
2454
|
setDefaultThrottleMs(value) {
|
|
2423
2455
|
if (value !== void 0 && (!Number.isFinite(value) || value < 0)) {
|
|
2424
|
-
this.
|
|
2456
|
+
this.logAndEmitError(`CopilotKitCore.setDefaultThrottleMs: value must be a non-negative finite number or undefined, got ${value}. Keeping current value (${this._defaultThrottleMs}).`, {
|
|
2457
|
+
error: /* @__PURE__ */ new Error(`setDefaultThrottleMs: invalid value (${value}), keeping current value (${this._defaultThrottleMs})`),
|
|
2458
|
+
code: CopilotKitCoreErrorCode.SUBSCRIBER_CALLBACK_FAILED,
|
|
2459
|
+
context: {
|
|
2460
|
+
value,
|
|
2461
|
+
currentValue: this._defaultThrottleMs
|
|
2462
|
+
}
|
|
2463
|
+
});
|
|
2425
2464
|
return;
|
|
2426
2465
|
}
|
|
2427
2466
|
this._defaultThrottleMs = value;
|
|
@@ -2542,6 +2581,137 @@ var CopilotKitCore = class {
|
|
|
2542
2581
|
} };
|
|
2543
2582
|
}
|
|
2544
2583
|
/**
|
|
2584
|
+
* Subscribe to an agent's notification and lifecycle events with
|
|
2585
|
+
* optional configuration (e.g. throttling).
|
|
2586
|
+
*
|
|
2587
|
+
* Wraps every callback with error protection (`safeCall`) and applies
|
|
2588
|
+
* the options before delegating to `agent.subscribe()`.
|
|
2589
|
+
*
|
|
2590
|
+
* See {@link SubscribeToAgentSubscriber} for the accepted callback subset
|
|
2591
|
+
* and the rationale for excluding AG-UI event handlers.
|
|
2592
|
+
*/
|
|
2593
|
+
subscribeToAgentWithOptions(agent, subscriber, options) {
|
|
2594
|
+
const resolved = options?.throttleMs ?? this._defaultThrottleMs ?? 0;
|
|
2595
|
+
let effectiveMs = 0;
|
|
2596
|
+
if (!Number.isFinite(resolved) || resolved < 0) {
|
|
2597
|
+
const source = options?.throttleMs !== void 0 ? "throttleMs" : "defaultThrottleMs";
|
|
2598
|
+
this.logAndEmitError(`CopilotKitCore.subscribeToAgentWithOptions: ${source} must be a non-negative finite number, got ${resolved}. Falling back to unthrottled.`, {
|
|
2599
|
+
error: /* @__PURE__ */ new Error(`subscribeToAgentWithOptions: invalid ${source} (${resolved}), falling back to unthrottled`),
|
|
2600
|
+
code: CopilotKitCoreErrorCode.SUBSCRIBER_CALLBACK_FAILED,
|
|
2601
|
+
context: {
|
|
2602
|
+
agentId: agent.agentId,
|
|
2603
|
+
source,
|
|
2604
|
+
value: resolved
|
|
2605
|
+
}
|
|
2606
|
+
});
|
|
2607
|
+
} else effectiveMs = resolved;
|
|
2608
|
+
const agentLabel = agent.agentId || "(unknown agent)";
|
|
2609
|
+
const safeCall = (label, fn, ...args) => {
|
|
2610
|
+
const reportError = (err, verb) => {
|
|
2611
|
+
this.logAndEmitError(`CopilotKitCore.subscribeToAgentWithOptions[${agentLabel}]: ${label} callback ${verb}:`, {
|
|
2612
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
2613
|
+
code: CopilotKitCoreErrorCode.SUBSCRIBER_CALLBACK_FAILED,
|
|
2614
|
+
context: {
|
|
2615
|
+
agentId: agent.agentId,
|
|
2616
|
+
callback: label
|
|
2617
|
+
}
|
|
2618
|
+
});
|
|
2619
|
+
};
|
|
2620
|
+
try {
|
|
2621
|
+
const result = fn(...args);
|
|
2622
|
+
if (result instanceof Promise) return result.catch((err) => {
|
|
2623
|
+
reportError(err, "rejected");
|
|
2624
|
+
});
|
|
2625
|
+
return result;
|
|
2626
|
+
} catch (err) {
|
|
2627
|
+
reportError(err, "threw");
|
|
2628
|
+
}
|
|
2629
|
+
};
|
|
2630
|
+
const guardAll = (sub) => {
|
|
2631
|
+
const guarded = {};
|
|
2632
|
+
if (sub.onMessagesChanged) {
|
|
2633
|
+
const fn = sub.onMessagesChanged;
|
|
2634
|
+
guarded.onMessagesChanged = (params) => safeCall("onMessagesChanged", fn, params);
|
|
2635
|
+
}
|
|
2636
|
+
if (sub.onStateChanged) {
|
|
2637
|
+
const fn = sub.onStateChanged;
|
|
2638
|
+
guarded.onStateChanged = (params) => safeCall("onStateChanged", fn, params);
|
|
2639
|
+
}
|
|
2640
|
+
if (sub.onRunInitialized) {
|
|
2641
|
+
const fn = sub.onRunInitialized;
|
|
2642
|
+
guarded.onRunInitialized = (params) => safeCall("onRunInitialized", fn, params);
|
|
2643
|
+
}
|
|
2644
|
+
if (sub.onRunFinalized) {
|
|
2645
|
+
const fn = sub.onRunFinalized;
|
|
2646
|
+
guarded.onRunFinalized = (params) => safeCall("onRunFinalized", fn, params);
|
|
2647
|
+
}
|
|
2648
|
+
if (sub.onRunFailed) {
|
|
2649
|
+
const fn = sub.onRunFailed;
|
|
2650
|
+
guarded.onRunFailed = (params) => safeCall("onRunFailed", fn, params);
|
|
2651
|
+
}
|
|
2652
|
+
if (sub.onRunErrorEvent) {
|
|
2653
|
+
const fn = sub.onRunErrorEvent;
|
|
2654
|
+
guarded.onRunErrorEvent = (params) => safeCall("onRunErrorEvent", fn, params);
|
|
2655
|
+
}
|
|
2656
|
+
return guarded;
|
|
2657
|
+
};
|
|
2658
|
+
for (const key of Object.keys(subscriber)) if (typeof subscriber[key] === "function" && !ALLOWED_KEYS.has(key)) {
|
|
2659
|
+
const message = `CopilotKitCore.subscribeToAgentWithOptions[${agentLabel}]: callback "${key}" is not supported and was dropped. Supported callbacks: ${Array.from(ALLOWED_KEYS).join(", ")}. Use agent.subscribe() directly for event handlers and per-item notifications.`;
|
|
2660
|
+
this.logAndEmitError(message, {
|
|
2661
|
+
error: new Error(message),
|
|
2662
|
+
code: CopilotKitCoreErrorCode.SUBSCRIBER_CALLBACK_FAILED,
|
|
2663
|
+
context: {
|
|
2664
|
+
agentId: agent.agentId,
|
|
2665
|
+
droppedCallback: key
|
|
2666
|
+
}
|
|
2667
|
+
}, "warn");
|
|
2668
|
+
}
|
|
2669
|
+
if (effectiveMs <= 0) {
|
|
2670
|
+
const subscription = agent.subscribe(guardAll(subscriber));
|
|
2671
|
+
return { unsubscribe: () => subscription.unsubscribe() };
|
|
2672
|
+
}
|
|
2673
|
+
let active = true;
|
|
2674
|
+
let latestMessagesParams = null;
|
|
2675
|
+
let latestStateParams = null;
|
|
2676
|
+
const flushPending = () => {
|
|
2677
|
+
if (active && subscriber.onMessagesChanged && latestMessagesParams) {
|
|
2678
|
+
const params = latestMessagesParams;
|
|
2679
|
+
latestMessagesParams = null;
|
|
2680
|
+
safeCall("onMessagesChanged", subscriber.onMessagesChanged, params);
|
|
2681
|
+
}
|
|
2682
|
+
if (active && subscriber.onStateChanged && latestStateParams) {
|
|
2683
|
+
const params = latestStateParams;
|
|
2684
|
+
latestStateParams = null;
|
|
2685
|
+
safeCall("onStateChanged", subscriber.onStateChanged, params);
|
|
2686
|
+
}
|
|
2687
|
+
};
|
|
2688
|
+
const throttler = new Throttler(flushPending, {
|
|
2689
|
+
wait: effectiveMs,
|
|
2690
|
+
leading: true,
|
|
2691
|
+
trailing: true
|
|
2692
|
+
});
|
|
2693
|
+
const lifecycleOnly = {};
|
|
2694
|
+
if (subscriber.onRunInitialized) lifecycleOnly.onRunInitialized = subscriber.onRunInitialized;
|
|
2695
|
+
if (subscriber.onRunFinalized) lifecycleOnly.onRunFinalized = subscriber.onRunFinalized;
|
|
2696
|
+
if (subscriber.onRunFailed) lifecycleOnly.onRunFailed = subscriber.onRunFailed;
|
|
2697
|
+
if (subscriber.onRunErrorEvent) lifecycleOnly.onRunErrorEvent = subscriber.onRunErrorEvent;
|
|
2698
|
+
const wrappedSubscriber = guardAll(lifecycleOnly);
|
|
2699
|
+
if (subscriber.onMessagesChanged) wrappedSubscriber.onMessagesChanged = (params) => {
|
|
2700
|
+
latestMessagesParams = params;
|
|
2701
|
+
throttler.maybeExecute();
|
|
2702
|
+
};
|
|
2703
|
+
if (subscriber.onStateChanged) wrappedSubscriber.onStateChanged = (params) => {
|
|
2704
|
+
latestStateParams = params;
|
|
2705
|
+
throttler.maybeExecute();
|
|
2706
|
+
};
|
|
2707
|
+
const subscription = agent.subscribe(wrappedSubscriber);
|
|
2708
|
+
return { unsubscribe: () => {
|
|
2709
|
+
active = false;
|
|
2710
|
+
throttler.cancel();
|
|
2711
|
+
subscription.unsubscribe();
|
|
2712
|
+
} };
|
|
2713
|
+
}
|
|
2714
|
+
/**
|
|
2545
2715
|
* Agent connectivity (delegated to RunHandler)
|
|
2546
2716
|
*/
|
|
2547
2717
|
async connectAgent(params) {
|
|
@@ -3088,15 +3258,18 @@ const threadDomainEvents = createActionGroup("Thread Domain", {
|
|
|
3088
3258
|
threadUpserted: props(),
|
|
3089
3259
|
threadDeleted: props()
|
|
3090
3260
|
});
|
|
3091
|
-
function
|
|
3092
|
-
return [...threads].sort((left, right) =>
|
|
3261
|
+
function sortThreadsByRecency(threads) {
|
|
3262
|
+
return [...threads].sort((left, right) => {
|
|
3263
|
+
const leftKey = left.lastRunAt ?? left.updatedAt ?? left.createdAt;
|
|
3264
|
+
return (right.lastRunAt ?? right.updatedAt ?? right.createdAt).localeCompare(leftKey);
|
|
3265
|
+
});
|
|
3093
3266
|
}
|
|
3094
3267
|
function upsertThread(threads, thread) {
|
|
3095
3268
|
const existingIndex = threads.findIndex((item) => item.id === thread.id);
|
|
3096
|
-
if (existingIndex === -1) return
|
|
3269
|
+
if (existingIndex === -1) return sortThreadsByRecency([...threads, thread]);
|
|
3097
3270
|
const next = [...threads];
|
|
3098
3271
|
next[existingIndex] = thread;
|
|
3099
|
-
return
|
|
3272
|
+
return sortThreadsByRecency(next);
|
|
3100
3273
|
}
|
|
3101
3274
|
const threadReducer = createReducer(initialThreadState, on(threadAdapterEvents.contextChanged, (state, { context }) => ({
|
|
3102
3275
|
...state,
|
|
@@ -3129,7 +3302,7 @@ const threadReducer = createReducer(initialThreadState, on(threadAdapterEvents.c
|
|
|
3129
3302
|
if (sessionId !== state.sessionId) return state;
|
|
3130
3303
|
return {
|
|
3131
3304
|
...state,
|
|
3132
|
-
threads:
|
|
3305
|
+
threads: sortThreadsByRecency(threads),
|
|
3133
3306
|
isLoading: false,
|
|
3134
3307
|
error: null,
|
|
3135
3308
|
metadataJoinCode: joinCode,
|