@truefoundry/assistant-ui-runtime 0.1.4 → 0.1.6-rc.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.
Files changed (57) hide show
  1. package/README.md +374 -190
  2. package/dist/index.d.ts +32 -29
  3. package/dist/index.js +334 -241
  4. package/dist/index.js.map +1 -1
  5. package/dist/plugins/truefoundry-agent-server-adapter/index.d.ts +30 -0
  6. package/dist/plugins/truefoundry-agent-server-adapter/index.js +198 -0
  7. package/dist/plugins/truefoundry-agent-server-adapter/index.js.map +1 -0
  8. package/dist/types-VUBzoJT2.d.ts +462 -0
  9. package/package.json +12 -4
  10. package/src/askUserQuestion.ts +3 -3
  11. package/src/collectPending.ts +1 -1
  12. package/src/convertTurnMessages.test.ts +141 -196
  13. package/src/convertTurnMessages.ts +131 -77
  14. package/src/createSubAgent.ts +1 -1
  15. package/src/draftAgentConfig.test.ts +26 -29
  16. package/src/extractTurnUserText.ts +1 -1
  17. package/src/foldPeerThreads.test.ts +1 -1
  18. package/src/foldPeerThreads.ts +3 -2
  19. package/src/index.ts +39 -4
  20. package/src/listPages.ts +21 -0
  21. package/src/loadSessionSnapshot.test.ts +9 -8
  22. package/src/loadSessionSnapshot.ts +9 -14
  23. package/src/mcpAuth.ts +6 -3
  24. package/src/messageCustomMetadata.ts +1 -1
  25. package/src/modelMessageContent.ts +1 -1
  26. package/src/modelMessageImageContent.test.ts +1 -1
  27. package/src/modelMessageImageContent.ts +7 -6
  28. package/src/plugins/truefoundry-agent-server-adapter/index.ts +285 -0
  29. package/src/private/agentSpec.ts +8 -3
  30. package/src/private/draftSessionBridge.ts +14 -13
  31. package/src/private/truefoundryDraftThreadListAdapter.test.ts +44 -49
  32. package/src/private/truefoundryDraftThreadListAdapter.ts +22 -16
  33. package/src/requiredActionInputs.ts +1 -1
  34. package/src/requiredActionsFromActiveUpdate.test.ts +1 -1
  35. package/src/server/eventUtils.ts +120 -0
  36. package/src/server/events.ts +246 -0
  37. package/src/server/index.ts +66 -0
  38. package/src/server/types.ts +313 -0
  39. package/src/sessionSnapshot.ts +1 -1
  40. package/src/sessions.ts +5 -21
  41. package/src/streamTurn.test.ts +175 -158
  42. package/src/streamTurn.ts +51 -57
  43. package/src/toolApproval.ts +4 -4
  44. package/src/toolResponse.ts +4 -4
  45. package/src/truefoundryExtras.ts +1 -1
  46. package/src/truefoundryOwnedSessionsThreadListAdapter.test.ts +26 -29
  47. package/src/truefoundryOwnedSessionsThreadListAdapter.ts +18 -23
  48. package/src/truefoundryThreadListAdapter.test.ts +16 -18
  49. package/src/truefoundryThreadListAdapter.ts +9 -9
  50. package/src/turnEventHelpers.ts +1 -1
  51. package/src/types.ts +2 -16
  52. package/src/useTrueFoundryAgentMessages.test.tsx +38 -70
  53. package/src/useTrueFoundryAgentMessages.ts +33 -45
  54. package/src/useTrueFoundryAgentRuntime.ts +11 -28
  55. package/src/private/bindDraftAgentSession.test.ts +0 -54
  56. package/src/private/bindDraftAgentSession.ts +0 -28
  57. package/src/private/getGatewayFromPrivateClient.ts +0 -13
@@ -2,8 +2,7 @@
2
2
  import type { ThreadMessage } from "@assistant-ui/core";
3
3
  import { act, renderHook, waitFor } from "@testing-library/react";
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
- import type { AgentSessionClient, Turn } from "truefoundry-gateway-sdk/agents";
6
- import type { PrivateAgentSessionClient } from "truefoundry-gateway-sdk/agents/private";
5
+ import type { AgentChatServer, Turn } from "./server/index.js";
7
6
 
