@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
package/src/streamTurn.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import type {
2
- AgentSession,
3
- Turn,
2
+ AgentChatServer,
3
+ PreviousTurnIdInput,
4
4
  TurnInputItem,
5
- } from "truefoundry-gateway-sdk/agents";
6
- import type { TruefoundryGatewayApi } from "truefoundry-gateway-sdk";
5
+ } from "./server/types.js";
6
+ import type { TurnStreamData } from "./server/events.js";
7
7
 
8
8
  import {
9
9
  streamTurnEvents,
@@ -18,11 +18,11 @@ export type StreamTurnOptions = {
18
18
  resumeMcpAuth?: boolean;
19
19
  inputs?: RequiredActionInput[];
20
20
  /**
21
- * Branch anchor for `prepareTurn`. Omit for `"auto"`. Pass `null` for a fresh
22
- * root turn — sent as `previous_turn_id: null` on the wire.
21
+ * Branch anchor for prepareAndExecuteTurn. Omit for `"auto"`. Pass `"none"` for a fresh
22
+ * root turn.
23
23
  */
24
- previousTurnId?: string | null;
25
- /** Extra headers for the createTurn request (`execute` request options). */
24
+ previousTurnId?: PreviousTurnIdInput;
25
+ /** Extra headers for the turn request. */
26
26
  headers?: Record<string, string>;
27
27
  };
28
28
 
@@ -36,9 +36,13 @@ function buildTurnInput(options: StreamTurnOptions): TurnInputItem[] {
36
36
  return [{ type: "user.message", content: options.userMessage ?? "" }];
37
37
  }
38
38
 
39
- function bindAbort(session: AgentSession, abortSignal: AbortSignal): () => void {
39
+ function bindAbort(
40
+ server: AgentChatServer,
41
+ sessionId: string,
42
+ abortSignal: AbortSignal,
43
+ ): () => void {
40
44
  const onAbort = () => {
41
- void session.cancel().catch(() => undefined);
45
+ void server.cancelSession({ sessionId }).catch(() => undefined);
42
46
  };
43
47
  if (abortSignal.aborted) {
44
48
  onAbort();
@@ -49,72 +53,50 @@ function bindAbort(session: AgentSession, abortSignal: AbortSignal): () => void
49
53
  }
50
54
 
51
55
  export async function* streamTurnContent(
52
- session: AgentSession,
56
+ server: AgentChatServer,
57
+ sessionId: string,
53
58
  foldState: PeerThreadFoldState,
54
59
  options: StreamTurnOptions,
55
60
  abortSignal: AbortSignal,
56
61
  groupRootBaseline?: readonly string[],
57
62
  /**
58
- * Called once with the gateway-assigned turn ID as soon as it becomes
59
- * available (after the first `turn.created` SSE event). Use this to
60
- * reconcile the locally-generated optimistic ID with the real gateway ID
61
- * so that edit/retry can find the turn in `buildSnapshotBeforeTurn`.
63
+ * Called once with the turn ID as soon as it becomes available (first
64
+ * `turn.created` SSE event). Use this to reconcile the locally-generated
65
+ * optimistic ID with the real turn ID.
62
66
  */
63
67
  onTurnIdAvailable?: (turnId: string) => void,
64
68
  ): AsyncGenerator<TurnStreamUpdate> {
65
- // When previousTurnId is explicitly null, pass it through to prepareTurn so the
66
- // SDK serializer emits `previous_turn_id: null` on the wire (first turn in session).
67
- // The SDK type doesn't admit null, but the Fern serializer handles it correctly.
68
- const previousTurnId: TruefoundryGatewayApi.PreviousTurnIdInput | null | undefined =
69
- options.previousTurnId === null
70
- ? null
71
- : (options.previousTurnId ?? "auto");
72
- const turn = session.prepareTurn({
73
- input: buildTurnInput(options),
74
- ...(previousTurnId !== undefined
75
- ? { previousTurnId: previousTurnId as TruefoundryGatewayApi.PreviousTurnIdInput }
76
- : {}),
77
- });
78
-
79
- const onAbort = bindAbort(session, abortSignal);
69
+ const onAbort = bindAbort(server, sessionId, abortSignal);
80
70
  if (abortSignal.aborted) {
81
71
  return;
82
72
  }
83
73
 
84
74
  let turnIdNotified = false;
85
- const notifyTurnIdIfAvailable = () => {
86
- if (!turnIdNotified && turn.id != null) {
87
- onTurnIdAvailable?.(turn.id);
75
+ const notifyTurnId = (turnId: string) => {
76
+ if (!turnIdNotified) {
77
+ onTurnIdAvailable?.(turnId);
88
78
  turnIdNotified = true;
89
79
  }
90
80
  };
91
81
 
82
+ const stream: AsyncIterable<TurnStreamData> = server.prepareAndExecuteTurn({
83
+ sessionId,
84
+ input: buildTurnInput(options),
85
+ previousTurnId: options.previousTurnId ?? "auto",
86
+ abortSignal,
87
+ ...(options.headers != null ? { headers: options.headers } : {}),
88
+ });
89
+
92
90
  try {
93
91
  for await (const update of streamTurnEvents(
94
- turn.execute(
95
- { stream: true },
96
- {
97
- abortSignal,
98
- ...(options.headers != null ? { headers: options.headers } : {}),
99
- },
100
- ),
92
+ stream,
101
93
  foldState,
102
94
  groupRootBaseline,
95
+ notifyTurnId,
103
96
  )) {
104
- // After the first `turn.created` event, `turn.id` is set.
105
- // Notify BEFORE yielding so the caller can update its tracking
106
- // before the snapshot is written with the stream update.
107
- notifyTurnIdIfAvailable();
108
97
  yield update;
109
98
  }
110
- // Handle streams that complete without yielding any content.
111
- notifyTurnIdIfAvailable();
112
99
  } catch (error) {
113
- // Error streams often throw on `turn.done` (status=error) without ever
114
- // yielding content (e.g. model not servable). `turn.id` is still set
115
- // after `turn.created` — notify so edit/retry can resolve the turn.
116
- // Same for AbortError after create: keep local ids aligned with gateway.
117
- notifyTurnIdIfAvailable();
118
100
  if (error instanceof Error && error.name === "AbortError") {
119
101
  return;
120
102
  }
@@ -126,14 +108,22 @@ export async function* streamTurnContent(
126
108
 
127
109
  /** TODO: wire `afterSequenceNumber` from the last ingested stream event to skip replay on reconnect. */
128
110
  export async function* resumeTurnStream(
129
- turn: Turn,
111
+ server: AgentChatServer,
112
+ sessionId: string,
113
+ turnId: string,
130
114
  foldState: PeerThreadFoldState,
131
115
  abortSignal: AbortSignal,
132
116
  afterSequenceNumber?: number,
133
117
  groupRootBaseline?: readonly string[],
134
118
  ): AsyncGenerator<TurnStreamUpdate> {
119
+ if (server.subscribeToTurn == null) {
120
+ throw new Error(
121
+ "resumeTurnStream requires AgentChatServer.subscribeToTurn",
122
+ );
123
+ }
124
+
135
125
  const onAbort = () => {
136
- void turn.session.cancel().catch(() => undefined);
126
+ void server.cancelSession({ sessionId }).catch(() => undefined);
137
127
  };
138
128
  if (abortSignal.aborted) {
139
129
  onAbort();
@@ -143,10 +133,14 @@ export async function* resumeTurnStream(
143
133
 
144
134
  try {
145
135
  yield* streamTurnEvents(
146
- turn.stream(
147
- afterSequenceNumber != null ? { afterSequenceNumber } : {},
148
- { abortSignal },
149
- ),
136
+ server.subscribeToTurn({
137
+ sessionId,
138
+ turnId,
139
+ ...(afterSequenceNumber != null
140
+ ? { afterSequenceNumber }
141
+ : {}),
142
+ abortSignal,
143
+ }),
150
144
  foldState,
151
145
  groupRootBaseline,
152
146
  );
@@ -8,7 +8,7 @@ import type {
8
8
  ToolApprovalRequiredEvent,
9
9
  Turn,
10
10
  UserToolApprovalEvent,
11
- } from "truefoundry-gateway-sdk/agents";
11
+ } from "./server/index.js";
12
12
 
13
13
  import { ROOT_THREAD_ID } from "./constants.js";
14
14
  import type { ToolApprovalMessageCustomMetadata } from "./messageCustomMetadata.js";
@@ -148,10 +148,10 @@ export function findApprovalRequiredInTurn(
148
148
  if (turn.state.status !== "done") {
149
149
  return undefined;
150
150
  }
151
- return turn.state.requiredActions?.find(
152
- (action): action is ToolApprovalRequiredEvent =>
153
- action.type === "tool.approval_required",
151
+ const found = turn.state.requiredActions?.find(
152
+ (action) => action.type === "tool.approval_required",
154
153
  );
154
+ return found?.type === "tool.approval_required" ? found : undefined;
155
155
  }
156
156
 
157
157
  function toolCallPartHasPendingApproval(part: ToolCallPart): boolean {
@@ -9,7 +9,7 @@ import type {
9
9
  Turn,
10
10
  TurnInputItem,
11
11
  UserToolResponseEvent,
12
- } from "truefoundry-gateway-sdk/agents";
12
+ } from "./server/index.js";
13
13
 
14
14
  import { ROOT_THREAD_ID } from "./constants.js";
15
15
  import {
@@ -85,10 +85,10 @@ export function findResponseRequiredInTurn(
85
85
  if (turn.state.status !== "done") {
86
86
  return undefined;
87
87
  }
88
- return turn.state.requiredActions?.find(
89
- (action): action is ToolResponseRequiredEvent =>
90
- action.type === "tool.response_required",
88
+ const found = turn.state.requiredActions?.find(
89
+ (action) => action.type === "tool.response_required",
91
90
  );
91
+ return found?.type === "tool.response_required" ? found : undefined;
92
92
  }
93
93
 
94
94
  function applyToolResponseToToolCall(
@@ -1,5 +1,5 @@
1
1
  import { createRuntimeExtras } from "@assistant-ui/core/internal";
2
- import type { McpAuthRequiredEvent } from "truefoundry-gateway-sdk/agents";
2
+ import type { McpAuthRequiredEvent } from "./server/index.js";
3
3
 
4
4
  import type { AgentSpec, AgentSpecUpdate } from "./private/agentSpec.js";
5
5
  import type { PendingApproval, PendingToolResponse } from "./collectPending.js";
@@ -1,53 +1,54 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
2
 
3
- import type { PrivateAgentSessionClient } from "truefoundry-gateway-sdk/agents/private";
3
+ import type { AgentChatServer, Session } from "./server/index.js";
4
4
 
5
5
  import { createTrueFoundryOwnedSessionsThreadListAdapter } from "./truefoundryOwnedSessionsThreadListAdapter.js";
6
6
 
7
- function mockNamedSession(id: string, title: string, updatedAt: string) {
7
+ function mockNamedSession(id: string, title: string, updatedAt: string): Session {
8
8
  return {
9
- type: "session" as const,
10
9
  id,
11
10
  agentName: "my-agent",
12
11
  title,
13
- createdBySubject: { type: "user" as const, id: "u1" },
14
12
  createdAt: updatedAt,
15
13
  updatedAt,
14
+ isMutable: false,
16
15
  };
17
16
  }
18
17
 
19
- function mockDraftSession(id: string, title: string | undefined, updatedAt: string) {
18
+ function mockDraftSession(
19
+ id: string,
20
+ title: string | undefined,
21
+ updatedAt: string,
22
+ ): Session {
20
23
  return {
21
- type: "session/draft" as const,
22
24
  id,
23
25
  agentSpec: { model: { name: "anthropic/claude-sonnet-4-6" } },
24
26
  title,
25
- createdBySubject: { type: "user" as const, id: "u1" },
26
27
  createdAt: updatedAt,
27
28
  updatedAt,
29
+ isMutable: true,
28
30
  };
29
31
  }
30
32
 
33
+ function mockServer(partial: Partial<AgentChatServer>): AgentChatServer {
34
+ return partial as AgentChatServer;
35
+ }
36
+
31
37
  describe("createTrueFoundryOwnedSessionsThreadListAdapter", () => {
32
38
  it("lists owned sessions (named + draft) with pagination cursor", async () => {
33
- const listOwnedSessions = vi.fn().mockResolvedValue({
39
+ const listSessions = vi.fn().mockResolvedValue({
34
40
  data: [
35
41
  mockNamedSession("s1", "Named chat", "2026-06-30T12:00:00.000Z"),
36
42
  mockDraftSession("d1", "Draft chat", "2026-06-30T11:00:00.000Z"),
37
43
  ],
38
- response: {
39
- pagination: { nextPageToken: "page-2", limit: 20 },
40
- },
44
+ nextPageToken: "page-2",
41
45
  });
42
- const privateClient = {
43
- listOwnedSessions,
44
- getDraftSession: vi.fn(),
45
- } as unknown as PrivateAgentSessionClient;
46
+ const server = mockServer({ listSessions, getSession: vi.fn() });
46
47
 
47
- const adapter = createTrueFoundryOwnedSessionsThreadListAdapter({ privateClient });
48
+ const adapter = createTrueFoundryOwnedSessionsThreadListAdapter({ server });
48
49
  const result = await adapter.list();
49
50
 
50
- expect(listOwnedSessions).toHaveBeenCalledWith(
51
+ expect(listSessions).toHaveBeenCalledWith(
51
52
  expect.objectContaining({
52
53
  limit: 20,
53
54
  pageToken: undefined,
@@ -72,28 +73,24 @@ describe("createTrueFoundryOwnedSessionsThreadListAdapter", () => {
72
73
  });
73
74
 
74
75
  it("falls back to model name for untitled drafts", async () => {
75
- const listOwnedSessions = vi.fn().mockResolvedValue({
76
+ const listSessions = vi.fn().mockResolvedValue({
76
77
  data: [mockDraftSession("d1", undefined, "2026-06-30T11:00:00.000Z")],
77
- response: { pagination: { limit: 20 } },
78
78
  });
79
- const privateClient = {
80
- listOwnedSessions,
81
- getDraftSession: vi.fn(),
82
- } as unknown as PrivateAgentSessionClient;
79
+ const server = mockServer({ listSessions, getSession: vi.fn() });
83
80
 
84
- const adapter = createTrueFoundryOwnedSessionsThreadListAdapter({ privateClient });
81
+ const adapter = createTrueFoundryOwnedSessionsThreadListAdapter({ server });
85
82
  const result = await adapter.list();
86
83
 
87
84
  expect(result.threads[0]?.title).toBe("anthropic/claude-sonnet-4-6");
88
85
  });
89
86
 
90
87
  it("throws on initialize because the adapter is read-only", async () => {
91
- const privateClient = {
92
- listOwnedSessions: vi.fn(),
93
- getDraftSession: vi.fn(),
94
- } as unknown as PrivateAgentSessionClient;
88
+ const server = mockServer({
89
+ listSessions: vi.fn(),
90
+ getSession: vi.fn(),
91
+ });
95
92
 
96
- const adapter = createTrueFoundryOwnedSessionsThreadListAdapter({ privateClient });
93
+ const adapter = createTrueFoundryOwnedSessionsThreadListAdapter({ server });
97
94
 
98
95
  await expect(adapter.initialize("local")).rejects.toThrow(/read-only/);
99
96
  });
@@ -1,34 +1,33 @@
1
1
  import type { RemoteThreadListAdapter } from "@assistant-ui/core";
2
- import type { AgentSession } from "truefoundry-gateway-sdk/agents";
3
- import type {
4
- AgentDraftSession,
5
- PrivateAgentSessionClient,
6
- } from "truefoundry-gateway-sdk/agents/private";
7
2
 
3
+ import type { AgentChatServer, Session } from "./server/types.js";
8
4
  import { draftSessionTitle } from "./private/agentSpec.js";
9
5
  import { sessionListStartTimestamp } from "./sessionListStartTimestamp.js";
10
6
 
11
7
  const THREAD_LIST_PAGE_SIZE = 20;
12
8
 
13
- function ownedSessionTitle(session: AgentSession | AgentDraftSession): string {
14
- if (session.type === "session/draft") {
15
- return draftSessionTitle(session);
9
+ function ownedSessionTitle(session: Session): string {
10
+ if (session.isMutable && session.agentSpec != null) {
11
+ return draftSessionTitle({
12
+ title: session.title,
13
+ agentSpec: session.agentSpec,
14
+ });
16
15
  }
17
- return session.title ?? session.agentName;
16
+ return session.title ?? session.agentName ?? session.id;
18
17
  }
19
18
 
20
19
  /**
21
- * Read-only thread-list adapter backed by `PrivateAgentSessionClient.listOwnedSessions`.
22
- * Returns every session the caller owns (named + draft), newest first.
20
+ * Read-only thread-list adapter backed by `AgentChatServer.listSessions`.
21
+ * Hosts that previously used listOwnedSessions should filter in their server impl.
23
22
  */
24
23
  export function createTrueFoundryOwnedSessionsThreadListAdapter(options: {
25
- privateClient: PrivateAgentSessionClient;
24
+ server: AgentChatServer;
26
25
  }): RemoteThreadListAdapter {
27
- const { privateClient } = options;
26
+ const { server } = options;
28
27
 
29
28
  return {
30
29
  async list({ after } = {}) {
31
- const page = await privateClient.listOwnedSessions({
30
+ const page = await server.listSessions({
32
31
  limit: THREAD_LIST_PAGE_SIZE,
33
32
  pageToken: after,
34
33
  startTimestamp: sessionListStartTimestamp(),
@@ -41,7 +40,7 @@ export function createTrueFoundryOwnedSessionsThreadListAdapter(options: {
41
40
  }));
42
41
  return {
43
42
  threads,
44
- nextCursor: page.response.pagination.nextPageToken ?? undefined,
43
+ nextCursor: page.nextPageToken ?? undefined,
45
44
  };
46
45
  },
47
46
 
@@ -52,16 +51,12 @@ export function createTrueFoundryOwnedSessionsThreadListAdapter(options: {
52
51
  },
53
52
 
54
53
  async fetch(remoteId) {
55
- // PrivateAgentSessionClient only exposes get for drafts. Named sessions
56
- // surface through listOwnedSessions; fetch falls back to draft get.
57
- const draft = await privateClient.getDraftSession({
58
- draftSessionId: remoteId,
59
- });
54
+ const session = await server.getSession({ sessionId: remoteId });
60
55
  return {
61
56
  status: "regular" as const,
62
- remoteId: draft.id,
63
- title: draftSessionTitle(draft),
64
- lastMessageAt: new Date(draft.updatedAt),
57
+ remoteId: session.id,
58
+ title: ownedSessionTitle(session),
59
+ lastMessageAt: new Date(session.updatedAt),
65
60
  };
66
61
  },
67
62
 
@@ -1,32 +1,30 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
2
 
3
- import type { AgentSessionClient } from "truefoundry-gateway-sdk/agents";
3
+ import type { AgentChatServer, Session } from "./server/index.js";
4
4
 
5
5
  import { createTrueFoundryThreadListAdapter } from "./truefoundryThreadListAdapter.js";
6
6
 
7
- function mockSession(id: string, title: string, updatedAt: string) {
7
+ function mockSession(id: string, title: string, updatedAt: string): Session {
8
8
  return {
9
9
  id,
10
10
  title,
11
11
  updatedAt,
12
+ createdAt: updatedAt,
13
+ isMutable: false,
12
14
  };
13
15
  }
14
16
 
15
- function mockListSessionsPage(
16
- sessions: ReturnType<typeof mockSession>[],
17
- nextPageToken?: string,
18
- ) {
17
+ function mockListSessionsPage(sessions: Session[], nextPageToken?: string) {
19
18
  return {
20
19
  data: sessions,
21
- response: {
22
- pagination: {
23
- nextPageToken,
24
- limit: 20,
25
- },
26
- },
20
+ ...(nextPageToken != null ? { nextPageToken } : {}),
27
21
  };
28
22
  }
29
23
 
24
+ function mockServer(partial: Partial<AgentChatServer>): AgentChatServer {
25
+ return partial as AgentChatServer;
26
+ }
27
+
30
28
  describe("createTrueFoundryThreadListAdapter", () => {
31
29
  it("lists the first page with limit and returns nextCursor", async () => {
32
30
  const listSessions = vi.fn().mockResolvedValue(
@@ -35,9 +33,9 @@ describe("createTrueFoundryThreadListAdapter", () => {
35
33
  "page-2",
36
34
  ),
37
35
  );
38
- const client = { listSessions } as unknown as AgentSessionClient;
36
+ const server = mockServer({ listSessions });
39
37
  const adapter = createTrueFoundryThreadListAdapter({
40
- client,
38
+ server,
41
39
  agentName: "my-agent",
42
40
  });
43
41
 
@@ -68,9 +66,9 @@ describe("createTrueFoundryThreadListAdapter", () => {
68
66
  [mockSession("s2", "Second", "2026-06-29T10:00:00.000Z")],
69
67
  ),
70
68
  );
71
- const client = { listSessions } as unknown as AgentSessionClient;
69
+ const server = mockServer({ listSessions });
72
70
  const adapter = createTrueFoundryThreadListAdapter({
73
- client,
71
+ server,
74
72
  agentName: "my-agent",
75
73
  });
76
74
 
@@ -90,9 +88,9 @@ describe("createTrueFoundryThreadListAdapter", () => {
90
88
  const listSessions = vi.fn().mockResolvedValue(
91
89
  mockListSessionsPage([mockSession("s1", "Only", "2026-06-30T10:00:00.000Z")]),
92
90
  );
93
- const client = { listSessions } as unknown as AgentSessionClient;
91
+ const server = mockServer({ listSessions });
94
92
  const adapter = createTrueFoundryThreadListAdapter({
95
- client,
93
+ server,
96
94
  agentName: "my-agent",
97
95
  });
98
96
 
@@ -1,20 +1,20 @@
1
1
  import type { RemoteThreadListAdapter } from "@assistant-ui/core";
2
- import type { AgentSessionClient } from "truefoundry-gateway-sdk/agents";
3
2
 
3
+ import type { AgentChatServer } from "./server/types.js";
4
4
  import { getSession } from "./sessions.js";
5
5
  import { sessionListStartTimestamp } from "./sessionListStartTimestamp.js";
6
6
 
7
7
  const THREAD_LIST_PAGE_SIZE = 20;
8
8
 
9
9
  export function createTrueFoundryThreadListAdapter(options: {
10
- client: AgentSessionClient;
10
+ server: AgentChatServer;
11
11
  agentName: string;
12
12
  }): RemoteThreadListAdapter {
13
- const { client, agentName } = options;
13
+ const { server, agentName } = options;
14
14
 
15
15
  return {
16
16
  async list({ after } = {}) {
17
- const page = await client.listSessions({
17
+ const page = await server.listSessions({
18
18
  agentName,
19
19
  limit: THREAD_LIST_PAGE_SIZE,
20
20
  pageToken: after,
@@ -23,26 +23,26 @@ export function createTrueFoundryThreadListAdapter(options: {
23
23
  const threads = page.data.map((session) => ({
24
24
  status: "regular" as const,
25
25
  remoteId: session.id,
26
- title: session.title,
26
+ title: session.title ?? undefined,
27
27
  lastMessageAt: new Date(session.updatedAt),
28
28
  }));
29
29
  return {
30
30
  threads,
31
- nextCursor: page.response.pagination.nextPageToken ?? undefined,
31
+ nextCursor: page.nextPageToken ?? undefined,
32
32
  };
33
33
  },
34
34
 
35
35
  async initialize(_threadId: string) {
36
- const session = await client.createSession({ agentName });
36
+ const session = await server.createSession({ agentName });
37
37
  return { remoteId: session.id, externalId: undefined };
38
38
  },
39
39
 
40
40
  async fetch(remoteId) {
41
- const session = await getSession(client, remoteId);
41
+ const session = await getSession(server, remoteId);
42
42
  return {
43
43
  status: "regular" as const,
44
44
  remoteId: session.id,
45
- title: session.title,
45
+ title: session.title ?? undefined,
46
46
  lastMessageAt: new Date(session.updatedAt),
47
47
  };
48
48
  },
@@ -1,4 +1,4 @@
1
- import type { Turn } from "truefoundry-gateway-sdk/agents";
1
+ import type { Turn } from "./server/index.js";
2
2
 
3
3
  import {
4
4
  buildMcpAuthTextParts,
package/src/types.ts CHANGED
@@ -6,10 +6,9 @@ import type {
6
6
  RealtimeVoiceAdapter,
7
7
  SpeechSynthesisAdapter,
8
8
  } from "@assistant-ui/core";
9
- import type { AgentSessionClient } from "truefoundry-gateway-sdk/agents";
10
- import type { PrivateAgentSessionClient } from "truefoundry-gateway-sdk/agents/private";
11
9
 
12
10
  import type { AgentSpec } from "./private/agentSpec.js";
11
+ import type { AgentChatServer } from "./server/types.js";
13
12
 
14
13
  export type NamedAgentConfig = {
15
14
  mode: "named";
@@ -25,7 +24,7 @@ export type DraftAgentConfig = {
25
24
  export type TrueFoundryAgentConfig = NamedAgentConfig | DraftAgentConfig;
26
25
 
27
26
  type TrueFoundryAgentRuntimeBaseOptions = ExternalStoreSharedOptions & {
28
- client: AgentSessionClient;
27
+ server: AgentChatServer;
29
28
  initialSessionId?: string | undefined;
30
29
  threadId?: string | undefined;
31
30
  onThreadIdChange?: ((threadId: string | undefined) => void) | undefined;
@@ -47,16 +46,10 @@ export type UseTrueFoundryAgentRuntimeOptions = TrueFoundryAgentRuntimeBaseOptio
47
46
  agent?: TrueFoundryAgentConfig | undefined;
48
47
  /** Legacy named-agent shorthand. Prefer `agent: { mode: "named", agentName }`. */
49
48
  agentName?: string | undefined;
50
- /**
51
- * Required when `agent.mode === "draft"`. Also used for sandbox file downloads
52
- * (named or draft).
53
- */
54
- privateClient?: PrivateAgentSessionClient | undefined;
55
49
  };
56
50
 
57
51
  export type ResolvedTrueFoundryAgentRuntimeOptions = TrueFoundryAgentRuntimeBaseOptions & {
58
52
  agent: TrueFoundryAgentConfig;
59
- privateClient?: PrivateAgentSessionClient | undefined;
60
53
  };
61
54
 
62
55
  export function resolveTrueFoundryAgentConfig(
@@ -81,15 +74,8 @@ export function resolveTrueFoundryAgentRuntimeOptions(
81
74
  ): ResolvedTrueFoundryAgentRuntimeOptions {
82
75
  const agent = resolveTrueFoundryAgentConfig(options);
83
76
 
84
- if (agent.mode === "draft" && options.privateClient == null) {
85
- throw new Error(
86
- "Draft agent mode requires a `privateClient` PrivateAgentSessionClient.",
87
- );
88
- }
89
-
90
77
  return {
91
78
  ...options,
92
79
  agent,
93
- privateClient: options.privateClient,
94
80
  };
95
81
  }