@truefoundry/assistant-ui-runtime 0.1.3-rc.1 → 0.1.3-rc.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truefoundry/assistant-ui-runtime",
3
- "version": "0.1.3-rc.1",
3
+ "version": "0.1.3-rc.2",
4
4
  "description": "TrueFoundry Gateway agent runtime adapter for assistant-ui",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/src/streamTurn.ts CHANGED
@@ -53,6 +53,13 @@ export async function* streamTurnContent(
53
53
  options: StreamTurnOptions,
54
54
  abortSignal: AbortSignal,
55
55
  groupRootBaseline?: readonly string[],
56
+ /**
57
+ * Called once with the gateway-assigned turn ID as soon as it becomes
58
+ * available (after the first `turn.created` SSE event). Use this to
59
+ * reconcile the locally-generated optimistic ID with the real gateway ID
60
+ * so that edit/retry can find the turn in `buildSnapshotBeforeTurn`.
61
+ */
62
+ onTurnIdAvailable?: (turnId: string) => void,
56
63
  ): AsyncGenerator<TurnStreamUpdate> {
57
64
  const previousTurnId =
58
65
  options.previousTurnId === null
@@ -69,7 +76,8 @@ export async function* streamTurnContent(
69
76
  }
70
77
 
71
78
  try {
72
- yield* streamTurnEvents(
79
+ let turnIdNotified = false;
80
+ for await (const update of streamTurnEvents(
73
81
  turn.execute(
74
82
  { stream: true },
75
83
  {
@@ -79,7 +87,20 @@ export async function* streamTurnContent(
79
87
  ),
80
88
  foldState,
81
89
  groupRootBaseline,
82
- );
90
+ )) {
91
+ // After the first `turn.created` event, `turn.id` is set.
92
+ // Notify BEFORE yielding so the caller can update its tracking
93
+ // before the snapshot is written with the stream update.
94
+ if (!turnIdNotified && turn.id != null) {
95
+ onTurnIdAvailable?.(turn.id);
96
+ turnIdNotified = true;
97
+ }
98
+ yield update;
99
+ }
100
+ // Handle streams that complete without yielding any content.
101
+ if (!turnIdNotified && turn.id != null) {
102
+ onTurnIdAvailable?.(turn.id);
103
+ }
83
104
  } catch (error) {
84
105
  if (error instanceof Error && error.name === "AbortError") {
85
106
  return;
@@ -353,7 +353,14 @@ export function useTrueFoundryAgentMessages({
353
353
  const runStream = useCallback(
354
354
  (
355
355
  createStream: (signal: AbortSignal) => AsyncGenerator<TurnStreamUpdate>,
356
- turnId: string,
356
+ /**
357
+ * A mutable ref whose `.current` is the turn ID to use for
358
+ * `activeStream.turnId`. Callers that capture the gateway turn ID
359
+ * via `onTurnIdAvailable` update this ref in-place so that both the
360
+ * pending-update flush and `commitActiveStream` always see the real
361
+ * gateway ID rather than the locally-generated optimistic one.
362
+ */
363
+ turnIdRef: { current: string },
357
364
  isContinuation: boolean,
358
365
  ): Promise<void> => {
359
366
  const streamGeneration = ++streamGenerationRef.current;
@@ -368,7 +375,6 @@ export function useTrueFoundryAgentMessages({
368
375
  // message tree (UI hang). The buffer belongs to this stream only.
369
376
  let pendingStreamUpdate: {
370
377
  update: TurnStreamUpdate;
371
- turnId: string;
372
378
  isContinuation: boolean;
373
379
  } | null = null;
374
380
  let streamUpdateRaf: number | null = null;
@@ -383,12 +389,14 @@ export function useTrueFoundryAgentMessages({
383
389
  ) {
384
390
  return;
385
391
  }
386
- const { update, turnId: pendingTurnId, isContinuation: pendingIsContinuation } =
387
- pending;
392
+ const { update, isContinuation: pendingIsContinuation } = pending;
388
393
  setSnapshot((prev) =>
389
394
  replaceSessionSnapshot(prev, {
390
395
  activeStream: {
391
- turnId: pendingTurnId,
396
+ // Read from the ref so we always use the latest ID,
397
+ // including any gateway ID that arrived after the RAf
398
+ // was scheduled.
399
+ turnId: turnIdRef.current,
392
400
  update,
393
401
  isContinuation: pendingIsContinuation,
394
402
  },
@@ -397,7 +405,7 @@ export function useTrueFoundryAgentMessages({
397
405
  };
398
406
 
399
407
  const applyStreamUpdate = (update: TurnStreamUpdate) => {
400
- pendingStreamUpdate = { update, turnId, isContinuation };
408
+ pendingStreamUpdate = { update, isContinuation };
401
409
  if (streamUpdateRaf == null) {
402
410
  streamUpdateRaf = requestAnimationFrame(flushPendingStreamUpdate);
403
411
  }
@@ -540,7 +548,7 @@ export function useTrueFoundryAgentMessages({
540
548
  undefined,
541
549
  loadedSnapshot.groupRootBaseline,
542
550
  ),
543
- turn.id,
551
+ { current: turn.id },
544
552
  isContinuation,
545
553
  ).catch(() => undefined);
546
554
  }
@@ -589,6 +597,11 @@ export function useTrueFoundryAgentMessages({
589
597
  ? continuationTurnId
590
598
  : generateId();
591
599
 
600
+ // Mutable ref so runStream always reads the latest ID. For new
601
+ // user-message turns the local `generateId()` value is replaced
602
+ // with the gateway-assigned ID once the first SSE event arrives.
603
+ const turnIdRef = { current: turnId };
604
+
592
605
  if ("inputs" in options) {
593
606
  applyUserToolResponsesToFold(
594
607
  snapshotRef.current.fold,
@@ -681,9 +694,26 @@ export function useTrueFoundryAgentMessages({
681
694
  },
682
695
  signal,
683
696
  groupRootBaseline,
697
+ // Rename the optimistic local ID to the gateway turn ID
698
+ // so that edit/retry can resolve the turn via the gateway.
699
+ (gatewayTurnId) => {
700
+ const oldId = turnIdRef.current;
701
+ if (gatewayTurnId === oldId) return;
702
+ turnIdRef.current = gatewayTurnId;
703
+ // Rename in the ref immediately so any synchronous read
704
+ // (e.g. commitActiveStream) sees the correct ID.
705
+ const renamePendingUser = (prev: SessionSnapshot): SessionSnapshot => {
706
+ if (prev.pendingUser?.turnId !== oldId) return prev;
707
+ return replaceSessionSnapshot(prev, {
708
+ pendingUser: { ...prev.pendingUser, turnId: gatewayTurnId },
709
+ });
710
+ };
711
+ snapshotRef.current = renamePendingUser(snapshotRef.current);
712
+ setSnapshot(renamePendingUser);
713
+ },
684
714
  );
685
715
  },
686
- turnId,
716
+ turnIdRef,
687
717
  isContinuation,
688
718
  );
689
719
  },
@@ -784,7 +814,7 @@ export function useTrueFoundryAgentMessages({
784
814
  undefined,
785
815
  snapshotRef.current.groupRootBaseline,
786
816
  ),
787
- turn.id,
817
+ { current: turn.id },
788
818
  true,
789
819
  );
790
820
  }, [runStream]);