@assistant-ui/react-langchain 0.0.27 → 0.0.29

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.
@@ -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 { LangChainBaseMessage, LangChainContentBlock, LangChainToolCall, RemoveUIMessage, UIMessage, UseStreamRuntimeOptions } from "./types.js";
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":";;;;cAmEa,mCAAmC,yBAAyB;;;;;;;cAa5D,8BACX,mBAAmB,wBACnB,uBACC,eAAe"}
1
+ {"version":3,"file":"streamingTiming.d.ts","names":[],"sources":["../src/streamingTiming.ts"],"mappings":";;;cAQa,gEAAiC,yBAAA;;;;;;;cASjC,8BACX,mBAAmB,wBACnB,uBACC,eAAe"}
@@ -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 findAiMessage = (messages, messageId) => messages.find((m) => getMessageType(m) === "ai" && m.id === messageId);
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 {\n useStreamingTiming,\n type StreamingTimingAccessors,\n} from \"@assistant-ui/core/react\";\nimport type { LangChainBaseMessage, LangChainContentBlock } from \"./types\";\nimport { getMessageType } from \"./convertMessages\";\n\nconst findAiMessage = (\n messages: readonly LangChainBaseMessage[],\n messageId: string,\n): LangChainBaseMessage | undefined =>\n messages.find((m) => getMessageType(m) === \"ai\" && m.id === messageId);\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\nconst getTextLength = (\n messages: readonly LangChainBaseMessage[],\n messageId: string,\n): number => {\n const m = findAiMessage(messages, messageId);\n if (!m) return 0;\n const content = m.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\nconst getToolCallCount = (\n messages: readonly LangChainBaseMessage[],\n messageId: string,\n): number => findAiMessage(messages, messageId)?.tool_calls?.length ?? 0;\n\nconst getAssistantMessageId = (\n messages: readonly LangChainBaseMessage[],\n): string | undefined => {\n for (let i = messages.length - 1; i >= 0; i--) {\n const m = messages[i];\n if (m && getMessageType(m) === \"ai\" && m.id) return m.id;\n }\n return undefined;\n};\n\nexport const langChainStreamingTimingAccessors: StreamingTimingAccessors<LangChainBaseMessage> =\n {\n getAssistantMessageId,\n getTextLength,\n getToolCallCount,\n };\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":";;;;AAUA,MAAM,iBACJ,UACA,cAEA,SAAS,MAAM,MAAM,eAAe,CAAC,MAAM,QAAQ,EAAE,OAAO,SAAS;AAEvE,MAAM,uBAAuB,SAGf;CACZ,IAAI,KAAK,WAAW,KAAK,QAAQ,SAAS,GACxC,OAAO,KAAK,QAAQ,KAAK,MAAM,GAAG,QAAQ,EAAE,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC;CAC/D,OAAO,KAAK,WAAW,UAAU;AACnC;AAEA,MAAM,iBACJ,UACA,cACW;CACX,MAAM,IAAI,cAAc,UAAU,SAAS;CAC3C,IAAI,CAAC,GAAG,OAAO;CACf,MAAM,UAAU,EAAE;CAClB,IAAI,OAAO,YAAY,UAAU,OAAO,QAAQ;CAChD,IAAI,CAAC,MAAM,QAAQ,OAAO,GAAG,OAAO;CACpC,IAAI,MAAM;CACV,KAAK,MAAM,QAAQ,SACjB,QAAQ,KAAK,MAAb;EACE,KAAK;EACL,KAAK;GACH,IAAI,OAAO,KAAK,SAAS,UAAU,OAAO,KAAK,KAAK;GACpD;EACF,KAAK;GACH,IAAI,OAAO,KAAK,aAAa,UAAU,OAAO,KAAK,SAAS;GAC5D;EACF,KAAK,aACH,OAAO,oBAAoB,IAAI;CAEnC;CAEF,OAAO;AACT;AAEA,MAAM,oBACJ,UACA,cACW,cAAc,UAAU,SAAS,CAAC,EAAE,YAAY,UAAU;AAEvE,MAAM,yBACJ,aACuB;CACvB,KAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;EAC7C,MAAM,IAAI,SAAS;EACnB,IAAI,KAAK,eAAe,CAAC,MAAM,QAAQ,EAAE,IAAI,OAAO,EAAE;CACxD;AAEF;AAEA,MAAa,oCACX;CACE;CACA;CACA;AACF;;;;;;;AAQF,MAAa,+BACX,UACA,cAEA,mBAAmB,UAAU,WAAW,iCAAiC"}
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
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;KAoBY;EACN;EAAc;;EACd;EAAoB;;EACpB;EAAmB;IAAsB;;;EACzC;EAAkB;;EAElB;EACA,UAAU;IAAQ;IAAsB;;EACxC;;EAGA;EACA;EACA;EACA;EACA;IAAa;;;EAGb;EACA;EACA;EACA;EACA;IAAa;;;EAGb;EACA;EACA;EACA;EACA;IAAa;;;EAGb;EACA;EACA;EACA;;EAEA;;KAEM;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
+ {"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 { UIMessage, UseStreamRuntimeOptions } from "./types.js";
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":";;;cAsCa,2BACX,WAAW;;kBAAA;;;;;;;;;cAgBA,0BACX,mBACC,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;cAyjBF,mBAAoB,YAAY,yDAAuB"}
1
+ {"version":3,"file":"useStreamRuntime.d.ts","names":[],"sources":["../src/useStreamRuntime.ts"],"mappings":";;;;cAoDa,2BACX,WAAW;;kBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;cAqjBA,mBAAoB,YAAY,yDAAuB"}
@@ -1,44 +1,24 @@
1
1
  "use client";
2
- import { convertLangChainBaseMessage, getMessageContent, getMessageType } from "./convertMessages.js";
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
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
11
+ import { useCallback, useEffect, useInsertionEffect, useMemo, useRef, useState } from "react";
10
12
  import { generateId, getExternalStoreMessages, pickExternalStoreSharedOptions } from "@assistant-ui/core";
11
13
  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
- * Group the graph's accumulated `UIMessage`s by the assistant message they
17
- * belong to. Non-array state and entries without a parent link are dropped.
18
- * The parent id comes from `metadata.message_id` (Python SDK) or
19
- * `metadata.id` (JS SDK).
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",
@@ -89,7 +69,9 @@ const useStreamThreadRuntime = (options) => {
89
69
  isRunning: effectiveIsRunning
90
70
  });
