@copilotkit/core 1.56.3 → 1.56.4

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 CHANGED
@@ -128,6 +128,8 @@ function ɵobservePhoenixSocketHealth$(socketSignals$, maxConsecutiveErrors) {
128
128
  //#endregion
129
129
  //#region src/intelligence-agent.ts
130
130
  const CLIENT_AG_UI_EVENT = "ag_ui_event";
131
+ const REPLAY_COMPLETE_EVENT = "replay_complete";
132
+ const STREAM_IDLE_EVENT = "stream_idle";
131
133
  const STOP_RUN_EVENT = "stop_run";
132
134
  var AgentThreadLockedError = class extends Error {
133
135
  constructor(threadId) {
@@ -240,10 +242,14 @@ var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAg
240
242
  run(input) {
241
243
  this.threadId = input.threadId;
242
244
  this.canonicalRunId = input.runId;
243
- return (0, rxjs.defer)(() => this.requestJoinCredentials$("run", input)).pipe((0, rxjs.switchMap)((credentials) => this.observeThread$(input, credentials, {
244
- completeOnRunError: false,
245
- streamMode: "run"
246
- })));
245
+ return (0, rxjs.defer)(() => this.requestJoinCredentials$("run", input)).pipe((0, rxjs.switchMap)((credentials) => {
246
+ if (credentials === null) return (0, rxjs.throwError)(() => /* @__PURE__ */ new Error("REST run request returned no credentials"));
247
+ const canonicalInput = this.applyCanonicalRunIdentity(input, credentials, { fallbackToInputRunId: true });
248
+ return this.observeThread$(canonicalInput, credentials, {
249
+ completeOnRunError: false,
250
+ streamMode: "run"
251
+ });
252
+ }));
247
253
  }
248
254
  /**
249
255
  * Reconnect to an existing thread by fetching websocket credentials and
@@ -251,19 +257,15 @@ var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAg
251
257
  */
252
258
  connect(input) {
253
259
  this.threadId = input.threadId;
254
- this.canonicalRunId = input.runId;
255
- return (0, rxjs.defer)(() => this.requestConnectPlan$(input)).pipe((0, rxjs.switchMap)((plan) => {
256
- if (plan === null) return rxjs.EMPTY;
257
- if (plan.mode === "bootstrap") {
258
- this.setLastSeenEventId(input.threadId, plan.latestEventId);
259
- return (0, rxjs.from)(plan.events);
260
- }
261
- this.setLastSeenEventId(input.threadId, plan.joinFromEventId);
262
- return (0, rxjs.concat)((0, rxjs.from)(plan.events), this.observeThread$(input, { joinToken: plan.joinToken }, {
263
- completeOnRunError: true,
264
- streamMode: "connect",
265
- replayCursor: plan.joinFromEventId
266
- }));
260
+ this.canonicalRunId = null;
261
+ this.clearReconnectCursor(input.threadId);
262
+ return (0, rxjs.defer)(() => this.requestJoinCredentials$("connect", input)).pipe((0, rxjs.switchMap)((credentials) => {
263
+ if (credentials === null) return rxjs.EMPTY;
264
+ const canonicalInput = this.applyCanonicalRunIdentity(input, credentials, { fallbackToInputRunId: false });
265
+ return this.observeThread$(canonicalInput, credentials, {
266
+ completeOnRunError: false,
267
+ streamMode: "connect"
268
+ });
267
269
  }));
268
270
  }
269
271
  /**
@@ -280,7 +282,6 @@ var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAg
280
282
  ownSocket.disconnect();
281
283
  if (this.socket === ownSocket) this.socket = null;
282
284
  }
283
- if (this.threadId) this.sharedState.lastSeenEventIds.delete(this.threadId);
284
285
  this.canonicalRunId = null;
285
286
  }
286
287
  cleanup() {
@@ -302,80 +303,56 @@ var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAg
302
303
  tools: input.tools,
303
304
  context: input.context,
304
305
  state: input.state,
305
- forwardedProps: input.forwardedProps
306
+ forwardedProps: input.forwardedProps,
307
+ ...mode === "connect" ? { lastSeenEventId: this.getReconnectCursor(input) } : {}
306
308
  }),
307
309
  ...this.config.credentials ? { credentials: this.config.credentials } : {}
308
310
  });
309
- if (response.status === 409) throw new AgentThreadLockedError(input.threadId);
311
+ if (response.status === 204 && mode === "connect") return null;
312
+ if (response.status === 409 && mode === "run") throw new AgentThreadLockedError(input.threadId);
310
313
  if (!response.ok) {
311
314
  const text = await response.text().catch(() => "");
312
315
  throw new Error(text || response.statusText || String(response.status));
313
316
  }
314
- const payload = await response.json();
315
- if (!payload.joinToken) throw new Error("missing joinToken");
316
- return { joinToken: payload.joinToken };
317
+ return this.normalizeJoinCredentials(await response.json(), input);
317
318
  } catch (error) {
318
319
  if (error instanceof AgentThreadLockedError) throw error;
319
320
  throw new Error(`REST ${mode} request failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
320
321
  }
321
322
  });
322
323
  }
323
- requestConnectPlan$(input) {
324
- return (0, rxjs.defer)(async () => {
325
- try {
326
- const response = await fetch(this.buildRuntimeUrl("connect"), {
327
- method: "POST",
328
- headers: {
329
- "Content-Type": "application/json",
330
- ...this.config.headers
331
- },
332
- body: JSON.stringify({
333
- threadId: input.threadId,
334
- runId: input.runId,
335
- messages: input.messages,
336
- tools: input.tools,
337
- context: input.context,
338
- state: input.state,
339
- forwardedProps: input.forwardedProps,
340
- lastSeenEventId: this.getReconnectCursor(input)
341
- }),
342
- ...this.config.credentials ? { credentials: this.config.credentials } : {}
343
- });
344
- if (response.status === 204) return null;
345
- if (!response.ok) {
346
- const text = await response.text().catch(() => "");
347
- throw new Error(text || response.statusText || String(response.status));
348
- }
349
- return this.normalizeConnectPlan(await response.json());
350
- } catch (error) {
351
- throw new Error(`REST connect request failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
352
- }
353
- });
354
- }
355
- normalizeConnectPlan(payload) {
324
+ normalizeJoinCredentials(payload, input) {
356
325
  const envelope = payload && typeof payload === "object" ? payload : null;
357
- if (envelope?.mode === "bootstrap") return {
358
- mode: "bootstrap",
359
- latestEventId: typeof envelope.latestEventId === "string" ? envelope.latestEventId : null,
360
- events: Array.isArray(envelope.events) ? envelope.events : []
326
+ const realtime = envelope?.realtime && typeof envelope.realtime === "object" ? envelope.realtime : null;
327
+ if (typeof envelope?.joinToken !== "string" || !envelope.joinToken) throw new Error("missing joinToken");
328
+ if (typeof realtime?.clientUrl !== "string" || !realtime.clientUrl) throw new Error("missing realtime.clientUrl");
329
+ if (typeof realtime.topic !== "string" || !realtime.topic) throw new Error("missing realtime.topic");
330
+ return {
331
+ threadId: typeof envelope.threadId === "string" && envelope.threadId ? envelope.threadId : input.threadId,
332
+ runId: typeof envelope.runId === "string" && envelope.runId ? envelope.runId : null,
333
+ joinToken: envelope.joinToken,
334
+ realtime: {
335
+ clientUrl: realtime.clientUrl,
336
+ topic: realtime.topic
337
+ }
361
338
  };
362
- if (envelope?.mode === "live") {
363
- if (typeof envelope.joinToken !== "string" || envelope.joinToken.length === 0) throw new Error("missing joinToken");
364
- return {
365
- mode: "live",
366
- joinToken: envelope.joinToken,
367
- joinFromEventId: typeof envelope.joinFromEventId === "string" ? envelope.joinFromEventId : null,
368
- events: Array.isArray(envelope.events) ? envelope.events : []
369
- };
370
- }
371
- throw new Error("invalid connect plan");
372
339
  }
373
340
  observeThread$(input, credentials, options) {
341
+ return this.observeThreadSession$(input, credentials, options).pipe((0, rxjs_operators.catchError)((error) => {
342
+ if (!this.isSocketReconnectExhaustedError(error)) return (0, rxjs.throwError)(() => error);
343
+ return this.requestJoinCredentials$("connect", input).pipe((0, rxjs.switchMap)((refreshedCredentials) => refreshedCredentials === null ? rxjs.EMPTY : this.observeThread$(this.applyCanonicalRunIdentity(input, refreshedCredentials, { fallbackToInputRunId: options.streamMode === "run" }), refreshedCredentials, {
344
+ ...options,
345
+ channelMode: "connect",
346
+ replayCursor: this.getReconnectCursor(input)
347
+ })));
348
+ }));
349
+ }
350
+ observeThreadSession$(input, credentials, options) {
374
351
  return (0, rxjs.defer)(() => {
375
352
  let ownSocket = null;
376
353
  let ownChannel = null;
377
354
  const socket$ = ɵphoenixSocket$({
378
- url: this.config.url,
355
+ url: credentials.realtime.clientUrl,
379
356
  options: {
380
357
  params: {
381
358
  ...this.config.socketParams,
@@ -391,10 +368,10 @@ var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAg
391
368
  bufferSize: 1,
392
369
  refCount: true
393
370
  }));
394
- const { topic, params } = this.createThreadChannelDescriptor(input, options.streamMode, options.replayCursor);
371
+ const params = this.createThreadChannelParams(input, options.channelMode ?? options.streamMode, options.replayCursor);
395
372
  const channel$ = ɵphoenixChannel$({
396
373
  socket$,
397
- topic,
374
+ topic: credentials.realtime.topic,
398
375
  params
399
376
  }).pipe((0, rxjs_operators.tap)(({ channel }) => {
400
377
  ownChannel = channel;
@@ -404,8 +381,14 @@ var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAg
404
381
  refCount: true
405
382
  }));
406
383
  const threadEvents$ = this.observeThreadEvents$(input.threadId, channel$, options).pipe((0, rxjs_operators.share)());
407
- const threadCompleted$ = threadEvents$.pipe((0, rxjs_operators.ignoreElements)(), (0, rxjs_operators.endWith)(null), (0, rxjs_operators.take)(1));
408
- return (0, rxjs.merge)(this.joinThreadChannel$(channel$), this.observeSocketHealth$(socket$).pipe((0, rxjs_operators.takeUntil)(threadCompleted$)), threadEvents$).pipe((0, rxjs_operators.finalize)(() => this.cleanupOwned(ownChannel, ownSocket)));
384
+ const replayComplete$ = this.observeControlEvent$(input.threadId, channel$, REPLAY_COMPLETE_EVENT).pipe((0, rxjs_operators.ignoreElements)(), (0, rxjs_operators.share)());
385
+ const streamIdle$ = this.observeControlEvent$(input.threadId, channel$, STREAM_IDLE_EVENT).pipe((0, rxjs_operators.shareReplay)({
386
+ bufferSize: 1,
387
+ refCount: true
388
+ }));
389
+ const streamIdleCompletion$ = options.streamMode === "connect" ? streamIdle$.pipe((0, rxjs_operators.take)(1)) : rxjs.EMPTY;
390
+ const terminal$ = (0, rxjs.merge)(threadEvents$.pipe((0, rxjs_operators.ignoreElements)(), (0, rxjs_operators.endWith)(null), (0, rxjs_operators.take)(1)), streamIdleCompletion$);
391
+ return (0, rxjs.merge)(this.joinThreadChannel$(channel$), this.observeSocketHealth$(socket$).pipe((0, rxjs_operators.takeUntil)(terminal$)), threadEvents$.pipe((0, rxjs_operators.takeUntil)(streamIdleCompletion$)), replayComplete$.pipe((0, rxjs_operators.takeUntil)(terminal$)), streamIdleCompletion$.pipe((0, rxjs_operators.ignoreElements)())).pipe((0, rxjs_operators.finalize)(() => this.cleanupOwned(ownChannel, ownSocket)));
409
392
  });
410
393
  }
411
394
  joinThreadChannel$(channel$) {
@@ -417,16 +400,23 @@ var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAg
417
400
  observeThreadEvents$(threadId, channel$, options) {
418
401
  return channel$.pipe((0, rxjs_operators.switchMap)(({ channel }) => this.observeChannelEvent$(channel, CLIENT_AG_UI_EVENT)), (0, rxjs_operators.tap)((payload) => {
419
402
  this.updateLastSeenEventId(threadId, payload);
420
- }), (0, rxjs_operators.mergeMap)((payload) => (0, rxjs.from)(this.createThreadNotifications(payload, options.completeOnRunError))), (0, rxjs.dematerialize)());
403
+ }), (0, rxjs_operators.mergeMap)((payload) => this.createThreadNotifications(payload, {
404
+ completeOnRunError: options.completeOnRunError,
405
+ completeOnRunFinished: options.streamMode === "run",
406
+ errorOnRunError: options.streamMode === "run"
407
+ })), (0, rxjs.dematerialize)());
408
+ }
409
+ observeControlEvent$(threadId, channel$, eventName) {
410
+ return channel$.pipe((0, rxjs_operators.switchMap)(({ channel }) => this.observeChannelEvent$(channel, eventName)), (0, rxjs_operators.tap)((payload) => this.updateLastSeenEventIdFromControl(threadId, payload)));
421
411
  }
422
412
  observeChannelEvent$(channel, eventName) {
423
413
  return ɵobservePhoenixEvent$(channel, eventName);
424
414
  }
425
- createThreadNotifications(payload, completeOnRunError) {
426
- if (payload.type === _ag_ui_client.EventType.RUN_FINISHED) return [rxjs.Notification.createNext(payload), rxjs.Notification.createComplete()];
415
+ createThreadNotifications(payload, options) {
416
+ if (payload.type === _ag_ui_client.EventType.RUN_FINISHED) return options.completeOnRunFinished ? [rxjs.Notification.createNext(payload), rxjs.Notification.createComplete()] : [rxjs.Notification.createNext(payload)];
427
417
  if (payload.type === _ag_ui_client.EventType.RUN_ERROR) {
428
418
  const errorMessage = payload.message ?? "Run error";
429
- return completeOnRunError ? [rxjs.Notification.createNext(payload), rxjs.Notification.createComplete()] : [rxjs.Notification.createNext(payload), rxjs.Notification.createError(new Error(errorMessage))];
419
+ return options.completeOnRunError ? [rxjs.Notification.createNext(payload), rxjs.Notification.createComplete()] : options.errorOnRunError ? [rxjs.Notification.createNext(payload), rxjs.Notification.createError(new Error(errorMessage))] : [rxjs.Notification.createNext(payload)];
430
420
  }
431
421
  return [rxjs.Notification.createNext(payload)];
432
422
  }
@@ -435,34 +425,31 @@ var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAg
435
425
  const origin = typeof window !== "undefined" && window.location ? window.location.origin : "http://localhost";
436
426
  return new URL(path, new URL(this.config.runtimeUrl, origin)).toString();
437
427
  }
438
- createThreadChannelDescriptor(input, streamMode, replayCursor) {
439
- const params = streamMode === "run" ? {
428
+ createThreadChannelParams(input, streamMode, replayCursor) {
429
+ return streamMode === "run" ? {
440
430
  stream_mode: "run",
441
431
  run_id: input.runId
442
432
  } : {
443
433
  stream_mode: "connect",
444
434
  last_seen_event_id: replayCursor === void 0 ? this.getReconnectCursor(input) : replayCursor
445
435
  };
446
- return {
447
- topic: `thread:${input.threadId}`,
448
- params
449
- };
450
436
  }
451
437
  getLastSeenEventId(threadId) {
452
438
  return this.sharedState.lastSeenEventIds.get(threadId) ?? null;
453
439
  }
454
440
  getReconnectCursor(input) {
455
- return this.hasLocalThreadMessages(input) ? this.getLastSeenEventId(input.threadId) : null;
441
+ return this.getLastSeenEventId(input.threadId);
456
442
  }
457
- hasLocalThreadMessages(input) {
458
- return Array.isArray(input.messages) && input.messages.length > 0;
443
+ clearReconnectCursor(threadId) {
444
+ this.sharedState.lastSeenEventIds.delete(threadId);
459
445
  }
460
446
  updateLastSeenEventId(threadId, payload) {
461
447
  const eventId = this.readEventId(payload);
462
448
  if (!eventId) return;
463
449
  this.sharedState.lastSeenEventIds.set(threadId, eventId);
464
450
  }
465
- setLastSeenEventId(threadId, eventId) {
451
+ updateLastSeenEventIdFromControl(threadId, payload) {
452
+ const eventId = this.readControlEventId(payload);
466
453
  if (!eventId) return;
467
454
  this.sharedState.lastSeenEventIds.set(threadId, eventId);
468
455
  }
@@ -472,6 +459,24 @@ var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAg
472
459
  const runnerEventId = metadata.cpki_event_id;
473
460
  return typeof runnerEventId === "string" ? runnerEventId : null;
474
461
  }
462
+ readControlEventId(payload) {
463
+ if (!payload || typeof payload !== "object") return null;
464
+ const latestEventId = payload.latestEventId;
465
+ return typeof latestEventId === "string" ? latestEventId : null;
466
+ }
467
+ applyCanonicalRunIdentity(input, credentials, options) {
468
+ this.threadId = credentials.threadId;
469
+ const runId = credentials.runId ?? (options.fallbackToInputRunId ? input.runId : null);
470
+ this.canonicalRunId = runId;
471
+ return {
472
+ ...input,
473
+ threadId: credentials.threadId,
474
+ ...runId === null ? {} : { runId }
475
+ };
476
+ }
477
+ isSocketReconnectExhaustedError(error) {
478
+ return error instanceof Error && error.message.includes("WebSocket connection failed after");
479
+ }
475
480
  };
476
481
 
477
482
  //#endregion