@assistant-ui/react-langchain 0.0.31 → 0.0.33
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/dist/attachSubagentTranscripts.d.ts +19 -12
- package/dist/attachSubagentTranscripts.d.ts.map +1 -1
- package/dist/attachSubagentTranscripts.js +8 -4
- package/dist/attachSubagentTranscripts.js.map +1 -1
- package/dist/convertMessages.d.ts +8 -11
- package/dist/convertMessages.d.ts.map +1 -1
- package/dist/convertMessages.js +53 -15
- package/dist/convertMessages.js.map +1 -1
- package/dist/converter.d.ts +137 -138
- package/dist/converter.d.ts.map +1 -1
- package/dist/converter.js +2 -1
- package/dist/converter.js.map +1 -1
- package/dist/findForkCheckpointInHistory.d.ts +17 -19
- package/dist/findForkCheckpointInHistory.d.ts.map +1 -1
- package/dist/hooks.d.ts +19 -22
- package/dist/hooks.d.ts.map +1 -1
- package/dist/index.d.ts +7 -8
- package/dist/index.d.ts.map +1 -0
- package/dist/resolveForkCheckpoint.d.ts +5 -7
- package/dist/resolveForkCheckpoint.d.ts.map +1 -1
- package/dist/runtimeExtras.d.ts +2 -5
- package/dist/runtimeExtras.d.ts.map +1 -1
- package/dist/sdkIdentity.d.ts +2 -5
- package/dist/sdkIdentity.d.ts.map +1 -1
- package/dist/sdkIdentity.js +1 -1
- package/dist/streamingTiming.d.ts +4 -7
- package/dist/streamingTiming.d.ts.map +1 -1
- package/dist/subagentMessagesProjection.d.ts +17 -0
- package/dist/subagentMessagesProjection.d.ts.map +1 -0
- package/dist/subagentMessagesProjection.js +40 -0
- package/dist/subagentMessagesProjection.js.map +1 -0
- package/dist/types.d.ts +106 -109
- package/dist/types.d.ts.map +1 -1
- package/dist/uiMessages.d.ts +37 -10
- package/dist/uiMessages.d.ts.map +1 -1
- package/dist/uiMessages.js +55 -5
- package/dist/uiMessages.js.map +1 -1
- package/dist/useStreamRuntime.d.ts +9 -12
- package/dist/useStreamRuntime.d.ts.map +1 -1
- package/dist/useStreamRuntime.js +102 -28
- package/dist/useStreamRuntime.js.map +1 -1
- package/dist/useSubagentTranscripts.d.ts +5 -8
- package/dist/useSubagentTranscripts.d.ts.map +1 -1
- package/dist/useSubagentTranscripts.js +129 -34
- package/dist/useSubagentTranscripts.js.map +1 -1
- package/package.json +16 -11
- package/src/attachSubagentTranscripts.test.ts +13 -5
- package/src/attachSubagentTranscripts.ts +26 -7
- package/src/convertMessages.test.ts +69 -0
- package/src/convertMessages.ts +46 -8
- package/src/converter.ts +6 -0
- package/src/subagentMessagesProjection.test.ts +175 -0
- package/src/subagentMessagesProjection.ts +50 -0
- package/src/uiMessages.test.ts +149 -0
- package/src/uiMessages.ts +79 -4
- package/src/useStreamRuntime.test.tsx +212 -0
- package/src/useStreamRuntime.ts +202 -50
- package/src/useStreamRuntime.voice.test.tsx +712 -0
- package/src/useSubagentTranscripts.test.tsx +795 -52
- package/src/useSubagentTranscripts.ts +269 -50
|
@@ -1015,6 +1015,8 @@ describe("useStreamRuntime subagent transcripts", () => {
|
|
|
1015
1015
|
status: "running",
|
|
1016
1016
|
parentId: null,
|
|
1017
1017
|
depth: 1,
|
|
1018
|
+
startedAt: new Date(1_000),
|
|
1019
|
+
completedAt: null,
|
|
1018
1020
|
},
|
|
1019
1021
|
],
|
|
1020
1022
|
]);
|
|
@@ -1069,4 +1071,214 @@ describe("useStreamRuntime subagent transcripts", () => {
|
|
|
1069
1071
|
|
|
1070
1072
|
expect(nestedTranscript()).toBe(rendered);
|
|
1071
1073
|
});
|
|
1074
|
+
|
|
1075
|
+
it("keeps messages and transcripts across equal copies of the UI state", async () => {
|
|
1076
|
+
const stream = createMockStream([
|
|
1077
|
+
message("human-1", "human", "delegate"),
|
|
1078
|
+
{
|
|
1079
|
+
id: "root-ai",
|
|
1080
|
+
_getType: () => "ai",
|
|
1081
|
+
content: "",
|
|
1082
|
+
tool_calls: [{ id: "task-one", name: "task", args: {} }],
|
|
1083
|
+
},
|
|
1084
|
+
]);
|
|
1085
|
+
const transcript = [message("nested-ai", "ai", "nested answer")];
|
|
1086
|
+
stream.subagents = new Map([
|
|
1087
|
+
[
|
|
1088
|
+
"task-one",
|
|
1089
|
+
{
|
|
1090
|
+
id: "task-one",
|
|
1091
|
+
namespace: ["tools:task-one"],
|
|
1092
|
+
status: "running",
|
|
1093
|
+
parentId: null,
|
|
1094
|
+
depth: 1,
|
|
1095
|
+
startedAt: new Date(1_000),
|
|
1096
|
+
completedAt: null,
|
|
1097
|
+
},
|
|
1098
|
+
],
|
|
1099
|
+
]);
|
|
1100
|
+
stream[streamController]!.registry.acquire.mockReturnValue({
|
|
1101
|
+
store: { getSnapshot: () => transcript, subscribe: () => () => {} },
|
|
1102
|
+
release: vi.fn(),
|
|
1103
|
+
});
|
|
1104
|
+
const uiState = (points: number[]) => [
|
|
1105
|
+
{
|
|
1106
|
+
type: "ui",
|
|
1107
|
+
id: "ui-root",
|
|
1108
|
+
name: "chart",
|
|
1109
|
+
props: { points },
|
|
1110
|
+
metadata: { message_id: "root-ai" },
|
|
1111
|
+
},
|
|
1112
|
+
{
|
|
1113
|
+
type: "ui",
|
|
1114
|
+
id: "ui-nested",
|
|
1115
|
+
name: "chart",
|
|
1116
|
+
props: { points },
|
|
1117
|
+
metadata: { message_id: "nested-ai" },
|
|
1118
|
+
},
|
|
1119
|
+
];
|
|
1120
|
+
stream.values = { ui: uiState([1, 2]) };
|
|
1121
|
+
const { auiResult, rerender } = renderAui(stream);
|
|
1122
|
+
const nestedTranscript = () => {
|
|
1123
|
+
const { messages } = auiResult.current.thread.getState();
|
|
1124
|
+
for (const threadMessage of messages) {
|
|
1125
|
+
for (const part of threadMessage.content) {
|
|
1126
|
+
if (part.type === "tool-call" && part.toolCallId === "task-one")
|
|
1127
|
+
return part.messages;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
return undefined;
|
|
1131
|
+
};
|
|
1132
|
+
|
|
1133
|
+
await waitFor(() =>
|
|
1134
|
+
expect(nestedTranscript()?.[0]?.content).toMatchObject([
|
|
1135
|
+
{ type: "text", text: "nested answer" },
|
|
1136
|
+
{ type: "data", name: "chart", data: { points: [1, 2] } },
|
|
1137
|
+
]),
|
|
1138
|
+
);
|
|
1139
|
+
const [human, ai] = auiResult.current.thread.getState().messages;
|
|
1140
|
+
expect(ai?.content).toMatchObject([
|
|
1141
|
+
{ type: "tool-call", toolCallId: "task-one" },
|
|
1142
|
+
{ type: "data", name: "chart", data: { points: [1, 2] } },
|
|
1143
|
+
]);
|
|
1144
|
+
const rendered = nestedTranscript();
|
|
1145
|
+
|
|
1146
|
+
for (let i = 0; i < 3; i++) {
|
|
1147
|
+
stream.values = { ui: uiState([1, 2]) };
|
|
1148
|
+
await act(async () => {
|
|
1149
|
+
rerender();
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
const messages = auiResult.current.thread.getState().messages;
|
|
1154
|
+
expect(messages[0]).toBe(human);
|
|
1155
|
+
expect(messages[1]).toBe(ai);
|
|
1156
|
+
expect(nestedTranscript()).toBe(rendered);
|
|
1157
|
+
|
|
1158
|
+
stream.values = { ui: uiState([1, 2, 3]) };
|
|
1159
|
+
await act(async () => {
|
|
1160
|
+
rerender();
|
|
1161
|
+
});
|
|
1162
|
+
|
|
1163
|
+
expect(
|
|
1164
|
+
auiResult.current.thread.getState().messages[1]?.content,
|
|
1165
|
+
).toMatchObject([
|
|
1166
|
+
{ type: "tool-call", toolCallId: "task-one" },
|
|
1167
|
+
{ type: "data", name: "chart", data: { points: [1, 2, 3] } },
|
|
1168
|
+
]);
|
|
1169
|
+
expect(nestedTranscript()?.[0]?.content).toMatchObject([
|
|
1170
|
+
{ type: "text", text: "nested answer" },
|
|
1171
|
+
{ type: "data", name: "chart", data: { points: [1, 2, 3] } },
|
|
1172
|
+
]);
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
it("keeps messages and transcripts when custom events carry no UI update", async () => {
|
|
1176
|
+
const stream = createMockStream([
|
|
1177
|
+
message("human-1", "human", "delegate"),
|
|
1178
|
+
{
|
|
1179
|
+
id: "root-ai",
|
|
1180
|
+
_getType: () => "ai",
|
|
1181
|
+
content: "",
|
|
1182
|
+
tool_calls: [{ id: "task-one", name: "task", args: {} }],
|
|
1183
|
+
},
|
|
1184
|
+
]);
|
|
1185
|
+
const transcript = [message("nested-ai", "ai", "nested answer")];
|
|
1186
|
+
stream.subagents = new Map([
|
|
1187
|
+
[
|
|
1188
|
+
"task-one",
|
|
1189
|
+
{
|
|
1190
|
+
id: "task-one",
|
|
1191
|
+
namespace: ["tools:task-one"],
|
|
1192
|
+
status: "running",
|
|
1193
|
+
parentId: null,
|
|
1194
|
+
depth: 1,
|
|
1195
|
+
startedAt: new Date(1_000),
|
|
1196
|
+
completedAt: null,
|
|
1197
|
+
},
|
|
1198
|
+
],
|
|
1199
|
+
]);
|
|
1200
|
+
stream[streamController]!.registry.acquire.mockReturnValue({
|
|
1201
|
+
store: { getSnapshot: () => transcript, subscribe: () => () => {} },
|
|
1202
|
+
release: vi.fn(),
|
|
1203
|
+
});
|
|
1204
|
+
const chartEvent = (
|
|
1205
|
+
props: Record<string, unknown>,
|
|
1206
|
+
metadata: Record<string, unknown> = {},
|
|
1207
|
+
) => ({
|
|
1208
|
+
method: "custom",
|
|
1209
|
+
params: {
|
|
1210
|
+
namespace: ["tools:task-one"],
|
|
1211
|
+
data: {
|
|
1212
|
+
type: "ui",
|
|
1213
|
+
id: "ui-1",
|
|
1214
|
+
name: "chart",
|
|
1215
|
+
props,
|
|
1216
|
+
metadata: { message_id: "nested-ai", ...metadata },
|
|
1217
|
+
},
|
|
1218
|
+
},
|
|
1219
|
+
});
|
|
1220
|
+
const events = [
|
|
1221
|
+
chartEvent({ points: [1, 2] }),
|
|
1222
|
+
chartEvent({ label: "first" }, { merge: true }),
|
|
1223
|
+
];
|
|
1224
|
+
mockUseChannel.mockReturnValue(events);
|
|
1225
|
+
const { auiResult, rerender } = renderAui(stream);
|
|
1226
|
+
const nestedTranscript = () => {
|
|
1227
|
+
const { messages } = auiResult.current.thread.getState();
|
|
1228
|
+
for (const threadMessage of messages) {
|
|
1229
|
+
for (const part of threadMessage.content) {
|
|
1230
|
+
if (part.type === "tool-call" && part.toolCallId === "task-one")
|
|
1231
|
+
return part.messages;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
return undefined;
|
|
1235
|
+
};
|
|
1236
|
+
|
|
1237
|
+
await waitFor(() =>
|
|
1238
|
+
expect(nestedTranscript()?.[0]?.content).toMatchObject([
|
|
1239
|
+
{ type: "text", text: "nested answer" },
|
|
1240
|
+
{
|
|
1241
|
+
type: "data",
|
|
1242
|
+
name: "chart",
|
|
1243
|
+
data: { points: [1, 2], label: "first" },
|
|
1244
|
+
},
|
|
1245
|
+
]),
|
|
1246
|
+
);
|
|
1247
|
+
const [human, ai] = auiResult.current.thread.getState().messages;
|
|
1248
|
+
const rendered = nestedTranscript();
|
|
1249
|
+
|
|
1250
|
+
const withProgress = [
|
|
1251
|
+
...events,
|
|
1252
|
+
{
|
|
1253
|
+
method: "custom",
|
|
1254
|
+
params: { namespace: [], data: { name: "progress", payload: 1 } },
|
|
1255
|
+
},
|
|
1256
|
+
];
|
|
1257
|
+
mockUseChannel.mockReturnValue(withProgress);
|
|
1258
|
+
await act(async () => {
|
|
1259
|
+
rerender();
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
const messages = auiResult.current.thread.getState().messages;
|
|
1263
|
+
expect(messages[0]).toBe(human);
|
|
1264
|
+
expect(messages[1]).toBe(ai);
|
|
1265
|
+
expect(nestedTranscript()).toBe(rendered);
|
|
1266
|
+
|
|
1267
|
+
mockUseChannel.mockReturnValue([
|
|
1268
|
+
...withProgress,
|
|
1269
|
+
chartEvent({ label: "second" }, { merge: true }),
|
|
1270
|
+
]);
|
|
1271
|
+
await act(async () => {
|
|
1272
|
+
rerender();
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
expect(nestedTranscript()?.[0]?.content).toMatchObject([
|
|
1276
|
+
{ type: "text", text: "nested answer" },
|
|
1277
|
+
{
|
|
1278
|
+
type: "data",
|
|
1279
|
+
name: "chart",
|
|
1280
|
+
data: { points: [1, 2], label: "second" },
|
|
1281
|
+
},
|
|
1282
|
+
]);
|
|
1283
|
+
});
|
|
1072
1284
|
});
|
package/src/useStreamRuntime.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/// <reference types="@assistant-ui/core/store" />
|
|
1
|
+
/// <reference types="@assistant-ui/core/store" preserve="true" />
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
4
|
import {
|
|
@@ -19,6 +19,7 @@ import type { ThreadMessage } from "@assistant-ui/core";
|
|
|
19
19
|
import {
|
|
20
20
|
createCloudThreadListAdapterCreateFallback,
|
|
21
21
|
createToolCallCancellationStub,
|
|
22
|
+
getThreadMessageText,
|
|
22
23
|
scanPendingToolCalls,
|
|
23
24
|
} from "@assistant-ui/core/internal";
|
|
24
25
|
import {
|
|
@@ -29,14 +30,13 @@ import {
|
|
|
29
30
|
} from "@assistant-ui/core/react";
|
|
30
31
|
import { useAui, useAuiState } from "@assistant-ui/store";
|
|
31
32
|
import { STREAM_CONTROLLER, useChannel, useStream } from "@langchain/react";
|
|
32
|
-
import type { Channel } from "@langchain/react";
|
|
33
33
|
import type {
|
|
34
34
|
LangChainBaseMessage,
|
|
35
35
|
LangChainToolCall,
|
|
36
36
|
UIMessage,
|
|
37
37
|
UseStreamRuntimeOptions,
|
|
38
38
|
} from "./types";
|
|
39
|
-
import { groupUIMessagesByParent } from "./converter";
|
|
39
|
+
import { getMessageModality, groupUIMessagesByParent } from "./converter";
|
|
40
40
|
export { groupUIMessagesByParent } from "./converter";
|
|
41
41
|
import {
|
|
42
42
|
convertLangChainBaseMessage,
|
|
@@ -48,14 +48,19 @@ import {
|
|
|
48
48
|
createAttachMemo,
|
|
49
49
|
} from "./attachSubagentTranscripts";
|
|
50
50
|
import { useSubagentTranscripts } from "./useSubagentTranscripts";
|
|
51
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
createUIFoldMemo,
|
|
53
|
+
createUISnapshotMemo,
|
|
54
|
+
foldUIUpdates,
|
|
55
|
+
mergeUIMessages,
|
|
56
|
+
reconcileUISnapshot,
|
|
57
|
+
UI_CUSTOM_CHANNELS,
|
|
58
|
+
} from "./uiMessages";
|
|
52
59
|
import { langChainExtras } from "./runtimeExtras";
|
|
53
60
|
import { resolveForkCheckpoint } from "./resolveForkCheckpoint";
|
|
54
61
|
import { useLangChainStreamingTiming } from "./streamingTiming";
|
|
55
62
|
import { LANGCHAIN_SDK } from "./sdkIdentity";
|
|
56
63
|
|
|
57
|
-
const UI_CUSTOM_CHANNELS: readonly Channel[] = ["custom"];
|
|
58
|
-
|
|
59
64
|
export const runConfigToSubmitOptions = (
|
|
60
65
|
runConfig: AppendMessage["runConfig"],
|
|
61
66
|
) =>
|
|
@@ -92,6 +97,15 @@ const toStagedHumanMessage = (
|
|
|
92
97
|
content: getMessageContent(msg),
|
|
93
98
|
});
|
|
94
99
|
|
|
100
|
+
const toStagedMessageInput = (message: LangChainBaseMessage) => ({
|
|
101
|
+
id: message.id,
|
|
102
|
+
type: getMessageType(message) === "ai" ? ("ai" as const) : ("human" as const),
|
|
103
|
+
content: message.content,
|
|
104
|
+
...(message.additional_kwargs && {
|
|
105
|
+
additional_kwargs: message.additional_kwargs,
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
|
|
95
109
|
const humanContentText = (content: LangChainBaseMessage["content"]) => {
|
|
96
110
|
if (typeof content === "string") return content;
|
|
97
111
|
if (!Array.isArray(content)) return "";
|
|
@@ -164,12 +178,17 @@ const useStreamThreadRuntime = (
|
|
|
164
178
|
);
|
|
165
179
|
const effectiveIsRunning = stream.isLoading || hasExecutingTools;
|
|
166
180
|
|
|
167
|
-
const
|
|
181
|
+
const [uiSnapshotMemo] = useState(createUISnapshotMemo);
|
|
182
|
+
const uiStateValue = reconcileUISnapshot(
|
|
183
|
+
stream.values[uiStateKey],
|
|
184
|
+
uiSnapshotMemo,
|
|
185
|
+
);
|
|
168
186
|
|
|
169
187
|
const customEvents = useChannel(stream, UI_CUSTOM_CHANNELS);
|
|
188
|
+
const [uiFoldMemo] = useState(createUIFoldMemo);
|
|
170
189
|
const liveUiMessages = useMemo(
|
|
171
|
-
() => foldUIUpdates(customEvents),
|
|
172
|
-
[customEvents],
|
|
190
|
+
() => foldUIUpdates(customEvents, uiFoldMemo),
|
|
191
|
+
[customEvents, uiFoldMemo],
|
|
173
192
|
);
|
|
174
193
|
|
|
175
194
|
const mergedUiMessages = useMemo(
|
|
@@ -302,6 +321,7 @@ const useStreamThreadRuntime = (
|
|
|
302
321
|
runConfig: AppendMessage["runConfig"];
|
|
303
322
|
reconcileOnEcho: boolean;
|
|
304
323
|
baseMessageCount: number;
|
|
324
|
+
transcriptStatus?: "unsent" | "sent";
|
|
305
325
|
}
|
|
306
326
|
>(),
|
|
307
327
|
);
|
|
@@ -357,19 +377,121 @@ const useStreamThreadRuntime = (
|
|
|
357
377
|
}, [stream.messages]);
|
|
358
378
|
|
|
359
379
|
const getStagedRun = (parentId: string | null) => {
|
|
360
|
-
|
|
380
|
+
const parent = parentId
|
|
381
|
+
? stagedMessagesRef.current.get(parentId)
|
|
382
|
+
: undefined;
|
|
383
|
+
if (!parent || parent.transcriptStatus === "sent") return null;
|
|
361
384
|
|
|
362
385
|
const staged: LangChainBaseMessage[] = [];
|
|
363
386
|
for (const message of visibleMessagesRef.current) {
|
|
364
|
-
|
|
365
|
-
|
|
387
|
+
const entry = message.id
|
|
388
|
+
? stagedMessagesRef.current.get(message.id)
|
|
389
|
+
: undefined;
|
|
390
|
+
if (entry && entry.transcriptStatus !== "sent") {
|
|
391
|
+
staged.push(entry.message);
|
|
366
392
|
}
|
|
367
393
|
if (message.id === parentId) break;
|
|
368
394
|
}
|
|
369
395
|
|
|
370
396
|
return {
|
|
371
397
|
messages: staged,
|
|
372
|
-
runConfig:
|
|
398
|
+
runConfig: parent.runConfig,
|
|
399
|
+
};
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const appendVoiceTranscript = (message: ThreadMessage) => {
|
|
403
|
+
const transcript = {
|
|
404
|
+
id: message.id,
|
|
405
|
+
_getType: () => (message.role === "assistant" ? "ai" : "human"),
|
|
406
|
+
content: getThreadMessageText(message),
|
|
407
|
+
...(message.metadata.modality && {
|
|
408
|
+
additional_kwargs: { modality: message.metadata.modality },
|
|
409
|
+
}),
|
|
410
|
+
};
|
|
411
|
+
stagedMessagesRef.current.set(transcript.id, {
|
|
412
|
+
message: transcript,
|
|
413
|
+
runConfig: undefined,
|
|
414
|
+
reconcileOnEcho: false,
|
|
415
|
+
baseMessageCount: streamRef.current.messages.length,
|
|
416
|
+
transcriptStatus: "unsent",
|
|
417
|
+
});
|
|
418
|
+
const nextMessages = [...visibleMessagesRef.current, transcript];
|
|
419
|
+
visibleMessagesRef.current = nextMessages;
|
|
420
|
+
setStagedMessages(nextMessages);
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
const getUnsentTranscripts = () =>
|
|
424
|
+
visibleMessagesRef.current.filter(
|
|
425
|
+
(message) =>
|
|
426
|
+
message.id !== undefined &&
|
|
427
|
+
stagedMessagesRef.current.get(message.id)?.transcriptStatus ===
|
|
428
|
+
"unsent",
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
const setTranscriptStatus = (
|
|
432
|
+
messages: readonly LangChainBaseMessage[],
|
|
433
|
+
status: "unsent" | "sent",
|
|
434
|
+
) => {
|
|
435
|
+
for (const message of messages) {
|
|
436
|
+
const staged = message.id
|
|
437
|
+
? stagedMessagesRef.current.get(message.id)
|
|
438
|
+
: undefined;
|
|
439
|
+
if (staged?.transcriptStatus) staged.transcriptStatus = status;
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// Reserving before the submit keeps an overlapping submit from carrying the
|
|
444
|
+
// same transcript; a failed submit hands it back to the next run.
|
|
445
|
+
const submitCarryingTranscripts = async (
|
|
446
|
+
transcripts: readonly LangChainBaseMessage[],
|
|
447
|
+
submit: () => Promise<void>,
|
|
448
|
+
) => {
|
|
449
|
+
setTranscriptStatus(transcripts, "sent");
|
|
450
|
+
try {
|
|
451
|
+
await submit();
|
|
452
|
+
} catch (error) {
|
|
453
|
+
setTranscriptStatus(transcripts, "unsent");
|
|
454
|
+
throw error;
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
const dropTranscripts = (messages: readonly LangChainBaseMessage[]) => {
|
|
459
|
+
for (const message of messages) {
|
|
460
|
+
if (
|
|
461
|
+
message.id &&
|
|
462
|
+
stagedMessagesRef.current.get(message.id)?.transcriptStatus
|
|
463
|
+
)
|
|
464
|
+
removeStagedMessage(message.id);
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
const isTranscriptMessage = (message: LangChainBaseMessage) => {
|
|
469
|
+
const staged = message.id
|
|
470
|
+
? stagedMessagesRef.current.get(message.id)
|
|
471
|
+
: undefined;
|
|
472
|
+
if (staged?.transcriptStatus === "unsent") return true;
|
|
473
|
+
const type = getMessageType(message);
|
|
474
|
+
return (
|
|
475
|
+
(type === "human" || type === "ai") &&
|
|
476
|
+
getMessageModality(message.additional_kwargs) !== undefined
|
|
477
|
+
);
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
// A transcript reaches the graph in the same input as the message after it, so
|
|
481
|
+
// no checkpoint ends at one; a fork starts before the trailing transcripts.
|
|
482
|
+
const planForkTranscripts = (parentId: string | null) => {
|
|
483
|
+
const visible = visibleMessagesRef.current;
|
|
484
|
+
const parentIndex =
|
|
485
|
+
parentId == null ? -1 : visible.findIndex((m) => m.id === parentId);
|
|
486
|
+
if (parentId != null && parentIndex === -1)
|
|
487
|
+
return { forkParentId: parentId, transcripts: [], truncated: [] };
|
|
488
|
+
|
|
489
|
+
let start = parentIndex + 1;
|
|
490
|
+
while (start > 0 && isTranscriptMessage(visible[start - 1]!)) start--;
|
|
491
|
+
return {
|
|
492
|
+
forkParentId: start > 0 ? (visible[start - 1]!.id ?? null) : null,
|
|
493
|
+
transcripts: visible.slice(start, parentIndex + 1),
|
|
494
|
+
truncated: visible.slice(parentIndex + 1),
|
|
373
495
|
};
|
|
374
496
|
};
|
|
375
497
|
|
|
@@ -459,27 +581,32 @@ const useStreamThreadRuntime = (
|
|
|
459
581
|
// longer holds appends on that barrier.
|
|
460
582
|
try {
|
|
461
583
|
const { externalId } = await aui.threadListItem.initialize();
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
584
|
+
const transcripts = getUnsentTranscripts();
|
|
585
|
+
await submitCarryingTranscripts(transcripts, () =>
|
|
586
|
+
streamRef.current.submit(
|
|
587
|
+
{
|
|
588
|
+
[messagesKey]: [
|
|
589
|
+
...cancellations,
|
|
590
|
+
...transcripts.map(toStagedMessageInput),
|
|
591
|
+
{
|
|
592
|
+
id: stagedMessageId,
|
|
593
|
+
type: "human",
|
|
594
|
+
content,
|
|
595
|
+
},
|
|
596
|
+
],
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
...runConfigToSubmitOptions(msg.runConfig),
|
|
600
|
+
...(externalId != null ? { threadId: externalId } : {}),
|
|
601
|
+
},
|
|
602
|
+
),
|
|
477
603
|
);
|
|
478
604
|
} catch (error) {
|
|
479
605
|
removeStagedMessage(stagedMessageId);
|
|
480
606
|
throw error;
|
|
481
607
|
}
|
|
482
608
|
},
|
|
609
|
+
onVoiceTranscript: appendVoiceTranscript,
|
|
483
610
|
onAddToolResult: async ({
|
|
484
611
|
messageId,
|
|
485
612
|
toolCallId,
|
|
@@ -510,9 +637,18 @@ const useStreamThreadRuntime = (
|
|
|
510
637
|
onReload: async (parentId, config) => {
|
|
511
638
|
const stagedRun = getStagedRun(parentId);
|
|
512
639
|
if (stagedRun) {
|
|
640
|
+
if (
|
|
641
|
+
config.sourceId &&
|
|
642
|
+
stagedMessagesRef.current.get(config.sourceId)?.transcriptStatus
|
|
643
|
+
)
|
|
644
|
+
removeStagedMessage(config.sourceId);
|
|
513
645
|
const promotedIds = new Set<string>();
|
|
514
646
|
for (const message of stagedRun.messages) {
|
|
515
|
-
if (
|
|
647
|
+
if (
|
|
648
|
+
!message.id ||
|
|
649
|
+
stagedMessagesRef.current.get(message.id)?.transcriptStatus
|
|
650
|
+
)
|
|
651
|
+
continue;
|
|
516
652
|
promotedIds.add(message.id);
|
|
517
653
|
stagedMessagesRef.current.delete(message.id);
|
|
518
654
|
}
|
|
@@ -528,15 +664,13 @@ const useStreamThreadRuntime = (
|
|
|
528
664
|
}
|
|
529
665
|
const runConfig = config.runConfig ?? stagedRun.runConfig;
|
|
530
666
|
setActiveRunConfig(runConfig);
|
|
531
|
-
await
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
},
|
|
539
|
-
runConfigToSubmitOptions(runConfig),
|
|
667
|
+
await submitCarryingTranscripts(stagedRun.messages, () =>
|
|
668
|
+
stream.submit(
|
|
669
|
+
{
|
|
670
|
+
[messagesKey]: stagedRun.messages.map(toStagedMessageInput),
|
|
671
|
+
},
|
|
672
|
+
runConfigToSubmitOptions(runConfig),
|
|
673
|
+
),
|
|
540
674
|
);
|
|
541
675
|
return;
|
|
542
676
|
}
|
|
@@ -544,21 +678,30 @@ const useStreamThreadRuntime = (
|
|
|
544
678
|
const threadId = externalId;
|
|
545
679
|
if (!threadId || parentId == null) return;
|
|
546
680
|
const s = streamRef.current;
|
|
681
|
+
const fork = planForkTranscripts(parentId);
|
|
547
682
|
const checkpointId = await resolveForkCheckpoint(
|
|
548
683
|
s.client,
|
|
549
684
|
threadId,
|
|
550
685
|
s.messages as readonly LangChainBaseMessage[],
|
|
551
|
-
|
|
686
|
+
fork.forkParentId,
|
|
552
687
|
config.sourceId,
|
|
553
688
|
s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),
|
|
554
689
|
messagesKey,
|
|
555
690
|
);
|
|
556
691
|
if (!checkpointId) return;
|
|
692
|
+
dropTranscripts(fork.truncated);
|
|
557
693
|
setActiveRunConfig(config.runConfig);
|
|
558
|
-
await
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
694
|
+
await submitCarryingTranscripts(fork.transcripts, () =>
|
|
695
|
+
s.submit(
|
|
696
|
+
fork.transcripts.length > 0
|
|
697
|
+
? { [messagesKey]: fork.transcripts.map(toStagedMessageInput) }
|
|
698
|
+
: null,
|
|
699
|
+
{
|
|
700
|
+
forkFrom: checkpointId,
|
|
701
|
+
...runConfigToSubmitOptions(config.runConfig),
|
|
702
|
+
},
|
|
703
|
+
),
|
|
704
|
+
);
|
|
562
705
|
},
|
|
563
706
|
onEdit: async (message) => {
|
|
564
707
|
if (!(message.startRun ?? message.role === "user")) {
|
|
@@ -583,24 +726,33 @@ const useStreamThreadRuntime = (
|
|
|
583
726
|
const threadId = externalId;
|
|
584
727
|
if (!threadId) return;
|
|
585
728
|
const s = streamRef.current;
|
|
729
|
+
const fork = planForkTranscripts(message.parentId);
|
|
586
730
|
const checkpointId = await resolveForkCheckpoint(
|
|
587
731
|
s.client,
|
|
588
732
|
threadId,
|
|
589
733
|
s.messages as readonly LangChainBaseMessage[],
|
|
590
|
-
|
|
734
|
+
fork.forkParentId,
|
|
591
735
|
message.sourceId,
|
|
592
736
|
s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),
|
|
593
737
|
messagesKey,
|
|
594
738
|
);
|
|
595
739
|
if (!checkpointId) return;
|
|
740
|
+
dropTranscripts(fork.truncated);
|
|
596
741
|
const content = getMessageContent(message);
|
|
597
742
|
setActiveRunConfig(message.runConfig);
|
|
598
|
-
await
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
743
|
+
await submitCarryingTranscripts(fork.transcripts, () =>
|
|
744
|
+
s.submit(
|
|
745
|
+
{
|
|
746
|
+
[messagesKey]: [
|
|
747
|
+
...fork.transcripts.map(toStagedMessageInput),
|
|
748
|
+
{ type: "human", content },
|
|
749
|
+
],
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
forkFrom: checkpointId,
|
|
753
|
+
...runConfigToSubmitOptions(message.runConfig),
|
|
754
|
+
},
|
|
755
|
+
),
|
|
604
756
|
);
|
|
605
757
|
},
|
|
606
758
|
onCancel:
|