@assistant-ui/react-langgraph 0.5.0 → 0.5.2
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/LangGraphMessageAccumulator.d.ts +20 -0
- package/dist/LangGraphMessageAccumulator.d.ts.map +1 -0
- package/dist/LangGraphMessageAccumulator.js +3 -2
- package/dist/LangGraphMessageAccumulator.js.map +1 -1
- package/dist/appendLangChainChunk.d.ts +3 -0
- package/dist/appendLangChainChunk.d.ts.map +1 -0
- package/dist/appendLangChainChunk.js +2 -1
- package/dist/appendLangChainChunk.js.map +1 -1
- package/dist/convertLangChainMessages.d.ts +3 -0
- package/dist/convertLangChainMessages.d.ts.map +1 -0
- package/dist/convertLangChainMessages.js +4 -2
- package/dist/convertLangChainMessages.js.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +66 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/useLangGraphMessages.d.ts +34 -0
- package/dist/useLangGraphMessages.d.ts.map +1 -0
- package/dist/useLangGraphMessages.js +4 -3
- package/dist/useLangGraphMessages.js.map +1 -1
- package/dist/useLangGraphRuntime.d.ts +2653 -0
- package/dist/useLangGraphRuntime.d.ts.map +1 -0
- package/dist/useLangGraphRuntime.js +12 -11
- package/dist/useLangGraphRuntime.js.map +1 -1
- package/package.json +7 -6
- package/src/LangGraphMessageAccumulator.ts +63 -0
- package/src/appendLangChainChunk.ts +55 -0
- package/src/convertLangChainMessages.ts +81 -0
- package/src/index.ts +31 -0
- package/src/types.ts +76 -0
- package/src/useLangGraphMessages.ts +102 -0
- package/src/useLangGraphRuntime.ts +273 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { LangChainMessage, LangChainToolCall } from "./types";
|
|
3
|
+
import {
|
|
4
|
+
useExternalMessageConverter,
|
|
5
|
+
useExternalStoreRuntime,
|
|
6
|
+
useThread,
|
|
7
|
+
useThreadListItemRuntime,
|
|
8
|
+
} from "@assistant-ui/react";
|
|
9
|
+
import { convertLangChainMessages } from "./convertLangChainMessages";
|
|
10
|
+
import {
|
|
11
|
+
LangGraphCommand,
|
|
12
|
+
LangGraphInterruptState,
|
|
13
|
+
LangGraphSendMessageConfig,
|
|
14
|
+
LangGraphStreamCallback,
|
|
15
|
+
useLangGraphMessages,
|
|
16
|
+
} from "./useLangGraphMessages";
|
|
17
|
+
import { AttachmentAdapter } from "@assistant-ui/react";
|
|
18
|
+
import { AppendMessage } from "@assistant-ui/react";
|
|
19
|
+
import { ExternalStoreAdapter } from "@assistant-ui/react";
|
|
20
|
+
import { FeedbackAdapter } from "@assistant-ui/react";
|
|
21
|
+
import { SpeechSynthesisAdapter } from "@assistant-ui/react";
|
|
22
|
+
import { appendLangChainChunk } from "./appendLangChainChunk";
|
|
23
|
+
|
|
24
|
+
const getPendingToolCalls = (messages: LangChainMessage[]) => {
|
|
25
|
+
const pendingToolCalls = new Map<string, LangChainToolCall>();
|
|
26
|
+
for (const message of messages) {
|
|
27
|
+
if (message.type === "ai") {
|
|
28
|
+
for (const toolCall of message.tool_calls ?? []) {
|
|
29
|
+
pendingToolCalls.set(toolCall.id, toolCall);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (message.type === "tool") {
|
|
33
|
+
pendingToolCalls.delete(message.tool_call_id);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return [...pendingToolCalls.values()];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const getMessageContent = (msg: AppendMessage) => {
|
|
41
|
+
const allContent = [
|
|
42
|
+
...msg.content,
|
|
43
|
+
...(msg.attachments?.flatMap((a) => a.content) ?? []),
|
|
44
|
+
];
|
|
45
|
+
const content = allContent.map((part) => {
|
|
46
|
+
const type = part.type;
|
|
47
|
+
switch (type) {
|
|
48
|
+
case "text":
|
|
49
|
+
return { type: "text" as const, text: part.text };
|
|
50
|
+
case "image":
|
|
51
|
+
return { type: "image_url" as const, image_url: { url: part.image } };
|
|
52
|
+
|
|
53
|
+
case "tool-call":
|
|
54
|
+
throw new Error("Tool call appends are not supported.");
|
|
55
|
+
|
|
56
|
+
default:
|
|
57
|
+
const _exhaustiveCheck: "reasoning" | "source" | "file" | "audio" =
|
|
58
|
+
type;
|
|
59
|
+
throw new Error(
|
|
60
|
+
`Unsupported append content part type: ${_exhaustiveCheck}`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (content.length === 1 && content[0]?.type === "text") {
|
|
66
|
+
return content[0].text ?? "";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return content;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const symbolLangGraphRuntimeExtras = Symbol("langgraph-runtime-extras");
|
|
73
|
+
type LangGraphRuntimeExtras = {
|
|
74
|
+
[symbolLangGraphRuntimeExtras]: true;
|
|
75
|
+
send: (
|
|
76
|
+
messages: LangChainMessage[],
|
|
77
|
+
config: LangGraphSendMessageConfig,
|
|
78
|
+
) => Promise<void>;
|
|
79
|
+
interrupt: LangGraphInterruptState | undefined;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const asLangGraphRuntimeExtras = (extras: unknown): LangGraphRuntimeExtras => {
|
|
83
|
+
if (
|
|
84
|
+
typeof extras !== "object" ||
|
|
85
|
+
extras == null ||
|
|
86
|
+
!(symbolLangGraphRuntimeExtras in extras)
|
|
87
|
+
)
|
|
88
|
+
throw new Error(
|
|
89
|
+
"This method can only be called when you are using useLangGraphRuntime",
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return extras as LangGraphRuntimeExtras;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const useLangGraphInterruptState = () => {
|
|
96
|
+
const { interrupt } = useThread((t) => asLangGraphRuntimeExtras(t.extras));
|
|
97
|
+
return interrupt;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const useLangGraphSend = () => {
|
|
101
|
+
const { send } = useThread((t) => asLangGraphRuntimeExtras(t.extras));
|
|
102
|
+
return send;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const useLangGraphSendCommand = () => {
|
|
106
|
+
const send = useLangGraphSend();
|
|
107
|
+
return (command: LangGraphCommand) => send([], { command });
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const useLangGraphRuntime = ({
|
|
111
|
+
autoCancelPendingToolCalls,
|
|
112
|
+
adapters: { attachments, feedback, speech } = {},
|
|
113
|
+
unstable_allowCancellation,
|
|
114
|
+
stream,
|
|
115
|
+
threadId,
|
|
116
|
+
onSwitchToNewThread,
|
|
117
|
+
onSwitchToThread,
|
|
118
|
+
}: {
|
|
119
|
+
/**
|
|
120
|
+
* @deprecated For thread management use `useCloudThreadListRuntime` instead. This option will be removed in a future version.
|
|
121
|
+
*/
|
|
122
|
+
threadId?: string | undefined;
|
|
123
|
+
autoCancelPendingToolCalls?: boolean | undefined;
|
|
124
|
+
unstable_allowCancellation?: boolean | undefined;
|
|
125
|
+
stream: LangGraphStreamCallback<LangChainMessage>;
|
|
126
|
+
/**
|
|
127
|
+
* @deprecated For thread management use `useCloudThreadListRuntime` instead. This option will be removed in a future version.
|
|
128
|
+
*/
|
|
129
|
+
onSwitchToNewThread?: () => Promise<void> | void;
|
|
130
|
+
onSwitchToThread?: (threadId: string) => Promise<{
|
|
131
|
+
messages: LangChainMessage[];
|
|
132
|
+
interrupts?: LangGraphInterruptState[];
|
|
133
|
+
}>;
|
|
134
|
+
adapters?:
|
|
135
|
+
| {
|
|
136
|
+
attachments?: AttachmentAdapter;
|
|
137
|
+
speech?: SpeechSynthesisAdapter;
|
|
138
|
+
feedback?: FeedbackAdapter;
|
|
139
|
+
}
|
|
140
|
+
| undefined;
|
|
141
|
+
}) => {
|
|
142
|
+
const {
|
|
143
|
+
interrupt,
|
|
144
|
+
setInterrupt,
|
|
145
|
+
messages,
|
|
146
|
+
sendMessage,
|
|
147
|
+
cancel,
|
|
148
|
+
setMessages,
|
|
149
|
+
} = useLangGraphMessages({
|
|
150
|
+
appendMessage: appendLangChainChunk,
|
|
151
|
+
stream,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const [isRunning, setIsRunning] = useState(false);
|
|
155
|
+
const handleSendMessage = async (
|
|
156
|
+
messages: LangChainMessage[],
|
|
157
|
+
config: LangGraphSendMessageConfig,
|
|
158
|
+
) => {
|
|
159
|
+
try {
|
|
160
|
+
setIsRunning(true);
|
|
161
|
+
await sendMessage(messages, config);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error("Error streaming messages:", error);
|
|
164
|
+
} finally {
|
|
165
|
+
setIsRunning(false);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const threadMessages = useExternalMessageConverter({
|
|
170
|
+
callback: convertLangChainMessages,
|
|
171
|
+
messages,
|
|
172
|
+
isRunning,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const switchToThread = !onSwitchToThread
|
|
176
|
+
? undefined
|
|
177
|
+
: async (externalId: string) => {
|
|
178
|
+
const { messages, interrupts } = await onSwitchToThread(externalId);
|
|
179
|
+
setMessages(messages);
|
|
180
|
+
setInterrupt(interrupts?.[0]);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const threadList: NonNullable<
|
|
184
|
+
ExternalStoreAdapter["adapters"]
|
|
185
|
+
>["threadList"] = {
|
|
186
|
+
threadId,
|
|
187
|
+
onSwitchToNewThread: !onSwitchToNewThread
|
|
188
|
+
? undefined
|
|
189
|
+
: async () => {
|
|
190
|
+
await onSwitchToNewThread();
|
|
191
|
+
setMessages([]);
|
|
192
|
+
},
|
|
193
|
+
onSwitchToThread: switchToThread,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const loadingRef = useRef(false);
|
|
197
|
+
const threadListItemRuntime = useThreadListItemRuntime({ optional: true });
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
if (!threadListItemRuntime || !switchToThread || loadingRef.current) return;
|
|
200
|
+
|
|
201
|
+
const externalId = threadListItemRuntime.getState().externalId;
|
|
202
|
+
if (externalId) {
|
|
203
|
+
loadingRef.current = true;
|
|
204
|
+
switchToThread(externalId).finally(() => {
|
|
205
|
+
loadingRef.current = false;
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
return useExternalStoreRuntime({
|
|
212
|
+
isRunning,
|
|
213
|
+
messages: threadMessages,
|
|
214
|
+
adapters: {
|
|
215
|
+
attachments,
|
|
216
|
+
feedback,
|
|
217
|
+
speech,
|
|
218
|
+
threadList,
|
|
219
|
+
},
|
|
220
|
+
extras: {
|
|
221
|
+
[symbolLangGraphRuntimeExtras]: true,
|
|
222
|
+
interrupt,
|
|
223
|
+
send: handleSendMessage,
|
|
224
|
+
} satisfies LangGraphRuntimeExtras,
|
|
225
|
+
onNew: (msg) => {
|
|
226
|
+
const cancellations =
|
|
227
|
+
autoCancelPendingToolCalls !== false
|
|
228
|
+
? getPendingToolCalls(messages).map(
|
|
229
|
+
(t) =>
|
|
230
|
+
({
|
|
231
|
+
type: "tool",
|
|
232
|
+
name: t.name,
|
|
233
|
+
tool_call_id: t.id,
|
|
234
|
+
content: JSON.stringify({ cancelled: true }),
|
|
235
|
+
}) satisfies LangChainMessage & { type: "tool" },
|
|
236
|
+
)
|
|
237
|
+
: [];
|
|
238
|
+
|
|
239
|
+
return handleSendMessage(
|
|
240
|
+
[
|
|
241
|
+
...cancellations,
|
|
242
|
+
{
|
|
243
|
+
type: "human",
|
|
244
|
+
content: getMessageContent(msg),
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
{
|
|
248
|
+
runConfig: msg.runConfig,
|
|
249
|
+
},
|
|
250
|
+
);
|
|
251
|
+
},
|
|
252
|
+
onAddToolResult: async ({ toolCallId, toolName, result }) => {
|
|
253
|
+
// TODO parallel human in the loop calls
|
|
254
|
+
await handleSendMessage(
|
|
255
|
+
[
|
|
256
|
+
{
|
|
257
|
+
type: "tool",
|
|
258
|
+
name: toolName,
|
|
259
|
+
tool_call_id: toolCallId,
|
|
260
|
+
content: JSON.stringify(result),
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
// TODO reuse runconfig here!
|
|
264
|
+
{},
|
|
265
|
+
);
|
|
266
|
+
},
|
|
267
|
+
onCancel: unstable_allowCancellation
|
|
268
|
+
? async () => {
|
|
269
|
+
cancel();
|
|
270
|
+
}
|
|
271
|
+
: undefined,
|
|
272
|
+
});
|
|
273
|
+
};
|