@truefoundry/assistant-ui-runtime 0.1.5 → 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.
- package/README.md +374 -190
- package/dist/index.d.ts +32 -29
- package/dist/index.js +330 -236
- package/dist/index.js.map +1 -1
- package/dist/plugins/truefoundry-agent-server-adapter/index.d.ts +30 -0
- package/dist/plugins/truefoundry-agent-server-adapter/index.js +198 -0
- package/dist/plugins/truefoundry-agent-server-adapter/index.js.map +1 -0
- package/dist/types-VUBzoJT2.d.ts +462 -0
- package/package.json +10 -2
- package/src/askUserQuestion.ts +3 -3
- package/src/buildEditedUserMessageContent.test.ts +2 -2
- package/src/collectPending.ts +1 -1
- package/src/convertTurnMessages.test.ts +141 -196
- package/src/convertTurnMessages.ts +130 -76
- package/src/createSubAgent.ts +1 -1
- package/src/draftAgentConfig.test.ts +26 -29
- package/src/extractTurnUserText.ts +1 -1
- package/src/foldPeerThreads.test.ts +1 -1
- package/src/foldPeerThreads.ts +3 -2
- package/src/index.ts +39 -4
- package/src/listPages.ts +21 -0
- package/src/loadSessionSnapshot.test.ts +9 -8
- package/src/loadSessionSnapshot.ts +9 -14
- package/src/mcpAuth.ts +6 -3
- package/src/messageCustomMetadata.ts +1 -1
- package/src/modelMessageContent.ts +1 -1
- package/src/modelMessageImageContent.test.ts +1 -1
- package/src/modelMessageImageContent.ts +7 -6
- package/src/plugins/truefoundry-agent-server-adapter/index.ts +285 -0
- package/src/private/agentSpec.ts +8 -3
- package/src/private/draftSessionBridge.ts +14 -13
- package/src/private/truefoundryDraftThreadListAdapter.test.ts +44 -49
- package/src/private/truefoundryDraftThreadListAdapter.ts +22 -16
- package/src/requiredActionInputs.ts +1 -1
- package/src/requiredActionsFromActiveUpdate.test.ts +1 -1
- package/src/server/eventUtils.ts +120 -0
- package/src/server/events.ts +246 -0
- package/src/server/index.ts +66 -0
- package/src/server/types.ts +313 -0
- package/src/sessionSnapshot.ts +1 -1
- package/src/sessions.ts +5 -21
- package/src/streamTurn.test.ts +172 -155
- package/src/streamTurn.ts +51 -48
- package/src/toolApproval.ts +4 -4
- package/src/toolResponse.ts +4 -4
- package/src/truefoundryExtras.ts +1 -1
- package/src/truefoundryOwnedSessionsThreadListAdapter.test.ts +26 -29
- package/src/truefoundryOwnedSessionsThreadListAdapter.ts +18 -23
- package/src/truefoundryThreadListAdapter.test.ts +16 -18
- package/src/truefoundryThreadListAdapter.ts +7 -7
- package/src/turnEventHelpers.ts +1 -1
- package/src/types.ts +2 -16
- package/src/useTrueFoundryAgentMessages.test.tsx +38 -70
- package/src/useTrueFoundryAgentMessages.ts +32 -44
- package/src/useTrueFoundryAgentRuntime.ts +11 -28
- package/src/private/bindDraftAgentSession.test.ts +0 -54
- package/src/private/bindDraftAgentSession.ts +0 -28
- package/src/private/getGatewayFromPrivateClient.ts +0 -13
package/README.md
CHANGED
|
@@ -1,55 +1,77 @@
|
|
|
1
|
-
# truefoundry
|
|
1
|
+
# @truefoundry/assistant-ui-runtime
|
|
2
2
|
|
|
3
|
-
TrueFoundry
|
|
3
|
+
TrueFoundry agent runtime adapter for [assistant-ui](https://www.assistant-ui.com/).
|
|
4
4
|
|
|
5
5
|
Connect assistant-ui components (`Thread`, `Composer`, tool UIs, `ThreadList`) to TrueFoundry agent sessions via `useTrueFoundryAgentRuntime`. The adapter maps gateway turns and streaming events onto assistant-ui's external-store runtime, including multi-agent nesting, tool approvals, ask-user tool responses, MCP auth, batched resume, resumable streams, and composer attachment forwarding on send.
|
|
6
6
|
|
|
7
7
|
## Requirements
|
|
8
8
|
|
|
9
9
|
- **React** `^18 || ^19` (peer dependency)
|
|
10
|
-
- **`truefoundry-gateway-sdk`** (peer dependency) — provides `AgentSessionClient` and agent types
|
|
11
10
|
- **`@assistant-ui/react`** in the host app for the UI primitives
|
|
12
|
-
-
|
|
11
|
+
- An **`AgentChatServer`** implementation — either use the built-in TrueFoundry gateway plugin (see below) or bring your own
|
|
12
|
+
|
|
13
|
+
Bundled deps `@assistant-ui/core` and `@assistant-ui/store` are pulled in automatically.
|
|
13
14
|
|
|
14
15
|
## Installation
|
|
15
16
|
|
|
16
17
|
```bash
|
|
17
|
-
npm install @
|
|
18
|
+
npm install @truefoundry/assistant-ui-runtime @assistant-ui/react
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
If using the built-in TrueFoundry gateway adapter plugin, also install the gateway SDK:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install truefoundry-gateway-sdk
|
|
18
25
|
```
|
|
19
26
|
|
|
20
27
|
## Quickstart
|
|
21
28
|
|
|
22
|
-
### 1. Create an `
|
|
29
|
+
### 1. Create an `AgentChatServer`
|
|
30
|
+
|
|
31
|
+
The runtime accepts any object implementing the `AgentChatServer` interface — a flat, stateless port with methods like `createSession`, `listSessions`, `prepareAndExecuteTurn`, etc. It never reads credentials itself.
|
|
23
32
|
|
|
24
|
-
|
|
33
|
+
Using the built-in TrueFoundry gateway plugin (requires `truefoundry-gateway-sdk`):
|
|
25
34
|
|
|
26
35
|
```tsx
|
|
27
|
-
import {
|
|
36
|
+
import { createTrueFoundryChatServer } from "@truefoundry/assistant-ui-runtime/plugins/truefoundry-agent-server-adapter";
|
|
28
37
|
|
|
29
|
-
const
|
|
38
|
+
const server = createTrueFoundryChatServer({
|
|
30
39
|
apiKey: process.env.TFY_API_KEY!,
|
|
31
|
-
|
|
40
|
+
baseUrl: process.env.TFY_GATEWAY_URL!,
|
|
32
41
|
});
|
|
33
42
|
```
|
|
34
43
|
|
|
35
|
-
|
|
44
|
+
Or bring your own implementation against any backend:
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import type { AgentChatServer } from "@truefoundry/assistant-ui-runtime";
|
|
48
|
+
|
|
49
|
+
const server: AgentChatServer = {
|
|
50
|
+
createSession: async (req) => { /* ... */ },
|
|
51
|
+
listSessions: async (req) => { /* ... */ },
|
|
52
|
+
getSession: async (req) => { /* ... */ },
|
|
53
|
+
updateSession: async (req) => { /* ... */ },
|
|
54
|
+
prepareAndExecuteTurn: (req) => { /* return AsyncIterable<TurnStreamData> */ },
|
|
55
|
+
cancelSession: async (req) => { /* ... */ },
|
|
56
|
+
listTurns: async (req) => { /* ... */ },
|
|
57
|
+
getTurn: async (req) => { /* ... */ },
|
|
58
|
+
listEvents: async (req) => { /* ... */ },
|
|
59
|
+
};
|
|
60
|
+
```
|
|
36
61
|
|
|
37
|
-
### 2. Set up the
|
|
62
|
+
### 2. Set up the runtime
|
|
38
63
|
|
|
39
64
|
```tsx
|
|
40
65
|
"use client";
|
|
41
66
|
|
|
42
67
|
import { AssistantRuntimeProvider } from "@assistant-ui/react";
|
|
43
|
-
import { useTrueFoundryAgentRuntime } from "truefoundry
|
|
68
|
+
import { useTrueFoundryAgentRuntime } from "@truefoundry/assistant-ui-runtime";
|
|
44
69
|
import { Thread } from "@/components/assistant-ui/thread";
|
|
45
70
|
|
|
46
|
-
const AGENT_NAME = process.env.TFY_AGENT_NAME!;
|
|
47
|
-
const client = new AgentSessionClient({ /* ... */ });
|
|
48
|
-
|
|
49
71
|
export function MyAssistant() {
|
|
50
72
|
const runtime = useTrueFoundryAgentRuntime({
|
|
51
|
-
|
|
52
|
-
agentName:
|
|
73
|
+
server,
|
|
74
|
+
agentName: "support-bot",
|
|
53
75
|
});
|
|
54
76
|
|
|
55
77
|
return (
|
|
@@ -84,46 +106,50 @@ See the assistant-ui [Thread UI guide](https://www.assistant-ui.com/docs/ui/thre
|
|
|
84
106
|
|
|
85
107
|
| Option | Type | Required | Description |
|
|
86
108
|
|--------|------|----------|-------------|
|
|
87
|
-
| `
|
|
88
|
-
| `agentName` | `string` | Yes | Saved agent to run
|
|
109
|
+
| `server` | `AgentChatServer` | Yes | Server implementation. The runtime never reads credentials itself. |
|
|
110
|
+
| `agentName` | `string` | Yes* | Saved agent to run. *Or use `agent` for draft mode. |
|
|
111
|
+
| `agent` | `NamedAgentConfig \| DraftAgentConfig` | No | Discriminated agent source. Overrides `agentName` when set. |
|
|
89
112
|
| `initialSessionId` | `string` | No | Pin an existing session once on mount (uncontrolled). |
|
|
90
113
|
| `threadId` | `string` | No | Controlled active session id; reactive and URL-syncable. |
|
|
91
114
|
| `onThreadIdChange` | `(threadId: string \| undefined) => void` | No | Fires when the active session changes. |
|
|
92
115
|
| `onError` | `(error: unknown) => void` | No | Invoked on stream/load/turn errors. |
|
|
93
|
-
| `adapters` | `{ attachments?, speech?, dictation?, voice?, feedback? }` | No | Optional assistant-ui adapters forwarded to the runtime.
|
|
116
|
+
| `adapters` | `{ attachments?, speech?, dictation?, voice?, feedback? }` | No | Optional assistant-ui adapters forwarded to the runtime. |
|
|
94
117
|
|
|
95
118
|
### Specifying the agent
|
|
96
119
|
|
|
97
|
-
|
|
120
|
+
Named agent (saved on the gateway):
|
|
98
121
|
|
|
99
122
|
```tsx
|
|
100
123
|
const runtime = useTrueFoundryAgentRuntime({
|
|
101
|
-
|
|
124
|
+
server,
|
|
102
125
|
agentName: "support-bot",
|
|
103
126
|
});
|
|
104
127
|
```
|
|
105
128
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
Pass optional assistant-ui adapters through `adapters`. Attachments are **opt-in**: wire the built-in adapter when you want composer file pick / previews and gateway forwarding on send.
|
|
129
|
+
Draft agent (inline spec, mutable):
|
|
109
130
|
|
|
110
131
|
```tsx
|
|
111
|
-
import { trueFoundryAttachmentAdapter, useTrueFoundryAgentRuntime } from "truefoundry-agents-assistant-ui-runtime";
|
|
112
|
-
|
|
113
132
|
const runtime = useTrueFoundryAgentRuntime({
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
133
|
+
server,
|
|
134
|
+
agent: {
|
|
135
|
+
mode: "draft",
|
|
136
|
+
defaultAgentSpec: { model: { name: "gpt-4o" } },
|
|
137
|
+
onAgentSpecChange: (spec) => console.log("spec updated", spec),
|
|
138
|
+
},
|
|
117
139
|
});
|
|
118
140
|
```
|
|
119
141
|
|
|
120
|
-
|
|
142
|
+
### Adding adapters
|
|
143
|
+
|
|
144
|
+
Attachments are **opt-in**: wire the built-in adapter when you want composer file pick / previews and gateway forwarding on send.
|
|
121
145
|
|
|
122
146
|
```tsx
|
|
147
|
+
import { trueFoundryAttachmentAdapter, useTrueFoundryAgentRuntime } from "@truefoundry/assistant-ui-runtime";
|
|
148
|
+
|
|
123
149
|
const runtime = useTrueFoundryAgentRuntime({
|
|
124
|
-
|
|
150
|
+
server,
|
|
125
151
|
agentName,
|
|
126
|
-
adapters: { attachments: trueFoundryAttachmentAdapter
|
|
152
|
+
adapters: { attachments: trueFoundryAttachmentAdapter },
|
|
127
153
|
});
|
|
128
154
|
```
|
|
129
155
|
|
|
@@ -131,7 +157,7 @@ const runtime = useTrueFoundryAgentRuntime({
|
|
|
131
157
|
|
|
132
158
|
```tsx
|
|
133
159
|
const runtime = useTrueFoundryAgentRuntime({
|
|
134
|
-
|
|
160
|
+
server,
|
|
135
161
|
agentName,
|
|
136
162
|
initialSessionId: "ses_abc123",
|
|
137
163
|
});
|
|
@@ -139,11 +165,11 @@ const runtime = useTrueFoundryAgentRuntime({
|
|
|
139
165
|
|
|
140
166
|
### Bring your own session ID (no session list)
|
|
141
167
|
|
|
142
|
-
|
|
168
|
+
Pin the active session with `initialSessionId` (one-time) or controlled `threadId` (reactive, URL-syncable). Omit `<ThreadList>` — the session list adapter only powers that UI.
|
|
143
169
|
|
|
144
170
|
```tsx
|
|
145
171
|
const runtime = useTrueFoundryAgentRuntime({
|
|
146
|
-
|
|
172
|
+
server,
|
|
147
173
|
agentName,
|
|
148
174
|
initialSessionId: "ses_abc123",
|
|
149
175
|
});
|
|
@@ -157,16 +183,291 @@ return (
|
|
|
157
183
|
|
|
158
184
|
Each gateway session corresponds to one assistant-ui thread.
|
|
159
185
|
|
|
186
|
+
## `AgentChatServer` interface
|
|
187
|
+
|
|
188
|
+
The runtime operates against a flat server port — no session-with-methods objects, no SDK dependency. Any backend can implement this interface:
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
interface AgentChatServer {
|
|
192
|
+
createSession(req: CreateSessionRequest): Promise<Session>;
|
|
193
|
+
listSessions(req?: ListSessionsParams): Promise<ListResult<Session>>;
|
|
194
|
+
getSession(req: { sessionId: string }): Promise<Session>;
|
|
195
|
+
updateSession(req: UpdateSessionRequest): Promise<Session>;
|
|
196
|
+
|
|
197
|
+
prepareAndExecuteTurn(req: {
|
|
198
|
+
sessionId: string;
|
|
199
|
+
input?: TurnInputItem[];
|
|
200
|
+
previousTurnId?: PreviousTurnIdInput;
|
|
201
|
+
abortSignal?: AbortSignal;
|
|
202
|
+
headers?: Record<string, string>;
|
|
203
|
+
}): AsyncIterable<TurnStreamData>;
|
|
204
|
+
|
|
205
|
+
cancelSession(req: { sessionId: string }): Promise<void>;
|
|
206
|
+
deleteSession?(req: { sessionId: string }): Promise<void>;
|
|
207
|
+
|
|
208
|
+
listTurns(req: { sessionId: string; limit?: number; pageToken?: string; order?: "asc" | "desc" }): Promise<ListResult<Turn>>;
|
|
209
|
+
getTurn(req: { sessionId: string; turnId: string }): Promise<Turn>;
|
|
210
|
+
listEvents(req: { sessionId: string; pageToken?: string; lastTurnId?: string; limit?: number }): Promise<ListResult<SessionEventItem>>;
|
|
211
|
+
|
|
212
|
+
listTurnEvents?(req: { sessionId: string; turnId: string; limit?: number; pageToken?: string; order?: "asc" | "desc" }): Promise<ListResult<TurnEvent>>;
|
|
213
|
+
subscribeToTurn?(req: { sessionId: string; turnId: string; afterSequenceNumber?: number; abortSignal?: AbortSignal }): AsyncIterable<TurnStreamData>;
|
|
214
|
+
downloadSandboxFile?(sandboxId: string, req: { path: string }): Promise<Blob>;
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`ListResult<T>` is `{ data: T[]; nextPageToken?: string }` — flat token-based pagination.
|
|
219
|
+
|
|
220
|
+
### Implementing your own backend
|
|
221
|
+
|
|
222
|
+
Below is a fully-typed class implementing `AgentChatServer` against a custom REST API. Use this as a starting point when integrating your own agent backend:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import type {
|
|
226
|
+
AgentChatServer,
|
|
227
|
+
CreateSessionRequest,
|
|
228
|
+
ListResult,
|
|
229
|
+
ListSessionsParams,
|
|
230
|
+
Session,
|
|
231
|
+
SessionEventItem,
|
|
232
|
+
Turn,
|
|
233
|
+
TurnEvent,
|
|
234
|
+
TurnInputItem,
|
|
235
|
+
TurnStreamData,
|
|
236
|
+
UpdateSessionRequest,
|
|
237
|
+
} from "@truefoundry/assistant-ui-runtime";
|
|
238
|
+
|
|
239
|
+
class MyAgentChatServer implements AgentChatServer {
|
|
240
|
+
constructor(private baseUrl: string, private authToken: string) {}
|
|
241
|
+
|
|
242
|
+
private async request<T>(path: string, init?: RequestInit): Promise<T> {
|
|
243
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
244
|
+
...init,
|
|
245
|
+
headers: {
|
|
246
|
+
"Content-Type": "application/json",
|
|
247
|
+
Authorization: `Bearer ${this.authToken}`,
|
|
248
|
+
...init?.headers,
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
|
252
|
+
return res.json() as Promise<T>;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async createSession(req: CreateSessionRequest): Promise<Session> {
|
|
256
|
+
return this.request("/sessions", {
|
|
257
|
+
method: "POST",
|
|
258
|
+
body: JSON.stringify(req),
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async listSessions(req?: ListSessionsParams): Promise<ListResult<Session>> {
|
|
263
|
+
const params = new URLSearchParams();
|
|
264
|
+
if (req?.limit) params.set("limit", String(req.limit));
|
|
265
|
+
if (req?.pageToken) params.set("pageToken", req.pageToken);
|
|
266
|
+
if (req?.agentName) params.set("agentName", req.agentName);
|
|
267
|
+
return this.request(`/sessions?${params}`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async getSession(req: { sessionId: string }): Promise<Session> {
|
|
271
|
+
return this.request(`/sessions/${req.sessionId}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async updateSession(req: UpdateSessionRequest): Promise<Session> {
|
|
275
|
+
return this.request(`/sessions/${req.sessionId}`, {
|
|
276
|
+
method: "PATCH",
|
|
277
|
+
body: JSON.stringify({ agentSpec: req.agentSpec, title: req.title }),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
prepareAndExecuteTurn(req: {
|
|
282
|
+
sessionId: string;
|
|
283
|
+
input?: TurnInputItem[];
|
|
284
|
+
previousTurnId?: string;
|
|
285
|
+
abortSignal?: AbortSignal;
|
|
286
|
+
}): AsyncIterable<TurnStreamData> {
|
|
287
|
+
const self = this;
|
|
288
|
+
return {
|
|
289
|
+
[Symbol.asyncIterator]() {
|
|
290
|
+
return self.streamTurn(req);
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private async *streamTurn(req: {
|
|
296
|
+
sessionId: string;
|
|
297
|
+
input?: TurnInputItem[];
|
|
298
|
+
previousTurnId?: string;
|
|
299
|
+
abortSignal?: AbortSignal;
|
|
300
|
+
}): AsyncGenerator<TurnStreamData> {
|
|
301
|
+
const res = await fetch(`${this.baseUrl}/sessions/${req.sessionId}/turns`, {
|
|
302
|
+
method: "POST",
|
|
303
|
+
headers: {
|
|
304
|
+
"Content-Type": "application/json",
|
|
305
|
+
Authorization: `Bearer ${this.authToken}`,
|
|
306
|
+
Accept: "text/event-stream",
|
|
307
|
+
},
|
|
308
|
+
body: JSON.stringify({
|
|
309
|
+
input: req.input,
|
|
310
|
+
previousTurnId: req.previousTurnId,
|
|
311
|
+
}),
|
|
312
|
+
signal: req.abortSignal,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
|
316
|
+
const reader = res.body!.getReader();
|
|
317
|
+
const decoder = new TextDecoder();
|
|
318
|
+
let buffer = "";
|
|
319
|
+
|
|
320
|
+
while (true) {
|
|
321
|
+
const { done, value } = await reader.read();
|
|
322
|
+
if (done) break;
|
|
323
|
+
|
|
324
|
+
buffer += decoder.decode(value, { stream: true });
|
|
325
|
+
const lines = buffer.split("\n");
|
|
326
|
+
buffer = lines.pop() ?? "";
|
|
327
|
+
|
|
328
|
+
for (const line of lines) {
|
|
329
|
+
if (!line.startsWith("data: ")) continue;
|
|
330
|
+
const json = line.slice(6);
|
|
331
|
+
if (json === "[DONE]") return;
|
|
332
|
+
yield JSON.parse(json) as TurnStreamData;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async cancelSession(req: { sessionId: string }): Promise<void> {
|
|
338
|
+
await this.request(`/sessions/${req.sessionId}/cancel`, { method: "POST" });
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async listTurns(req: {
|
|
342
|
+
sessionId: string;
|
|
343
|
+
limit?: number;
|
|
344
|
+
pageToken?: string;
|
|
345
|
+
}): Promise<ListResult<Turn>> {
|
|
346
|
+
const params = new URLSearchParams();
|
|
347
|
+
if (req.limit) params.set("limit", String(req.limit));
|
|
348
|
+
if (req.pageToken) params.set("pageToken", req.pageToken);
|
|
349
|
+
return this.request(`/sessions/${req.sessionId}/turns?${params}`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async getTurn(req: { sessionId: string; turnId: string }): Promise<Turn> {
|
|
353
|
+
return this.request(`/sessions/${req.sessionId}/turns/${req.turnId}`);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async listEvents(req: {
|
|
357
|
+
sessionId: string;
|
|
358
|
+
pageToken?: string;
|
|
359
|
+
lastTurnId?: string;
|
|
360
|
+
limit?: number;
|
|
361
|
+
}): Promise<ListResult<SessionEventItem>> {
|
|
362
|
+
const params = new URLSearchParams();
|
|
363
|
+
if (req.limit) params.set("limit", String(req.limit));
|
|
364
|
+
if (req.pageToken) params.set("pageToken", req.pageToken);
|
|
365
|
+
if (req.lastTurnId) params.set("lastTurnId", req.lastTurnId);
|
|
366
|
+
return this.request(`/sessions/${req.sessionId}/events?${params}`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async listTurnEvents(req: {
|
|
370
|
+
sessionId: string;
|
|
371
|
+
turnId: string;
|
|
372
|
+
limit?: number;
|
|
373
|
+
pageToken?: string;
|
|
374
|
+
}): Promise<ListResult<TurnEvent>> {
|
|
375
|
+
const params = new URLSearchParams();
|
|
376
|
+
if (req.limit) params.set("limit", String(req.limit));
|
|
377
|
+
if (req.pageToken) params.set("pageToken", req.pageToken);
|
|
378
|
+
return this.request(
|
|
379
|
+
`/sessions/${req.sessionId}/turns/${req.turnId}/events?${params}`,
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
subscribeToTurn(req: {
|
|
384
|
+
sessionId: string;
|
|
385
|
+
turnId: string;
|
|
386
|
+
afterSequenceNumber?: number;
|
|
387
|
+
abortSignal?: AbortSignal;
|
|
388
|
+
}): AsyncIterable<TurnStreamData> {
|
|
389
|
+
const self = this;
|
|
390
|
+
return {
|
|
391
|
+
[Symbol.asyncIterator]() {
|
|
392
|
+
return self.streamSubscribe(req);
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
private async *streamSubscribe(req: {
|
|
398
|
+
sessionId: string;
|
|
399
|
+
turnId: string;
|
|
400
|
+
afterSequenceNumber?: number;
|
|
401
|
+
abortSignal?: AbortSignal;
|
|
402
|
+
}): AsyncGenerator<TurnStreamData> {
|
|
403
|
+
const params = new URLSearchParams();
|
|
404
|
+
if (req.afterSequenceNumber != null) {
|
|
405
|
+
params.set("after", String(req.afterSequenceNumber));
|
|
406
|
+
}
|
|
407
|
+
const res = await fetch(
|
|
408
|
+
`${this.baseUrl}/sessions/${req.sessionId}/turns/${req.turnId}/stream?${params}`,
|
|
409
|
+
{
|
|
410
|
+
headers: {
|
|
411
|
+
Authorization: `Bearer ${this.authToken}`,
|
|
412
|
+
Accept: "text/event-stream",
|
|
413
|
+
},
|
|
414
|
+
signal: req.abortSignal,
|
|
415
|
+
},
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
|
419
|
+
const reader = res.body!.getReader();
|
|
420
|
+
const decoder = new TextDecoder();
|
|
421
|
+
let buffer = "";
|
|
422
|
+
|
|
423
|
+
while (true) {
|
|
424
|
+
const { done, value } = await reader.read();
|
|
425
|
+
if (done) break;
|
|
426
|
+
|
|
427
|
+
buffer += decoder.decode(value, { stream: true });
|
|
428
|
+
const lines = buffer.split("\n");
|
|
429
|
+
buffer = lines.pop() ?? "";
|
|
430
|
+
|
|
431
|
+
for (const line of lines) {
|
|
432
|
+
if (!line.startsWith("data: ")) continue;
|
|
433
|
+
const json = line.slice(6);
|
|
434
|
+
if (json === "[DONE]") return;
|
|
435
|
+
yield JSON.parse(json) as TurnStreamData;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Then use it with the runtime:
|
|
443
|
+
|
|
444
|
+
```tsx
|
|
445
|
+
const server = new MyAgentChatServer("https://api.example.com", authToken);
|
|
446
|
+
|
|
447
|
+
function App() {
|
|
448
|
+
const runtime = useTrueFoundryAgentRuntime({
|
|
449
|
+
server,
|
|
450
|
+
agentName: "my-agent",
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
return (
|
|
454
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
455
|
+
<Thread />
|
|
456
|
+
</AssistantRuntimeProvider>
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
160
461
|
## Multi-agent (nested sub-agents)
|
|
161
462
|
|
|
162
|
-
TrueFoundry sub-agents are discovered at runtime via `thread.created` and nested under `ToolCallMessagePart.messages`. The gateway
|
|
463
|
+
TrueFoundry sub-agents are discovered at runtime via `thread.created` and nested under `ToolCallMessagePart.messages`. The gateway sends `title` on `thread.created` / `thread.done`. This runtime copies it onto `metadata.custom.subAgent.title` for the first nested message of each child thread; `name` comes from `agentInfo.name` on the same event.
|
|
163
464
|
|
|
164
|
-
Render nested threads with `MessagePartPrimitive.Messages` inside your tool fallback
|
|
465
|
+
Render nested threads with `MessagePartPrimitive.Messages` inside your tool fallback:
|
|
165
466
|
|
|
166
467
|
```tsx
|
|
167
468
|
import { MessagePartPrimitive, MessagePrimitive } from "@assistant-ui/react";
|
|
168
469
|
import { useAuiState } from "@assistant-ui/store";
|
|
169
|
-
import type { TrueFoundryMessageCustomMetadata } from "truefoundry
|
|
470
|
+
import type { TrueFoundryMessageCustomMetadata } from "@truefoundry/assistant-ui-runtime";
|
|
170
471
|
|
|
171
472
|
function NestedSubAgentAssistantMessage() {
|
|
172
473
|
const custom = useAuiState(
|
|
@@ -194,10 +495,6 @@ function NestedSubAgentAssistantMessage() {
|
|
|
194
495
|
/>
|
|
195
496
|
```
|
|
196
497
|
|
|
197
|
-
For a collapsed tool-row header, cast the spawning `create_sub_agent` tool part’s `artifact` to `SubAgentArtifact` and read `subAgents[].title` (or `agentInfo.name`):
|
|
198
|
-
|
|
199
|
-
Alternative: register `defineToolkit({ create_sub_agent: ... })` — all sub-agents share that one system tool name.
|
|
200
|
-
|
|
201
498
|
See the [Multi-Agent Chat UI guide](https://www.assistant-ui.com/docs/tools/multi-agent).
|
|
202
499
|
|
|
203
500
|
## Tool approvals
|
|
@@ -237,7 +534,7 @@ import {
|
|
|
237
534
|
useTrueFoundryApprovals,
|
|
238
535
|
useTrueFoundryToolResponses,
|
|
239
536
|
useTrueFoundryMcpAuth,
|
|
240
|
-
} from "truefoundry
|
|
537
|
+
} from "@truefoundry/assistant-ui-runtime";
|
|
241
538
|
|
|
242
539
|
function ApprovalBar() {
|
|
243
540
|
const { pending, respond } = useTrueFoundryApprovals();
|
|
@@ -293,28 +590,13 @@ function McpAuthContinue() {
|
|
|
293
590
|
|
|
294
591
|
### Action hooks (any render context, including nested sub-agents)
|
|
295
592
|
|
|
296
|
-
```tsx
|
|
297
|
-
import { useTrueFoundryRespondToToolApproval } from "truefoundry-agents-assistant-ui-runtime";
|
|
298
|
-
|
|
299
|
-
function NestedToolApprovalButton({ approvalId }: { approvalId: string }) {
|
|
300
|
-
const respond = useTrueFoundryRespondToToolApproval();
|
|
301
|
-
return (
|
|
302
|
-
<button onClick={() => respond({ approvalId, approved: true })}>
|
|
303
|
-
Allow
|
|
304
|
-
</button>
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
The action-only hooks return a single callback you can call from any render context (root or nested sub-agent thread). All four follow the same pattern:
|
|
310
|
-
|
|
311
593
|
```tsx
|
|
312
594
|
import {
|
|
313
595
|
useTrueFoundryRespondToToolApproval,
|
|
314
596
|
useTrueFoundryRespondToToolResponse,
|
|
315
597
|
useTrueFoundryResumeMcpAuth,
|
|
316
598
|
useTrueFoundryCancel,
|
|
317
|
-
} from "truefoundry
|
|
599
|
+
} from "@truefoundry/assistant-ui-runtime";
|
|
318
600
|
|
|
319
601
|
const respondToApproval = useTrueFoundryRespondToToolApproval();
|
|
320
602
|
const respondToResponse = useTrueFoundryRespondToToolResponse();
|
|
@@ -331,170 +613,85 @@ void cancel();
|
|
|
331
613
|
|
|
332
614
|
| Hook | Returns | Description |
|
|
333
615
|
|------|---------|-------------|
|
|
334
|
-
| `useTrueFoundryApprovals()` | `{ pending
|
|
335
|
-
| `useTrueFoundryToolResponses()` | `{ pending
|
|
336
|
-
| `useTrueFoundryMcpAuth()` | `{ pending
|
|
337
|
-
| `useTrueFoundryRespondToToolApproval()` | `(r
|
|
338
|
-
| `useTrueFoundryRespondToToolResponse()` | `(r
|
|
339
|
-
| `useTrueFoundryResumeMcpAuth()` | `() => Promise<void>` | Resume
|
|
340
|
-
| `useTrueFoundryCancel()` | `() => Promise<void>` | Cancel the active turn.
|
|
341
|
-
| `
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
- `PendingApproval` = `{ approvalId, threadId, toolName, args, argsText }`
|
|
346
|
-
- `PendingToolResponse` = `{ toolCallId, threadId, toolName, args, argsText, question?, options? }`
|
|
347
|
-
- `RespondToToolApprovalOptions` = `{ approvalId, approved, optionId?, reason? }`
|
|
348
|
-
- `RespondToToolResponseOptions` = `{ toolCallId, content }`
|
|
349
|
-
|
|
350
|
-
> **Read vs. action hooks.** The three `use*Approvals` / `use*ToolResponses` / `use*McpAuth` read hooks subscribe to extras state and re-render when pending items change — use them in thread-level UI (e.g. an approval bar). The four action-only hooks (`useTrueFoundryRespondTo*`, `useTrueFoundryResumeMcpAuth`, `useTrueFoundryCancel`) read the action via `trueFoundryExtras.get(aui)` and do **not** subscribe to state, so they are safe to call from nested sub-agent renderers where only a readonly context is available.
|
|
616
|
+
| `useTrueFoundryApprovals()` | `{ pending, respond }` | Pending tool approvals plus a respond action. |
|
|
617
|
+
| `useTrueFoundryToolResponses()` | `{ pending, respond }` | Pending ask-user prompts plus a respond action. |
|
|
618
|
+
| `useTrueFoundryMcpAuth()` | `{ pending, resume }` | Pending MCP OAuth pause state plus a resume action. |
|
|
619
|
+
| `useTrueFoundryRespondToToolApproval()` | `(r) => void` | Respond to a tool approval from any render context. |
|
|
620
|
+
| `useTrueFoundryRespondToToolResponse()` | `(r) => void` | Respond to an ask-user prompt from any render context. |
|
|
621
|
+
| `useTrueFoundryResumeMcpAuth()` | `() => Promise<void>` | Resume after MCP OAuth. |
|
|
622
|
+
| `useTrueFoundryCancel()` | `() => Promise<void>` | Cancel the active turn. |
|
|
623
|
+
| `useTrueFoundryResetFromTurn()` | n/a | Re-submit a user turn (branch/reset). |
|
|
624
|
+
| `useTrueFoundryReload()` | n/a | Retry the current session load. |
|
|
625
|
+
| `useTrueFoundryHistoryPagination()` | `{ hasOlderHistory, isLoadingOlderHistory, loadOlderHistory }` | Scroll-up older history. |
|
|
351
626
|
|
|
352
627
|
### Low-level namespace
|
|
353
628
|
|
|
354
629
|
```tsx
|
|
355
|
-
import { trueFoundryExtras, type TrueFoundryRuntimeExtras } from "truefoundry
|
|
630
|
+
import { trueFoundryExtras, type TrueFoundryRuntimeExtras } from "@truefoundry/assistant-ui-runtime";
|
|
356
631
|
|
|
357
|
-
// Throws outside useTrueFoundryAgentRuntime:
|
|
358
632
|
const extras = trueFoundryExtras.use();
|
|
359
|
-
|
|
360
|
-
// Safe with fallback (returns default outside runtime):
|
|
361
633
|
const pending = trueFoundryExtras.use((e) => e.pendingApprovals, []);
|
|
362
634
|
```
|
|
363
635
|
|
|
364
|
-
`TrueFoundryRuntimeExtras` fields:
|
|
365
|
-
|
|
366
|
-
| Field | Type | Purpose |
|
|
367
|
-
|-------|------|---------|
|
|
368
|
-
| `pendingApprovals` | `PendingApproval[]` | Undecided tool approvals across all threads |
|
|
369
|
-
| `pendingToolResponses` | `PendingToolResponse[]` | Unanswered ask-user / client-side tool prompts |
|
|
370
|
-
| `pendingMcpAuth` | `{ mcpServers } \| null` | MCP OAuth pause state |
|
|
371
|
-
| `respondToToolApproval` | `(r: { approvalId, approved, reason? }) => void` | Stage approval; batch-send when complete |
|
|
372
|
-
| `respondToToolResponse` | `(r: { toolCallId, content }) => void` | Stage answer; batch-send when complete |
|
|
373
|
-
| `resumeMcpAuth` | `() => Promise<void>` | Resume after OAuth |
|
|
374
|
-
| `cancel` | `() => Promise<void>` | Cancel the active turn: calls `session.cancel()` and lets the stream drain to its terminal `turn.done` (reconciles on next session load) |
|
|
375
|
-
| `resetFromTurn` | `(turnId: string) => Promise<void>` | Re-submit a user turn (branch/reset) |
|
|
376
|
-
| `reload` | `() => void` | Retry the current session load |
|
|
377
|
-
| `downloadSandboxFile` | `(path: string) => Promise<Blob>` | Download a file from the session sandbox |
|
|
378
|
-
| `hasOlderHistory` | `boolean` | True when another older `listEvents` page is available |
|
|
379
|
-
| `isLoadingOlderHistory` | `boolean` | True while `loadOlderHistory` is in flight |
|
|
380
|
-
| `loadOlderHistory` | `() => Promise<void>` | Prepend the next older history window (scroll-up) |
|
|
381
|
-
| `draft` | `TrueFoundryDraftRuntimeExtras \| null` | Draft-mode agent spec sync extras |
|
|
382
|
-
|
|
383
|
-
Per-part `respondToApproval` from assistant-ui still works for root-thread tool UIs; extras complements that for global chrome and nested renderers.
|
|
384
|
-
|
|
385
636
|
## Cancellation
|
|
386
637
|
|
|
387
|
-
`cancel()`
|
|
388
|
-
|
|
389
|
-
(Hard aborts still happen when *switching away* — starting a new run or changing sessions abandons the previous turn.)
|
|
638
|
+
`cancel()` calls `server.cancelSession()` and then keeps consuming the active stream: the backend closes the SSE gracefully by emitting a terminal `turn.done` event before ending the stream. No explicit reconcile is performed — the cancelled turn is terminal, and local state reconciles against the authoritative event log on the next session load.
|
|
390
639
|
|
|
391
640
|
## Resumable streams
|
|
392
641
|
|
|
393
|
-
Works out of the box — no server route or Redis store. TrueFoundry persists every turn server-side; on reload or reconnect the runtime calls `
|
|
394
|
-
|
|
395
|
-
> **TODO:** Track the last ingested `sequenceNumber` and pass `afterSequenceNumber` on reconnect to avoid replaying already-seen events.
|
|
396
|
-
|
|
397
|
-
Contrast with the [AI SDK resumable streams guide](https://www.assistant-ui.com/docs/guides/resumable-streams), which requires a separate encoded-byte store.
|
|
642
|
+
Works out of the box — no server route or Redis store. TrueFoundry persists every turn server-side; on reload or reconnect the runtime calls `subscribeToTurn` and replays events into the fold (idempotent). Running turns are detected on session load and resumed automatically.
|
|
398
643
|
|
|
399
644
|
## History pagination
|
|
400
645
|
|
|
401
646
|
Thread open no longer drains every turn. Initial load:
|
|
402
647
|
|
|
403
|
-
1. `listTurns({ limit: 1 })` once — detect a running turn
|
|
648
|
+
1. `listTurns({ limit: 1 })` once — detect a running turn.
|
|
404
649
|
2. One (or a few) `listEvents` page(s) for the newest complete user-message group.
|
|
405
650
|
3. Clears `isLoading`, then resumes a running turn via subscribe if needed.
|
|
406
651
|
|
|
407
|
-
Older history is opt-in via
|
|
652
|
+
Older history is opt-in via `useTrueFoundryHistoryPagination()`:
|
|
408
653
|
|
|
409
654
|
```tsx
|
|
410
655
|
const { hasOlderHistory, isLoadingOlderHistory, loadOlderHistory } =
|
|
411
656
|
useTrueFoundryHistoryPagination();
|
|
412
657
|
|
|
413
|
-
// e.g. IntersectionObserver at the top of the message list
|
|
414
658
|
if (hasOlderHistory && !isLoadingOlderHistory) {
|
|
415
659
|
void loadOlderHistory();
|
|
416
660
|
}
|
|
417
661
|
```
|
|
418
662
|
|
|
419
|
-
`loadOlderHistory` prepends older turns without aborting an active stream.
|
|
420
|
-
|
|
421
|
-
## Public API
|
|
422
|
-
|
|
423
|
-
Everything below is exported from the package root (`truefoundry-agents-assistant-ui-runtime`).
|
|
424
|
-
|
|
425
|
-
| Export | Kind | Purpose |
|
|
426
|
-
|--------|------|---------|
|
|
427
|
-
| `useTrueFoundryAgentRuntime` | hook | Main entry point. Returns an assistant-ui runtime bound to gateway sessions. |
|
|
428
|
-
| `UseTrueFoundryAgentRuntimeOptions` | type | Options for the hook (see table above). |
|
|
429
|
-
| `useTrueFoundryApprovals` | hook | `{ pending, respond }` for tool approvals via extras. |
|
|
430
|
-
| `useTrueFoundryToolResponses` | hook | `{ pending, respond }` for ask-user / `tool.response_required` prompts. |
|
|
431
|
-
| `useTrueFoundryMcpAuth` | hook | `{ pending, resume }` for MCP OAuth pause/resume. |
|
|
432
|
-
| `useTrueFoundryRespondToToolApproval` | hook | Action callback via `trueFoundryExtras.get(aui)` — works in nested renderers. |
|
|
433
|
-
| `useTrueFoundryRespondToToolResponse` | hook | Same pattern for tool responses. |
|
|
434
|
-
| `useTrueFoundryResumeMcpAuth` | hook | Same pattern for MCP resume. |
|
|
435
|
-
| `useTrueFoundryCancel` | hook | Same pattern for cancel. |
|
|
436
|
-
| `useTrueFoundryHistoryPagination` | hook | `{ hasOlderHistory, isLoadingOlderHistory, loadOlderHistory }` for scroll-up history. |
|
|
437
|
-
| `trueFoundryExtras` | namespace | `createRuntimeExtras` channel — `.use()`, `.get(aui)`, `.provide()`. |
|
|
438
|
-
| `TrueFoundryRuntimeExtras` | type | Shape provided into the runtime extras slot. |
|
|
439
|
-
| `PendingApproval`, `PendingToolResponse` | types | Derived pending items for UI rendering. |
|
|
440
|
-
| `createTrueFoundryThreadListAdapter` | fn | Builds the cursor-paginated `RemoteThreadListAdapter` powering `<ThreadList>` (`list({ after })` → `nextCursor`). Used internally; exported for custom wiring. |
|
|
441
|
-
| `getSession` | fn | `(client, sessionId) => Promise<AgentSession>` convenience wrapper. |
|
|
442
|
-
| `convertTurnsToThreadMessages` | fn | Loads a session's turns and folds them into assistant-ui `ThreadMessage[]` (`ConvertTurnsResult`). |
|
|
443
|
-
| `buildTurnAssistantContent` | fn | Folds a single turn's events into assistant content parts. |
|
|
444
|
-
| `repositoryItemsFromMessages` | fn | Converts messages into `ExportedMessageRepositoryItem[]` for history export. |
|
|
445
|
-
| `getTurnMessageContent` | fn | Extracts the text payload from an `AppendMessage`. |
|
|
446
|
-
| `ConvertTurnsResult` | type | Result of `convertTurnsToThreadMessages` (`messages`, `foldState`, `runningTurn?`, `unstable_resume?`). |
|
|
447
|
-
| `collectApprovalInputs` | fn | Collects decided approvals from a message into `user.tool_approval` inputs. |
|
|
448
|
-
| `collectResponseInputs` | fn | Collects staged answers into `user.tool_response` inputs. |
|
|
449
|
-
| `collectRequiredActionInputs` | fn | Collects both approval + response inputs once nothing is pending. |
|
|
450
|
-
| `messageHasPendingApprovals` | fn | True if a message still has undecided tool approvals. |
|
|
451
|
-
| `messageHasPendingResponses` | fn | True if a message still has unanswered tool responses. |
|
|
452
|
-
| `messageHasPendingRequiredActions` | fn | True if either approvals or responses are still pending. |
|
|
453
|
-
| `findPausedAssistantMessage` | fn | Last assistant message in `requires-action` state. |
|
|
454
|
-
| `toTrueFoundryApprovalInputs` | fn | Applies an approval decision and returns gateway inputs. |
|
|
455
|
-
| `SubAgentArtifact`, `SubAgentCustomMetadata` | types | Shapes attached to sub-agent tool calls / nested messages. |
|
|
456
|
-
| `TrueFoundryMessageCustomMetadata` | type | Typed keys on `ThreadMessage.metadata.custom` written by this adapter. |
|
|
457
|
-
| `ROOT_THREAD_ID` | const | The literal `"main"` — the gateway's root thread id. |
|
|
458
|
-
|
|
459
663
|
## Architecture (source map)
|
|
460
664
|
|
|
461
|
-
For contributors
|
|
665
|
+
For contributors working inside this package. Source lives in `src/`; the published entry point is `dist/index.js` (built by `tsup`).
|
|
462
666
|
|
|
463
667
|
| File | Responsibility |
|
|
464
668
|
|------|----------------|
|
|
669
|
+
| `server/types.ts` | `AgentChatServer` + `AgentBuilderServer` interfaces, `Session`, `Turn`, `AgentSpec`, pagination types. |
|
|
670
|
+
| `server/events.ts` | Concrete turn/stream event types (`ModelMessageEvent`, `TurnCreatedEvent`, etc.). |
|
|
671
|
+
| `server/eventUtils.ts` | `isEventDelta()` + `mergeEventDelta()` — streaming delta merge logic. |
|
|
465
672
|
| `useTrueFoundryAgentRuntime.ts` | Public hook. Wires the external-store runtime, thread-list runtime, adapters, and extras. |
|
|
466
|
-
| `useTrueFoundryAgentMessages.ts` | Reactive `SessionSnapshot` store: load, stream ingestion, cancel, resume; derives `messages` via pure projection
|
|
673
|
+
| `useTrueFoundryAgentMessages.ts` | Reactive `SessionSnapshot` store: load, stream ingestion, cancel, resume; derives `messages` via pure projection. |
|
|
467
674
|
| `sessionSnapshot.ts` | `SessionSnapshot` shape, required-actions overlay, and immutable wrapper helpers. |
|
|
468
675
|
| `truefoundryExtras.ts` | `createRuntimeExtras` namespace and `TrueFoundryRuntimeExtras` type. |
|
|
469
676
|
| `hooks.ts` | Consumer hooks — read selectors + action callbacks via `.get(aui)`. |
|
|
470
677
|
| `collectPending.ts` | Derives `pendingApprovals`, `pendingToolResponses`, `pendingMcpAuth` from messages. |
|
|
471
678
|
| `requiredActionInputs.ts` | Combined gate + `collectRequiredActionInputs` for batched resume. |
|
|
472
|
-
| `truefoundryThreadListAdapter.ts` | `RemoteThreadListAdapter` — cursor-paginated session list
|
|
473
|
-
| `convertTurnMessages.ts` | `projectSessionMessages` pure projector; `
|
|
679
|
+
| `truefoundryThreadListAdapter.ts` | `RemoteThreadListAdapter` — cursor-paginated session list. |
|
|
680
|
+
| `convertTurnMessages.ts` | `projectSessionMessages` pure projector; `buildSnapshotFromSessionEvents` history ingest; stream-event aggregation. |
|
|
474
681
|
| `foldPeerThreads.ts` | `PeerThreadFoldState` — folds peer/sub-agent threads under their spawning tool call. |
|
|
475
|
-
| `
|
|
476
|
-
| `modelMessageContent.ts` | `model.message` events → assistant content parts (text, reasoning, tool calls). |
|
|
477
|
-
| `streamTurn.ts` | `streamTurnContent` / `resumeTurnStream` generators over `prepareTurn`/`stream`. |
|
|
682
|
+
| `streamTurn.ts` | `streamTurnContent` / `resumeTurnStream` generators over `AgentChatServer`. |
|
|
478
683
|
| `toolApproval.ts` | Approval state, decision mapping, and `user.tool_approval` input collection. |
|
|
479
684
|
| `toolResponse.ts` | Ask-user response state, staging, and `user.tool_response` input collection. |
|
|
480
|
-
| `
|
|
481
|
-
| `mcpAuth.ts` | MCP auth-required detection and structured authorize UI metadata. |
|
|
482
|
-
| `turnEventHelpers.ts` | Appends approval / response / MCP-auth status onto turn updates. |
|
|
483
|
-
| `createSubAgent.ts` | Detects the `create_sub_agent` system tool call. |
|
|
484
|
-
| `extractTurnUserText.ts` / `lastUserMessageText.ts` | Text extraction helpers. |
|
|
485
|
-
| `sessions.ts` | `getSession` wrapper. |
|
|
486
|
-
| `sessionListStartTimestamp.ts` | Default `listSessions` window (1 year). |
|
|
487
|
-
| `constants.ts` | `ROOT_THREAD_ID = "main"`. |
|
|
488
|
-
| `types.ts` / `turnStreamUpdate.ts` | Shared option and update types. |
|
|
685
|
+
| `listPages.ts` | `drainListPages` utility for exhausting token-paginated `ListResult` APIs. |
|
|
489
686
|
|
|
490
687
|
### Invariants
|
|
491
688
|
|
|
492
689
|
- One gateway **session** ⇄ one assistant-ui **thread** (`session.id` = thread `remoteId`).
|
|
493
690
|
- The root thread id is always `"main"` (`ROOT_THREAD_ID`); sub-agent threads nest beneath their `create_sub_agent` tool call.
|
|
494
|
-
- The runtime never holds credentials —
|
|
495
|
-
-
|
|
691
|
+
- The runtime never holds credentials — it only accepts a pre-built `AgentChatServer`.
|
|
692
|
+
- Event/turn types are defined in `src/server/events.ts` (first-party, no external SDK dependency).
|
|
496
693
|
- A paused turn's resume `input` must include **all** pending `user.tool_approval` and `user.tool_response` events across every thread in one batch.
|
|
497
|
-
-
|
|
694
|
+
- Two agent modes: **named** (`agentName`) and **draft** (`agent: { mode: "draft", defaultAgentSpec }`).
|
|
498
695
|
|
|
499
696
|
## Local development
|
|
500
697
|
|
|
@@ -506,7 +703,7 @@ pnpm test # vitest run
|
|
|
506
703
|
pnpm typecheck # tsc --noEmit
|
|
507
704
|
```
|
|
508
705
|
|
|
509
|
-
`dist/` is generated output and is gitignored. From the repo root, `pnpm build` builds this package
|
|
706
|
+
`dist/` is generated output and is gitignored. From the repo root, `pnpm build` builds this package.
|
|
510
707
|
|
|
511
708
|
## Unsupported assistant-ui features
|
|
512
709
|
|
|
@@ -514,22 +711,9 @@ Features below are not implemented in this adapter today. Other assistant-ui cap
|
|
|
514
711
|
|
|
515
712
|
| Feature | Notes |
|
|
516
713
|
|---------|-------|
|
|
517
|
-
| Attachment rendering | Attachments are forwarded to the gateway on send
|
|
714
|
+
| Attachment rendering | Attachments are forwarded to the gateway on send, but user message bubbles show text only. |
|
|
518
715
|
| Built-in `AttachmentAdapter` | Ships as `trueFoundryAttachmentAdapter` (opt-in via `adapters.attachments`). Not applied by default. |
|
|
519
|
-
| Speech
|
|
520
|
-
|
|
|
521
|
-
| Voice (`adapters.voice`) | Pass-through only. Not shipped. |
|
|
522
|
-
| Feedback (`adapters.feedback`) | Pass-through only. Ratings are not persisted to the gateway. |
|
|
523
|
-
| Message edit (`onEdit`) | Not wired. |
|
|
524
|
-
| Regenerate (`onReload`) | Not wired. |
|
|
525
|
-
| Message delete (`onDelete`) | Not wired. |
|
|
526
|
-
| Client-side tool results (`onAddToolResult`) | Not wired. |
|
|
527
|
-
| Tool call resume (`onResumeToolCall`) | Not wired. |
|
|
528
|
-
| Message queue (`queue`) | Not wired. |
|
|
529
|
-
| Branch switching | Not wired. |
|
|
716
|
+
| Speech / Dictation / Voice | Pass-through only. Not shipped. |
|
|
717
|
+
| Feedback | Pass-through only. Ratings are not persisted to the gateway. |
|
|
530
718
|
| Thread rename / archive / delete | Thread-list adapter no-ops. |
|
|
531
719
|
| Thread title generation | Returns an empty stream. |
|
|
532
|
-
| Generative UI message parts | Not mapped from gateway events. |
|
|
533
|
-
| Source citation parts | Not mapped from gateway events. |
|
|
534
|
-
| Message import / external state | `onImport`, `onExportExternalState`, `onLoadExternalState` not wired. |
|
|
535
|
-
| Composer suggestions | `suggestions` not populated. |
|