@truefoundry/assistant-ui-runtime 0.1.1 → 0.1.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.
@@ -16,11 +16,12 @@ import type { TrueFoundryGateway } from "truefoundry-gateway-sdk";
16
16
  import { ROOT_THREAD_ID } from "./constants.js";
17
17
  import {
18
18
  buildEditedUserMessageContent,
19
- buildSnapshotBeforeTurnIndex,
19
+ buildSnapshotBeforeTurn,
20
20
  computeGroupRootBaseline,
21
21
  extractTurnUserMessageContent,
22
+ prependOlderSessionHistory,
22
23
  projectSessionMessages,
23
- resolveGatewayBranchPreviousTurnId,
24
+ resolveGatewayBranchPreviousTurnIdForTurn,
24
25
  rootModelMessageIdsSinceBaseline,
25
26
  userMessageContentToText,
26
27
  type UserMessageContent,
@@ -69,6 +70,11 @@ export type UseTrueFoundryAgentMessagesOptions = {
69
70
  resolveConversationSessionId?: (remoteId: string) => Promise<string>;
70
71
  /** When set, turns bind to `/agents/sessions/{draftSessionId}/turns` after draft validation. */
71
72
  draftGateway?: TrueFoundryGateway;
73
+ /**
74
+ * Optional per-turn headers for createTurn. Invoked once per `sendTurn` after
75
+ * the session is resolved; return value is forwarded to `turn.execute`.
76
+ */
77
+ getTurnHeaders?: () => Promise<Record<string, string> | undefined>;
72
78
  };
73
79
 
74
80
  export type SendTurnOptions =
@@ -289,6 +295,7 @@ export function useTrueFoundryAgentMessages({
289
295
  initializeSession,
290
296
  resolveConversationSessionId,
291
297
  draftGateway,
298
+ getTurnHeaders,
292
299
  }: UseTrueFoundryAgentMessagesOptions) {
293
300
  const sessionOptions = useMemo<GetSessionOptions | undefined>(
294
301
  () =>
@@ -299,10 +306,12 @@ export function useTrueFoundryAgentMessages({
299
306
  const [snapshot, setSnapshot] = useState<SessionSnapshot>(createEmptySessionSnapshot);
300
307
  const [isRunning, setIsRunning] = useState(false);
301
308
  const [isLoading, setIsLoading] = useState(false);
309
+ const [isLoadingOlderHistory, setIsLoadingOlderHistory] = useState(false);
302
310
  const [loadRetryTrigger, setLoadRetryTrigger] = useState(0);
303
311
 
304
312
  const snapshotRef = useRef(snapshot);
305
313
  snapshotRef.current = snapshot;
314
+ const loadOlderInflightRef = useRef<Promise<void> | null>(null);
306
315
 
307
316
  const onErrorRef = useRef(onError);
308
317
  onErrorRef.current = onError;
@@ -310,6 +319,8 @@ export function useTrueFoundryAgentMessages({
310
319
  resolveConversationSessionIdRef.current = resolveConversationSessionId;
311
320
  const initializeSessionRef = useRef(initializeSession);
312
321
  initializeSessionRef.current = initializeSession;
322
+ const getTurnHeadersRef = useRef(getTurnHeaders);
323
+ getTurnHeadersRef.current = getTurnHeaders;
313
324
 
314
325
  const createdAtByMessageIdRef = useRef(new Map<string, Date>());
315
326
  const abortControllerRef = useRef<AbortController | null>(null);
@@ -477,9 +488,11 @@ export function useTrueFoundryAgentMessages({
477
488
  const generation = ++loadGenerationRef.current;
478
489
  ++streamGenerationRef.current;
479
490
  abortControllerRef.current?.abort();
491
+ loadOlderInflightRef.current = null;
480
492
  createdAtByMessageIdRef.current = new Map();
481
493
  setSnapshot(createEmptySessionSnapshot());
482
494
  setIsLoading(true);
495
+ setIsLoadingOlderHistory(false);
483
496
 
484
497
  try {
485
498
  const conversationSessionId = await resolveActiveSessionId(
@@ -564,6 +577,9 @@ export function useTrueFoundryAgentMessages({
564
577
  resolveConversationSessionIdRef.current,
565
578
  );
566
579
  const session = await getSession(client, conversationSessionId, sessionOptions);
580
+ const turnHeaders = await getTurnHeadersRef.current?.();
581
+ const streamHeaders =
582
+ turnHeaders != null ? { headers: turnHeaders } : {};
567
583
  const isContinuation =
568
584
  "inputs" in options ||
569
585
  ("resumeMcpAuth" in options && options.resumeMcpAuth === true);
@@ -639,7 +655,7 @@ export function useTrueFoundryAgentMessages({
639
655
  return streamTurnContent(
640
656
  session,
641
657
  snapshotRef.current.fold,
642
- { inputs: options.inputs },
658
+ { inputs: options.inputs, ...streamHeaders },
643
659
  signal,
644
660
  groupRootBaseline,
645
661
  );
@@ -648,7 +664,7 @@ export function useTrueFoundryAgentMessages({
648
664
  return streamTurnContent(
649
665
  session,
650
666
  snapshotRef.current.fold,
651
- { resumeMcpAuth: true },
667
+ { resumeMcpAuth: true, ...streamHeaders },
652
668
  signal,
653
669
  groupRootBaseline,
654
670
  );
@@ -661,6 +677,7 @@ export function useTrueFoundryAgentMessages({
661
677
  ...(options.previousTurnId !== undefined
662
678
  ? { previousTurnId: options.previousTurnId }
663
679
  : {}),
680
+ ...streamHeaders,
664
681
  },
665
682
  signal,
666
683
  groupRootBaseline,
@@ -782,8 +799,6 @@ export function useTrueFoundryAgentMessages({
782
799
  const committed = commitActiveStream(snapshotRef.current);
783
800
  setSnapshot(committed);
784
801
 
785
- const turnIndex = findTurnIndex(committed, turnId);
786
-
787
802
  await cancel();
788
803
 
789
804
  const conversationSessionId = await resolveActiveSessionId(
@@ -791,13 +806,13 @@ export function useTrueFoundryAgentMessages({
791
806
  resolveConversationSessionIdRef.current,
792
807
  );
793
808
  const session = await getSession(client, conversationSessionId, sessionOptions);
794
- const previousTurnId = await resolveGatewayBranchPreviousTurnId(
809
+ const previousTurnId = await resolveGatewayBranchPreviousTurnIdForTurn(
795
810
  session,
796
- turnIndex,
811
+ turnId,
797
812
  );
798
- const rewound = await buildSnapshotBeforeTurnIndex(
813
+ const rewound = await buildSnapshotBeforeTurn(
799
814
  session,
800
- turnIndex,
815
+ turnId,
801
816
  listEventsConcurrency,
802
817
  );
803
818
  createdAtByMessageIdRef.current = new Map();
@@ -855,10 +870,70 @@ export function useTrueFoundryAgentMessages({
855
870
  setLoadRetryTrigger((n) => n + 1);
856
871
  }, []);
857
872
 
873
+ const hasOlderHistory = snapshot.historyPagination?.hasOlder === true;
874
+
875
+ const loadOlderHistory = useCallback(async () => {
876
+ if (sessionId == null || isMain === false) {
877
+ return;
878
+ }
879
+ if (loadOlderInflightRef.current != null) {
880
+ return loadOlderInflightRef.current;
881
+ }
882
+
883
+ const current = snapshotRef.current;
884
+ if (current.historyPagination?.hasOlder !== true) {
885
+ return;
886
+ }
887
+ if (current.historyPagination.olderPageToken == null) {
888
+ return;
889
+ }
890
+
891
+ const generation = loadGenerationRef.current;
892
+ setIsLoadingOlderHistory(true);
893
+
894
+ const run = (async () => {
895
+ try {
896
+ const conversationSessionId = await resolveActiveSessionId(
897
+ sessionId,
898
+ resolveConversationSessionIdRef.current,
899
+ );
900
+ const session = await getSession(
901
+ client,
902
+ conversationSessionId,
903
+ sessionOptions,
904
+ );
905
+ const next = await prependOlderSessionHistory(
906
+ session,
907
+ snapshotRef.current,
908
+ );
909
+ if (generation !== loadGenerationRef.current) {
910
+ return;
911
+ }
912
+ setSnapshot(next);
913
+ } catch (error) {
914
+ if (generation === loadGenerationRef.current) {
915
+ onErrorRef.current?.(error);
916
+ }
917
+ throw error;
918
+ } finally {
919
+ if (generation === loadGenerationRef.current) {
920
+ setIsLoadingOlderHistory(false);
921
+ }
922
+ loadOlderInflightRef.current = null;
923
+ }
924
+ })();
925
+
926
+ loadOlderInflightRef.current = run;
927
+ return run;
928
+ }, [client, isMain, sessionId, sessionOptions]);
929
+
858
930
  return {
859
931
  messages,
860
932
  isRunning,
861
933
  isLoading,
934
+ isLoadingOlderHistory,
935
+ hasOlderHistory,
936
+ loadOlderHistory,
862
937
  retryLoad,
863
938
  sendTurn,
864
939
  cancel,
@@ -26,7 +26,10 @@ import {
26
26
  extractEditedText,
27
27
  parseTurnIdFromMessageId,
28
28
  } from "./convertTurnMessages.js";
29
- import { createDraftSessionBridge } from "./private/draftSessionBridge.js";
29
+ import {
30
+ createDraftSessionBridge,
31
+ DRAFT_SESSION_LAST_UPDATED_AT_HEADER,
32
+ } from "./private/draftSessionBridge.js";
30
33
  import { MCP_AUTH_RESUME_RUN_CUSTOM_KEY } from "./mcpAuth.js";
31
34
  import { createTrueFoundryDraftThreadListAdapter } from "./private/truefoundryDraftThreadListAdapter.js";
32
35
  import { trueFoundryExtras } from "./truefoundryExtras.js";
@@ -76,6 +79,20 @@ function useTrueFoundryAgentRuntimeImpl(
76
79
  onError,
77
80
  });
78
81
 
82
+ const takeTurnHeaderTimestampRef = useRef(draftSpec.takeTurnHeaderTimestamp);
83
+ takeTurnHeaderTimestampRef.current = draftSpec.takeTurnHeaderTimestamp;
84
+
85
+ const getTurnHeaders = useCallback(async () => {
86
+ if (agent.mode !== "draft") {
87
+ return undefined;
88
+ }
89
+ const updatedAt = await takeTurnHeaderTimestampRef.current();
90
+ if (updatedAt == null) {
91
+ return undefined;
92
+ }
93
+ return { [DRAFT_SESSION_LAST_UPDATED_AT_HEADER]: updatedAt };
94
+ }, [agent.mode]);
95
+
79
96
  const aui = useAui();
80
97
  const initializeSession = useCallback(
81
98
  () => aui.threadListItem().initialize(),
@@ -90,6 +107,9 @@ function useTrueFoundryAgentRuntimeImpl(
90
107
  messages,
91
108
  isRunning,
92
109
  isLoading,
110
+ isLoadingOlderHistory,
111
+ hasOlderHistory,
112
+ loadOlderHistory,
93
113
  sendTurn,
94
114
  cancel,
95
115
  respondToToolApproval,
@@ -106,6 +126,7 @@ function useTrueFoundryAgentRuntimeImpl(
106
126
  onError,
107
127
  initializeSession,
108
128
  draftGateway: agent.mode === "draft" ? gateway : undefined,
129
+ getTurnHeaders: agent.mode === "draft" ? getTurnHeaders : undefined,
109
130
  });
110
131
 
111
132
  if (agent.mode === "draft" && draftSpec.agentSpec != null) {
@@ -181,6 +202,9 @@ function useTrueFoundryAgentRuntimeImpl(
181
202
  onError?.(error);
182
203
  }),
183
204
  reload: retryLoad,
205
+ hasOlderHistory,
206
+ isLoadingOlderHistory,
207
+ loadOlderHistory,
184
208
  draft: draftExtras,
185
209
  }),
186
210
  unstable_enableToolInvocations: true,