8
7
  import { ROOT_THREAD_ID } from "./constants.js";
9
8
  import { collectPendingToolResponses } from "./collectPending.js";
@@ -18,7 +17,6 @@ import {
18
17
  replaceSessionSnapshot,
19
18
  type SessionSnapshot,
20
19
  } from "./sessionSnapshot.js";
21
- import { getSession } from "./sessions.js";
22
20
  import { resumeTurnStream, streamTurnContent } from "./streamTurn.js";
23
21
  import {
24
22
  messageHasPendingApprovals,
@@ -32,10 +30,6 @@ import {
32
30
  } from "./toolResponse.js";
33
31
  import { useTrueFoundryAgentMessages } from "./useTrueFoundryAgentMessages.js";
34
32
 
35
- vi.mock("./sessions.js", () => ({
36
- getSession: vi.fn(),
37
- }));
38
-
39
33
  vi.mock("./loadSessionSnapshot.js", () => ({
40
34
  loadSessionSnapshot: vi.fn(),
41
35
  }));
@@ -50,7 +44,9 @@ vi.mock("./convertTurnMessages.js", async (importOriginal) => {
50
44
  return actual;
51
45
  });
52
46
 
53
- const mockClient = {} as AgentSessionClient;
47
+ const mockServer = {
48
+ cancelSession: vi.fn().mockResolvedValue(undefined),
49
+ } as unknown as AgentChatServer;
54
50
 
55
51
  function snapshotWithAssistantMessage(
56
52
  message: Extract<ThreadMessage, { role: "assistant" }>,
@@ -282,7 +278,7 @@ async function* singleUpdateStream() {
282
278
  describe("useTrueFoundryAgentMessages", () => {
283
279
  beforeEach(() => {
284
280
  vi.clearAllMocks();
285
- vi.mocked(getSession).mockResolvedValue({} as never);
281
+ vi.mocked(mockServer.cancelSession).mockResolvedValue(undefined);
286
282
  vi.mocked(loadSessionSnapshot).mockResolvedValue(createEmptySessionSnapshot());
287
283
  vi.mocked(streamTurnContent).mockReturnValue(singleUpdateStream());
288
284
  vi.mocked(resumeTurnStream).mockReturnValue(singleUpdateStream());
@@ -294,12 +290,11 @@ describe("useTrueFoundryAgentMessages", () => {
294
290
 
295
291
  it("clears messages when sessionId is undefined", async () => {
296
292
  const { result } = renderHook(() =>
297
- useTrueFoundryAgentMessages({ client: mockClient, sessionId: undefined }),
293
+ useTrueFoundryAgentMessages({ server: mockServer, sessionId: undefined }),
298
294
  );
299
295
 
300
296
  await waitFor(() => expect(result.current.isLoading).toBe(false));
301
297
  expect(result.current.messages).toEqual([]);
302
- expect(getSession).not.toHaveBeenCalled();
303
298
  expect(loadSessionSnapshot).not.toHaveBeenCalled();
304
299
  });
305
300
 
@@ -311,7 +306,7 @@ describe("useTrueFoundryAgentMessages", () => {
311
306
 
312
307
  const { result } = renderHook(() =>
313
308
  useTrueFoundryAgentMessages({
314
- client: mockClient,
309
+ server: mockServer,
315
310
  sessionId: undefined,
316
311
  initializeSession,
317
312
  }),
@@ -324,7 +319,6 @@ describe("useTrueFoundryAgentMessages", () => {
324
319
  });
325
320
 
326
321
  expect(initializeSession).toHaveBeenCalledOnce();
327
- expect(getSession).toHaveBeenCalledWith(mockClient, "session-new");
328
322
  expect(streamTurnContent).toHaveBeenCalled();
329
323
  expect(loadSessionSnapshot).not.toHaveBeenCalled();
330
324
  });
@@ -339,7 +333,7 @@ describe("useTrueFoundryAgentMessages", () => {
339
333
 
340
334
  const { result } = renderHook(() =>
341
335
  useTrueFoundryAgentMessages({
342
- client: mockClient,
336
+ server: mockServer,
343
337
  sessionId: "session-1",
344
338
  getTurnHeaders,
345
339
  }),
@@ -353,7 +347,8 @@ describe("useTrueFoundryAgentMessages", () => {
353
347
 
354
348
  expect(getTurnHeaders).toHaveBeenCalledTimes(1);
355
349
  expect(streamTurnContent).toHaveBeenCalledWith(
356
- expect.anything(),
350
+ mockServer,
351
+ "session-1",
357
352
  expect.any(PeerThreadFoldState),
358
353
  {
359
354
  userMessage: "first",
@@ -371,7 +366,8 @@ describe("useTrueFoundryAgentMessages", () => {
371
366
 
372
367
  expect(getTurnHeaders).toHaveBeenCalledTimes(2);
373
368
  expect(streamTurnContent).toHaveBeenLastCalledWith(
374
- expect.anything(),
369
+ mockServer,
370
+ "session-1",
375
371
  expect.any(PeerThreadFoldState),
376
372
  { userMessage: "second" },
377
373
  expect.any(AbortSignal),
@@ -385,11 +381,11 @@ describe("useTrueFoundryAgentMessages", () => {
385
381
  );
386
382
 
387
383
  const { result } = renderHook(() =>
388
- useTrueFoundryAgentMessages({ client: mockClient, sessionId: "session-1" }),
384
+ useTrueFoundryAgentMessages({ server: mockServer, sessionId: "session-1" }),
389
385
  );
390
386
 
391
387
  await waitFor(() => expect(result.current.isLoading).toBe(false));
392
- expect(loadSessionSnapshot).toHaveBeenCalledWith(mockClient, "session-1");
388
+ expect(loadSessionSnapshot).toHaveBeenCalledWith(mockServer, "session-1");
393
389
  expect(result.current.messages).toHaveLength(1);
394
390
  expect(result.current.messages[0]?.role).toBe("user");
395
391
  });
@@ -412,7 +408,7 @@ describe("useTrueFoundryAgentMessages", () => {
412
408
  );
413
409
 
414
410
  const { result } = renderHook(() =>
415
- useTrueFoundryAgentMessages({ client: mockClient, sessionId: "session-1" }),
411
+ useTrueFoundryAgentMessages({ server: mockServer, sessionId: "session-1" }),
416
412
  );
417
413
 
418
414
  await waitFor(() => expect(result.current.isRunning).toBe(false));
@@ -455,7 +451,7 @@ describe("useTrueFoundryAgentMessages", () => {
455
451
  );
456
452
 
457
453
  const { result } = renderHook(() =>
458
- useTrueFoundryAgentMessages({ client: mockClient, sessionId: "session-1" }),
454
+ useTrueFoundryAgentMessages({ server: mockServer, sessionId: "session-1" }),
459
455
  );
460
456
 
461
457
  await waitFor(() => expect(result.current.isLoading).toBe(false));
@@ -474,7 +470,7 @@ describe("useTrueFoundryAgentMessages", () => {
474
470
 
475
471
  it("sendTurn appends a user message and streams the assistant reply", async () => {
476
472
  const { result } = renderHook(() =>
477
- useTrueFoundryAgentMessages({ client: mockClient, sessionId: "session-1" }),
473
+ useTrueFoundryAgentMessages({ server: mockServer, sessionId: "session-1" }),
478
474
  );
479
475
  await waitFor(() => expect(result.current.isLoading).toBe(false));
480
476
 
@@ -526,12 +522,6 @@ describe("useTrueFoundryAgentMessages", () => {
526
522
  }),
527
523
  fold,
528
524
  });
529
- vi.mocked(getSession).mockResolvedValue({
530
- cancel: vi.fn().mockResolvedValue(undefined),
531
- listTurns: vi.fn(async function* () {}),
532
- listEvents: vi.fn(async function* () {}),
533
- } as never);
534
-
535
525
  let releaseStream: (() => void) | undefined;
536
526
  vi.mocked(streamTurnContent).mockReturnValue(
537
527
  (async function* () {
@@ -545,7 +535,7 @@ describe("useTrueFoundryAgentMessages", () => {
545
535
  );
546
536
 
547
537
  const { result } = renderHook(() =>
548
- useTrueFoundryAgentMessages({ client: mockClient, sessionId: "session-1" }),
538
+ useTrueFoundryAgentMessages({ server: mockServer, sessionId: "session-1" }),
549
539
  );
550
540
  await waitFor(() => expect(result.current.isLoading).toBe(false));
551
541
  expect(result.current.messages.map((m) => m.role)).toEqual([
@@ -635,7 +625,7 @@ describe("useTrueFoundryAgentMessages", () => {
635
625
  );
636
626
 
637
627
  const { result } = renderHook(() =>
638
- useTrueFoundryAgentMessages({ client: mockClient, sessionId: "session-1" }),
628
+ useTrueFoundryAgentMessages({ server: mockServer, sessionId: "session-1" }),
639
629
  );
640
630
  await waitFor(() => expect(result.current.isLoading).toBe(false));
641
631
 
@@ -680,7 +670,7 @@ describe("useTrueFoundryAgentMessages", () => {
680
670
  );
681
671
 
682
672
  const { result } = renderHook(() =>
683
- useTrueFoundryAgentMessages({ client: mockClient, sessionId: "session-1" }),
673
+ useTrueFoundryAgentMessages({ server: mockServer, sessionId: "session-1" }),
684
674
  );
685
675
  await waitFor(() => expect(result.current.isLoading).toBe(false));
686
676
 
@@ -700,7 +690,7 @@ describe("useTrueFoundryAgentMessages", () => {
700
690
  );
701
691
 
702
692
  const { result } = renderHook(() =>
703
- useTrueFoundryAgentMessages({ client: mockClient, sessionId: "session-1" }),
693
+ useTrueFoundryAgentMessages({ server: mockServer, sessionId: "session-1" }),
704
694
  );
705
695
  await waitFor(() => expect(result.current.messages).toHaveLength(1));
706
696
 
@@ -718,7 +708,8 @@ describe("useTrueFoundryAgentMessages", () => {
718
708
  });
719
709
 
720
710
  expect(streamTurnContent).toHaveBeenCalledWith(
721
- expect.anything(),
711
+ mockServer,
712
+ "session-1",
722
713
  expect.any(PeerThreadFoldState),
723
714
  {
724
715
  inputs: [
@@ -743,7 +734,7 @@ describe("useTrueFoundryAgentMessages", () => {
743
734
  );
744
735
 
745
736
  const { result } = renderHook(() =>
746
- useTrueFoundryAgentMessages({ client: mockClient, sessionId: "session-1" }),
737
+ useTrueFoundryAgentMessages({ server: mockServer, sessionId: "session-1" }),
747
738
  );
748
739
  await waitFor(() => expect(result.current.messages).toHaveLength(1));
749
740
 
@@ -777,7 +768,7 @@ describe("useTrueFoundryAgentMessages", () => {
777
768
  );
778
769
 
779
770
  const { result } = renderHook(() =>
780
- useTrueFoundryAgentMessages({ client: mockClient, sessionId: "session-1" }),
771
+ useTrueFoundryAgentMessages({ server: mockServer, sessionId: "session-1" }),
781
772
  );
782
773
  await waitFor(() => expect(result.current.messages).toHaveLength(1));
783
774
 
@@ -799,7 +790,8 @@ describe("useTrueFoundryAgentMessages", () => {
799
790
 
800
791
  await waitFor(() => expect(streamTurnContent).toHaveBeenCalled());
801
792
  expect(streamTurnContent).toHaveBeenCalledWith(
802
- expect.anything(),
793
+ mockServer,
794
+ "session-1",
803
795
  expect.any(PeerThreadFoldState),
804
796
  {
805
797
  inputs: [
@@ -836,7 +828,7 @@ describe("useTrueFoundryAgentMessages", () => {
836
828
  );
837
829
 
838
830
  const { result } = renderHook(() =>
839
- useTrueFoundryAgentMessages({ client: mockClient, sessionId: "session-1" }),
831
+ useTrueFoundryAgentMessages({ server: mockServer, sessionId: "session-1" }),
840
832
  );
841
833
  await waitFor(() => expect(result.current.messages).toHaveLength(2));
842
834
  expect(collectPendingToolResponses(result.current.messages)).toHaveLength(1);
@@ -858,13 +850,13 @@ describe("useTrueFoundryAgentMessages", () => {
858
850
  });
859
851
 
860
852
  describe("batched resume invariant", () => {
861
- it("issues exactly one prepareTurn input batch across root and sub-agent threads", async () => {
853
+ it("issues exactly one prepareAndExecuteTurn input batch across root and sub-agent threads", async () => {
862
854
  vi.mocked(loadSessionSnapshot).mockResolvedValue(
863
855
  snapshotWithAssistantMessage(assistantMessageWithMultiThreadPendingActions()),
864
856
  );
865
857
 
866
858
  const { result } = renderHook(() =>
867
- useTrueFoundryAgentMessages({ client: mockClient, sessionId: "session-1" }),
859
+ useTrueFoundryAgentMessages({ server: mockServer, sessionId: "session-1" }),
868
860
  );
869
861
  await waitFor(() => expect(result.current.messages).toHaveLength(1));
870
862
 
@@ -885,7 +877,8 @@ describe("useTrueFoundryAgentMessages", () => {
885
877
 
886
878
  await waitFor(() => expect(streamTurnContent).toHaveBeenCalledTimes(1));
887
879
  expect(streamTurnContent).toHaveBeenCalledWith(
888
- expect.anything(),
880
+ mockServer,
881
+ "session-1",
889
882
  expect.any(PeerThreadFoldState),
890
883
  {
891
884
  inputs: [
@@ -914,7 +907,7 @@ describe("useTrueFoundryAgentMessages", () => {
914
907
  );
915
908
 
916
909
  const { result } = renderHook(() =>
917
- useTrueFoundryAgentMessages({ client: mockClient, sessionId: "session-1" }),
910
+ useTrueFoundryAgentMessages({ server: mockServer, sessionId: "session-1" }),
918
911
  );
919
912
  await waitFor(() => expect(result.current.messages).toHaveLength(1));
920
913
 
@@ -931,7 +924,7 @@ describe("useTrueFoundryAgentMessages", () => {
931
924
  });
932
925
  });
933
926
 
934
- it("cancel drains the stream gracefully and calls session.cancel", async () => {
927
+ it("cancel drains the stream gracefully and calls cancelSession", async () => {
935
928
  let resolveStream: (() => void) | undefined;
936
929
  vi.mocked(streamTurnContent).mockReturnValue(
937
930
  (async function* () {
@@ -941,15 +934,14 @@ describe("useTrueFoundryAgentMessages", () => {
941
934
  });
942
935
  })(),
943
936
  );
944
- // session.cancel() makes the backend close the SSE stream gracefully,
937
+ // cancelSession makes the backend close the SSE stream gracefully,
945
938
  // which ends the active iterator on its own.
946
- const cancel = vi.fn().mockImplementation(async () => {
939
+ vi.mocked(mockServer.cancelSession).mockImplementation(async () => {
947
940
  resolveStream?.();
948
941
  });
949
- vi.mocked(getSession).mockResolvedValue({ cancel } as never);
950
942
 
951
943
  const { result } = renderHook(() =>
952
- useTrueFoundryAgentMessages({ client: mockClient, sessionId: "session-1" }),
944
+ useTrueFoundryAgentMessages({ server: mockServer, sessionId: "session-1" }),
953
945
  );
954
946
  await waitFor(() => expect(result.current.isLoading).toBe(false));
955
947
 
@@ -964,34 +956,10 @@ describe("useTrueFoundryAgentMessages", () => {
964
956
  await sendPromise;
965
957
  });
966
958
 
967
- expect(cancel).toHaveBeenCalled();
959
+ expect(mockServer.cancelSession).toHaveBeenCalledWith({ sessionId: "session-1" });
968
960
  expect(result.current.isRunning).toBe(false);
969
961
  // No reconcile is triggered by cancel; the session was only loaded once
970
962
  // on mount and reconciles against the event log on the next page load.
971
963
  expect(loadSessionSnapshot).toHaveBeenCalledTimes(1);
972
964
  });
973
-
974
- it("cancel resolves the session through the private client in draft mode", async () => {
975
- const cancel = vi.fn().mockResolvedValue(undefined);
976
- vi.mocked(getSession).mockResolvedValue({ cancel } as never);
977
- const privateClient = {} as PrivateAgentSessionClient;
978
-
979
- const { result } = renderHook(() =>
980
- useTrueFoundryAgentMessages({
981
- client: mockClient,
982
- sessionId: "draft-session-1",
983
- privateClient,
984
- }),
985
- );
986
- await waitFor(() => expect(result.current.isLoading).toBe(false));
987
-
988
- await act(async () => {
989
- await result.current.cancel();
990
- });
991
-
992
- expect(cancel).toHaveBeenCalled();
993
- expect(getSession).toHaveBeenCalledWith(mockClient, "draft-session-1", {
994
- privateClient,
995
- });
996
- });
997
965
  });
@@ -3,15 +3,14 @@
3
3
  import { generateId } from "@assistant-ui/core";
4
4
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
5
5
  import type {
6
- AgentSessionClient,
7
6
  McpAuthRequiredEvent,
8
7
  ToolApprovalRequiredEvent,
9
8
  ToolResponseRequiredEvent,
10
9
  Turn,
11
10
  TurnInputItem,
12
11
  TurnStateDone,
13
- } from "truefoundry-gateway-sdk/agents";
14
- import type { PrivateAgentSessionClient } from "truefoundry-gateway-sdk/agents/private";
12
+ } from "./server/index.js";
13
+ import type { AgentChatServer } from "./server/types.js";
15
14
 
16
15
  import { ROOT_THREAD_ID } from "./constants.js";
17
16
  import {
@@ -36,7 +35,6 @@ import {
36
35
  messageHasPendingRequiredActions,
37
36
  type RequiredActionInput,
38
37
  } from "./requiredActionInputs.js";
39
- import { getSession, type GetSessionOptions } from "./sessions.js";
40
38
  import {
41
39
  createEmptySessionSnapshot,
42
40
  replaceSessionSnapshot,
@@ -56,7 +54,7 @@ import {
56
54
  import type { TurnStreamUpdate } from "./turnStreamUpdate.js";
57
55
 
58
56
  export type UseTrueFoundryAgentMessagesOptions = {
59
- client: AgentSessionClient;
57
+ server: AgentChatServer;
60
58
  sessionId: string | undefined;
61
59
  /** When true the thread is the currently selected (main) thread. */
62
60
  isMain?: boolean | undefined;
@@ -68,8 +66,6 @@ export type UseTrueFoundryAgentMessagesOptions = {
68
66
  }>;
69
67
  /** Maps a thread `remoteId` to the gateway session id used for turns. */
70
68
  resolveConversationSessionId?: (remoteId: string) => Promise<string>;
71
- /** When set, turns bind via PrivateAgentSessionClient.getDraftSession. */
72
- privateClient?: PrivateAgentSessionClient;
73
69
  /**
74
70
  * Optional per-turn headers for createTurn. Invoked once per `sendTurn` after
75
71
  * the session is resolved; return value is forwarded to `turn.execute`.
@@ -287,22 +283,15 @@ function resolveTurnInput(
287
283
  }
288
284
 
289
285
  export function useTrueFoundryAgentMessages({
290
- client,
286
+ server,
291
287
  sessionId,
292
288
  isMain,
293
289
  listEventsConcurrency,
294
290
  onError,
295
291
  initializeSession,
296
292
  resolveConversationSessionId,
297
- privateClient,
298
293
  getTurnHeaders,
299
294
  }: UseTrueFoundryAgentMessagesOptions) {
300
- const sessionOptions = useMemo<GetSessionOptions | undefined>(
301
- () =>
302
- privateClient != null ? { privateClient } : undefined,
303
- [privateClient],
304
- );
305
-
306
295
  const [snapshot, setSnapshot] = useState<SessionSnapshot>(createEmptySessionSnapshot);
307
296
  const [isRunning, setIsRunning] = useState(false);
308
297
  const [isLoading, setIsLoading] = useState(false);
@@ -508,9 +497,8 @@ export function useTrueFoundryAgentMessages({
508
497
  resolveConversationSessionIdRef.current,
509
498
  );
510
499
  const loadedSnapshot = await loadSessionSnapshot(
511
- client,
500
+ server,
512
501
  conversationSessionId,
513
- sessionOptions,
514
502
  (snap) => {
515
503
  if (generation === loadGenerationRef.current) {
516
504
  setSnapshot(snap);
@@ -542,7 +530,9 @@ export function useTrueFoundryAgentMessages({
542
530
  void runStream(
543
531
  (signal) =>
544
532
  resumeTurnStream(
545
- turn,
533
+ server,
534
+ conversationSessionId,
535
+ turn.id,
546
536
  loadedSnapshot.fold,
547
537
  signal,
548
538
  undefined,
@@ -562,7 +552,7 @@ export function useTrueFoundryAgentMessages({
562
552
  setIsLoading(false);
563
553
  }
564
554
  }
565
- }, [client, runStream, sessionId, sessionOptions, loadRetryTrigger, isMain]);
555
+ }, [server, runStream, sessionId, loadRetryTrigger, isMain]);
566
556
 
567
557
  useEffect(() => {
568
558
  void load().catch(() => undefined);
@@ -584,7 +574,6 @@ export function useTrueFoundryAgentMessages({
584
574
  activeSessionId,
585
575
  resolveConversationSessionIdRef.current,
586
576
  );
587
- const session = await getSession(client, conversationSessionId, sessionOptions);
588
577
  const turnHeaders = await getTurnHeadersRef.current?.();
589
578
  const streamHeaders =
590
579
  turnHeaders != null ? { headers: turnHeaders } : {};
@@ -596,7 +585,7 @@ export function useTrueFoundryAgentMessages({
596
585
  isContinuation && continuationTurnId != null
597
586
  ? continuationTurnId
598
587
  : generateId();
599
- // First turns must send previousTurnId: null.
588
+ // First turns must send previousTurnId: "none".
600
589
  const isFirstTurnInSession =
601
590
  "userMessage" in options &&
602
591
  options.previousTurnId === undefined &&
@@ -673,7 +662,8 @@ export function useTrueFoundryAgentMessages({
673
662
  (signal) => {
674
663
  if ("inputs" in options) {
675
664
  return streamTurnContent(
676
- session,
665
+ server,
666
+ conversationSessionId,
677
667
  snapshotRef.current.fold,
678
668
  { inputs: options.inputs, ...streamHeaders },
679
669
  signal,
@@ -682,7 +672,8 @@ export function useTrueFoundryAgentMessages({
682
672
  }
683
673
  if ("resumeMcpAuth" in options) {
684
674
  return streamTurnContent(
685
- session,
675
+ server,
676
+ conversationSessionId,
686
677
  snapshotRef.current.fold,
687
678
  { resumeMcpAuth: true, ...streamHeaders },
688
679
  signal,
@@ -690,14 +681,15 @@ export function useTrueFoundryAgentMessages({
690
681
  );
691
682
  }
692
683
  return streamTurnContent(
693
- session,
684
+ server,
685
+ conversationSessionId,
694
686
  snapshotRef.current.fold,
695
687
  {
696
688
  userMessage: options.userMessage,
697
689
  ...(options.previousTurnId !== undefined
698
- ? { previousTurnId: options.previousTurnId }
690
+ ? { previousTurnId: options.previousTurnId ?? "none" }
699
691
  : isFirstTurnInSession
700
- ? { previousTurnId: null }
692
+ ? { previousTurnId: "none" }
701
693
  : {}),
702
694
  ...streamHeaders,
703
695
  },
@@ -726,7 +718,7 @@ export function useTrueFoundryAgentMessages({
726
718
  isContinuation,
727
719
  );
728
720
  },
729
- [client, runStream, sessionId, sessionOptions],
721
+ [server, runStream, sessionId],
730
722
  );
731
723
 
732
724
  const cancel = useCallback(async () => {
@@ -738,17 +730,16 @@ export function useTrueFoundryAgentMessages({
738
730
  sessionId,
739
731
  resolveConversationSessionIdRef.current,
740
732
  );
741
- const session = await getSession(client, conversationSessionId, sessionOptions);
742
733
  // Request cancellation but keep consuming the stream. After cancel(),
743
734
  // the backend gracefully closes the SSE stream: it emits a terminal
744
735
  // turn.done event and then ends the stream, which lets the active run
745
736
  // drain to completion on its own instead of being torn down mid-flight.
746
- await session.cancel().catch(() => undefined);
737
+ await server.cancelSession({ sessionId: conversationSessionId }).catch(() => undefined);
747
738
  // Wait for the in-flight stream to finish draining. No explicit
748
739
  // reconcile is needed here — the cancelled turn is terminal and local
749
740
  // state reconciles against the event log on the next session load.
750
741
  await activeRunRef.current?.catch(() => undefined);
751
- }, [client, sessionId, sessionOptions]);
742
+ }, [server, sessionId]);
752
743
 
753
744
  const isRunningRef = useRef(isRunning);
754
745
  isRunningRef.current = isRunning;
@@ -817,7 +808,9 @@ export function useTrueFoundryAgentMessages({
817
808
  await runStream(
818
809
  (signal) =>
819
810
  resumeTurnStream(
820
- turn,
811
+ server,
812
+ turn.sessionId,
813
+ turn.id,
821
814
  snapshotRef.current.fold,
822
815
  signal,
823
816
  undefined,
@@ -826,7 +819,7 @@ export function useTrueFoundryAgentMessages({
826
819
  { current: turn.id },
827
820
  true,
828
821
  );
829
- }, [runStream]);
822
+ }, [runStream, server]);
830
823
 
831
824
  const branchFromTurn = useCallback(
832
825
  async (turnId: string, userMessage: UserMessageContent) => {
@@ -844,13 +837,14 @@ export function useTrueFoundryAgentMessages({
844
837
  activeSessionId,
845
838
  resolveConversationSessionIdRef.current,
846
839
  );
847
- const session = await getSession(client, conversationSessionId, sessionOptions);
848
840
  const previousTurnId = await resolveGatewayBranchPreviousTurnIdForTurn(
849
- session,
841
+ server,
842
+ conversationSessionId,
850
843
  turnId,
851
844
  );
852
845
  const rewound = await buildSnapshotBeforeTurn(
853
- session,
846
+ server,
847
+ conversationSessionId,
854
848
  turnId,
855
849
  listEventsConcurrency,
856
850
  );
@@ -868,11 +862,10 @@ export function useTrueFoundryAgentMessages({
868
862
  },
869
863
  [
870
864
  cancel,
871
- client,
865
+ server,
872
866
  listEventsConcurrency,
873
867
  sendTurn,
874
868
  sessionId,
875
- sessionOptions,
876
869
  ],
877
870
  );
878
871
 
@@ -936,13 +929,9 @@ export function useTrueFoundryAgentMessages({
936
929
  sessionId,
937
930
  resolveConversationSessionIdRef.current,
938
931
  );
939
- const session = await getSession(
940
- client,
941
- conversationSessionId,
942
- sessionOptions,
943
- );
944
932
  const next = await prependOlderSessionHistory(
945
- session,
933
+ server,
934
+ conversationSessionId,
946
935
  snapshotRef.current,
947
936
  );
948
937
  if (generation !== loadGenerationRef.current) {
@@ -964,7 +953,7 @@ export function useTrueFoundryAgentMessages({
964
953
 
965
954
  loadOlderInflightRef.current = run;
966
955
  return run;
967
- }, [client, isMain, sessionId, sessionOptions]);
956
+ }, [server, isMain, sessionId]);
968
957
 
969
958
  return {
970
959
  messages,
@@ -986,4 +975,3 @@ export function useTrueFoundryAgentMessages({
986
975
  }
987
976
 
988
977
  export { findPausedAssistantMessage, MCP_AUTH_RESUME_RUN_CUSTOM_KEY };
989
-