@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
|
@@ -7,7 +7,6 @@ import type {
|
|
|
7
7
|
ThreadUserMessagePart,
|
|
8
8
|
} from "@assistant-ui/core";
|
|
9
9
|
import type {
|
|
10
|
-
AgentSession,
|
|
11
10
|
McpAuthRequiredEvent,
|
|
12
11
|
Turn,
|
|
13
12
|
TurnCreatedEvent,
|
|
@@ -15,9 +14,11 @@ import type {
|
|
|
15
14
|
TurnEvent,
|
|
16
15
|
TurnInputItem,
|
|
17
16
|
TurnStreamData,
|
|
18
|
-
} from "
|
|
17
|
+
} from "./server/index.js";
|
|
18
|
+
import type { AgentChatServer } from "./server/types.js";
|
|
19
19
|
|
|
20
20
|
import { ROOT_THREAD_ID } from "./constants.js";
|
|
21
|
+
import { drainListPages } from "./listPages.js";
|
|
21
22
|
import { extractTurnUserText } from "./extractTurnUserText.js";
|
|
22
23
|
import {
|
|
23
24
|
buildRootAssistantContent,
|
|
@@ -260,22 +261,18 @@ function oldestCompleteTurnGroupState(
|
|
|
260
261
|
}
|
|
261
262
|
|
|
262
263
|
async function fetchSessionEventsPage(
|
|
263
|
-
|
|
264
|
+
server: AgentChatServer,
|
|
265
|
+
sessionId: string,
|
|
264
266
|
options?: FetchSessionEventsOptions & { pageToken?: string },
|
|
265
267
|
): Promise<SessionEventsPageResult> {
|
|
266
|
-
const page = await
|
|
268
|
+
const page = await server.listEvents({
|
|
269
|
+
sessionId,
|
|
267
270
|
limit: SESSION_EVENTS_PAGE_SIZE,
|
|
268
271
|
...(options?.lastTurnId != null ? { lastTurnId: options.lastTurnId } : {}),
|
|
269
272
|
...(options?.pageToken != null ? { pageToken: options.pageToken } : {}),
|
|
270
273
|
});
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
};
|
|
274
|
-
const olderPageToken = response.pagination?.nextPageToken;
|
|
275
|
-
const hasOlder =
|
|
276
|
-
typeof page.hasNextPage === "function"
|
|
277
|
-
? page.hasNextPage()
|
|
278
|
-
: olderPageToken != null && olderPageToken !== "";
|
|
274
|
+
const olderPageToken = page.nextPageToken;
|
|
275
|
+
const hasOlder = olderPageToken != null && olderPageToken !== "";
|
|
279
276
|
return {
|
|
280
277
|
itemsNewestFirst: page.data as GatewaySessionEventItem[],
|
|
281
278
|
...(olderPageToken != null && olderPageToken !== ""
|
|
@@ -290,7 +287,8 @@ async function fetchSessionEventsPage(
|
|
|
290
287
|
* turn group (or history is exhausted).
|
|
291
288
|
*/
|
|
292
289
|
async function fetchSessionEventsWindow(
|
|
293
|
-
|
|
290
|
+
server: AgentChatServer,
|
|
291
|
+
sessionId: string,
|
|
294
292
|
options?: FetchSessionEventsOptions & { pageToken?: string },
|
|
295
293
|
): Promise<{
|
|
296
294
|
itemsAsc: GatewaySessionEventItem[];
|
|
@@ -303,7 +301,7 @@ async function fetchSessionEventsWindow(
|
|
|
303
301
|
let hasOlder = false;
|
|
304
302
|
|
|
305
303
|
for (let pageCount = 0; pageCount < MAX_HISTORY_BOUNDARY_PAGES; pageCount++) {
|
|
306
|
-
const page = await fetchSessionEventsPage(
|
|
304
|
+
const page = await fetchSessionEventsPage(server, sessionId, {
|
|
307
305
|
...options,
|
|
308
306
|
...(pageToken != null ? { pageToken } : {}),
|
|
309
307
|
});
|
|
@@ -336,25 +334,27 @@ async function fetchSessionEventsWindow(
|
|
|
336
334
|
}
|
|
337
335
|
|
|
338
336
|
/**
|
|
339
|
-
* Fetches session-level events via `
|
|
337
|
+
* Fetches session-level events via `server.listEvents()`. The API returns pages
|
|
340
338
|
* in desc order (newest first); the collected array is reversed before returning
|
|
341
339
|
* so callers receive events in chronological (asc) order.
|
|
342
340
|
*
|
|
343
341
|
* Used by rewind/edit paths that need the full ancestor window.
|
|
344
342
|
*/
|
|
345
343
|
async function fetchAllSessionEvents(
|
|
346
|
-
|
|
344
|
+
server: AgentChatServer,
|
|
345
|
+
sessionId: string,
|
|
347
346
|
options?: FetchSessionEventsOptions,
|
|
348
347
|
): Promise<GatewaySessionEventItem[]> {
|
|
349
|
-
const items
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
348
|
+
const items = await drainListPages((pageToken) =>
|
|
349
|
+
server.listEvents({
|
|
350
|
+
sessionId,
|
|
351
|
+
limit: SESSION_EVENTS_PAGE_SIZE,
|
|
352
|
+
...(options?.lastTurnId != null ? { lastTurnId: options.lastTurnId } : {}),
|
|
353
|
+
...(pageToken != null ? { pageToken } : {}),
|
|
354
|
+
}),
|
|
355
|
+
);
|
|
356
356
|
items.reverse();
|
|
357
|
-
return items;
|
|
357
|
+
return items as GatewaySessionEventItem[];
|
|
358
358
|
}
|
|
359
359
|
|
|
360
360
|
function cloneThreadBucket(bucket: ThreadBucket): ThreadBucket {
|
|
@@ -545,16 +545,17 @@ function attachRunningTurn(
|
|
|
545
545
|
* update the UI progressively while the processing loop runs.
|
|
546
546
|
*/
|
|
547
547
|
export async function buildSnapshotFromSessionEvents(
|
|
548
|
-
|
|
548
|
+
server: AgentChatServer,
|
|
549
|
+
sessionId: string,
|
|
549
550
|
onProgress?: (snap: SessionSnapshot) => void,
|
|
550
551
|
): Promise<SessionSnapshot> {
|
|
551
552
|
// Detect a running turn with a single listTurns page — do not drain pagination.
|
|
552
|
-
const turnsPage = await
|
|
553
|
+
const turnsPage = await server.listTurns({ sessionId, limit: 1 });
|
|
553
554
|
const newestTurn = turnsPage.data[0] as Turn | undefined;
|
|
554
555
|
const runningTurn =
|
|
555
556
|
newestTurn?.state?.status === "running" ? newestTurn : undefined;
|
|
556
557
|
|
|
557
|
-
const window = await fetchSessionEventsWindow(
|
|
558
|
+
const window = await fetchSessionEventsWindow(server, sessionId);
|
|
558
559
|
const historyPagination: SessionHistoryPagination = {
|
|
559
560
|
hasOlder: window.hasOlder,
|
|
560
561
|
...(window.olderPageToken != null
|
|
@@ -578,7 +579,8 @@ export async function buildSnapshotFromSessionEvents(
|
|
|
578
579
|
* without tearing down live stream / pending UI state.
|
|
579
580
|
*/
|
|
580
581
|
export async function prependOlderSessionHistory(
|
|
581
|
-
|
|
582
|
+
server: AgentChatServer,
|
|
583
|
+
sessionId: string,
|
|
582
584
|
snapshot: SessionSnapshot,
|
|
583
585
|
): Promise<SessionSnapshot> {
|
|
584
586
|
const pagination = snapshot.historyPagination;
|
|
@@ -586,7 +588,7 @@ export async function prependOlderSessionHistory(
|
|
|
586
588
|
return snapshot;
|
|
587
589
|
}
|
|
588
590
|
|
|
589
|
-
const window = await fetchSessionEventsWindow(
|
|
591
|
+
const window = await fetchSessionEventsWindow(server, sessionId, {
|
|
590
592
|
pageToken: pagination.olderPageToken,
|
|
591
593
|
});
|
|
592
594
|
if (window.itemsAsc.length === 0) {
|
|
@@ -782,28 +784,46 @@ function buildAssistantMessage(
|
|
|
782
784
|
}
|
|
783
785
|
|
|
784
786
|
async function ingestTurnEventsIntoFold(
|
|
787
|
+
server: AgentChatServer,
|
|
788
|
+
sessionId: string,
|
|
789
|
+
turnId: string,
|
|
785
790
|
foldState: PeerThreadFoldState,
|
|
786
|
-
turn: Pick<Turn, "listEvents">,
|
|
787
791
|
): Promise<void> {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
792
|
+
if (server.listTurnEvents == null) {
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
const events = await drainListPages((pageToken) =>
|
|
796
|
+
server.listTurnEvents!({
|
|
797
|
+
sessionId,
|
|
798
|
+
turnId,
|
|
799
|
+
order: "asc",
|
|
800
|
+
limit: TURN_EVENTS_PAGE_SIZE,
|
|
801
|
+
...(pageToken != null ? { pageToken } : {}),
|
|
802
|
+
}),
|
|
803
|
+
);
|
|
804
|
+
for (const event of events) {
|
|
805
|
+
ingestTurnEvent(foldState, event as TurnEvent);
|
|
793
806
|
}
|
|
794
807
|
}
|
|
795
808
|
|
|
796
809
|
async function fetchTurnEvents(
|
|
797
|
-
|
|
810
|
+
server: AgentChatServer,
|
|
811
|
+
sessionId: string,
|
|
812
|
+
turnId: string,
|
|
798
813
|
): Promise<TurnEvent[]> {
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
order: "asc",
|
|
802
|
-
limit: TURN_EVENTS_PAGE_SIZE,
|
|
803
|
-
})) {
|
|
804
|
-
events.push(event);
|
|
814
|
+
if (server.listTurnEvents == null) {
|
|
815
|
+
return [];
|
|
805
816
|
}
|
|
806
|
-
|
|
817
|
+
const events = await drainListPages((pageToken) =>
|
|
818
|
+
server.listTurnEvents!({
|
|
819
|
+
sessionId,
|
|
820
|
+
turnId,
|
|
821
|
+
order: "asc",
|
|
822
|
+
limit: TURN_EVENTS_PAGE_SIZE,
|
|
823
|
+
...(pageToken != null ? { pageToken } : {}),
|
|
824
|
+
}),
|
|
825
|
+
);
|
|
826
|
+
return events as TurnEvent[];
|
|
807
827
|
}
|
|
808
828
|
|
|
809
829
|
function ingestCollectedEventsIntoFold(
|
|
@@ -816,6 +836,8 @@ function ingestCollectedEventsIntoFold(
|
|
|
816
836
|
}
|
|
817
837
|
|
|
818
838
|
async function fetchAllTurnEventsWithConcurrency(
|
|
839
|
+
server: AgentChatServer,
|
|
840
|
+
sessionId: string,
|
|
819
841
|
turns: Turn[],
|
|
820
842
|
concurrency: number,
|
|
821
843
|
): Promise<TurnEvent[][]> {
|
|
@@ -824,10 +846,12 @@ async function fetchAllTurnEventsWithConcurrency(
|
|
|
824
846
|
for (let i = 0; i < turns.length; i++) {
|
|
825
847
|
const idx = i;
|
|
826
848
|
const turn = turns[idx]!;
|
|
827
|
-
const p: Promise<void> = fetchTurnEvents(turn).then(
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
849
|
+
const p: Promise<void> = fetchTurnEvents(server, sessionId, turn.id).then(
|
|
850
|
+
(events) => {
|
|
851
|
+
results[idx] = events;
|
|
852
|
+
pool.delete(p);
|
|
853
|
+
},
|
|
854
|
+
);
|
|
831
855
|
pool.add(p);
|
|
832
856
|
if (pool.size >= concurrency) await Promise.race(pool);
|
|
833
857
|
}
|
|
@@ -1209,19 +1233,25 @@ function ingestTurnsIntoSnapshot(
|
|
|
1209
1233
|
}
|
|
1210
1234
|
|
|
1211
1235
|
export async function buildSnapshotFromSession(
|
|
1212
|
-
|
|
1236
|
+
server: AgentChatServer,
|
|
1237
|
+
sessionId: string,
|
|
1213
1238
|
concurrency: number = DEFAULT_LIST_EVENTS_CONCURRENCY,
|
|
1214
1239
|
): Promise<SessionSnapshot> {
|
|
1215
1240
|
// Completed history comes from session-level listEvents. The session API
|
|
1216
|
-
// excludes the running turn — hydrate that turn via
|
|
1241
|
+
// excludes the running turn — hydrate that turn via listTurnEvents so
|
|
1217
1242
|
// convertTurnsToThreadMessages still surfaces in-flight content.
|
|
1218
|
-
const snapshot = await buildSnapshotFromSessionEvents(
|
|
1243
|
+
const snapshot = await buildSnapshotFromSessionEvents(server, sessionId);
|
|
1219
1244
|
if (snapshot.runningTurn == null) {
|
|
1220
1245
|
return snapshot;
|
|
1221
1246
|
}
|
|
1222
1247
|
|
|
1223
1248
|
const turn = snapshot.runningTurn;
|
|
1224
|
-
const eventArrays = await fetchAllTurnEventsWithConcurrency(
|
|
1249
|
+
const eventArrays = await fetchAllTurnEventsWithConcurrency(
|
|
1250
|
+
server,
|
|
1251
|
+
sessionId,
|
|
1252
|
+
[turn],
|
|
1253
|
+
concurrency,
|
|
1254
|
+
);
|
|
1225
1255
|
ingestTurnsIntoSnapshot(snapshot, [turn], eventArrays);
|
|
1226
1256
|
|
|
1227
1257
|
// Hydrated in-flight content lives in `turns` / `fold` now — drop the
|
|
@@ -1236,23 +1266,31 @@ export async function buildSnapshotFromSession(
|
|
|
1236
1266
|
|
|
1237
1267
|
/** Rebuilds session state from turns strictly before `beforeTurnId` (excludes that turn). */
|
|
1238
1268
|
export async function buildSnapshotBeforeTurn(
|
|
1239
|
-
|
|
1269
|
+
server: AgentChatServer,
|
|
1270
|
+
sessionId: string,
|
|
1240
1271
|
beforeTurnId: string,
|
|
1241
1272
|
concurrency: number = DEFAULT_LIST_EVENTS_CONCURRENCY,
|
|
1242
1273
|
): Promise<SessionSnapshot> {
|
|
1243
|
-
const turns = await listSessionTurnsOrdered(
|
|
1274
|
+
const turns = await listSessionTurnsOrdered(server, sessionId);
|
|
1244
1275
|
|
|
1245
1276
|
const beforeIndex = turns.findIndex((turn) => turn.id === beforeTurnId);
|
|
1246
1277
|
if (beforeIndex === -1) {
|
|
1247
1278
|
throw new Error(`Turn ${beforeTurnId} not found in session`);
|
|
1248
1279
|
}
|
|
1249
1280
|
|
|
1250
|
-
return buildSnapshotBeforeTurnIndex(
|
|
1281
|
+
return buildSnapshotBeforeTurnIndex(
|
|
1282
|
+
server,
|
|
1283
|
+
sessionId,
|
|
1284
|
+
beforeIndex,
|
|
1285
|
+
concurrency,
|
|
1286
|
+
turns,
|
|
1287
|
+
);
|
|
1251
1288
|
}
|
|
1252
1289
|
|
|
1253
|
-
/** Rebuilds session state from the first `turnIndex`
|
|
1290
|
+
/** Rebuilds session state from the first `turnIndex` turns (excludes that turn). */
|
|
1254
1291
|
export async function buildSnapshotBeforeTurnIndex(
|
|
1255
|
-
|
|
1292
|
+
server: AgentChatServer,
|
|
1293
|
+
sessionId: string,
|
|
1256
1294
|
turnIndex: number,
|
|
1257
1295
|
_concurrency: number = DEFAULT_LIST_EVENTS_CONCURRENCY,
|
|
1258
1296
|
orderedTurns?: Turn[],
|
|
@@ -1261,7 +1299,7 @@ export async function buildSnapshotBeforeTurnIndex(
|
|
|
1261
1299
|
return createEmptySessionSnapshot();
|
|
1262
1300
|
}
|
|
1263
1301
|
|
|
1264
|
-
const turns = orderedTurns ?? (await listSessionTurnsOrdered(
|
|
1302
|
+
const turns = orderedTurns ?? (await listSessionTurnsOrdered(server, sessionId));
|
|
1265
1303
|
const turnsToInclude = turns.slice(0, turnIndex);
|
|
1266
1304
|
const lastTurnId = turnsToInclude.at(-1)?.id;
|
|
1267
1305
|
if (lastTurnId == null) {
|
|
@@ -1270,61 +1308,71 @@ export async function buildSnapshotBeforeTurnIndex(
|
|
|
1270
1308
|
|
|
1271
1309
|
// Anchor the session events window at the newest included turn so the
|
|
1272
1310
|
// ancestor chain matches `[turns[0], …, turns[turnIndex - 1]]`.
|
|
1273
|
-
const items = await fetchAllSessionEvents(
|
|
1311
|
+
const items = await fetchAllSessionEvents(server, sessionId, { lastTurnId });
|
|
1274
1312
|
const snapshot = createEmptySessionSnapshot();
|
|
1275
1313
|
ingestSessionEventsIntoSnapshot(snapshot, items);
|
|
1276
1314
|
return snapshot;
|
|
1277
1315
|
}
|
|
1278
1316
|
|
|
1279
|
-
/**
|
|
1317
|
+
/** Turn id to branch from when resubmitting at `turnIndex` (`"none"` for first turn). */
|
|
1280
1318
|
export async function resolveGatewayBranchPreviousTurnId(
|
|
1281
|
-
|
|
1319
|
+
server: AgentChatServer,
|
|
1320
|
+
sessionId: string,
|
|
1282
1321
|
turnIndex: number,
|
|
1283
1322
|
orderedTurns?: Turn[],
|
|
1284
1323
|
): Promise<string> {
|
|
1285
1324
|
if (turnIndex <= 0) {
|
|
1286
1325
|
return "none";
|
|
1287
1326
|
}
|
|
1288
|
-
const turns = orderedTurns ?? (await listSessionTurnsOrdered(
|
|
1327
|
+
const turns = orderedTurns ?? (await listSessionTurnsOrdered(server, sessionId));
|
|
1289
1328
|
return turns[turnIndex - 1]?.id ?? "none";
|
|
1290
1329
|
}
|
|
1291
1330
|
|
|
1292
1331
|
/** Resolves `previousTurnId` by turn id so partial history windows stay correct. */
|
|
1293
1332
|
export async function resolveGatewayBranchPreviousTurnIdForTurn(
|
|
1294
|
-
|
|
1333
|
+
server: AgentChatServer,
|
|
1334
|
+
sessionId: string,
|
|
1295
1335
|
turnId: string,
|
|
1296
1336
|
): Promise<string> {
|
|
1297
|
-
const turns = await listSessionTurnsOrdered(
|
|
1337
|
+
const turns = await listSessionTurnsOrdered(server, sessionId);
|
|
1298
1338
|
const turnIndex = turns.findIndex((turn) => turn.id === turnId);
|
|
1299
|
-
return resolveGatewayBranchPreviousTurnId(
|
|
1339
|
+
return resolveGatewayBranchPreviousTurnId(server, sessionId, turnIndex, turns);
|
|
1300
1340
|
}
|
|
1301
1341
|
|
|
1302
|
-
async function listSessionTurnsOrdered(
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1342
|
+
async function listSessionTurnsOrdered(
|
|
1343
|
+
server: AgentChatServer,
|
|
1344
|
+
sessionId: string,
|
|
1345
|
+
): Promise<Turn[]> {
|
|
1346
|
+
const turns = await drainListPages((pageToken) =>
|
|
1347
|
+
server.listTurns({
|
|
1348
|
+
sessionId,
|
|
1349
|
+
...(pageToken != null ? { pageToken } : {}),
|
|
1350
|
+
}),
|
|
1351
|
+
);
|
|
1307
1352
|
turns.reverse();
|
|
1308
1353
|
return turns;
|
|
1309
1354
|
}
|
|
1310
1355
|
|
|
1311
1356
|
export async function buildTurnAssistantContent(
|
|
1312
|
-
|
|
1357
|
+
server: AgentChatServer,
|
|
1358
|
+
sessionId: string,
|
|
1359
|
+
turn: Pick<Turn, "id" | "state">,
|
|
1313
1360
|
foldState?: PeerThreadFoldState,
|
|
1314
1361
|
): Promise<AssistantContentPart[]> {
|
|
1315
1362
|
const state = foldState ?? new PeerThreadFoldState();
|
|
1316
1363
|
const beforeCount =
|
|
1317
1364
|
state.threads.get(ROOT_THREAD_ID)?.modelMessageIds.length ?? 0;
|
|
1318
|
-
await ingestTurnEventsIntoFold(
|
|
1365
|
+
await ingestTurnEventsIntoFold(server, sessionId, turn.id, state);
|
|
1319
1366
|
const afterIds = state.threads.get(ROOT_THREAD_ID)?.modelMessageIds ?? [];
|
|
1320
1367
|
const rootModelMessageIds = afterIds.slice(beforeCount);
|
|
1321
1368
|
return buildTurnUpdateFromFold(state, turn, rootModelMessageIds).content;
|
|
1322
1369
|
}
|
|
1323
1370
|
|
|
1324
1371
|
export async function convertTurnsToThreadMessages(
|
|
1325
|
-
|
|
1372
|
+
server: AgentChatServer,
|
|
1373
|
+
sessionId: string,
|
|
1326
1374
|
): Promise<ConvertTurnsResult> {
|
|
1327
|
-
const snapshot = await buildSnapshotFromSession(
|
|
1375
|
+
const snapshot = await buildSnapshotFromSession(server, sessionId);
|
|
1328
1376
|
const messages = projectSessionMessages(snapshot);
|
|
1329
1377
|
|
|
1330
1378
|
return {
|
|
@@ -1456,14 +1504,14 @@ export function parseTurnIdFromMessageId(messageId: string): string {
|
|
|
1456
1504
|
return messageId.replace(/-user$/, "");
|
|
1457
1505
|
}
|
|
1458
1506
|
|
|
1459
|
-
/** Parent turn id for branching before `turnId`; `
|
|
1507
|
+
/** Parent turn id for branching before `turnId`; `null` when editing the first turn. */
|
|
1460
1508
|
export function resolveBranchPreviousTurnId(
|
|
1461
1509
|
turns: readonly SessionTurnRecord[],
|
|
1462
1510
|
turnId: string,
|
|
1463
|
-
): string {
|
|
1511
|
+
): string | null {
|
|
1464
1512
|
const turnIndex = turns.findIndex((turn) => turn.id === turnId);
|
|
1465
1513
|
if (turnIndex <= 0) {
|
|
1466
|
-
return
|
|
1514
|
+
return null;
|
|
1467
1515
|
}
|
|
1468
1516
|
return turns[turnIndex - 1]!.id;
|
|
1469
1517
|
}
|
|
@@ -1544,6 +1592,7 @@ export async function* streamTurnEvents(
|
|
|
1544
1592
|
stream: AsyncIterable<TurnStreamData>,
|
|
1545
1593
|
foldState: PeerThreadFoldState,
|
|
1546
1594
|
groupRootBaseline?: readonly string[],
|
|
1595
|
+
onTurnIdAvailable?: (turnId: string) => void,
|
|
1547
1596
|
): AsyncGenerator<TurnStreamUpdate> {
|
|
1548
1597
|
let pendingMcpAuth: McpAuthRequiredEvent | undefined;
|
|
1549
1598
|
let sandboxId: string | undefined;
|
|
@@ -1572,6 +1621,11 @@ export async function* streamTurnEvents(
|
|
|
1572
1621
|
for await (const data of stream) {
|
|
1573
1622
|
const event = data.event;
|
|
1574
1623
|
|
|
1624
|
+
if (event.type === "turn.created") {
|
|
1625
|
+
onTurnIdAvailable?.(event.turnId);
|
|
1626
|
+
continue;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1575
1629
|
if (event.type === "sandbox.created") {
|
|
1576
1630
|
sandboxId = event.sandboxId;
|
|
1577
1631
|
continue;
|
package/src/createSubAgent.ts
CHANGED
|
@@ -5,8 +5,7 @@ import {
|
|
|
5
5
|
resolveTrueFoundryAgentConfig,
|
|
6
6
|
resolveTrueFoundryAgentRuntimeOptions,
|
|
7
7
|
} from "./types.js";
|
|
8
|
-
import type {
|
|
9
|
-
import type { PrivateAgentSessionClient } from "truefoundry-gateway-sdk/agents/private";
|
|
8
|
+
import type { AgentChatServer } from "./server/index.js";
|
|
10
9
|
|
|
11
10
|
describe("resolveTrueFoundryAgentConfig", () => {
|
|
12
11
|
it("supports legacy agentName", () => {
|
|
@@ -42,27 +41,25 @@ describe("resolveTrueFoundryAgentConfig", () => {
|
|
|
42
41
|
});
|
|
43
42
|
|
|
44
43
|
describe("resolveTrueFoundryAgentRuntimeOptions", () => {
|
|
45
|
-
const
|
|
44
|
+
const server = {} as AgentChatServer;
|
|
46
45
|
|
|
47
|
-
it("
|
|
48
|
-
expect(() =>
|
|
49
|
-
resolveTrueFoundryAgentRuntimeOptions({
|
|
50
|
-
client,
|
|
51
|
-
agent: { mode: "draft", defaultAgentSpec: { model: { name: "x" } } },
|
|
52
|
-
}),
|
|
53
|
-
).toThrow(/privateClient/);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("accepts privateClient for draft mode", () => {
|
|
57
|
-
const privateClient = {} as PrivateAgentSessionClient;
|
|
46
|
+
it("accepts draft mode without a private client", () => {
|
|
58
47
|
const resolved = resolveTrueFoundryAgentRuntimeOptions({
|
|
59
|
-
|
|
60
|
-
privateClient,
|
|
48
|
+
server,
|
|
61
49
|
agent: { mode: "draft", defaultAgentSpec: { model: { name: "x" } } },
|
|
62
50
|
});
|
|
63
|
-
expect(resolved.
|
|
51
|
+
expect(resolved.server).toBe(server);
|
|
64
52
|
expect(resolved.agent.mode).toBe("draft");
|
|
65
53
|
});
|
|
54
|
+
|
|
55
|
+
it("resolves named mode with server", () => {
|
|
56
|
+
const resolved = resolveTrueFoundryAgentRuntimeOptions({
|
|
57
|
+
server,
|
|
58
|
+
agent: { mode: "named", agentName: "my-agent" },
|
|
59
|
+
});
|
|
60
|
+
expect(resolved.server).toBe(server);
|
|
61
|
+
expect(resolved.agent).toEqual({ mode: "named", agentName: "my-agent" });
|
|
62
|
+
});
|
|
66
63
|
});
|
|
67
64
|
|
|
68
65
|
describe("mergeAgentSpec", () => {
|
|
@@ -70,42 +67,42 @@ describe("mergeAgentSpec", () => {
|
|
|
70
67
|
const base = {
|
|
71
68
|
model: {
|
|
72
69
|
name: "anthropic/claude-sonnet-4-6",
|
|
73
|
-
params: { maxTokens: 1024,
|
|
70
|
+
params: { maxTokens: 1024, reasoningEffort: "medium" },
|
|
74
71
|
},
|
|
75
72
|
};
|
|
76
73
|
const next = mergeAgentSpec(base, {
|
|
77
|
-
model: { params: {
|
|
74
|
+
model: { params: { reasoningEffort: "high" } },
|
|
78
75
|
});
|
|
79
|
-
expect(next.model.params).toEqual({ maxTokens: 1024,
|
|
76
|
+
expect(next.model.params).toEqual({ maxTokens: 1024, reasoningEffort: "high" });
|
|
80
77
|
});
|
|
81
78
|
|
|
82
79
|
it("replaces mcpServers array wholesale", () => {
|
|
83
80
|
const base: AgentSpec = {
|
|
84
81
|
model: { name: "openai/gpt-4o" },
|
|
85
|
-
mcpServers: [{
|
|
82
|
+
mcpServers: [{ id: "github", name: "github" }],
|
|
86
83
|
};
|
|
87
84
|
const next = mergeAgentSpec(base, {
|
|
88
|
-
mcpServers: [{
|
|
85
|
+
mcpServers: [{ id: "slack", name: "slack" }],
|
|
89
86
|
});
|
|
90
|
-
expect(next.mcpServers).toEqual([{
|
|
87
|
+
expect(next.mcpServers).toEqual([{ id: "slack", name: "slack" }]);
|
|
91
88
|
});
|
|
92
89
|
|
|
93
90
|
it("replaces skills array wholesale", () => {
|
|
94
|
-
const base = {
|
|
91
|
+
const base: AgentSpec = {
|
|
95
92
|
model: { name: "openai/gpt-4o" },
|
|
96
|
-
skills: [{
|
|
93
|
+
skills: [{ id: "skill-a", name: "skill-a" }],
|
|
97
94
|
};
|
|
98
95
|
const next = mergeAgentSpec(base, {
|
|
99
|
-
skills: [{
|
|
96
|
+
skills: [{ id: "skill-b", name: "skill-b" }],
|
|
100
97
|
});
|
|
101
|
-
expect(next.skills).toEqual([{
|
|
98
|
+
expect(next.skills).toEqual([{ id: "skill-b", name: "skill-b" }]);
|
|
102
99
|
});
|
|
103
100
|
|
|
104
101
|
it("model partial update does not clear mcpServers or skills", () => {
|
|
105
102
|
const base: AgentSpec = {
|
|
106
103
|
model: { name: "openai/gpt-4o", params: { maxTokens: 1024 } },
|
|
107
|
-
mcpServers: [{
|
|
108
|
-
skills: [{
|
|
104
|
+
mcpServers: [{ id: "github", name: "github" }],
|
|
105
|
+
skills: [{ id: "skill-a", name: "skill-a" }],
|
|
109
106
|
};
|
|
110
107
|
const next = mergeAgentSpec(base, {
|
|
111
108
|
model: { name: "anthropic/claude-sonnet-4-6" },
|
package/src/foldPeerThreads.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { MessageStatus, ThreadMessage } from "@assistant-ui/core";
|
|
2
|
-
import type { ThreadCreatedEvent, ToolResponseRequiredEvent } from "truefoundry-gateway-sdk/agents";
|
|
3
2
|
import {
|
|
4
3
|
isEventDelta,
|
|
4
|
+
type ThreadCreatedEvent,
|
|
5
|
+
type ToolResponseRequiredEvent,
|
|
5
6
|
type TurnEvent,
|
|
6
7
|
type TurnStreamingEvent,
|
|
7
|
-
} from "
|
|
8
|
+
} from "./server/index.js";
|
|
8
9
|
|
|
9
10
|
import { parseAskUserQuestionArgs } from "./askUserQuestion.js";
|
|
10
11
|
import { isCreateSubAgentToolCall } from "./createSubAgent.js";
|
package/src/index.ts
CHANGED
|
@@ -12,15 +12,18 @@ export {
|
|
|
12
12
|
} from "./convertTurnMessages.js";
|
|
13
13
|
export type { ConvertTurnsResult, UserMessageContent } from "./convertTurnMessages.js";
|
|
14
14
|
export { ROOT_THREAD_ID } from "./constants.js";
|
|
15
|
-
export type {
|
|
15
|
+
export type {
|
|
16
|
+
UseTrueFoundryAgentRuntimeOptions,
|
|
17
|
+
NamedAgentConfig,
|
|
18
|
+
DraftAgentConfig,
|
|
19
|
+
TrueFoundryAgentConfig,
|
|
20
|
+
} from "./types.js";
|
|
16
21
|
export type { AgentSpec, AgentSpecUpdate, DraftSession } from "./private/agentSpec.js";
|
|
17
22
|
export { mergeAgentSpec, draftSessionTitle } from "./private/agentSpec.js";
|
|
18
23
|
export { createTrueFoundryDraftThreadListAdapter } from "./private/truefoundryDraftThreadListAdapter.js";
|
|
19
24
|
export { createTrueFoundryOwnedSessionsThreadListAdapter } from "./truefoundryOwnedSessionsThreadListAdapter.js";
|
|
20
25
|
export { createDraftSessionBridge } from "./private/draftSessionBridge.js";
|
|
21
26
|
export type { DraftSessionBridge } from "./private/draftSessionBridge.js";
|
|
22
|
-
export { PrivateAgentSessionClient } from "truefoundry-gateway-sdk/agents/private";
|
|
23
|
-
export type { AgentDraftSession } from "truefoundry-gateway-sdk/agents/private";
|
|
24
27
|
export {
|
|
25
28
|
useTrueFoundryAgentSpec,
|
|
26
29
|
useTrueFoundryUpdateAgentSpec,
|
|
@@ -35,7 +38,6 @@ export type {
|
|
|
35
38
|
ToolApprovalMessageCustomMetadata,
|
|
36
39
|
ToolResponseMessageCustomMetadata,
|
|
37
40
|
} from "./messageCustomMetadata.js";
|
|
38
|
-
export type { SandboxCreatedEvent } from "truefoundry-gateway-sdk/agents";
|
|
39
41
|
export type { PendingApproval, PendingToolResponse } from "./collectPending.js";
|
|
40
42
|
export type { TrueFoundryRuntimeExtras } from "./truefoundryExtras.js";
|
|
41
43
|
export { trueFoundryExtras } from "./truefoundryExtras.js";
|
|
@@ -71,3 +73,36 @@ export {
|
|
|
71
73
|
export { createTrueFoundryThreadListAdapter } from "./truefoundryThreadListAdapter.js";
|
|
72
74
|
export { getSession } from "./sessions.js";
|
|
73
75
|
export { trueFoundryAttachmentAdapter } from "./attachmentAdapter.js";
|
|
76
|
+
|
|
77
|
+
export type {
|
|
78
|
+
AgentChatServer,
|
|
79
|
+
AgentBuilderServer,
|
|
80
|
+
Session,
|
|
81
|
+
Turn,
|
|
82
|
+
TurnState,
|
|
83
|
+
TurnStateDone,
|
|
84
|
+
TurnInputItem,
|
|
85
|
+
AgentSpec as ServerAgentSpec,
|
|
86
|
+
ListResult,
|
|
87
|
+
CreateSessionRequest,
|
|
88
|
+
UpdateSessionRequest,
|
|
89
|
+
ListSessionsParams,
|
|
90
|
+
UserMessage,
|
|
91
|
+
UserToolApprovalEvent,
|
|
92
|
+
UserToolResponseEvent,
|
|
93
|
+
PreviousTurnIdInput,
|
|
94
|
+
} from "./server/index.js";
|
|
95
|
+
export type {
|
|
96
|
+
SandboxCreatedEvent,
|
|
97
|
+
McpAuthRequiredEvent,
|
|
98
|
+
ModelMessageEvent,
|
|
99
|
+
TurnEvent,
|
|
100
|
+
TurnStreamingEvent,
|
|
101
|
+
TurnStreamData,
|
|
102
|
+
SessionEventItem,
|
|
103
|
+
ToolCall,
|
|
104
|
+
ThreadCreatedEvent,
|
|
105
|
+
ToolApprovalRequiredEvent,
|
|
106
|
+
ToolResponseRequiredEvent,
|
|
107
|
+
} from "./server/index.js";
|
|
108
|
+
export { isEventDelta, mergeEventDelta } from "./server/index.js";
|
package/src/listPages.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ListResult } from "./server/types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Drains all pages of a token-paginated list into a single array.
|
|
5
|
+
* Newest-first APIs should reverse after calling this.
|
|
6
|
+
*/
|
|
7
|
+
export async function drainListPages<T>(
|
|
8
|
+
fetchPage: (pageToken?: string) => Promise<ListResult<T>>,
|
|
9
|
+
): Promise<T[]> {
|
|
10
|
+
const items: T[] = [];
|
|
11
|
+
let pageToken: string | undefined;
|
|
12
|
+
for (;;) {
|
|
13
|
+
const page = await fetchPage(pageToken);
|
|
14
|
+
items.push(...page.data);
|
|
15
|
+
if (page.nextPageToken == null || page.nextPageToken === "") {
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
pageToken = page.nextPageToken;
|
|
19
|
+
}
|
|
20
|
+
return items;
|
|
21
|
+
}
|