@tanstack/ai-client 0.28.0 → 0.29.0

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.
@@ -125,6 +125,8 @@ export declare class ChatClient<TTools extends ReadonlyArray<AnyClientTool> = an
125
125
  private processingResolve;
126
126
  private errorReportedGeneration;
127
127
  private streamGeneration;
128
+ private continuationGeneration;
129
+ private streamContinuationGeneration;
128
130
  private continuationSkipped;
129
131
  private draining;
130
132
  private sessionGenerating;
@@ -239,6 +241,7 @@ export declare class ChatClient<TTools extends ReadonlyArray<AnyClientTool> = an
239
241
  retryInterrupts(): void;
240
242
  /** Unsafe low-level resume escape hatch. Prefer bound interrupt methods. */
241
243
  resumeInterruptsUnsafe(resume: Array<RunAgentResumeItem>, state?: ChatResumeState): Promise<boolean>;
244
+ private resumeInterruptsUnsafeForGeneration;
242
245
  /** @deprecated Use bound interrupt methods or resumeInterruptsUnsafe(). */
243
246
  resumeInterrupts(resume: Array<RunAgentResumeItem>, state?: ChatResumeState): Promise<boolean>;
244
247
  private submitInterruptBatch;
@@ -1,5 +1,6 @@
1
1
  import { prepareResolvedByokHeaders, resolveByokProviderId } from "./byok/resolve.js";
2
2
  import { createNoOpChatDevtoolsBridge } from "./devtools-noop.js";
3
+ import { normalizeMessagesDates } from "./message-date-normalizer.js";
3
4
  import { fetcherToConnectionAdapter, getChunkRunId as getChunkRunId$1, normalizeConnectionAdapter } from "./connection-adapters.js";
4
5
  import { ChatPersistor } from "./client-persistor.js";
5
6
  import { ClearedStreamTracker } from "./cleared-stream-tracker.js";
