@assistant-ui/react-langchain 0.0.14 → 0.0.17
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 +1 -1
- package/dist/convertMessages.d.ts +37 -3
- package/dist/convertMessages.d.ts.map +1 -1
- package/dist/convertMessages.js +63 -11
- package/dist/convertMessages.js.map +1 -1
- package/dist/findForkCheckpointInHistory.d.ts +26 -0
- package/dist/findForkCheckpointInHistory.d.ts.map +1 -0
- package/dist/findForkCheckpointInHistory.js +34 -0
- package/dist/findForkCheckpointInHistory.js.map +1 -0
- package/dist/hooks.d.ts +88 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +99 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.js +4 -2
- package/dist/resolveForkCheckpoint.d.ts +17 -0
- package/dist/resolveForkCheckpoint.d.ts.map +1 -0
- package/dist/resolveForkCheckpoint.js +26 -0
- package/dist/resolveForkCheckpoint.js.map +1 -0
- package/dist/runtimeExtras.d.ts +7 -0
- package/dist/runtimeExtras.d.ts.map +1 -0
- package/dist/runtimeExtras.js +7 -0
- package/dist/runtimeExtras.js.map +1 -0
- package/dist/streamingTiming.d.ts +16 -0
- package/dist/streamingTiming.d.ts.map +1 -0
- package/dist/streamingTiming.js +53 -0
- package/dist/streamingTiming.js.map +1 -0
- package/dist/types.d.ts +113 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/uiMessages.d.ts +24 -0
- package/dist/uiMessages.d.ts.map +1 -0
- package/dist/uiMessages.js +68 -0
- package/dist/uiMessages.js.map +1 -0
- package/dist/useStreamRuntime.d.ts +15 -76
- package/dist/useStreamRuntime.d.ts.map +1 -1
- package/dist/useStreamRuntime.js +190 -111
- package/dist/useStreamRuntime.js.map +1 -1
- package/package.json +9 -9
- package/src/__tests__/langChainTestUtils.ts +22 -0
- package/src/convertMessages.test.ts +210 -0
- package/src/convertMessages.ts +111 -11
- package/src/findForkCheckpointInHistory.test.ts +263 -0
- package/src/findForkCheckpointInHistory.ts +68 -0
- package/src/groupUIMessagesByParent.test.ts +61 -0
- package/src/hooks.ts +156 -0
- package/src/index.ts +20 -3
- package/src/resolveForkCheckpoint.test.ts +202 -0
- package/src/resolveForkCheckpoint.ts +49 -0
- package/src/runConfigToSubmitOptions.test.ts +24 -0
- package/src/runtimeExtras.ts +5 -0
- package/src/streamingTiming.test.ts +118 -0
- package/src/streamingTiming.ts +85 -0
- package/src/types.ts +147 -3
- package/src/uiMessages.test.ts +190 -0
- package/src/uiMessages.ts +91 -0
- package/src/useLangChainError.test.tsx +48 -0
- package/src/useLangChainInterrupts.test.tsx +48 -0
- package/src/useLangChainRespond.test.tsx +49 -0
- package/src/useLangChainRespondAll.test.tsx +52 -0
- package/src/useLangChainState.test.tsx +14 -26
- package/src/useLangChainStream.test.tsx +42 -0
- package/src/useLangChainSubagents.test.tsx +48 -0
- package/src/useLangChainSubgraphs.test.tsx +48 -0
- package/src/useLangChainToolCalls.test.tsx +48 -0
- package/src/useStreamRuntime.test.tsx +232 -0
- package/src/useStreamRuntime.ts +311 -234
package/src/useStreamRuntime.ts
CHANGED
|
@@ -1,89 +1,70 @@
|
|
|
1
1
|
/// <reference types="@assistant-ui/core/store" />
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
|
-
import { useMemo, useRef, useState } from "react";
|
|
5
|
-
import type {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
FeedbackAdapter,
|
|
11
|
-
RealtimeVoiceAdapter,
|
|
12
|
-
RemoteThreadListAdapter,
|
|
13
|
-
SpeechSynthesisAdapter,
|
|
14
|
-
ToolExecutionStatus,
|
|
4
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
5
|
+
import type { AppendMessage, ToolExecutionStatus } from "@assistant-ui/core";
|
|
6
|
+
import {
|
|
7
|
+
generateId,
|
|
8
|
+
getExternalStoreMessages,
|
|
9
|
+
pickExternalStoreSharedOptions,
|
|
15
10
|
} from "@assistant-ui/core";
|
|
16
|
-
import {
|
|
11
|
+
import type { ThreadMessage } from "@assistant-ui/core";
|
|
17
12
|
import {
|
|
18
13
|
useCloudThreadListAdapter,
|
|
19
14
|
useExternalStoreRuntime,
|
|
20
15
|
useExternalMessageConverter,
|
|
21
16
|
useRemoteThreadListRuntime,
|
|
22
17
|
} from "@assistant-ui/core/react";
|
|
23
|
-
import {
|
|
24
|
-
import
|
|
25
|
-
import {
|
|
26
|
-
import type {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
};
|
|
18
|
+
import { useAuiState } from "@assistant-ui/store";
|
|
19
|
+
import { STREAM_CONTROLLER, useChannel, useStream } from "@langchain/react";
|
|
20
|
+
import type { Channel } from "@langchain/react";
|
|
21
|
+
import type {
|
|
22
|
+
LangChainBaseMessage,
|
|
23
|
+
LangChainToolCall,
|
|
24
|
+
UIMessage,
|
|
25
|
+
UseStreamRuntimeOptions,
|
|
26
|
+
} from "./types";
|
|
27
|
+
import {
|
|
28
|
+
convertLangChainBaseMessage,
|
|
29
|
+
getMessageContent,
|
|
30
|
+
getMessageType,
|
|
31
|
+
} from "./convertMessages";
|
|
32
|
+
import { foldUIUpdates, mergeUIMessages } from "./uiMessages";
|
|
33
|
+
import { langChainExtras } from "./runtimeExtras";
|
|
34
|
+
import { resolveForkCheckpoint } from "./resolveForkCheckpoint";
|
|
35
|
+
import { useLangChainStreamingTiming } from "./streamingTiming";
|
|
42
36
|
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
typeof extras !== "object" ||
|
|
46
|
-
extras == null ||
|
|
47
|
-
!(symbolLangChainRuntimeExtras in extras)
|
|
48
|
-
)
|
|
49
|
-
throw new Error(
|
|
50
|
-
"This method can only be called when you are using useStreamRuntime",
|
|
51
|
-
);
|
|
52
|
-
return extras as LangChainRuntimeExtras;
|
|
53
|
-
};
|
|
37
|
+
const UI_CUSTOM_CHANNELS: readonly Channel[] = ["custom"];
|
|
54
38
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
create?: (() => Promise<{ externalId: string | undefined }>) | undefined;
|
|
85
|
-
/** Custom thread-deletion hook, forwarded to the cloud adapter. */
|
|
86
|
-
delete?: ((threadId: string) => Promise<void>) | undefined;
|
|
39
|
+
export const runConfigToSubmitOptions = (
|
|
40
|
+
runConfig: AppendMessage["runConfig"],
|
|
41
|
+
) =>
|
|
42
|
+
runConfig?.custom
|
|
43
|
+
? { config: { configurable: runConfig.custom } }
|
|
44
|
+
: undefined;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Group the graph's accumulated `UIMessage`s by the assistant message they
|
|
48
|
+
* belong to. Non-array state and entries without a parent link are dropped.
|
|
49
|
+
* The parent id comes from `metadata.message_id` (Python SDK) or
|
|
50
|
+
* `metadata.id` (JS SDK).
|
|
51
|
+
*/
|
|
52
|
+
export const groupUIMessagesByParent = (
|
|
53
|
+
value: unknown,
|
|
54
|
+
): Map<string, UIMessage[]> => {
|
|
55
|
+
const map = new Map<string, UIMessage[]>();
|
|
56
|
+
if (!Array.isArray(value)) return map;
|
|
57
|
+
for (const ui of value as UIMessage[]) {
|
|
58
|
+
const parentId = ui.metadata?.message_id ?? ui.metadata?.id;
|
|
59
|
+
if (!parentId) continue;
|
|
60
|
+
const existing = map.get(parentId);
|
|
61
|
+
if (existing) {
|
|
62
|
+
existing.push(ui);
|
|
63
|
+
} else {
|
|
64
|
+
map.set(parentId, [ui]);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return map;
|
|
87
68
|
};
|
|
88
69
|
|
|
89
70
|
const getPendingToolCalls = (
|
|
@@ -101,65 +82,29 @@ const getPendingToolCalls = (
|
|
|
101
82
|
return [...pending.values()];
|
|
102
83
|
};
|
|
103
84
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
: never;
|
|
113
|
-
|
|
114
|
-
const getMessageContent = (msg: AppendMessage) => {
|
|
115
|
-
const allContent = [
|
|
116
|
-
...msg.content,
|
|
117
|
-
...(msg.attachments?.flatMap((a) => a.content) ?? []),
|
|
118
|
-
];
|
|
119
|
-
|
|
120
|
-
const hasNonText = allContent.some(
|
|
121
|
-
(part) => part.type === "file" || part.type === "image",
|
|
122
|
-
);
|
|
123
|
-
const hasText = allContent.some((part) => part.type === "text");
|
|
124
|
-
if (hasNonText && !hasText) {
|
|
125
|
-
allContent.unshift({ type: "text", text: " " });
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const content = allContent.map((part) => {
|
|
129
|
-
const type = part.type;
|
|
130
|
-
switch (type) {
|
|
131
|
-
case "text":
|
|
132
|
-
return { type: "text" as const, text: part.text };
|
|
133
|
-
case "image":
|
|
134
|
-
return { type: "image_url" as const, image_url: { url: part.image } };
|
|
135
|
-
case "file":
|
|
136
|
-
return {
|
|
137
|
-
type: "file" as const,
|
|
138
|
-
data: part.data,
|
|
139
|
-
mime_type: part.mimeType,
|
|
140
|
-
metadata: { filename: part.filename ?? "file" },
|
|
141
|
-
source_type: "base64" as const,
|
|
142
|
-
};
|
|
143
|
-
case "tool-call":
|
|
144
|
-
throw new Error("Tool call appends are not supported.");
|
|
145
|
-
default: {
|
|
146
|
-
const _exhaustiveCheck:
|
|
147
|
-
| "reasoning"
|
|
148
|
-
| "source"
|
|
149
|
-
| "audio"
|
|
150
|
-
| "data"
|
|
151
|
-
| "generative-ui" = type;
|
|
152
|
-
throw new Error(
|
|
153
|
-
`Unsupported append message part type: ${_exhaustiveCheck}`,
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
});
|
|
85
|
+
const toStagedHumanMessage = (
|
|
86
|
+
msg: AppendMessage,
|
|
87
|
+
id = generateId(),
|
|
88
|
+
): LangChainBaseMessage & { id: string } => ({
|
|
89
|
+
id,
|
|
90
|
+
_getType: () => "human",
|
|
91
|
+
content: getMessageContent(msg),
|
|
92
|
+
});
|
|
158
93
|
|
|
159
|
-
|
|
160
|
-
|
|
94
|
+
const truncateLangChainBaseMessages = (
|
|
95
|
+
threadMessages: readonly ThreadMessage[],
|
|
96
|
+
parentId: string | null,
|
|
97
|
+
): LangChainBaseMessage[] => {
|
|
98
|
+
if (parentId === null) return [];
|
|
99
|
+
const parentIndex = threadMessages.findIndex((m) => m.id === parentId);
|
|
100
|
+
if (parentIndex === -1) return [];
|
|
101
|
+
const truncated: LangChainBaseMessage[] = [];
|
|
102
|
+
for (let i = 0; i <= parentIndex && i < threadMessages.length; i++) {
|
|
103
|
+
truncated.push(
|
|
104
|
+
...getExternalStoreMessages<LangChainBaseMessage>(threadMessages[i]!),
|
|
105
|
+
);
|
|
161
106
|
}
|
|
162
|
-
return
|
|
107
|
+
return truncated;
|
|
163
108
|
};
|
|
164
109
|
|
|
165
110
|
type DistributiveOmit<T, K extends keyof any> = T extends unknown
|
|
@@ -175,6 +120,7 @@ const useStreamThreadRuntime = (
|
|
|
175
120
|
const { adapters, autoCancelPendingToolCalls, unstable_allowCancellation } =
|
|
176
121
|
options;
|
|
177
122
|
const messagesKey = options.messagesKey ?? "messages";
|
|
123
|
+
const uiStateKey = options.uiStateKey ?? "ui";
|
|
178
124
|
|
|
179
125
|
const externalId = useAuiState((s) => s.threadListItem.externalId) as
|
|
180
126
|
| string
|
|
@@ -185,6 +131,9 @@ const useStreamThreadRuntime = (
|
|
|
185
131
|
options.threadId = externalId;
|
|
186
132
|
|
|
187
133
|
const stream = useStream(options);
|
|
134
|
+
const [stagedMessages, setStagedMessages] = useState<
|
|
135
|
+
LangChainBaseMessage[] | null
|
|
136
|
+
>(null);
|
|
188
137
|
|
|
189
138
|
const [toolStatuses, setToolStatuses] = useState<
|
|
190
139
|
Record<string, ToolExecutionStatus>
|
|
@@ -194,42 +143,160 @@ const useStreamThreadRuntime = (
|
|
|
194
143
|
);
|
|
195
144
|
const effectiveIsRunning = stream.isLoading || hasExecutingTools;
|
|
196
145
|
|
|
146
|
+
const uiStateValue = stream.values[uiStateKey];
|
|
147
|
+
|
|
148
|
+
const customEvents = useChannel(stream, UI_CUSTOM_CHANNELS);
|
|
149
|
+
const liveUiMessages = useMemo(
|
|
150
|
+
() => foldUIUpdates(customEvents),
|
|
151
|
+
[customEvents],
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const mergedUiMessages = useMemo(
|
|
155
|
+
() => mergeUIMessages(liveUiMessages, uiStateValue),
|
|
156
|
+
[liveUiMessages, uiStateValue],
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const visibleMessages =
|
|
160
|
+
stagedMessages ?? (stream.messages as LangChainBaseMessage[]);
|
|
161
|
+
|
|
162
|
+
const messageTiming = useLangChainStreamingTiming(
|
|
163
|
+
visibleMessages,
|
|
164
|
+
effectiveIsRunning,
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const convertWithUI = useMemo<
|
|
168
|
+
useExternalMessageConverter.Callback<LangChainBaseMessage>
|
|
169
|
+
>(() => {
|
|
170
|
+
const uiMessagesByParent = groupUIMessagesByParent(mergedUiMessages);
|
|
171
|
+
return (message, metadata) =>
|
|
172
|
+
convertLangChainBaseMessage(message, {
|
|
173
|
+
...metadata,
|
|
174
|
+
uiMessagesByParent,
|
|
175
|
+
messageTiming,
|
|
176
|
+
});
|
|
177
|
+
}, [mergedUiMessages, messageTiming]);
|
|
178
|
+
|
|
197
179
|
const threadMessages = useExternalMessageConverter({
|
|
198
|
-
callback:
|
|
199
|
-
messages:
|
|
180
|
+
callback: convertWithUI,
|
|
181
|
+
messages: visibleMessages,
|
|
200
182
|
isRunning: effectiveIsRunning,
|
|
201
183
|
});
|
|
202
184
|
|
|
203
185
|
const streamRef = useRef(stream);
|
|
204
186
|
streamRef.current = stream;
|
|
205
187
|
|
|
188
|
+
const visibleMessagesRef = useRef(visibleMessages);
|
|
189
|
+
visibleMessagesRef.current = visibleMessages;
|
|
190
|
+
|
|
191
|
+
const threadMessagesRef = useRef(threadMessages);
|
|
192
|
+
threadMessagesRef.current = threadMessages;
|
|
193
|
+
|
|
194
|
+
const stagedMessagesRef = useRef(
|
|
195
|
+
new Map<
|
|
196
|
+
string,
|
|
197
|
+
{
|
|
198
|
+
message: LangChainBaseMessage & { id: string };
|
|
199
|
+
runConfig: AppendMessage["runConfig"];
|
|
200
|
+
}
|
|
201
|
+
>(),
|
|
202
|
+
);
|
|
203
|
+
const stagedBaseMessagesRef = useRef<LangChainBaseMessage[] | null>(null);
|
|
204
|
+
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
if (stagedMessagesRef.current.size === 0) return;
|
|
207
|
+
|
|
208
|
+
// Staged edits must keep their truncated base while stream updates arrive before promotion.
|
|
209
|
+
const baseMessages =
|
|
210
|
+
stagedBaseMessagesRef.current ??
|
|
211
|
+
(stream.messages as LangChainBaseMessage[]);
|
|
212
|
+
const baseMessageIds = new Set(
|
|
213
|
+
baseMessages.flatMap((message) => (message.id ? [message.id] : [])),
|
|
214
|
+
);
|
|
215
|
+
const remainingStagedMessages: LangChainBaseMessage[] = [];
|
|
216
|
+
const seenStagedIds = new Set<string>();
|
|
217
|
+
for (const message of visibleMessagesRef.current) {
|
|
218
|
+
if (!message.id || seenStagedIds.has(message.id)) continue;
|
|
219
|
+
if (baseMessageIds.has(message.id)) continue;
|
|
220
|
+
const staged = stagedMessagesRef.current.get(message.id);
|
|
221
|
+
if (!staged) continue;
|
|
222
|
+
remainingStagedMessages.push(staged.message);
|
|
223
|
+
seenStagedIds.add(message.id);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (remainingStagedMessages.length === 0) {
|
|
227
|
+
stagedBaseMessagesRef.current = null;
|
|
228
|
+
visibleMessagesRef.current = baseMessages;
|
|
229
|
+
setStagedMessages(null);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const nextMessages = [...baseMessages, ...remainingStagedMessages];
|
|
234
|
+
visibleMessagesRef.current = nextMessages;
|
|
235
|
+
setStagedMessages(nextMessages);
|
|
236
|
+
}, [stream.messages]);
|
|
237
|
+
|
|
238
|
+
const getStagedRun = (parentId: string | null) => {
|
|
239
|
+
if (!parentId || !stagedMessagesRef.current.has(parentId)) return null;
|
|
240
|
+
|
|
241
|
+
const staged: LangChainBaseMessage[] = [];
|
|
242
|
+
for (const message of visibleMessagesRef.current) {
|
|
243
|
+
if (message.id && stagedMessagesRef.current.has(message.id)) {
|
|
244
|
+
staged.push(stagedMessagesRef.current.get(message.id)!.message);
|
|
245
|
+
}
|
|
246
|
+
if (message.id === parentId) break;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
messages: staged,
|
|
251
|
+
runConfig: stagedMessagesRef.current.get(parentId)!.runConfig,
|
|
252
|
+
};
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const stageUserMessage = (msg: AppendMessage) => {
|
|
256
|
+
const stagedMessage = toStagedHumanMessage(msg);
|
|
257
|
+
stagedMessagesRef.current.set(stagedMessage.id, {
|
|
258
|
+
message: stagedMessage,
|
|
259
|
+
runConfig: msg.runConfig,
|
|
260
|
+
});
|
|
261
|
+
const nextMessages = [...visibleMessagesRef.current, stagedMessage];
|
|
262
|
+
visibleMessagesRef.current = nextMessages;
|
|
263
|
+
setStagedMessages(nextMessages);
|
|
264
|
+
};
|
|
265
|
+
|
|
206
266
|
const extras = useMemo(
|
|
207
|
-
()
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
267
|
+
() =>
|
|
268
|
+
langChainExtras.provide({
|
|
269
|
+
interrupt: stream.interrupt,
|
|
270
|
+
interrupts: stream.interrupts,
|
|
271
|
+
toolCalls: stream.toolCalls,
|
|
272
|
+
subagents: stream.subagents,
|
|
273
|
+
subgraphs: stream.subgraphs,
|
|
274
|
+
stream,
|
|
275
|
+
error: stream.error,
|
|
276
|
+
submit: stream.submit,
|
|
277
|
+
respond: stream.respond,
|
|
278
|
+
respondAll: stream.respondAll,
|
|
279
|
+
values: stream.values,
|
|
280
|
+
messagesKey,
|
|
281
|
+
}),
|
|
282
|
+
[stream, messagesKey],
|
|
222
283
|
);
|
|
223
284
|
|
|
224
285
|
const runtime = useExternalStoreRuntime({
|
|
225
286
|
...pickExternalStoreSharedOptions(options),
|
|
226
287
|
isRunning: effectiveIsRunning,
|
|
288
|
+
isLoading: stream.isThreadLoading,
|
|
227
289
|
messages: threadMessages,
|
|
228
290
|
adapters,
|
|
229
291
|
extras,
|
|
230
292
|
unstable_enableToolInvocations: true,
|
|
231
293
|
setToolStatuses,
|
|
232
294
|
onNew: async (msg) => {
|
|
295
|
+
if (!(msg.startRun ?? msg.role === "user")) {
|
|
296
|
+
stageUserMessage(msg);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
233
300
|
const content = getMessageContent(msg);
|
|
234
301
|
const cancellations =
|
|
235
302
|
autoCancelPendingToolCalls !== false
|
|
@@ -243,9 +310,10 @@ const useStreamThreadRuntime = (
|
|
|
243
310
|
status: "error" as const,
|
|
244
311
|
}))
|
|
245
312
|
: [];
|
|
246
|
-
await stream.submit(
|
|
247
|
-
[messagesKey]: [...cancellations, { type: "human", content }],
|
|
248
|
-
|
|
313
|
+
await stream.submit(
|
|
314
|
+
{ [messagesKey]: [...cancellations, { type: "human", content }] },
|
|
315
|
+
runConfigToSubmitOptions(msg.runConfig),
|
|
316
|
+
);
|
|
249
317
|
},
|
|
250
318
|
onAddToolResult: async ({
|
|
251
319
|
toolCallId,
|
|
@@ -267,6 +335,96 @@ const useStreamThreadRuntime = (
|
|
|
267
335
|
],
|
|
268
336
|
});
|
|
269
337
|
},
|
|
338
|
+
onReload: async (parentId, config) => {
|
|
339
|
+
const stagedRun = getStagedRun(parentId);
|
|
340
|
+
if (stagedRun) {
|
|
341
|
+
const promotedIds = new Set<string>();
|
|
342
|
+
for (const message of stagedRun.messages) {
|
|
343
|
+
if (!message.id) continue;
|
|
344
|
+
promotedIds.add(message.id);
|
|
345
|
+
stagedMessagesRef.current.delete(message.id);
|
|
346
|
+
}
|
|
347
|
+
stagedBaseMessagesRef.current = null;
|
|
348
|
+
if (stagedMessagesRef.current.size > 0) {
|
|
349
|
+
const nextMessages = visibleMessagesRef.current.filter(
|
|
350
|
+
(message) => !message.id || !promotedIds.has(message.id),
|
|
351
|
+
);
|
|
352
|
+
visibleMessagesRef.current = nextMessages;
|
|
353
|
+
setStagedMessages(nextMessages);
|
|
354
|
+
} else {
|
|
355
|
+
setStagedMessages(null);
|
|
356
|
+
}
|
|
357
|
+
await stream.submit(
|
|
358
|
+
{
|
|
359
|
+
[messagesKey]: stagedRun.messages.map((message) => ({
|
|
360
|
+
id: message.id,
|
|
361
|
+
type: "human",
|
|
362
|
+
content: message.content,
|
|
363
|
+
})),
|
|
364
|
+
},
|
|
365
|
+
runConfigToSubmitOptions(config.runConfig ?? stagedRun.runConfig),
|
|
366
|
+
);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const threadId = externalId;
|
|
371
|
+
if (!threadId || parentId == null) return;
|
|
372
|
+
const s = streamRef.current;
|
|
373
|
+
const checkpointId = await resolveForkCheckpoint(
|
|
374
|
+
s.client,
|
|
375
|
+
threadId,
|
|
376
|
+
s.messages as readonly LangChainBaseMessage[],
|
|
377
|
+
parentId,
|
|
378
|
+
config.sourceId,
|
|
379
|
+
s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),
|
|
380
|
+
messagesKey,
|
|
381
|
+
);
|
|
382
|
+
if (!checkpointId) return;
|
|
383
|
+
await s.submit(null, {
|
|
384
|
+
forkFrom: checkpointId,
|
|
385
|
+
...runConfigToSubmitOptions(config.runConfig),
|
|
386
|
+
});
|
|
387
|
+
},
|
|
388
|
+
onEdit: async (message) => {
|
|
389
|
+
if (!(message.startRun ?? message.role === "user")) {
|
|
390
|
+
const truncated = truncateLangChainBaseMessages(
|
|
391
|
+
threadMessagesRef.current,
|
|
392
|
+
message.parentId,
|
|
393
|
+
);
|
|
394
|
+
const stagedMessage = toStagedHumanMessage(message);
|
|
395
|
+
stagedMessagesRef.current.set(stagedMessage.id, {
|
|
396
|
+
message: stagedMessage,
|
|
397
|
+
runConfig: message.runConfig,
|
|
398
|
+
});
|
|
399
|
+
stagedBaseMessagesRef.current = truncated;
|
|
400
|
+
const nextMessages = [...truncated, stagedMessage];
|
|
401
|
+
visibleMessagesRef.current = nextMessages;
|
|
402
|
+
setStagedMessages(nextMessages);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const threadId = externalId;
|
|
407
|
+
if (!threadId) return;
|
|
408
|
+
const s = streamRef.current;
|
|
409
|
+
const checkpointId = await resolveForkCheckpoint(
|
|
410
|
+
s.client,
|
|
411
|
+
threadId,
|
|
412
|
+
s.messages as readonly LangChainBaseMessage[],
|
|
413
|
+
message.parentId,
|
|
414
|
+
message.sourceId,
|
|
415
|
+
s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),
|
|
416
|
+
messagesKey,
|
|
417
|
+
);
|
|
418
|
+
if (!checkpointId) return;
|
|
419
|
+
const content = getMessageContent(message);
|
|
420
|
+
await s.submit(
|
|
421
|
+
{ [messagesKey]: [{ type: "human", content }] },
|
|
422
|
+
{
|
|
423
|
+
forkFrom: checkpointId,
|
|
424
|
+
...runConfigToSubmitOptions(message.runConfig),
|
|
425
|
+
},
|
|
426
|
+
);
|
|
427
|
+
},
|
|
270
428
|
onCancel:
|
|
271
429
|
unstable_allowCancellation !== false
|
|
272
430
|
? async () => {
|
|
@@ -308,6 +466,7 @@ export const useStreamRuntime = (rawOptions: UseStreamRuntimeOptions) => {
|
|
|
308
466
|
unstable_threadListAdapter,
|
|
309
467
|
create,
|
|
310
468
|
delete: deleteFn,
|
|
469
|
+
onThreadIdChange,
|
|
311
470
|
...options
|
|
312
471
|
} = rawOptions;
|
|
313
472
|
|
|
@@ -327,88 +486,6 @@ export const useStreamRuntime = (rawOptions: UseStreamRuntimeOptions) => {
|
|
|
327
486
|
},
|
|
328
487
|
adapter,
|
|
329
488
|
allowNesting: true,
|
|
489
|
+
onThreadIdChange,
|
|
330
490
|
});
|
|
331
491
|
};
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Read the current LangGraph interrupt state from the runtime extras.
|
|
335
|
-
*/
|
|
336
|
-
export const useLangChainInterruptState = () => {
|
|
337
|
-
return useAuiState((s) => {
|
|
338
|
-
const extras = s.thread.extras;
|
|
339
|
-
if (!extras) return undefined;
|
|
340
|
-
return asLangChainRuntimeExtras(extras).interrupt;
|
|
341
|
-
});
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Returns a function to submit raw state updates to the LangGraph agent,
|
|
346
|
-
* bypassing the normal message flow. Useful for sending interrupt resume
|
|
347
|
-
* commands.
|
|
348
|
-
*/
|
|
349
|
-
export const useLangChainSubmit = () => {
|
|
350
|
-
const aui = useAui();
|
|
351
|
-
return (
|
|
352
|
-
values: Record<string, unknown> | null | undefined,
|
|
353
|
-
options?: Record<string, unknown>,
|
|
354
|
-
) => {
|
|
355
|
-
const extras = aui.thread().getState().extras;
|
|
356
|
-
const { submit } = asLangChainRuntimeExtras(extras);
|
|
357
|
-
return submit(values, options);
|
|
358
|
-
};
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* Submit a list of LangChain-shaped messages on the current thread.
|
|
363
|
-
* Parity helper for migrating from `useLangGraphSend`. Routes to
|
|
364
|
-
* `useStream().submit({ [messagesKey]: messages }, options)`.
|
|
365
|
-
*/
|
|
366
|
-
export const useLangChainSend = () => {
|
|
367
|
-
const aui = useAui();
|
|
368
|
-
return (
|
|
369
|
-
messages: readonly LangChainBaseMessage[],
|
|
370
|
-
options?: Record<string, unknown>,
|
|
371
|
-
) => {
|
|
372
|
-
const { submit, messagesKey } = asLangChainRuntimeExtras(
|
|
373
|
-
aui.thread().getState().extras,
|
|
374
|
-
);
|
|
375
|
-
return submit({ [messagesKey]: messages }, options);
|
|
376
|
-
};
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Submit a `useStream` command (e.g. interrupt resume). Parity helper
|
|
381
|
-
* for migrating from `useLangGraphSendCommand`. Note that v1's command
|
|
382
|
-
* shape (`{ resume?, goto?, update? }`) differs from the legacy
|
|
383
|
-
* `{ resume: string }` form — to carry a payload, use the input or
|
|
384
|
-
* `stream.respond` instead.
|
|
385
|
-
*/
|
|
386
|
-
export const useLangChainSendCommand = () => {
|
|
387
|
-
const submit = useLangChainSubmit();
|
|
388
|
-
return (command: Record<string, unknown>) => submit(null, { command });
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* Read a custom LangGraph state key from the current thread. Mirrors
|
|
393
|
-
* `useStream().values[key]` from `@langchain/react` and updates when the
|
|
394
|
-
* stream emits new state.
|
|
395
|
-
*
|
|
396
|
-
* @example
|
|
397
|
-
* ```tsx
|
|
398
|
-
* const todos = useLangChainState<Todo[]>("todos");
|
|
399
|
-
* const files = useLangChainState<Record<string, string>>("files", {});
|
|
400
|
-
* ```
|
|
401
|
-
*/
|
|
402
|
-
export function useLangChainState<T>(key: string): T | undefined;
|
|
403
|
-
export function useLangChainState<T>(key: string, defaultValue: T): T;
|
|
404
|
-
export function useLangChainState<T>(
|
|
405
|
-
key: string,
|
|
406
|
-
defaultValue?: T,
|
|
407
|
-
): T | undefined {
|
|
408
|
-
return useAuiState((s) => {
|
|
409
|
-
const extras = s.thread.extras;
|
|
410
|
-
if (!extras) return defaultValue;
|
|
411
|
-
const value = asLangChainRuntimeExtras(extras).values[key] as T | undefined;
|
|
412
|
-
return value !== undefined ? value : defaultValue;
|
|
413
|
-
});
|
|
414
|
-
}
|