@assistant-ui/react-langchain 0.0.27 → 0.0.28
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/convertMessages.d.ts +2 -76
- package/dist/convertMessages.d.ts.map +1 -1
- package/dist/convertMessages.js +3 -150
- package/dist/convertMessages.js.map +1 -1
- package/dist/converter.d.ts +180 -0
- package/dist/converter.d.ts.map +1 -0
- package/dist/converter.js +213 -0
- package/dist/converter.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/streamingTiming.d.ts +1 -2
- package/dist/streamingTiming.d.ts.map +1 -1
- package/dist/streamingTiming.js +2 -36
- package/dist/streamingTiming.js.map +1 -1
- package/dist/types.d.ts +2 -55
- package/dist/types.d.ts.map +1 -1
- package/dist/useStreamRuntime.d.ts +2 -8
- package/dist/useStreamRuntime.d.ts.map +1 -1
- package/dist/useStreamRuntime.js +12 -37
- package/dist/useStreamRuntime.js.map +1 -1
- package/package.json +23 -9
- package/src/convertMessages.test.ts +99 -0
- package/src/convertMessages.ts +13 -208
- package/src/converter.ts +372 -0
- package/src/streamingTiming.test.tsx +37 -0
- package/src/streamingTiming.ts +5 -68
- package/src/types.ts +1 -39
- package/src/useStreamRuntime.ts +29 -46
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { parseDataUrl, resolveFilePartSource } from "@assistant-ui/core/internal";
|
|
2
|
+
//#region src/converter.ts
|
|
3
|
+
const convertLangChainContentBlock = (part) => {
|
|
4
|
+
switch (part.type) {
|
|
5
|
+
case "text":
|
|
6
|
+
case "text_delta": return {
|
|
7
|
+
type: "text",
|
|
8
|
+
text: part.text
|
|
9
|
+
};
|
|
10
|
+
case "image_url": {
|
|
11
|
+
const image = typeof part.image_url === "string" ? part.image_url : part.image_url?.url;
|
|
12
|
+
if (!image) return null;
|
|
13
|
+
return {
|
|
14
|
+
type: "image",
|
|
15
|
+
image
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
case "file": return {
|
|
19
|
+
type: "file",
|
|
20
|
+
filename: part.metadata?.filename ?? "file",
|
|
21
|
+
data: part.source_type === "url" ? part.url : part.source_type === "id" ? part.id : part.data,
|
|
22
|
+
mimeType: part.mime_type ?? "application/octet-stream",
|
|
23
|
+
...(part.source_type === "url" || part.source_type === "id") && { sourceType: part.source_type }
|
|
24
|
+
};
|
|
25
|
+
case "audio": {
|
|
26
|
+
const mimeType = part.mime_type ?? "application/octet-stream";
|
|
27
|
+
const subtype = mimeType.startsWith("audio/") ? mimeType.slice(6) : void 0;
|
|
28
|
+
return {
|
|
29
|
+
type: "file",
|
|
30
|
+
filename: subtype ? `audio.${subtype}` : "audio",
|
|
31
|
+
data: part.data,
|
|
32
|
+
mimeType
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
case "thinking": return {
|
|
36
|
+
type: "reasoning",
|
|
37
|
+
text: part.thinking
|
|
38
|
+
};
|
|
39
|
+
case "reasoning": return {
|
|
40
|
+
type: "reasoning",
|
|
41
|
+
text: part.summary && part.summary.length > 0 ? part.summary.map((s) => s?.text ?? "").join("\n\n\n") : part.reasoning ?? ""
|
|
42
|
+
};
|
|
43
|
+
case "tool_use":
|
|
44
|
+
case "input_json_delta": return null;
|
|
45
|
+
default: return;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const hasVisibleText = (text) => typeof text === "string" && text.trim() !== "";
|
|
49
|
+
/**
|
|
50
|
+
* Audio output arrives outside the content array: providers leave `content`
|
|
51
|
+
* empty and put the spoken text in `additional_kwargs.audio.transcript`. The
|
|
52
|
+
* audio bytes stay behind because no provider reports their media type, and a
|
|
53
|
+
* streamed response carries raw PCM rather than a playable file.
|
|
54
|
+
*/
|
|
55
|
+
const withAudioTranscript = (parts, additionalKwargs) => {
|
|
56
|
+
const transcript = (additionalKwargs?.audio)?.transcript;
|
|
57
|
+
if (typeof transcript !== "string" || !hasVisibleText(transcript)) return parts;
|
|
58
|
+
if (parts.some((part) => part.type === "text" && hasVisibleText(part.text))) return parts;
|
|
59
|
+
return [...parts.filter((part) => part.type !== "text"), {
|
|
60
|
+
type: "text",
|
|
61
|
+
text: transcript
|
|
62
|
+
}];
|
|
63
|
+
};
|
|
64
|
+
const getCustomMetadata = (additionalKwargs) => additionalKwargs?.metadata ?? {};
|
|
65
|
+
const uiMessageToDataPart = (ui) => ({
|
|
66
|
+
type: "data",
|
|
67
|
+
name: ui.name,
|
|
68
|
+
data: ui.props
|
|
69
|
+
});
|
|
70
|
+
/**
|
|
71
|
+
* Audio media types that reach a provider's audio input through the LangChain
|
|
72
|
+
* `audio` block. langchain-core derives OpenAI's `input_audio.format` by
|
|
73
|
+
* splitting `mime_type` on `/`, and that format is a wav-or-mp3 enum, so
|
|
74
|
+
* `audio/mpeg` passes the converter and is rejected at the provider.
|
|
75
|
+
*/
|
|
76
|
+
const audioBlockMimeTypes = /* @__PURE__ */ new Map([
|
|
77
|
+
["audio/mp3", "audio/mp3"],
|
|
78
|
+
["audio/mpeg", "audio/mp3"],
|
|
79
|
+
["audio/wav", "audio/wav"],
|
|
80
|
+
["audio/wave", "audio/wav"],
|
|
81
|
+
["audio/x-wav", "audio/wav"]
|
|
82
|
+
]);
|
|
83
|
+
const getMessageContent = (msg) => {
|
|
84
|
+
const allContent = [...msg.content, ...msg.attachments?.flatMap((a) => a.content) ?? []];
|
|
85
|
+
const hasNonText = allContent.some((part) => part.type === "file" || part.type === "image" || part.type === "audio");
|
|
86
|
+
const hasText = allContent.some((part) => part.type === "text");
|
|
87
|
+
if (hasNonText && !hasText) allContent.unshift({
|
|
88
|
+
type: "text",
|
|
89
|
+
text: " "
|
|
90
|
+
});
|
|
91
|
+
const content = allContent.flatMap((part) => {
|
|
92
|
+
const type = part.type;
|
|
93
|
+
switch (type) {
|
|
94
|
+
case "text": return {
|
|
95
|
+
type: "text",
|
|
96
|
+
text: part.text
|
|
97
|
+
};
|
|
98
|
+
case "image": return {
|
|
99
|
+
type: "image_url",
|
|
100
|
+
image_url: { url: part.image }
|
|
101
|
+
};
|
|
102
|
+
case "file": {
|
|
103
|
+
const metadata = { filename: part.filename ?? "file" };
|
|
104
|
+
if (part.sourceType === "id") return {
|
|
105
|
+
type: "file",
|
|
106
|
+
id: part.data,
|
|
107
|
+
mime_type: part.mimeType,
|
|
108
|
+
filename: metadata.filename,
|
|
109
|
+
metadata,
|
|
110
|
+
source_type: "id"
|
|
111
|
+
};
|
|
112
|
+
const source = resolveFilePartSource(part);
|
|
113
|
+
if (source.kind === "url") return {
|
|
114
|
+
type: "file",
|
|
115
|
+
url: source.url,
|
|
116
|
+
mime_type: part.mimeType,
|
|
117
|
+
filename: metadata.filename,
|
|
118
|
+
metadata,
|
|
119
|
+
source_type: "url"
|
|
120
|
+
};
|
|
121
|
+
const audioMimeType = audioBlockMimeTypes.get(source.mimeType.toLowerCase());
|
|
122
|
+
if (audioMimeType) return {
|
|
123
|
+
type: "audio",
|
|
124
|
+
data: source.data,
|
|
125
|
+
mime_type: audioMimeType,
|
|
126
|
+
source_type: "base64"
|
|
127
|
+
};
|
|
128
|
+
return {
|
|
129
|
+
type: "file",
|
|
130
|
+
data: source.data,
|
|
131
|
+
mime_type: source.mimeType,
|
|
132
|
+
filename: metadata.filename,
|
|
133
|
+
metadata,
|
|
134
|
+
source_type: "base64"
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
case "audio": return {
|
|
138
|
+
type: "audio",
|
|
139
|
+
data: parseDataUrl(part.audio.data)?.data ?? part.audio.data,
|
|
140
|
+
mime_type: `audio/${part.audio.format}`,
|
|
141
|
+
source_type: "base64"
|
|
142
|
+
};
|
|
143
|
+
case "data": return [];
|
|
144
|
+
case "tool-call": throw new Error("Tool call appends are not supported.");
|
|
145
|
+
default: throw new Error(`Unsupported append message part type: ${type}`);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
if (content.length === 1 && content[0]?.type === "text") return content[0].text ?? "";
|
|
149
|
+
return content;
|
|
150
|
+
};
|
|
151
|
+
const reasoningTextLength = (part) => {
|
|
152
|
+
if (part.summary && part.summary.length > 0) return part.summary.map((s) => s?.text ?? "").join("\n\n\n").length;
|
|
153
|
+
return part.reasoning?.length ?? 0;
|
|
154
|
+
};
|
|
155
|
+
const createLangChainStreamingTimingAccessors = (getType) => {
|
|
156
|
+
const findAiMessage = (messages, messageId) => messages.find((message) => getType(message) === "ai" && message.id === messageId);
|
|
157
|
+
const getTextLength = (messages, messageId) => {
|
|
158
|
+
const message = findAiMessage(messages, messageId);
|
|
159
|
+
if (!message) return 0;
|
|
160
|
+
const content = message.content;
|
|
161
|
+
if (typeof content === "string") return content.length;
|
|
162
|
+
if (!Array.isArray(content)) return 0;
|
|
163
|
+
let len = 0;
|
|
164
|
+
for (const part of content) switch (part.type) {
|
|
165
|
+
case "text":
|
|
166
|
+
case "text_delta":
|
|
167
|
+
if (typeof part.text === "string") len += part.text.length;
|
|
168
|
+
break;
|
|
169
|
+
case "thinking":
|
|
170
|
+
if (typeof part.thinking === "string") len += part.thinking.length;
|
|
171
|
+
break;
|
|
172
|
+
case "reasoning": len += reasoningTextLength(part);
|
|
173
|
+
}
|
|
174
|
+
return len;
|
|
175
|
+
};
|
|
176
|
+
const getToolCallCount = (messages, messageId) => findAiMessage(messages, messageId)?.tool_calls?.length ?? 0;
|
|
177
|
+
const getAssistantMessageId = (messages) => {
|
|
178
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
179
|
+
const message = messages[i];
|
|
180
|
+
if (message && getType(message) === "ai" && message.id) return message.id;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
return {
|
|
184
|
+
getAssistantMessageId,
|
|
185
|
+
getTextLength,
|
|
186
|
+
getToolCallCount
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Resolve the assistant message a `UIMessage` belongs to: the parent id comes
|
|
191
|
+
* from `metadata.message_id` (Python SDK) or `metadata.id` (JS SDK).
|
|
192
|
+
*/
|
|
193
|
+
const getUIMessageParentId = (ui) => ui.metadata?.message_id ?? ui.metadata?.id;
|
|
194
|
+
/**
|
|
195
|
+
* Group the graph's accumulated `UIMessage`s by the assistant message they
|
|
196
|
+
* belong to. Non-array state and entries without a parent link are dropped.
|
|
197
|
+
*/
|
|
198
|
+
const groupUIMessagesByParent = (value) => {
|
|
199
|
+
const map = /* @__PURE__ */ new Map();
|
|
200
|
+
if (!Array.isArray(value)) return map;
|
|
201
|
+
for (const ui of value) {
|
|
202
|
+
const parentId = getUIMessageParentId(ui);
|
|
203
|
+
if (!parentId) continue;
|
|
204
|
+
const existing = map.get(parentId);
|
|
205
|
+
if (existing) existing.push(ui);
|
|
206
|
+
else map.set(parentId, [ui]);
|
|
207
|
+
}
|
|
208
|
+
return map;
|
|
209
|
+
};
|
|
210
|
+
//#endregion
|
|
211
|
+
export { convertLangChainContentBlock, createLangChainStreamingTimingAccessors, getCustomMetadata, getMessageContent, getUIMessageParentId, groupUIMessagesByParent, uiMessageToDataPart, withAudioTranscript };
|
|
212
|
+
|
|
213
|
+
//# sourceMappingURL=converter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"converter.js","names":["_exhaustiveCheck"],"sources":["../src/converter.ts"],"sourcesContent":["import type {\n AppendMessage,\n DataMessagePart,\n ThreadAssistantMessage,\n ThreadUserMessage,\n} from \"@assistant-ui/core\";\nimport {\n parseDataUrl,\n resolveFilePartSource,\n} from \"@assistant-ui/core/internal\";\nimport type { StreamingTimingAccessors } from \"@assistant-ui/core/react\";\n\n/** Known content block types from @langchain/core messages. */\nexport type LangChainContentBlock =\n | { type: \"text\"; text: string }\n | { type: \"text_delta\"; text: string }\n | { type: \"image_url\"; image_url: string | { url?: string } }\n | { type: \"thinking\"; thinking: string }\n | {\n type: \"reasoning\";\n summary?: Array<{ type: \"summary_text\"; text?: string }>;\n reasoning?: string;\n }\n | {\n type: \"file\";\n data: string;\n mime_type: string;\n source_type?: \"base64\";\n metadata?: { filename?: string };\n }\n | {\n type: \"file\";\n url: string;\n mime_type?: string;\n source_type: \"url\";\n metadata?: { filename?: string };\n }\n | {\n type: \"file\";\n id: string;\n mime_type?: string;\n source_type: \"id\";\n metadata?: { filename?: string };\n }\n | {\n type: \"audio\";\n data: string;\n mime_type: string;\n source_type: \"base64\";\n }\n | { type: \"tool_use\" | \"input_json_delta\" };\n\ntype ConvertedContentPart =\n | ThreadUserMessage[\"content\"][number]\n | ThreadAssistantMessage[\"content\"][number];\n\nexport const convertLangChainContentBlock = (\n part: LangChainContentBlock,\n): ConvertedContentPart | null | undefined => {\n const type = part.type;\n switch (type) {\n case \"text\":\n case \"text_delta\":\n return { type: \"text\" as const, text: part.text };\n case \"image_url\": {\n const image =\n typeof part.image_url === \"string\"\n ? part.image_url\n : part.image_url?.url;\n if (!image) return null;\n return { type: \"image\" as const, image };\n }\n case \"file\":\n return {\n type: \"file\" as const,\n filename: part.metadata?.filename ?? \"file\",\n data:\n part.source_type === \"url\"\n ? part.url\n : part.source_type === \"id\"\n ? part.id\n : part.data,\n mimeType: part.mime_type ?? \"application/octet-stream\",\n ...((part.source_type === \"url\" || part.source_type === \"id\") && {\n sourceType: part.source_type,\n }),\n };\n case \"audio\": {\n const mimeType = part.mime_type ?? \"application/octet-stream\";\n const subtype = mimeType.startsWith(\"audio/\")\n ? mimeType.slice(\"audio/\".length)\n : undefined;\n return {\n type: \"file\" as const,\n filename: subtype ? `audio.${subtype}` : \"audio\",\n data: part.data,\n mimeType,\n };\n }\n case \"thinking\":\n return { type: \"reasoning\" as const, text: part.thinking };\n case \"reasoning\":\n return {\n type: \"reasoning\" as const,\n text:\n part.summary && part.summary.length > 0\n ? part.summary.map((s) => s?.text ?? \"\").join(\"\\n\\n\\n\")\n : (part.reasoning ?? \"\"),\n };\n case \"tool_use\":\n case \"input_json_delta\":\n return null;\n default:\n return undefined;\n }\n};\n\nconst hasVisibleText = (text: unknown): boolean =>\n typeof text === \"string\" && text.trim() !== \"\";\n\n/**\n * Audio output arrives outside the content array: providers leave `content`\n * empty and put the spoken text in `additional_kwargs.audio.transcript`. The\n * audio bytes stay behind because no provider reports their media type, and a\n * streamed response carries raw PCM rather than a playable file.\n */\nexport const withAudioTranscript = <T extends { type: string; text?: unknown }>(\n parts: readonly T[],\n additionalKwargs: Record<string, unknown> | undefined,\n): readonly (T | { type: \"text\"; text: string })[] => {\n const audio = additionalKwargs?.audio as { transcript?: unknown } | undefined;\n const transcript = audio?.transcript;\n if (typeof transcript !== \"string\" || !hasVisibleText(transcript))\n return parts;\n if (parts.some((part) => part.type === \"text\" && hasVisibleText(part.text)))\n return parts;\n return [\n ...parts.filter((part) => part.type !== \"text\"),\n { type: \"text\" as const, text: transcript },\n ];\n};\n\nexport const getCustomMetadata = (\n additionalKwargs: Record<string, unknown> | undefined,\n): Record<string, unknown> =>\n (additionalKwargs?.metadata as Record<string, unknown>) ?? {};\n\nexport const uiMessageToDataPart = <\n TUIMessage extends { name: string; props: Record<string, unknown> },\n>(\n ui: TUIMessage,\n): DataMessagePart => ({\n type: \"data\",\n name: ui.name,\n data: ui.props,\n});\n\n/**\n * Audio media types that reach a provider's audio input through the LangChain\n * `audio` block. langchain-core derives OpenAI's `input_audio.format` by\n * splitting `mime_type` on `/`, and that format is a wav-or-mp3 enum, so\n * `audio/mpeg` passes the converter and is rejected at the provider.\n */\nconst audioBlockMimeTypes = new Map<string, \"audio/mp3\" | \"audio/wav\">([\n [\"audio/mp3\", \"audio/mp3\"],\n [\"audio/mpeg\", \"audio/mp3\"],\n [\"audio/wav\", \"audio/wav\"],\n [\"audio/wave\", \"audio/wav\"],\n [\"audio/x-wav\", \"audio/wav\"],\n]);\n\nexport const getMessageContent = (msg: AppendMessage) => {\n const allContent = [\n ...msg.content,\n ...(msg.attachments?.flatMap((a) => a.content) ?? []),\n ];\n\n const hasNonText = allContent.some(\n (part) =>\n part.type === \"file\" || part.type === \"image\" || part.type === \"audio\",\n );\n const hasText = allContent.some((part) => part.type === \"text\");\n if (hasNonText && !hasText) {\n allContent.unshift({ type: \"text\", text: \" \" });\n }\n\n const content = allContent.flatMap((part) => {\n const type = part.type;\n switch (type) {\n case \"text\":\n return { type: \"text\" as const, text: part.text };\n case \"image\":\n return { type: \"image_url\" as const, image_url: { url: part.image } };\n case \"file\": {\n const metadata = { filename: part.filename ?? \"file\" };\n if (part.sourceType === \"id\") {\n return {\n type: \"file\" as const,\n id: part.data,\n mime_type: part.mimeType,\n filename: metadata.filename,\n metadata,\n source_type: \"id\" as const,\n };\n }\n const source = resolveFilePartSource(part);\n if (source.kind === \"url\") {\n return {\n type: \"file\" as const,\n url: source.url,\n mime_type: part.mimeType,\n filename: metadata.filename,\n metadata,\n source_type: \"url\" as const,\n };\n }\n const audioMimeType = audioBlockMimeTypes.get(\n source.mimeType.toLowerCase(),\n );\n if (audioMimeType) {\n return {\n type: \"audio\" as const,\n data: source.data,\n mime_type: audioMimeType,\n source_type: \"base64\" as const,\n };\n }\n return {\n type: \"file\" as const,\n data: source.data,\n mime_type: source.mimeType,\n filename: metadata.filename,\n metadata,\n source_type: \"base64\" as const,\n };\n }\n case \"audio\": {\n const parsed = parseDataUrl(part.audio.data);\n return {\n type: \"audio\" as const,\n data: parsed?.data ?? part.audio.data,\n mime_type: `audio/${part.audio.format}`,\n source_type: \"base64\" as const,\n };\n }\n case \"data\":\n return [];\n case \"tool-call\":\n throw new Error(\"Tool call appends are not supported.\");\n default: {\n const _exhaustiveCheck: \"reasoning\" | \"source\" | \"generative-ui\" = type;\n throw new Error(\n `Unsupported append message part type: ${_exhaustiveCheck}`,\n );\n }\n }\n });\n\n if (content.length === 1 && content[0]?.type === \"text\") {\n return content[0].text ?? \"\";\n }\n return content;\n};\n\nconst reasoningTextLength = (part: {\n readonly summary?: ReadonlyArray<{ readonly text?: string }>;\n readonly reasoning?: string;\n}): number => {\n if (part.summary && part.summary.length > 0)\n return part.summary.map((s) => s?.text ?? \"\").join(\"\\n\\n\\n\").length;\n return part.reasoning?.length ?? 0;\n};\n\nexport const createLangChainStreamingTimingAccessors = <\n TMessage extends {\n id?: string | undefined;\n content?: unknown;\n tool_calls?: readonly unknown[] | undefined;\n },\n>(\n getType: (message: TMessage) => string,\n): StreamingTimingAccessors<TMessage> => {\n const findAiMessage = (\n messages: readonly TMessage[],\n messageId: string,\n ): TMessage | undefined =>\n messages.find(\n (message) => getType(message) === \"ai\" && message.id === messageId,\n );\n\n const getTextLength = (\n messages: readonly TMessage[],\n messageId: string,\n ): number => {\n const message = findAiMessage(messages, messageId);\n if (!message) return 0;\n const content = message.content;\n if (typeof content === \"string\") return content.length;\n if (!Array.isArray(content)) return 0;\n let len = 0;\n for (const part of content as readonly LangChainContentBlock[]) {\n switch (part.type) {\n case \"text\":\n case \"text_delta\":\n if (typeof part.text === \"string\") len += part.text.length;\n break;\n case \"thinking\":\n if (typeof part.thinking === \"string\") len += part.thinking.length;\n break;\n case \"reasoning\":\n len += reasoningTextLength(part);\n break;\n }\n }\n return len;\n };\n\n const getToolCallCount = (\n messages: readonly TMessage[],\n messageId: string,\n ): number => findAiMessage(messages, messageId)?.tool_calls?.length ?? 0;\n\n const getAssistantMessageId = (\n messages: readonly TMessage[],\n ): string | undefined => {\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages[i];\n if (message && getType(message) === \"ai\" && message.id) return message.id;\n }\n return undefined;\n };\n\n return {\n getAssistantMessageId,\n getTextLength,\n getToolCallCount,\n };\n};\n\n/**\n * Resolve the assistant message a `UIMessage` belongs to: the parent id comes\n * from `metadata.message_id` (Python SDK) or `metadata.id` (JS SDK).\n */\nexport const getUIMessageParentId = (ui: {\n metadata?: { message_id?: string; id?: string } | undefined;\n}): string | undefined => ui.metadata?.message_id ?? ui.metadata?.id;\n\n/**\n * Group the graph's accumulated `UIMessage`s by the assistant message they\n * belong to. Non-array state and entries without a parent link are dropped.\n */\nexport const groupUIMessagesByParent = <\n T extends {\n metadata?: { message_id?: string; id?: string } | undefined;\n },\n>(\n value: unknown,\n): Map<string, T[]> => {\n const map = new Map<string, T[]>();\n if (!Array.isArray(value)) return map;\n for (const ui of value as T[]) {\n const parentId = getUIMessageParentId(ui);\n if (!parentId) continue;\n const existing = map.get(parentId);\n if (existing) {\n existing.push(ui);\n } else {\n map.set(parentId, [ui]);\n }\n }\n return map;\n};\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LangChainContentBlock } from "./converter.js";
|
|
2
|
+
import { LangChainBaseMessage, LangChainToolCall, RemoveUIMessage, UIMessage, UseStreamRuntimeOptions } from "./types.js";
|
|
2
3
|
import { convertLangChainBaseMessage } from "./convertMessages.js";
|
|
3
4
|
import { useLangChainError, useLangChainInterruptState, useLangChainInterrupts, useLangChainRespond, useLangChainRespondAll, useLangChainSend, useLangChainSendCommand, useLangChainState, useLangChainStream, useLangChainSubagents, useLangChainSubgraphs, useLangChainSubmit, useLangChainToolCalls } from "./hooks.js";
|
|
4
5
|
import { useStreamRuntime } from "./useStreamRuntime.js";
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { LangChainBaseMessage } from "./types.js";
|
|
2
|
-
import { StreamingTimingAccessors } from "@assistant-ui/core/react";
|
|
3
2
|
import { MessageTiming } from "@assistant-ui/core";
|
|
4
3
|
//#region src/streamingTiming.d.ts
|
|
5
|
-
declare const langChainStreamingTimingAccessors: StreamingTimingAccessors<LangChainBaseMessage>;
|
|
4
|
+
declare const langChainStreamingTimingAccessors: import("@assistant-ui/core").StreamingTimingAccessors<LangChainBaseMessage>;
|
|
6
5
|
/**
|
|
7
6
|
* Tracks per-message streaming timing for LangChain messages. Delegates to
|
|
8
7
|
* the shared `useStreamingTiming` primitive in `@assistant-ui/core/react`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streamingTiming.d.ts","names":[],"sources":["../src/streamingTiming.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"streamingTiming.d.ts","names":[],"sources":["../src/streamingTiming.ts"],"mappings":";;;cAQa,gEAAiC,yBAAA;;;;;;;cASjC,8BACX,mBAAmB,wBACnB,uBACC,eAAe"}
|
package/dist/streamingTiming.js
CHANGED
|
@@ -1,43 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { createLangChainStreamingTimingAccessors } from "./converter.js";
|
|
2
3
|
import { getMessageType } from "./convertMessages.js";
|
|
3
4
|
import { useStreamingTiming } from "@assistant-ui/core/react";
|
|
4
5
|
//#region src/streamingTiming.ts
|
|
5
|
-
const
|
|
6
|
-
const reasoningTextLength = (part) => {
|
|
7
|
-
if (part.summary && part.summary.length > 0) return part.summary.map((s) => s?.text ?? "").join("\n\n\n").length;
|
|
8
|
-
return part.reasoning?.length ?? 0;
|
|
9
|
-
};
|
|
10
|
-
const getTextLength = (messages, messageId) => {
|
|
11
|
-
const m = findAiMessage(messages, messageId);
|
|
12
|
-
if (!m) return 0;
|
|
13
|
-
const content = m.content;
|
|
14
|
-
if (typeof content === "string") return content.length;
|
|
15
|
-
if (!Array.isArray(content)) return 0;
|
|
16
|
-
let len = 0;
|
|
17
|
-
for (const part of content) switch (part.type) {
|
|
18
|
-
case "text":
|
|
19
|
-
case "text_delta":
|
|
20
|
-
if (typeof part.text === "string") len += part.text.length;
|
|
21
|
-
break;
|
|
22
|
-
case "thinking":
|
|
23
|
-
if (typeof part.thinking === "string") len += part.thinking.length;
|
|
24
|
-
break;
|
|
25
|
-
case "reasoning": len += reasoningTextLength(part);
|
|
26
|
-
}
|
|
27
|
-
return len;
|
|
28
|
-
};
|
|
29
|
-
const getToolCallCount = (messages, messageId) => findAiMessage(messages, messageId)?.tool_calls?.length ?? 0;
|
|
30
|
-
const getAssistantMessageId = (messages) => {
|
|
31
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
32
|
-
const m = messages[i];
|
|
33
|
-
if (m && getMessageType(m) === "ai" && m.id) return m.id;
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
const langChainStreamingTimingAccessors = {
|
|
37
|
-
getAssistantMessageId,
|
|
38
|
-
getTextLength,
|
|
39
|
-
getToolCallCount
|
|
40
|
-
};
|
|
6
|
+
const langChainStreamingTimingAccessors = createLangChainStreamingTimingAccessors(getMessageType);
|
|
41
7
|
/**
|
|
42
8
|
* Tracks per-message streaming timing for LangChain messages. Delegates to
|
|
43
9
|
* the shared `useStreamingTiming` primitive in `@assistant-ui/core/react`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streamingTiming.js","names":[],"sources":["../src/streamingTiming.ts"],"sourcesContent":["\"use client\";\n\nimport type { MessageTiming } from \"@assistant-ui/core\";\nimport {
|
|
1
|
+
{"version":3,"file":"streamingTiming.js","names":[],"sources":["../src/streamingTiming.ts"],"sourcesContent":["\"use client\";\n\nimport type { MessageTiming } from \"@assistant-ui/core\";\nimport { useStreamingTiming } from \"@assistant-ui/core/react\";\nimport { createLangChainStreamingTimingAccessors } from \"./converter\";\nimport { getMessageType } from \"./convertMessages\";\nimport type { LangChainBaseMessage } from \"./types\";\n\nexport const langChainStreamingTimingAccessors =\n createLangChainStreamingTimingAccessors<LangChainBaseMessage>(getMessageType);\n\n/**\n * Tracks per-message streaming timing for LangChain messages. Delegates to\n * the shared `useStreamingTiming` primitive in `@assistant-ui/core/react`,\n * adapted to the `LangChainBaseMessage` shape (`_getType() -> \"ai\"`, content\n * blocks including text/thinking/reasoning, `tool_calls`).\n */\nexport const useLangChainStreamingTiming = (\n messages: readonly LangChainBaseMessage[],\n isRunning: boolean,\n): Record<string, MessageTiming> =>\n useStreamingTiming(messages, isRunning, langChainStreamingTimingAccessors);\n"],"mappings":";;;;;AAQA,MAAa,oCACX,wCAA8D,cAAc;;;;;;;AAQ9E,MAAa,+BACX,UACA,cAEA,mBAAmB,UAAU,WAAW,iCAAiC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,61 +1,8 @@
|
|
|
1
|
+
import { LangChainContentBlock } from "./converter.js";
|
|
1
2
|
import { AttachmentAdapter, DictationAdapter, ExternalStoreSharedOptions, FeedbackAdapter, MessageStatus, RealtimeVoiceAdapter, RemoteThreadListAdapter, SpeechSynthesisAdapter } from "@assistant-ui/core";
|
|
2
3
|
import { AnyStream, AssembledToolCall, SubagentDiscoverySnapshot, SubgraphDiscoverySnapshot, UseStreamOptions } from "@langchain/react";
|
|
3
4
|
import { AssistantCloud } from "assistant-cloud";
|
|
4
5
|
//#region src/types.d.ts
|
|
5
|
-
/** Known content block types from @langchain/core messages. */
|
|
6
|
-
type LangChainContentBlock = {
|
|
7
|
-
type: "text";
|
|
8
|
-
text: string;
|
|
9
|
-
} | {
|
|
10
|
-
type: "text_delta";
|
|
11
|
-
text: string;
|
|
12
|
-
} | {
|
|
13
|
-
type: "image_url";
|
|
14
|
-
image_url: string | {
|
|
15
|
-
url?: string;
|
|
16
|
-
};
|
|
17
|
-
} | {
|
|
18
|
-
type: "thinking";
|
|
19
|
-
thinking: string;
|
|
20
|
-
} | {
|
|
21
|
-
type: "reasoning";
|
|
22
|
-
summary?: Array<{
|
|
23
|
-
type: "summary_text";
|
|
24
|
-
text?: string;
|
|
25
|
-
}>;
|
|
26
|
-
reasoning?: string;
|
|
27
|
-
} | {
|
|
28
|
-
type: "file";
|
|
29
|
-
data: string;
|
|
30
|
-
mime_type: string;
|
|
31
|
-
source_type?: "base64";
|
|
32
|
-
metadata?: {
|
|
33
|
-
filename?: string;
|
|
34
|
-
};
|
|
35
|
-
} | {
|
|
36
|
-
type: "file";
|
|
37
|
-
url: string;
|
|
38
|
-
mime_type?: string;
|
|
39
|
-
source_type: "url";
|
|
40
|
-
metadata?: {
|
|
41
|
-
filename?: string;
|
|
42
|
-
};
|
|
43
|
-
} | {
|
|
44
|
-
type: "file";
|
|
45
|
-
id: string;
|
|
46
|
-
mime_type?: string;
|
|
47
|
-
source_type: "id";
|
|
48
|
-
metadata?: {
|
|
49
|
-
filename?: string;
|
|
50
|
-
};
|
|
51
|
-
} | {
|
|
52
|
-
type: "audio";
|
|
53
|
-
data: string;
|
|
54
|
-
mime_type: string;
|
|
55
|
-
source_type: "base64";
|
|
56
|
-
} | {
|
|
57
|
-
type: "tool_use" | "input_json_delta";
|
|
58
|
-
};
|
|
59
6
|
type LangChainToolCall = {
|
|
60
7
|
id: string;
|
|
61
8
|
name: string;
|
|
@@ -183,5 +130,5 @@ type LangChainRuntimeExtraOptions = ExternalStoreSharedOptions & {
|
|
|
183
130
|
};
|
|
184
131
|
type UseStreamRuntimeOptions = UseStreamOptions extends (infer O) ? O extends UseStreamOptions ? O & LangChainRuntimeExtraOptions : never : never;
|
|
185
132
|
//#endregion
|
|
186
|
-
export { LangChainBaseMessage, LangChainContentBlock, LangChainRuntimeExtraOptions, LangChainRuntimeExtras, LangChainToolCall, RemoveUIMessage, UIMessage, UseStreamRuntimeOptions };
|
|
133
|
+
export { LangChainBaseMessage, type LangChainContentBlock, LangChainRuntimeExtraOptions, LangChainRuntimeExtras, LangChainToolCall, RemoveUIMessage, UIMessage, UseStreamRuntimeOptions };
|
|
187
134
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;KAqBY;EACV;EACA;EACA,MAAM;;;;;;;;;KAUI,UACV,+BACA,eAAe,0BAA0B;EAEzC;EACA;EACA,MAAM;EACN,OAAO;EACP;IACE;IACA;IACA;IACA;IACA;IACA;KACC;;;;;;;KAQO;EACV;EACA;;;;;;;;;KAUU;EACV;EACA;EACA;EACA;EACA,oBAAoB;;EAEpB,sBAAsB;;EAEtB;;EAEA,SAAS;;EAET;;KAGU;EACV;IAAa;;EACb;IAAuB;IAAa;;EACpC,oBAAoB;EACpB,WAAW,oBAAoB;EAC/B,WAAW,oBAAoB;EAC/B,QAAQ;EACR;EACA,SACE,QAAQ,4CACR,UAAU,4BACP;EACL,UACE,mBACA,UAAU,4BACP;EACL,aACE,eAAe,yBACf,UAAU,4BACP;EACL,QAAQ;EACR;;KAGU,+BAA+B;;;;;;;;;EASzC,qBAAqB;EACrB,QAAQ;EACR;IAEM,cAAc;IACd,SAAS;IACT,YAAY;IACZ,QAAQ;IACR,WAAW;;;;;;;;EASjB;;;;;EAKA;;;;;EAKA,6BAA6B;;EAE7B,gBAAgB;IAAU;;;EAE1B,WAAW,qBAAqB;;;;;;;EAOhC;;KAOU,0BAA0B,gCAA+B,KACjE,UAAU,mBACR,IAAI"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/// <reference types="@assistant-ui/core/store" />
|
|
2
|
-
import {
|
|
2
|
+
import { groupUIMessagesByParent } from "./converter.js";
|
|
3
|
+
import { UseStreamRuntimeOptions } from "./types.js";
|
|
3
4
|
import { AppendMessage } from "@assistant-ui/core";
|
|
4
5
|
//#region src/useStreamRuntime.d.ts
|
|
5
6
|
declare const runConfigToSubmitOptions: (runConfig: AppendMessage["runConfig"]) => {
|
|
@@ -7,13 +8,6 @@ declare const runConfigToSubmitOptions: (runConfig: AppendMessage["runConfig"])
|
|
|
7
8
|
configurable: Record<string, unknown>;
|
|
8
9
|
};
|
|
9
10
|
} | undefined;
|
|
10
|
-
/**
|
|
11
|
-
* Group the graph's accumulated `UIMessage`s by the assistant message they
|
|
12
|
-
* belong to. Non-array state and entries without a parent link are dropped.
|
|
13
|
-
* The parent id comes from `metadata.message_id` (Python SDK) or
|
|
14
|
-
* `metadata.id` (JS SDK).
|
|
15
|
-
*/
|
|
16
|
-
declare const groupUIMessagesByParent: (value: unknown) => Map<string, UIMessage[]>;
|
|
17
11
|
/**
|
|
18
12
|
* Creates an assistant-ui runtime backed by LangChain's `useStream` hook.
|
|
19
13
|
* Accepts the same options as `useStream` from `@langchain/react`, plus
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStreamRuntime.d.ts","names":[],"sources":["../src/useStreamRuntime.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"useStreamRuntime.d.ts","names":[],"sources":["../src/useStreamRuntime.ts"],"mappings":";;;;cA6Ca,2BACX,WAAW;;kBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;cA+iBA,mBAAoB,YAAY,yDAAuB"}
|
package/dist/useStreamRuntime.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { getMessageContent, groupUIMessagesByParent } from "./converter.js";
|
|
3
|
+
import { convertLangChainBaseMessage, getMessageType } from "./convertMessages.js";
|
|
3
4
|
import { langChainExtras } from "./runtimeExtras.js";
|
|
4
5
|
import { foldUIUpdates, mergeUIMessages } from "./uiMessages.js";
|
|
5
6
|
import { resolveForkCheckpoint } from "./resolveForkCheckpoint.js";
|
|
6
7
|
import { useLangChainStreamingTiming } from "./streamingTiming.js";
|
|
8
|
+
import { createCloudThreadListAdapterCreateFallback, createToolCallCancellationStub, scanPendingToolCalls } from "@assistant-ui/core/internal";
|
|
7
9
|
import { useAui, useAuiState } from "@assistant-ui/store";
|
|
8
10
|
import { useCloudThreadListAdapter, useExternalMessageConverter, useExternalStoreRuntime, useRemoteThreadListRuntime } from "@assistant-ui/core/react";
|
|
9
11
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
@@ -12,33 +14,11 @@ import { STREAM_CONTROLLER, useChannel, useStream } from "@langchain/react";
|
|
|
12
14
|
//#region src/useStreamRuntime.ts
|
|
13
15
|
const UI_CUSTOM_CHANNELS = ["custom"];
|
|
14
16
|
const runConfigToSubmitOptions = (runConfig) => runConfig?.custom ? { config: { configurable: runConfig.custom } } : void 0;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
*/
|
|
21
|
-
const groupUIMessagesByParent = (value) => {
|
|
22
|
-
const map = /* @__PURE__ */ new Map();
|
|
23
|
-
if (!Array.isArray(value)) return map;
|
|
24
|
-
for (const ui of value) {
|
|
25
|
-
const parentId = ui.metadata?.message_id ?? ui.metadata?.id;
|
|
26
|
-
if (!parentId) continue;
|
|
27
|
-
const existing = map.get(parentId);
|
|
28
|
-
if (existing) existing.push(ui);
|
|
29
|
-
else map.set(parentId, [ui]);
|
|
30
|
-
}
|
|
31
|
-
return map;
|
|
32
|
-
};
|
|
33
|
-
const getPendingToolCalls = (messages) => {
|
|
34
|
-
const pending = /* @__PURE__ */ new Map();
|
|
35
|
-
for (const m of messages) {
|
|
36
|
-
const type = getMessageType(m);
|
|
37
|
-
if (type === "ai") for (const tc of m.tool_calls ?? []) pending.set(tc.id, tc);
|
|
38
|
-
else if (type === "tool" && m.tool_call_id) pending.delete(m.tool_call_id);
|
|
39
|
-
}
|
|
40
|
-
return [...pending.values()];
|
|
41
|
-
};
|
|
17
|
+
const getPendingToolCalls = (messages) => scanPendingToolCalls(messages, (message) => {
|
|
18
|
+
const type = getMessageType(message);
|
|
19
|
+
if (type === "ai") return { toolCalls: message.tool_calls ?? [] };
|
|
20
|
+
if (type === "tool" && message.tool_call_id) return { toolCallId: message.tool_call_id };
|
|
21
|
+
}, (toolCall) => toolCall.id);
|
|
42
22
|
const toStagedHumanMessage = (msg, id = generateId()) => ({
|
|
43
23
|
id,
|
|
44
24
|
_getType: () => "human",
|
|
@@ -218,7 +198,7 @@ const useStreamThreadRuntime = (options) => {
|
|
|
218
198
|
]);
|
|
219
199
|
return useExternalStoreRuntime({
|
|
220
200
|
...pickExternalStoreSharedOptions(options),
|
|
221
|
-
isRunning:
|
|
201
|
+
isRunning: stream.isLoading,
|
|
222
202
|
isLoading: stream.isThreadLoading,
|
|
223
203
|
messages: threadMessages,
|
|
224
204
|
adapters,
|
|
@@ -233,13 +213,7 @@ const useStreamThreadRuntime = (options) => {
|
|
|
233
213
|
const stagedMessageId = stageUserMessage(msg, true).id;
|
|
234
214
|
setActiveRunConfig(msg.runConfig);
|
|
235
215
|
const content = getMessageContent(msg);
|
|
236
|
-
const cancellations = autoCancelPendingToolCalls !== false ? getPendingToolCalls(streamRef.current.messages).map(
|
|
237
|
-
type: "tool",
|
|
238
|
-
name: t.name,
|
|
239
|
-
tool_call_id: t.id,
|
|
240
|
-
content: JSON.stringify({ cancelled: true }),
|
|
241
|
-
status: "error"
|
|
242
|
-
})) : [];
|
|
216
|
+
const cancellations = autoCancelPendingToolCalls !== false ? getPendingToolCalls(streamRef.current.messages).map(createToolCallCancellationStub) : [];
|
|
243
217
|
try {
|
|
244
218
|
const { externalId } = await aui.threadListItem.initialize();
|
|
245
219
|
await streamRef.current.submit({ [messagesKey]: [...cancellations, {
|
|
@@ -366,9 +340,10 @@ const useStreamRuntime = (rawOptions) => {
|
|
|
366
340
|
const { cloud, unstable_threadListAdapter, create, delete: deleteFn, onThreadIdChange, ...options } = rawOptions;
|
|
367
341
|
const optionsRef = useRef(options);
|
|
368
342
|
optionsRef.current = options;
|
|
343
|
+
const aui = useAui();
|
|
369
344
|
const cloudAdapter = useCloudThreadListAdapter({
|
|
370
345
|
cloud,
|
|
371
|
-
create,
|
|
346
|
+
create: createCloudThreadListAdapterCreateFallback(create, aui.threadListItem),
|
|
372
347
|
delete: deleteFn
|
|
373
348
|
});
|
|
374
349
|
return useRemoteThreadListRuntime({
|