@@ -205,6 +206,8 @@ var ChatClient = class {
205
206
  processingResolve = null;
206
207
  errorReportedGeneration = null;
207
208
  streamGeneration = 0;
209
+ continuationGeneration = 0;
210
+ streamContinuationGeneration = 0;
208
211
  continuationSkipped = false;
209
212
  draining = false;
210
213
  sessionGenerating = false;
@@ -333,6 +336,7 @@ var ChatClient = class {
333
336
  const clientTool = (this.activeClientTools ?? this.clientToolsRef.current).get(args.toolName);
334
337
  const executeFunc = clientTool?.execute;
335
338
  if (executeFunc) {
339
+ const continuationGeneration = this.continuationGeneration;
336
340
  const runEventContext = this.devtoolsBridge.getCurrentRunEventContext();
337
341
  const executionPromise = (async () => {
338
342
  try {
@@ -347,7 +351,7 @@ var ChatClient = class {
347
351
  tool: args.toolName,
348
352
  output,
349
353
  state: "output-available"
350
- }, clientTool, runEventContext);
354
+ }, clientTool, continuationGeneration, runEventContext);
351
355
  } catch (error) {
352
356
  await this.addToolResultForClientTool({
353
357
  toolCallId: args.toolCallId,
@@ -355,7 +359,7 @@ var ChatClient = class {
355
359
  output: null,
356
360
  state: "output-error",
357
361
  errorText: error.message
358
- }, clientTool, runEventContext);
362
+ }, clientTool, continuationGeneration, runEventContext);
359
363
  } finally {
360
364
  this.pendingToolExecutions.delete(args.toolCallId);
361
365
  }
@@ -491,7 +495,7 @@ var ChatClient = class {
491
495
  }
492
496
  if (this.disposed || !this.tailing) return;
493
497
  if (this.isLoading || this.abortController) return;
494
- if (result.messages.length > 0) this.processor.setMessages(result.messages);
498
+ if (result.messages.length > 0) this.processor.setMessages(normalizeMessagesDates(result.messages));
495
499
  if (result.interrupts && result.interrupts.pending.length > 0) this.applyResumeSnapshot({
496
500
  resumeState: {
497
501
  threadId: this.threadId,
@@ -649,10 +653,14 @@ var ChatClient = class {
649
653
  resumeInterruptsUnsafe(resume, state) {
650
654
  const target = state ?? this.lastResume;
651
655
  if (!target) return Promise.resolve(false);
656
+ return this.resumeInterruptsUnsafeForGeneration(resume, target, this.continuationGeneration);
657
+ }
658
+ resumeInterruptsUnsafeForGeneration(resume, target, continuationGeneration) {
659
+ if (continuationGeneration !== this.continuationGeneration) return Promise.resolve(false);
652
660
  if (this.isLoading) return new Promise((resolve, reject) => {
653
661
  this.queuePostStreamAction(async () => {
654
662
  try {
655
- resolve(await this.resumeInterruptsUnsafe(resume, target));
663
+ resolve(await this.resumeInterruptsUnsafeForGeneration(resume, target, continuationGeneration));
656
664
  } catch (error) {
657
665
  reject(error);
658
666
  }
@@ -668,6 +676,7 @@ var ChatClient = class {
668
676
  return this.resumeInterruptsUnsafe(resume, state);
669
677
  }
670
678
  async submitInterruptBatch(submission) {
679
+ const continuationGeneration = this.continuationGeneration;
671
680
  this.activeInterruptSubmission = submission;
672
681
  this.interruptSubmissionFailure = void 0;
673
682
  for (const resolution of submission.resolutions) {
@@ -676,12 +685,13 @@ var ChatClient = class {
676
685
  const approvalId = resolution.interruptId;
677
686
  this.processor.addToolApprovalResponse(approvalId, approved);
678
687
  }
679
- const resumed = await this.resumeInterruptsUnsafe([...submission.resolutions], {
688
+ const resumed = await this.resumeInterruptsUnsafeForGeneration([...submission.resolutions], {
680
689
  threadId: submission.threadId,
681
690
  runId: submission.interruptedRunId
682
- }).finally(() => {
683
- this.activeInterruptSubmission = void 0;
691
+ }, continuationGeneration).finally(() => {
692
+ if (this.activeInterruptSubmission === submission) this.activeInterruptSubmission = void 0;
684
693
  });
694
+ if (continuationGeneration !== this.continuationGeneration) return;
685
695
  const failure = this.takeInterruptSubmissionFailure();
686
696
  if (failure !== void 0) throw { errors: failure.errors };
687
697
  if (!resumed) throw new Error("Interrupt continuation could not be started.");
@@ -906,6 +916,7 @@ var ChatClient = class {
906
916
  threadId: this.threadId,
907
917
  runId
908
918
  };
919
+ this.streamContinuationGeneration = this.continuationGeneration;
909
920
  this.setIsLoading(true);
910
921
  this.setStatus("streaming");
911
922
  (async () => {
@@ -1224,6 +1235,7 @@ var ChatClient = class {
1224
1235
  async streamResponse() {
1225
1236
  if (this.isLoading) return false;
1226
1237
  const generation = ++this.streamGeneration;
1238
+ this.streamContinuationGeneration = this.continuationGeneration;
1227
1239
  const resumeThreadId = this.pendingResumeThreadId;
1228
1240
  const resumeParentRunId = this.pendingResumeParentRunId;
1229
1241
  const resumeItems = this.pendingResumeItems;
@@ -1390,9 +1402,13 @@ var ChatClient = class {
1390
1402
  * Stop the current stream
1391
1403
  */
1392
1404
  stop() {
1405
+ this.continuationGeneration++;
1393
1406
  const hadLocalStream = this.abortController !== null;
1394
1407
  this.cancelInFlightStream({ setReadyStatus: true });
1395
1408
  this.discardPendingSends();
1409
+ this.lastResume = null;
1410
+ this.activeInterruptSubmission = void 0;
1411
+ this.interruptManager.reset();
1396
1412
  if (hadLocalStream) this.resetSessionGenerating();
1397
1413
  this.events.stopped();
1398
1414
  }
@@ -1427,9 +1443,9 @@ var ChatClient = class {
1427
1443
  */
1428
1444
  async addToolResult(result) {
1429
1445
  const clientTool = this.clientToolsRef.current.get(result.tool);
1430
- await this.addToolResultForClientTool(result, clientTool);
1446
+ await this.addToolResultForClientTool(result, clientTool, this.streamContinuationGeneration);
1431
1447
  }
1432
- async addToolResultForClientTool(result, clientTool, context) {
1448
+ async addToolResultForClientTool(result, clientTool, continuationGeneration, context) {
1433
1449
  if (clientTool && result.state !== "output-error") try {
1434
1450
  result = {
1435
1451
  ...result,
@@ -1444,11 +1460,12 @@ var ChatClient = class {
1444
1460
  };
1445
1461
  }
1446
1462
  this.events.toolResultAdded(result.toolCallId, result.tool, result.output, result.state || "output-available", context);
1463
+ if (continuationGeneration !== this.continuationGeneration) return;
1447
1464
  this.processor.addToolResult(result.toolCallId, result.output, result.state === "output-error" ? result.errorText || "Tool execution failed" : void 0);
1448
1465
  this.devtoolsBridge.emitSnapshot();
1449
1466
  if (this.interruptManager.resolveClientToolOutput(result.toolCallId, result.state === "output-error" ? { error: result.errorText || "Tool execution failed" } : result.output)) return;
1450
1467
  if (this.isLoading) {
1451
- this.queuePostStreamAction(() => this.checkForContinuation());
1468
+ this.queuePostStreamAction(() => continuationGeneration === this.continuationGeneration ? this.checkForContinuation() : Promise.resolve());
1452
1469
  return;
1453
1470
  }
1454
1471
  await this.checkForContinuation();
@@ -1486,7 +1503,11 @@ var ChatClient = class {
1486
1503
  * Queue an action to be executed after the current stream ends
1487
1504
  */
1488
1505
  queuePostStreamAction(action) {
1489
- this.postStreamActions.push(action);
1506
+ const continuationGeneration = this.continuationGeneration;
1507
+ this.postStreamActions.push(async () => {
1508
+ if (continuationGeneration !== this.continuationGeneration) return;
1509
+ await action();
1510
+ });
1490
1511
  }
1491
1512
  /**
1492
1513
  * Drain and execute all queued post-stream actions
@@ -1505,6 +1526,7 @@ var ChatClient = class {
1505
1526
  * Check if we should continue the flow and do so if needed
1506
1527
  */
1507
1528
  async checkForContinuation() {
1529
+ if (this.streamContinuationGeneration !== this.continuationGeneration) return;
1508
1530
  if (this.hasPendingInterrupts()) return;
1509
1531
  if (this.continuationPending || this.isLoading) {
1510
1532
  this.continuationSkipped = true;