91
71
  const streamRef = useRef(stream);
92
- streamRef.current = stream;
72
+ useInsertionEffect(() => {
73
+ streamRef.current = stream;
74
+ }, [stream]);
93
75
  const activeRunConfigRef = useRef(void 0);
94
76
  const runConfigByMessageIdRef = useRef(/* @__PURE__ */ new Map());
95
77
  const activeThreadIdRef = useRef(externalId);
@@ -126,9 +108,13 @@ const useStreamThreadRuntime = (options) => {
126
108
  }
127
109
  }, [stream.messages]);
128
110
  const visibleMessagesRef = useRef(visibleMessages);
129
- visibleMessagesRef.current = visibleMessages;
111
+ useInsertionEffect(() => {
112
+ visibleMessagesRef.current = visibleMessages;
113
+ }, [visibleMessages]);
130
114
  const threadMessagesRef = useRef(threadMessages);
131
- threadMessagesRef.current = threadMessages;
115
+ useInsertionEffect(() => {
116
+ threadMessagesRef.current = threadMessages;
117
+ }, [threadMessages]);
132
118
  const stagedMessagesRef = useRef(/* @__PURE__ */ new Map());
133
119
  const stagedBaseMessagesRef = useRef(null);
134
120
  useEffect(() => {
@@ -218,7 +204,7 @@ const useStreamThreadRuntime = (options) => {
218
204
  ]);
219
205
  return useExternalStoreRuntime({
220
206
  ...pickExternalStoreSharedOptions(options),
221
- isRunning: effectiveIsRunning,
207
+ isRunning: stream.isLoading,
222
208
  isLoading: stream.isThreadLoading,
223
209
  messages: threadMessages,
224
210
  adapters,
@@ -233,13 +219,7 @@ const useStreamThreadRuntime = (options) => {
233
219
  const stagedMessageId = stageUserMessage(msg, true).id;
234
220
  setActiveRunConfig(msg.runConfig);
235
221
  const content = getMessageContent(msg);
236
- const cancellations = autoCancelPendingToolCalls !== false ? getPendingToolCalls(streamRef.current.messages).map((t) => ({
237
- type: "tool",
238
- name: t.name,
239
- tool_call_id: t.id,
240
- content: JSON.stringify({ cancelled: true }),
241
- status: "error"
242
- })) : [];
222
+ const cancellations = autoCancelPendingToolCalls !== false ? getPendingToolCalls(streamRef.current.messages).map(createToolCallCancellationStub) : [];
243
223
  try {
244
224
  const { externalId } = await aui.threadListItem.initialize();
245
225
  await streamRef.current.submit({ [messagesKey]: [...cancellations, {
@@ -364,16 +344,15 @@ const useStreamThreadRuntime = (options) => {
364
344
  */
365
345
  const useStreamRuntime = (rawOptions) => {
366
346
  const { cloud, unstable_threadListAdapter, create, delete: deleteFn, onThreadIdChange, ...options } = rawOptions;
367
- const optionsRef = useRef(options);
368
- optionsRef.current = options;
347
+ const aui = useAui();
369
348
  const cloudAdapter = useCloudThreadListAdapter({
370
349
  cloud,
371
- create,
350
+ create: createCloudThreadListAdapterCreateFallback(create, aui.threadListItem),
372
351
  delete: deleteFn
373
352
  });
374
353
  return useRemoteThreadListRuntime({
375
354
  runtimeHook: function RuntimeHook() {
376
- return useStreamThreadRuntime(optionsRef.current);
355
+ return useStreamThreadRuntime(options);
377
356
  },
378
357
  adapter: unstable_threadListAdapter ?? cloudAdapter,
379
358
  allowNesting: true,