@assistant-ui/react-langchain 0.0.34 → 0.0.35

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.
@@ -1,10 +1,11 @@
1
1
  import type { MessageTiming } from "@assistant-ui/core";
2
2
  import type { useExternalMessageConverter } from "@assistant-ui/core/react";
3
3
  import type { LangChainBaseMessage, UIMessage } from "./types.js";
4
- type LangChainMessageConverterMetadata = useExternalMessageConverter.Metadata & {
4
+ export type LangChainMessageConverterMetadata = useExternalMessageConverter.Metadata & {
5
5
  uiMessagesByParent?: Map<string, UIMessage[]>;
6
6
  messageTiming?: Record<string, MessageTiming>;
7
7
  };
8
+ export declare const createLangChainMetadataKey: () => useExternalMessageConverter.GetMetadataKey<LangChainBaseMessage>;
8
9
  export declare const getMessageType: (message: LangChainBaseMessage) => string;
9
10
  export declare const convertLangChainBaseMessage: (message: LangChainBaseMessage, metadata?: LangChainMessageConverterMetadata) => useExternalMessageConverter.Message;
10
11
  export { getMessageContent } from "./converter.js";
@@ -1 +1 @@
1
- {"version":3,"file":"convertMessages.d.ts","sourceRoot":"","sources":["../src/convertMessages.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAS5E,OAAO,KAAK,EACV,oBAAoB,EAEpB,SAAS,EACV,mBAAgB;AAEjB,KAAK,iCAAiC,GACpC,2BAA2B,CAAC,QAAQ,GAAG;IACrC,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CAC/C,CAAC;AAcJ,eAAO,MAAM,cAAc,GAAI,SAAS,oBAAoB,KAAG,MAQ9D,CAAC;AA4DF,eAAO,MAAM,2BAA2B,GACtC,SAAS,oBAAoB,EAC7B,WAAU,iCAAsC,KAC/C,2BAA2B,CAAC,OA4G9B,CAAC;AAEF,OAAO,EAAE,iBAAiB,EAAE,uBAAoB"}
1
+ {"version":3,"file":"convertMessages.d.ts","sourceRoot":"","sources":["../src/convertMessages.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAa5E,OAAO,KAAK,EACV,oBAAoB,EAEpB,SAAS,EACV,mBAAgB;AAEjB,MAAM,MAAM,iCAAiC,GAC3C,2BAA2B,CAAC,QAAQ,GAAG;IACrC,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CAC/C,CAAC;AAEJ,eAAO,MAAM,0BAA0B,QACjC,2BAA2B,CAAC,cAAc,CAAC,oBAAoB,CA0B/D,CAAC;AAcP,eAAO,MAAM,cAAc,GAAI,SAAS,oBAAoB,KAAG,MAQ9D,CAAC;AA4DF,eAAO,MAAM,2BAA2B,GACtC,SAAS,oBAAoB,EAC7B,WAAU,iCAAsC,KAC/C,2BAA2B,CAAC,OA4G9B,CAAC;AAEF,OAAO,EAAE,iBAAiB,EAAE,uBAAoB"}
@@ -1,6 +1,11 @@
1
1
  "use client";
2
2
  import { convertLangChainContentBlock, getCustomMetadata, getMessageContent, getMessageModality, uiMessageToDataPart, withAudioTranscript } from "./converter.js";
3
+ import { createExternalMessageMetadataKey, shallowArrayEqual } from "@assistant-ui/core/internal";
3
4
  //#region src/convertMessages.ts
5
+ const createLangChainMetadataKey = () => createExternalMessageMetadataKey([{
6
+ select: (message, metadata) => message.id && getMessageType(message) === "ai" ? metadata.uiMessagesByParent?.get(message.id) : void 0,
7
+ isEqual: (previous, current) => previous === current || previous !== void 0 && current !== void 0 && shallowArrayEqual(previous, current)
8
+ }, { select: (message, metadata) => message.id && getMessageType(message) === "ai" ? metadata.messageTiming?.[message.id] : void 0 }]);
4
9
  const warnedMalformedMessages = /* @__PURE__ */ new Set();
5
10
  const warnOnceInDevelopment = (message) => {
6
11
  if (typeof process === "undefined" || process?.env?.NODE_ENV !== "development") return;
@@ -135,6 +140,6 @@ const convertLangChainBaseMessage = (message, metadata = {}) => {
135
140
  }
136
141
  };
137
142
  //#endregion
138
- export { convertLangChainBaseMessage, getMessageContent, getMessageType };
143
+ export { convertLangChainBaseMessage, createLangChainMetadataKey, getMessageContent, getMessageType };
139
144
 
140
145
  //# sourceMappingURL=convertMessages.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"convertMessages.js","names":[],"sources":["../src/convertMessages.ts"],"sourcesContent":["\"use client\";\n\nimport type { MessageTiming } from \"@assistant-ui/core\";\nimport type { useExternalMessageConverter } from \"@assistant-ui/core/react\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\nimport {\n convertLangChainContentBlock,\n getCustomMetadata,\n getMessageModality,\n uiMessageToDataPart,\n withAudioTranscript,\n} from \"./converter\";\nimport type {\n LangChainBaseMessage,\n LangChainContentBlock,\n UIMessage,\n} from \"./types\";\n\ntype LangChainMessageConverterMetadata =\n useExternalMessageConverter.Metadata & {\n uiMessagesByParent?: Map<string, UIMessage[]>;\n messageTiming?: Record<string, MessageTiming>;\n };\n\nconst warnedMalformedMessages = new Set<string>();\nconst warnOnceInDevelopment = (message: string) => {\n if (\n typeof process === \"undefined\" ||\n process?.env?.NODE_ENV !== \"development\"\n )\n return;\n if (warnedMalformedMessages.has(message)) return;\n warnedMalformedMessages.add(message);\n console.warn(message);\n};\n\nexport const getMessageType = (message: LangChainBaseMessage): string => {\n if (typeof message._getType === \"function\") return message._getType();\n if (\"type\" in message)\n return (message as Record<string, unknown>).type as string;\n warnOnceInDevelopment(\n \"Cannot determine message type; rendering the message as system text\",\n );\n return \"unknown\";\n};\n\nconst contentBlocks = (content: unknown): readonly LangChainContentBlock[] => {\n if (content == null) return [];\n if (Array.isArray(content))\n return content.filter(\n (block) => typeof block === \"object\" && block !== null,\n );\n warnOnceInDevelopment(\n `Ignoring message content that is neither a string nor an array: ${typeof content}`,\n );\n return [];\n};\n\nconst normalizeToolCallArgs = (args: unknown): ReadonlyJSONObject => {\n if (typeof args !== \"object\" || args === null || Array.isArray(args)) {\n return {};\n }\n\n try {\n const prototype = Object.getPrototypeOf(args);\n return prototype === Object.prototype || prototype === null\n ? (args as ReadonlyJSONObject)\n : {};\n } catch {\n return {};\n }\n};\n\nconst toolCallArgs = (\n value: unknown,\n): { args: ReadonlyJSONObject; argsText: string } => {\n const args = normalizeToolCallArgs(value);\n try {\n const argsText = JSON.stringify(args);\n return typeof argsText === \"string\"\n ? { args, argsText }\n : { args: {}, argsText: \"{}\" };\n } catch {\n return { args: {}, argsText: \"{}\" };\n }\n};\n\nconst contentToParts = (content: unknown) => {\n if (typeof content === \"string\")\n return [{ type: \"text\" as const, text: content }];\n\n return contentBlocks(content)\n .map(convertLangChainContentBlock)\n .filter((part) => part !== null && part !== undefined);\n};\n\nconst getStringContent = (content: unknown): string => {\n if (typeof content === \"string\") return content;\n return contentBlocks(content)\n .filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n .map((c) => c.text)\n .join(\"\");\n};\n\nexport const convertLangChainBaseMessage = (\n message: LangChainBaseMessage,\n metadata: LangChainMessageConverterMetadata = {},\n): useExternalMessageConverter.Message => {\n const type = getMessageType(message);\n\n switch (type) {\n case \"system\":\n return {\n role: \"system\",\n id: message.id,\n content: [{ type: \"text\", text: getStringContent(message.content) }],\n metadata: {\n custom: getCustomMetadata(message.additional_kwargs),\n },\n };\n\n case \"human\": {\n const modality = getMessageModality(message.additional_kwargs);\n return {\n role: \"user\",\n id: message.id,\n content: contentToParts(message.content),\n metadata: {\n custom: getCustomMetadata(message.additional_kwargs),\n ...(modality && { modality }),\n },\n };\n }\n\n case \"ai\": {\n const toolCallParts =\n message.tool_calls\n ?.filter((tc) => {\n if (typeof tc?.name === \"string\") return true;\n warnOnceInDevelopment(\n \"Skipping a tool call without a name; its result is not shown either\",\n );\n return false;\n })\n .map((tc) => {\n const { args, argsText } = toolCallArgs(tc.args);\n return {\n type: \"tool-call\" as const,\n toolCallId: tc.id,\n toolName: tc.name,\n args,\n argsText,\n };\n }) ?? [];\n\n const assistantStatus =\n typeof message.status === \"object\" ? message.status : undefined;\n\n const uiDataParts =\n (message.id\n ? metadata.uiMessagesByParent\n ?.get(message.id)\n ?.map(uiMessageToDataPart)\n : undefined) ?? [];\n\n const timing = metadata.messageTiming?.[message.id ?? \"\"];\n const modality = getMessageModality(message.additional_kwargs);\n\n return {\n role: \"assistant\",\n id: message.id,\n content: [\n ...withAudioTranscript(\n contentToParts(message.content),\n message.additional_kwargs,\n ),\n ...toolCallParts,\n ...uiDataParts,\n ],\n metadata: {\n custom: getCustomMetadata(message.additional_kwargs),\n ...(timing && { timing }),\n ...(modality && { modality }),\n },\n ...(assistantStatus && { status: assistantStatus }),\n };\n }\n\n case \"tool\":\n return {\n role: \"tool\",\n // `joinExternalMessages` only checks the name against the tool call\n // when it is non-null, so an empty name manufactures a mismatch.\n toolName: message.name || undefined,\n toolCallId: message.tool_call_id ?? \"\",\n result: message.content,\n artifact: message.artifact,\n isError: message.status === \"error\",\n };\n\n default:\n return {\n role: \"system\",\n id: message.id,\n content: [\n {\n type: \"text\",\n text:\n typeof message.content === \"string\"\n ? message.content\n : (JSON.stringify(message.content) ?? \"\"),\n },\n ],\n };\n }\n};\n\nexport { getMessageContent } from \"./converter\";\n"],"mappings":";;;AAwBA,MAAM,0CAA0B,IAAI,IAAY;AAChD,MAAM,yBAAyB,YAAoB;CACjD,IACE,OAAO,YAAY,eACnB,SAAS,KAAK,aAAa,eAE3B;CACF,IAAI,wBAAwB,IAAI,OAAO,GAAG;CAC1C,wBAAwB,IAAI,OAAO;CACnC,QAAQ,KAAK,OAAO;AACtB;AAEA,MAAa,kBAAkB,YAA0C;CACvE,IAAI,OAAO,QAAQ,aAAa,YAAY,OAAO,QAAQ,SAAS;CACpE,IAAI,UAAU,SACZ,OAAQ,QAAoC;CAC9C,sBACE,qEACF;CACA,OAAO;AACT;AAEA,MAAM,iBAAiB,YAAuD;CAC5E,IAAI,WAAW,MAAM,OAAO,CAAC;CAC7B,IAAI,MAAM,QAAQ,OAAO,GACvB,OAAO,QAAQ,QACZ,UAAU,OAAO,UAAU,YAAY,UAAU,IACpD;CACF,sBACE,mEAAmE,OAAO,SAC5E;CACA,OAAO,CAAC;AACV;AAEA,MAAM,yBAAyB,SAAsC;CACnE,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM,QAAQ,IAAI,GACjE,OAAO,CAAC;CAGV,IAAI;EACF,MAAM,YAAY,OAAO,eAAe,IAAI;EAC5C,OAAO,cAAc,OAAO,aAAa,cAAc,OAClD,OACD,CAAC;CACP,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,MAAM,gBACJ,UACmD;CACnD,MAAM,OAAO,sBAAsB,KAAK;CACxC,IAAI;EACF,MAAM,WAAW,KAAK,UAAU,IAAI;EACpC,OAAO,OAAO,aAAa,WACvB;GAAE;GAAM;EAAS,IACjB;GAAE,MAAM,CAAC;GAAG,UAAU;EAAK;CACjC,QAAQ;EACN,OAAO;GAAE,MAAM,CAAC;GAAG,UAAU;EAAK;CACpC;AACF;AAEA,MAAM,kBAAkB,YAAqB;CAC3C,IAAI,OAAO,YAAY,UACrB,OAAO,CAAC;EAAE,MAAM;EAAiB,MAAM;CAAQ,CAAC;CAElD,OAAO,cAAc,OAAO,CAAC,CAC1B,IAAI,4BAA4B,CAAC,CACjC,QAAQ,SAAS,SAAS,QAAQ,SAAS,KAAA,CAAS;AACzD;AAEA,MAAM,oBAAoB,YAA6B;CACrD,IAAI,OAAO,YAAY,UAAU,OAAO;CACxC,OAAO,cAAc,OAAO,CAAC,CAC1B,QAAQ,MAA2C,EAAE,SAAS,MAAM,CAAC,CACrE,KAAK,MAAM,EAAE,IAAI,CAAC,CAClB,KAAK,EAAE;AACZ;AAEA,MAAa,+BACX,SACA,WAA8C,CAAC,MACP;CAGxC,QAFa,eAAe,OAEjB,GAAX;EACE,KAAK,UACH,OAAO;GACL,MAAM;GACN,IAAI,QAAQ;GACZ,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,iBAAiB,QAAQ,OAAO;GAAE,CAAC;GACnE,UAAU,EACR,QAAQ,kBAAkB,QAAQ,iBAAiB,EACrD;EACF;EAEF,KAAK,SAAS;GACZ,MAAM,WAAW,mBAAmB,QAAQ,iBAAiB;GAC7D,OAAO;IACL,MAAM;IACN,IAAI,QAAQ;IACZ,SAAS,eAAe,QAAQ,OAAO;IACvC,UAAU;KACR,QAAQ,kBAAkB,QAAQ,iBAAiB;KACnD,GAAI,YAAY,EAAE,SAAS;IAC7B;GACF;EACF;EAEA,KAAK,MAAM;GACT,MAAM,gBACJ,QAAQ,YACJ,QAAQ,OAAO;IACf,IAAI,OAAO,IAAI,SAAS,UAAU,OAAO;IACzC,sBACE,qEACF;IACA,OAAO;GACT,CAAC,CAAC,CACD,KAAK,OAAO;IACX,MAAM,EAAE,MAAM,aAAa,aAAa,GAAG,IAAI;IAC/C,OAAO;KACL,MAAM;KACN,YAAY,GAAG;KACf,UAAU,GAAG;KACb;KACA;IACF;GACF,CAAC,KAAK,CAAC;GAEX,MAAM,kBACJ,OAAO,QAAQ,WAAW,WAAW,QAAQ,SAAS,KAAA;GAExD,MAAM,eACH,QAAQ,KACL,SAAS,oBACL,IAAI,QAAQ,EAAE,CAAC,EACf,IAAI,mBAAmB,IAC3B,KAAA,MAAc,CAAC;GAErB,MAAM,SAAS,SAAS,gBAAgB,QAAQ,MAAM;GACtD,MAAM,WAAW,mBAAmB,QAAQ,iBAAiB;GAE7D,OAAO;IACL,MAAM;IACN,IAAI,QAAQ;IACZ,SAAS;KACP,GAAG,oBACD,eAAe,QAAQ,OAAO,GAC9B,QAAQ,iBACV;KACA,GAAG;KACH,GAAG;IACL;IACA,UAAU;KACR,QAAQ,kBAAkB,QAAQ,iBAAiB;KACnD,GAAI,UAAU,EAAE,OAAO;KACvB,GAAI,YAAY,EAAE,SAAS;IAC7B;IACA,GAAI,mBAAmB,EAAE,QAAQ,gBAAgB;GACnD;EACF;EAEA,KAAK,QACH,OAAO;GACL,MAAM;GAGN,UAAU,QAAQ,QAAQ,KAAA;GAC1B,YAAY,QAAQ,gBAAgB;GACpC,QAAQ,QAAQ;GAChB,UAAU,QAAQ;GAClB,SAAS,QAAQ,WAAW;EAC9B;EAEF,SACE,OAAO;GACL,MAAM;GACN,IAAI,QAAQ;GACZ,SAAS,CACP;IACE,MAAM;IACN,MACE,OAAO,QAAQ,YAAY,WACvB,QAAQ,UACP,KAAK,UAAU,QAAQ,OAAO,KAAK;GAC5C,CACF;EACF;CACJ;AACF"}
1
+ {"version":3,"file":"convertMessages.js","names":[],"sources":["../src/convertMessages.ts"],"sourcesContent":["\"use client\";\n\nimport type { MessageTiming } from \"@assistant-ui/core\";\nimport type { useExternalMessageConverter } from \"@assistant-ui/core/react\";\nimport {\n createExternalMessageMetadataKey,\n shallowArrayEqual,\n} from \"@assistant-ui/core/internal\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\nimport {\n convertLangChainContentBlock,\n getCustomMetadata,\n getMessageModality,\n uiMessageToDataPart,\n withAudioTranscript,\n} from \"./converter\";\nimport type {\n LangChainBaseMessage,\n LangChainContentBlock,\n UIMessage,\n} from \"./types\";\n\nexport type LangChainMessageConverterMetadata =\n useExternalMessageConverter.Metadata & {\n uiMessagesByParent?: Map<string, UIMessage[]>;\n messageTiming?: Record<string, MessageTiming>;\n };\n\nexport const createLangChainMetadataKey =\n (): useExternalMessageConverter.GetMetadataKey<LangChainBaseMessage> =>\n createExternalMessageMetadataKey<LangChainBaseMessage>([\n {\n select: (message, metadata) =>\n message.id && getMessageType(message) === \"ai\"\n ? (\n metadata as LangChainMessageConverterMetadata\n ).uiMessagesByParent?.get(message.id)\n : undefined,\n isEqual: (previous, current) =>\n previous === current ||\n (previous !== undefined &&\n current !== undefined &&\n shallowArrayEqual(\n previous as readonly UIMessage[],\n current as readonly UIMessage[],\n )),\n },\n {\n select: (message, metadata) =>\n message.id && getMessageType(message) === \"ai\"\n ? (metadata as LangChainMessageConverterMetadata).messageTiming?.[\n message.id\n ]\n : undefined,\n },\n ]);\n\nconst warnedMalformedMessages = new Set<string>();\nconst warnOnceInDevelopment = (message: string) => {\n if (\n typeof process === \"undefined\" ||\n process?.env?.NODE_ENV !== \"development\"\n )\n return;\n if (warnedMalformedMessages.has(message)) return;\n warnedMalformedMessages.add(message);\n console.warn(message);\n};\n\nexport const getMessageType = (message: LangChainBaseMessage): string => {\n if (typeof message._getType === \"function\") return message._getType();\n if (\"type\" in message)\n return (message as Record<string, unknown>).type as string;\n warnOnceInDevelopment(\n \"Cannot determine message type; rendering the message as system text\",\n );\n return \"unknown\";\n};\n\nconst contentBlocks = (content: unknown): readonly LangChainContentBlock[] => {\n if (content == null) return [];\n if (Array.isArray(content))\n return content.filter(\n (block) => typeof block === \"object\" && block !== null,\n );\n warnOnceInDevelopment(\n `Ignoring message content that is neither a string nor an array: ${typeof content}`,\n );\n return [];\n};\n\nconst normalizeToolCallArgs = (args: unknown): ReadonlyJSONObject => {\n if (typeof args !== \"object\" || args === null || Array.isArray(args)) {\n return {};\n }\n\n try {\n const prototype = Object.getPrototypeOf(args);\n return prototype === Object.prototype || prototype === null\n ? (args as ReadonlyJSONObject)\n : {};\n } catch {\n return {};\n }\n};\n\nconst toolCallArgs = (\n value: unknown,\n): { args: ReadonlyJSONObject; argsText: string } => {\n const args = normalizeToolCallArgs(value);\n try {\n const argsText = JSON.stringify(args);\n return typeof argsText === \"string\"\n ? { args, argsText }\n : { args: {}, argsText: \"{}\" };\n } catch {\n return { args: {}, argsText: \"{}\" };\n }\n};\n\nconst contentToParts = (content: unknown) => {\n if (typeof content === \"string\")\n return [{ type: \"text\" as const, text: content }];\n\n return contentBlocks(content)\n .map(convertLangChainContentBlock)\n .filter((part) => part !== null && part !== undefined);\n};\n\nconst getStringContent = (content: unknown): string => {\n if (typeof content === \"string\") return content;\n return contentBlocks(content)\n .filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n .map((c) => c.text)\n .join(\"\");\n};\n\nexport const convertLangChainBaseMessage = (\n message: LangChainBaseMessage,\n metadata: LangChainMessageConverterMetadata = {},\n): useExternalMessageConverter.Message => {\n const type = getMessageType(message);\n\n switch (type) {\n case \"system\":\n return {\n role: \"system\",\n id: message.id,\n content: [{ type: \"text\", text: getStringContent(message.content) }],\n metadata: {\n custom: getCustomMetadata(message.additional_kwargs),\n },\n };\n\n case \"human\": {\n const modality = getMessageModality(message.additional_kwargs);\n return {\n role: \"user\",\n id: message.id,\n content: contentToParts(message.content),\n metadata: {\n custom: getCustomMetadata(message.additional_kwargs),\n ...(modality && { modality }),\n },\n };\n }\n\n case \"ai\": {\n const toolCallParts =\n message.tool_calls\n ?.filter((tc) => {\n if (typeof tc?.name === \"string\") return true;\n warnOnceInDevelopment(\n \"Skipping a tool call without a name; its result is not shown either\",\n );\n return false;\n })\n .map((tc) => {\n const { args, argsText } = toolCallArgs(tc.args);\n return {\n type: \"tool-call\" as const,\n toolCallId: tc.id,\n toolName: tc.name,\n args,\n argsText,\n };\n }) ?? [];\n\n const assistantStatus =\n typeof message.status === \"object\" ? message.status : undefined;\n\n const uiDataParts =\n (message.id\n ? metadata.uiMessagesByParent\n ?.get(message.id)\n ?.map(uiMessageToDataPart)\n : undefined) ?? [];\n\n const timing = metadata.messageTiming?.[message.id ?? \"\"];\n const modality = getMessageModality(message.additional_kwargs);\n\n return {\n role: \"assistant\",\n id: message.id,\n content: [\n ...withAudioTranscript(\n contentToParts(message.content),\n message.additional_kwargs,\n ),\n ...toolCallParts,\n ...uiDataParts,\n ],\n metadata: {\n custom: getCustomMetadata(message.additional_kwargs),\n ...(timing && { timing }),\n ...(modality && { modality }),\n },\n ...(assistantStatus && { status: assistantStatus }),\n };\n }\n\n case \"tool\":\n return {\n role: \"tool\",\n // `joinExternalMessages` only checks the name against the tool call\n // when it is non-null, so an empty name manufactures a mismatch.\n toolName: message.name || undefined,\n toolCallId: message.tool_call_id ?? \"\",\n result: message.content,\n artifact: message.artifact,\n isError: message.status === \"error\",\n };\n\n default:\n return {\n role: \"system\",\n id: message.id,\n content: [\n {\n type: \"text\",\n text:\n typeof message.content === \"string\"\n ? message.content\n : (JSON.stringify(message.content) ?? \"\"),\n },\n ],\n };\n }\n};\n\nexport { getMessageContent } from \"./converter\";\n"],"mappings":";;;;AA4BA,MAAa,mCAET,iCAAuD,CACrD;CACE,SAAS,SAAS,aAChB,QAAQ,MAAM,eAAe,OAAO,MAAM,OAEpC,SACA,oBAAoB,IAAI,QAAQ,EAAE,IACpC,KAAA;CACN,UAAU,UAAU,YAClB,aAAa,WACZ,aAAa,KAAA,KACZ,YAAY,KAAA,KACZ,kBACE,UACA,OACF;AACN,GACA,EACE,SAAS,SAAS,aAChB,QAAQ,MAAM,eAAe,OAAO,MAAM,OACrC,SAA+C,gBAC9C,QAAQ,MAEV,KAAA,EACR,CACF,CAAC;AAEL,MAAM,0CAA0B,IAAI,IAAY;AAChD,MAAM,yBAAyB,YAAoB;CACjD,IACE,OAAO,YAAY,eACnB,SAAS,KAAK,aAAa,eAE3B;CACF,IAAI,wBAAwB,IAAI,OAAO,GAAG;CAC1C,wBAAwB,IAAI,OAAO;CACnC,QAAQ,KAAK,OAAO;AACtB;AAEA,MAAa,kBAAkB,YAA0C;CACvE,IAAI,OAAO,QAAQ,aAAa,YAAY,OAAO,QAAQ,SAAS;CACpE,IAAI,UAAU,SACZ,OAAQ,QAAoC;CAC9C,sBACE,qEACF;CACA,OAAO;AACT;AAEA,MAAM,iBAAiB,YAAuD;CAC5E,IAAI,WAAW,MAAM,OAAO,CAAC;CAC7B,IAAI,MAAM,QAAQ,OAAO,GACvB,OAAO,QAAQ,QACZ,UAAU,OAAO,UAAU,YAAY,UAAU,IACpD;CACF,sBACE,mEAAmE,OAAO,SAC5E;CACA,OAAO,CAAC;AACV;AAEA,MAAM,yBAAyB,SAAsC;CACnE,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM,QAAQ,IAAI,GACjE,OAAO,CAAC;CAGV,IAAI;EACF,MAAM,YAAY,OAAO,eAAe,IAAI;EAC5C,OAAO,cAAc,OAAO,aAAa,cAAc,OAClD,OACD,CAAC;CACP,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,MAAM,gBACJ,UACmD;CACnD,MAAM,OAAO,sBAAsB,KAAK;CACxC,IAAI;EACF,MAAM,WAAW,KAAK,UAAU,IAAI;EACpC,OAAO,OAAO,aAAa,WACvB;GAAE;GAAM;EAAS,IACjB;GAAE,MAAM,CAAC;GAAG,UAAU;EAAK;CACjC,QAAQ;EACN,OAAO;GAAE,MAAM,CAAC;GAAG,UAAU;EAAK;CACpC;AACF;AAEA,MAAM,kBAAkB,YAAqB;CAC3C,IAAI,OAAO,YAAY,UACrB,OAAO,CAAC;EAAE,MAAM;EAAiB,MAAM;CAAQ,CAAC;CAElD,OAAO,cAAc,OAAO,CAAC,CAC1B,IAAI,4BAA4B,CAAC,CACjC,QAAQ,SAAS,SAAS,QAAQ,SAAS,KAAA,CAAS;AACzD;AAEA,MAAM,oBAAoB,YAA6B;CACrD,IAAI,OAAO,YAAY,UAAU,OAAO;CACxC,OAAO,cAAc,OAAO,CAAC,CAC1B,QAAQ,MAA2C,EAAE,SAAS,MAAM,CAAC,CACrE,KAAK,MAAM,EAAE,IAAI,CAAC,CAClB,KAAK,EAAE;AACZ;AAEA,MAAa,+BACX,SACA,WAA8C,CAAC,MACP;CAGxC,QAFa,eAAe,OAEjB,GAAX;EACE,KAAK,UACH,OAAO;GACL,MAAM;GACN,IAAI,QAAQ;GACZ,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,iBAAiB,QAAQ,OAAO;GAAE,CAAC;GACnE,UAAU,EACR,QAAQ,kBAAkB,QAAQ,iBAAiB,EACrD;EACF;EAEF,KAAK,SAAS;GACZ,MAAM,WAAW,mBAAmB,QAAQ,iBAAiB;GAC7D,OAAO;IACL,MAAM;IACN,IAAI,QAAQ;IACZ,SAAS,eAAe,QAAQ,OAAO;IACvC,UAAU;KACR,QAAQ,kBAAkB,QAAQ,iBAAiB;KACnD,GAAI,YAAY,EAAE,SAAS;IAC7B;GACF;EACF;EAEA,KAAK,MAAM;GACT,MAAM,gBACJ,QAAQ,YACJ,QAAQ,OAAO;IACf,IAAI,OAAO,IAAI,SAAS,UAAU,OAAO;IACzC,sBACE,qEACF;IACA,OAAO;GACT,CAAC,CAAC,CACD,KAAK,OAAO;IACX,MAAM,EAAE,MAAM,aAAa,aAAa,GAAG,IAAI;IAC/C,OAAO;KACL,MAAM;KACN,YAAY,GAAG;KACf,UAAU,GAAG;KACb;KACA;IACF;GACF,CAAC,KAAK,CAAC;GAEX,MAAM,kBACJ,OAAO,QAAQ,WAAW,WAAW,QAAQ,SAAS,KAAA;GAExD,MAAM,eACH,QAAQ,KACL,SAAS,oBACL,IAAI,QAAQ,EAAE,CAAC,EACf,IAAI,mBAAmB,IAC3B,KAAA,MAAc,CAAC;GAErB,MAAM,SAAS,SAAS,gBAAgB,QAAQ,MAAM;GACtD,MAAM,WAAW,mBAAmB,QAAQ,iBAAiB;GAE7D,OAAO;IACL,MAAM;IACN,IAAI,QAAQ;IACZ,SAAS;KACP,GAAG,oBACD,eAAe,QAAQ,OAAO,GAC9B,QAAQ,iBACV;KACA,GAAG;KACH,GAAG;IACL;IACA,UAAU;KACR,QAAQ,kBAAkB,QAAQ,iBAAiB;KACnD,GAAI,UAAU,EAAE,OAAO;KACvB,GAAI,YAAY,EAAE,SAAS;IAC7B;IACA,GAAI,mBAAmB,EAAE,QAAQ,gBAAgB;GACnD;EACF;EAEA,KAAK,QACH,OAAO;GACL,MAAM;GAGN,UAAU,QAAQ,QAAQ,KAAA;GAC1B,YAAY,QAAQ,gBAAgB;GACpC,QAAQ,QAAQ;GAChB,UAAU,QAAQ;GAClB,SAAS,QAAQ,WAAW;EAC9B;EAEF,SACE,OAAO;GACL,MAAM;GACN,IAAI,QAAQ;GACZ,SAAS,CACP;IACE,MAAM;IACN,MACE,OAAO,QAAQ,YAAY,WACvB,QAAQ,UACP,KAAK,UAAU,QAAQ,OAAO,KAAK;GAC5C,CACF;EACF;CACJ;AACF"}
@@ -1,7 +1,7 @@
1
1
  //#region src/sdkIdentity.ts
2
2
  const LANGCHAIN_SDK = {
3
3
  name: "@assistant-ui/react-langchain",
4
- version: "0.0.34"
4
+ version: "0.0.35"
5
5
  };
6
6
  //#endregion
7
7
  export { LANGCHAIN_SDK };
@@ -1 +1 @@
1
- {"version":3,"file":"useStreamRuntime.d.ts","sourceRoot":"","sources":["../src/useStreamRuntime.ts"],"names":[],"mappings":";AAWA,OAAO,KAAK,EAAE,aAAa,EAAuB,MAAM,oBAAoB,CAAC;AAqB7E,OAAO,KAAK,EAIV,uBAAuB,EACxB,mBAAgB;AAEjB,OAAO,EAAE,uBAAuB,EAAE,uBAAoB;AAwBtD,eAAO,MAAM,wBAAwB,GACnC,WAAW,aAAa,CAAC,WAAW,CAAC;;;;aAIxB,CAAC;AAgsBhB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,gBAAgB,GAAI,YAAY,uBAAuB,kDA8BnE,CAAC"}
1
+ {"version":3,"file":"useStreamRuntime.d.ts","sourceRoot":"","sources":["../src/useStreamRuntime.ts"],"names":[],"mappings":";AAWA,OAAO,KAAK,EAAE,aAAa,EAAuB,MAAM,oBAAoB,CAAC;AAqB7E,OAAO,KAAK,EAIV,uBAAuB,EACxB,mBAAgB;AAEjB,OAAO,EAAE,uBAAuB,EAAE,uBAAoB;AA0BtD,eAAO,MAAM,wBAAwB,GACnC,WAAW,aAAa,CAAC,WAAW,CAAC;;;;aAIxB,CAAC;AA+rBhB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,gBAAgB,GAAI,YAAY,uBAAuB,kDA8BnE,CAAC"}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { attachSubagentTranscripts, createAttachMemo } from "./attachSubagentTranscripts.js";
3
3
  import { getMessageContent, getMessageModality, groupUIMessagesByParent } from "./converter.js";
4
- import { convertLangChainBaseMessage, getMessageType } from "./convertMessages.js";
4
+ import { convertLangChainBaseMessage, createLangChainMetadataKey, getMessageType } from "./convertMessages.js";
5
5
  import { langChainExtras } from "./runtimeExtras.js";
6
6
  import { useLangChainStreamingTiming } from "./streamingTiming.js";
7
7
  import { UI_CUSTOM_CHANNELS, createUIFoldMemo, createUISnapshotMemo, foldUIUpdates, mergeUIMessages, reconcileUISnapshot } from "./uiMessages.js";
@@ -67,15 +67,17 @@ const useStreamThreadRuntime = (options) => {
67
67
  const visibleMessages = stagedMessages ?? stream.messages;
68
68
  const messageTiming = useLangChainStreamingTiming(visibleMessages, effectiveIsRunning);
69
69
  const subagentTranscripts = useSubagentTranscripts(stream, uiMessagesByParent);
70
- const convertWithUI = useMemo(() => (message, metadata) => convertLangChainBaseMessage(message, {
71
- ...metadata,
70
+ const [getConverterMetadataKey] = useState(createLangChainMetadataKey);
71
+ const converterMetadata = useMemo(() => ({
72
72
  uiMessagesByParent,
73
73
  messageTiming
74
74
  }), [uiMessagesByParent, messageTiming]);
75
75
  const threadMessages = useExternalMessageConverter({
76
- callback: convertWithUI,
76
+ callback: convertLangChainBaseMessage,
77
77
  messages: visibleMessages,
78
- isRunning: effectiveIsRunning
78
+ isRunning: effectiveIsRunning,
79
+ metadata: converterMetadata,
80
+ getMetadataKey: getConverterMetadataKey
79
81
  });
80
82
  const [memo] = useState(createAttachMemo);
81
83
  const messagesWithTranscripts = useMemo(() => attachSubagentTranscripts(threadMessages, subagentTranscripts, memo), [
@@ -1 +1 @@
1
- {"version":3,"file":"useStreamRuntime.js","names":[],"sources":["../src/useStreamRuntime.ts"],"sourcesContent":["/// <reference types=\"@assistant-ui/core/store\" preserve=\"true\" />\n\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useInsertionEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { AppendMessage, ToolExecutionStatus } from \"@assistant-ui/core\";\nimport {\n generateId,\n getExternalStoreMessages,\n pickExternalStoreSharedOptions,\n} from \"@assistant-ui/core\";\nimport type { ThreadMessage } from \"@assistant-ui/core\";\nimport {\n createCloudThreadListAdapterCreateFallback,\n createToolCallCancellationStub,\n getThreadMessageText,\n scanPendingToolCalls,\n} from \"@assistant-ui/core/internal\";\nimport {\n useCloudThreadListAdapter,\n useExternalStoreRuntime,\n useExternalMessageConverter,\n useRemoteThreadListRuntime,\n} from \"@assistant-ui/core/react\";\nimport { useAui, useAuiState } from \"@assistant-ui/store\";\nimport { STREAM_CONTROLLER, useChannel, useStream } from \"@langchain/react\";\nimport type {\n LangChainBaseMessage,\n LangChainToolCall,\n UIMessage,\n UseStreamRuntimeOptions,\n} from \"./types\";\nimport { getMessageModality, groupUIMessagesByParent } from \"./converter\";\nexport { groupUIMessagesByParent } from \"./converter\";\nimport {\n convertLangChainBaseMessage,\n getMessageContent,\n getMessageType,\n} from \"./convertMessages\";\nimport {\n attachSubagentTranscripts,\n createAttachMemo,\n} from \"./attachSubagentTranscripts\";\nimport { useSubagentTranscripts } from \"./useSubagentTranscripts\";\nimport {\n createUIFoldMemo,\n createUISnapshotMemo,\n foldUIUpdates,\n mergeUIMessages,\n reconcileUISnapshot,\n UI_CUSTOM_CHANNELS,\n} from \"./uiMessages\";\nimport { langChainExtras } from \"./runtimeExtras\";\nimport { resolveForkCheckpoint } from \"./resolveForkCheckpoint\";\nimport { useLangChainStreamingTiming } from \"./streamingTiming\";\nimport { LANGCHAIN_SDK } from \"./sdkIdentity\";\n\nexport const runConfigToSubmitOptions = (\n runConfig: AppendMessage[\"runConfig\"],\n) =>\n runConfig?.custom\n ? { config: { configurable: runConfig.custom } }\n : undefined;\n\ntype NormalizedRunConfigOptions = NonNullable<\n ReturnType<typeof runConfigToSubmitOptions>\n>;\n\nconst getPendingToolCalls = (\n messages: readonly LangChainBaseMessage[],\n): LangChainToolCall[] =>\n scanPendingToolCalls(\n messages,\n (message) => {\n const type = getMessageType(message);\n if (type === \"ai\") return { toolCalls: message.tool_calls ?? [] };\n if (type === \"tool\" && message.tool_call_id) {\n return { toolCallId: message.tool_call_id };\n }\n return undefined;\n },\n (toolCall) => toolCall.id,\n );\n\nconst toStagedHumanMessage = (\n msg: AppendMessage,\n id = generateId(),\n): LangChainBaseMessage & { id: string } => ({\n id,\n _getType: () => \"human\",\n content: getMessageContent(msg),\n});\n\nconst toStagedMessageInput = (message: LangChainBaseMessage) => ({\n id: message.id,\n type: getMessageType(message) === \"ai\" ? (\"ai\" as const) : (\"human\" as const),\n content: message.content,\n ...(message.additional_kwargs && {\n additional_kwargs: message.additional_kwargs,\n }),\n});\n\nconst humanContentText = (content: LangChainBaseMessage[\"content\"]) => {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n return content\n .filter(\n (part): part is { type: \"text\"; text: string } =>\n typeof part === \"object\" &&\n part !== null &&\n part.type === \"text\" &&\n typeof part.text === \"string\",\n )\n .map((part) => part.text)\n .join(\"\");\n};\n\nconst hasSameMessageContent = (\n a: LangChainBaseMessage,\n b: LangChainBaseMessage,\n) => humanContentText(a.content) === humanContentText(b.content);\n\nconst truncateLangChainBaseMessages = (\n threadMessages: readonly ThreadMessage[],\n parentId: string | null,\n): LangChainBaseMessage[] => {\n if (parentId === null) return [];\n const parentIndex = threadMessages.findIndex((m) => m.id === parentId);\n if (parentIndex === -1) return [];\n const truncated: LangChainBaseMessage[] = [];\n for (let i = 0; i <= parentIndex && i < threadMessages.length; i++) {\n truncated.push(\n ...getExternalStoreMessages<LangChainBaseMessage>(threadMessages[i]!),\n );\n }\n return truncated;\n};\n\ntype DistributiveOmit<T, K extends keyof any> = T extends unknown\n ? Omit<T, K>\n : never;\n\nconst useStreamThreadRuntime = (\n options: DistributiveOmit<\n UseStreamRuntimeOptions,\n \"cloud\" | \"unstable_threadListAdapter\" | \"create\" | \"delete\"\n >,\n) => {\n const { adapters, autoCancelPendingToolCalls, unstable_allowCancellation } =\n options;\n const aui = useAui();\n const messagesKey = options.messagesKey ?? \"messages\";\n const uiStateKey = options.uiStateKey ?? \"ui\";\n\n const externalId = useAuiState((s) => s.threadListItem.externalId) as\n | string\n | null;\n // Object.assign preserves the discriminated transport union; object spread\n // collapses its arms and no longer satisfies UseStreamOptions.\n const stream = useStream(\n Object.assign({}, options, { threadId: externalId }),\n );\n const [stagedMessages, setStagedMessages] = useState<\n LangChainBaseMessage[] | null\n >(null);\n\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n const hasExecutingTools = Object.values(toolStatuses).some(\n (s) => s?.type === \"executing\",\n );\n const effectiveIsRunning = stream.isLoading || hasExecutingTools;\n\n const [uiSnapshotMemo] = useState(createUISnapshotMemo);\n const uiStateValue = reconcileUISnapshot(\n stream.values[uiStateKey],\n uiSnapshotMemo,\n );\n\n const customEvents = useChannel(stream, UI_CUSTOM_CHANNELS);\n const [uiFoldMemo] = useState(createUIFoldMemo);\n const liveUiMessages = useMemo(\n () => foldUIUpdates(customEvents, uiFoldMemo),\n [customEvents, uiFoldMemo],\n );\n\n const mergedUiMessages = useMemo(\n () => mergeUIMessages(liveUiMessages, uiStateValue),\n [liveUiMessages, uiStateValue],\n );\n\n const uiMessagesByParent = useMemo(\n () => groupUIMessagesByParent<UIMessage>(mergedUiMessages),\n [mergedUiMessages],\n );\n\n const visibleMessages =\n stagedMessages ?? (stream.messages as LangChainBaseMessage[]);\n\n const messageTiming = useLangChainStreamingTiming(\n visibleMessages,\n effectiveIsRunning,\n );\n\n const subagentTranscripts = useSubagentTranscripts(\n stream,\n uiMessagesByParent,\n );\n\n const convertWithUI = useMemo<\n useExternalMessageConverter.Callback<LangChainBaseMessage>\n >(\n () => (message, metadata) =>\n convertLangChainBaseMessage(message, {\n ...metadata,\n uiMessagesByParent,\n messageTiming,\n }),\n [uiMessagesByParent, messageTiming],\n );\n\n const threadMessages = useExternalMessageConverter({\n callback: convertWithUI,\n messages: visibleMessages,\n isRunning: effectiveIsRunning,\n });\n const [memo] = useState(createAttachMemo);\n const messagesWithTranscripts = useMemo(\n () => attachSubagentTranscripts(threadMessages, subagentTranscripts, memo),\n [threadMessages, subagentTranscripts, memo],\n );\n\n const streamRef = useRef(stream);\n useInsertionEffect(() => {\n streamRef.current = stream;\n }, [stream]);\n\n const activeRunConfigRef = useRef<\n NormalizedRunConfigOptions[\"config\"] | undefined\n >(undefined);\n const runConfigByMessageIdRef = useRef(\n new Map<string, NormalizedRunConfigOptions[\"config\"] | undefined>(),\n );\n const activeThreadIdRef = useRef(externalId);\n const setActiveRunConfig = useCallback(\n (runConfig: AppendMessage[\"runConfig\"]) => {\n activeRunConfigRef.current = runConfigToSubmitOptions(runConfig)?.config;\n },\n [],\n );\n const withActiveRunConfig = useCallback(\n (submitOptions?: Record<string, unknown>) => {\n if (submitOptions && \"config\" in submitOptions) return submitOptions;\n if (activeRunConfigRef.current === undefined) return submitOptions;\n return { ...submitOptions, config: activeRunConfigRef.current };\n },\n [],\n );\n\n useEffect(() => {\n if (\n activeThreadIdRef.current !== null &&\n activeThreadIdRef.current !== externalId\n ) {\n activeRunConfigRef.current = undefined;\n runConfigByMessageIdRef.current.clear();\n }\n activeThreadIdRef.current = externalId;\n }, [externalId]);\n\n useEffect(() => {\n const messages = stream.messages as readonly LangChainBaseMessage[];\n const owned = runConfigByMessageIdRef.current;\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages.at(i);\n if (\n !message?.id ||\n getMessageType(message) !== \"ai\" ||\n !message.tool_calls?.length\n ) {\n continue;\n }\n if (owned.has(message.id)) return;\n break;\n }\n for (const message of messages) {\n if (\n !message.id ||\n getMessageType(message) !== \"ai\" ||\n !message.tool_calls?.length ||\n owned.has(message.id)\n ) {\n continue;\n }\n owned.set(message.id, activeRunConfigRef.current);\n }\n }, [stream.messages]);\n\n const visibleMessagesRef = useRef(visibleMessages);\n useInsertionEffect(() => {\n visibleMessagesRef.current = visibleMessages;\n }, [visibleMessages]);\n\n const threadMessagesRef = useRef(messagesWithTranscripts);\n useInsertionEffect(() => {\n threadMessagesRef.current = messagesWithTranscripts;\n }, [messagesWithTranscripts]);\n\n const stagedMessagesRef = useRef(\n new Map<\n string,\n {\n message: LangChainBaseMessage & { id: string };\n runConfig: AppendMessage[\"runConfig\"];\n reconcileOnEcho: boolean;\n baseMessageCount: number;\n transcriptStatus?: \"unsent\" | \"sent\";\n }\n >(),\n );\n const stagedBaseMessagesRef = useRef<LangChainBaseMessage[] | null>(null);\n useEffect(() => {\n if (stagedMessagesRef.current.size === 0) return;\n\n // Staged edits must keep their truncated base while stream updates arrive before promotion.\n const baseMessages =\n stagedBaseMessagesRef.current ??\n (stream.messages as LangChainBaseMessage[]);\n const remainingStagedMessages: LangChainBaseMessage[] = [];\n const matchedBaseMessageIndexes = new Set<number>();\n const visibleStagedIds = new Set(\n visibleMessagesRef.current.flatMap((m) => (m.id ? [m.id] : [])),\n );\n for (const [id, staged] of stagedMessagesRef.current) {\n if (!visibleStagedIds.has(id)) continue;\n const echoed = baseMessages.some((message, index) => {\n if (matchedBaseMessageIndexes.has(index)) return false;\n if (message.id === id) {\n matchedBaseMessageIndexes.add(index);\n return true;\n }\n if (\n !staged.reconcileOnEcho ||\n index < staged.baseMessageCount ||\n getMessageType(message) !== \"human\" ||\n !hasSameMessageContent(message, staged.message)\n ) {\n return false;\n }\n matchedBaseMessageIndexes.add(index);\n return true;\n });\n if (echoed) stagedMessagesRef.current.delete(id);\n else remainingStagedMessages.push(staged.message);\n }\n\n if (remainingStagedMessages.length === 0) {\n stagedBaseMessagesRef.current = null;\n visibleMessagesRef.current = baseMessages;\n // Reconciling against the upstream stream mutates the staged refs above,\n // which cannot happen during render.\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setStagedMessages(null);\n return;\n }\n\n const nextMessages = [...baseMessages, ...remainingStagedMessages];\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n }, [stream.messages]);\n\n const getStagedRun = (parentId: string | null) => {\n const parent = parentId\n ? stagedMessagesRef.current.get(parentId)\n : undefined;\n if (!parent || parent.transcriptStatus === \"sent\") return null;\n\n const staged: LangChainBaseMessage[] = [];\n for (const message of visibleMessagesRef.current) {\n const entry = message.id\n ? stagedMessagesRef.current.get(message.id)\n : undefined;\n if (entry && entry.transcriptStatus !== \"sent\") {\n staged.push(entry.message);\n }\n if (message.id === parentId) break;\n }\n\n return {\n messages: staged,\n runConfig: parent.runConfig,\n };\n };\n\n const appendVoiceTranscript = (message: ThreadMessage) => {\n const transcript = {\n id: message.id,\n _getType: () => (message.role === \"assistant\" ? \"ai\" : \"human\"),\n content: getThreadMessageText(message),\n ...(message.metadata.modality && {\n additional_kwargs: { modality: message.metadata.modality },\n }),\n };\n stagedMessagesRef.current.set(transcript.id, {\n message: transcript,\n runConfig: undefined,\n reconcileOnEcho: false,\n baseMessageCount: streamRef.current.messages.length,\n transcriptStatus: \"unsent\",\n });\n const nextMessages = [...visibleMessagesRef.current, transcript];\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n };\n\n const getUnsentTranscripts = () =>\n visibleMessagesRef.current.filter(\n (message) =>\n message.id !== undefined &&\n stagedMessagesRef.current.get(message.id)?.transcriptStatus ===\n \"unsent\",\n );\n\n const setTranscriptStatus = (\n messages: readonly LangChainBaseMessage[],\n status: \"unsent\" | \"sent\",\n ) => {\n for (const message of messages) {\n const staged = message.id\n ? stagedMessagesRef.current.get(message.id)\n : undefined;\n if (staged?.transcriptStatus) staged.transcriptStatus = status;\n }\n };\n\n // Reserving before the submit keeps an overlapping submit from carrying the\n // same transcript; a failed submit hands it back to the next run.\n const submitCarryingTranscripts = async (\n transcripts: readonly LangChainBaseMessage[],\n submit: () => Promise<void>,\n ) => {\n setTranscriptStatus(transcripts, \"sent\");\n try {\n await submit();\n } catch (error) {\n setTranscriptStatus(transcripts, \"unsent\");\n throw error;\n }\n };\n\n const dropTranscripts = (messages: readonly LangChainBaseMessage[]) => {\n for (const message of messages) {\n if (\n message.id &&\n stagedMessagesRef.current.get(message.id)?.transcriptStatus\n )\n removeStagedMessage(message.id);\n }\n };\n\n const isTranscriptMessage = (message: LangChainBaseMessage) => {\n const staged = message.id\n ? stagedMessagesRef.current.get(message.id)\n : undefined;\n if (staged?.transcriptStatus === \"unsent\") return true;\n const type = getMessageType(message);\n return (\n (type === \"human\" || type === \"ai\") &&\n getMessageModality(message.additional_kwargs) !== undefined\n );\n };\n\n // A transcript reaches the graph in the same input as the message after it, so\n // no checkpoint ends at one; a fork starts before the trailing transcripts.\n const planForkTranscripts = (parentId: string | null) => {\n const visible = visibleMessagesRef.current;\n const parentIndex =\n parentId == null ? -1 : visible.findIndex((m) => m.id === parentId);\n if (parentId != null && parentIndex === -1)\n return { forkParentId: parentId, transcripts: [], truncated: [] };\n\n let start = parentIndex + 1;\n while (start > 0 && isTranscriptMessage(visible[start - 1]!)) start--;\n return {\n forkParentId: start > 0 ? (visible[start - 1]!.id ?? null) : null,\n transcripts: visible.slice(start, parentIndex + 1),\n truncated: visible.slice(parentIndex + 1),\n };\n };\n\n const stageUserMessage = (msg: AppendMessage, reconcileOnEcho = false) => {\n const stagedMessage = toStagedHumanMessage(msg);\n stagedMessagesRef.current.set(stagedMessage.id, {\n message: stagedMessage,\n runConfig: msg.runConfig,\n reconcileOnEcho,\n baseMessageCount: streamRef.current.messages.length,\n });\n const nextMessages = [...visibleMessagesRef.current, stagedMessage];\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n return stagedMessage;\n };\n\n const removeStagedMessage = (id: string) => {\n if (!stagedMessagesRef.current.delete(id)) return;\n const nextMessages = visibleMessagesRef.current.filter(\n (message) => message.id !== id,\n );\n visibleMessagesRef.current = nextMessages;\n if (stagedMessagesRef.current.size === 0) {\n stagedBaseMessagesRef.current = null;\n setStagedMessages(null);\n } else {\n setStagedMessages(nextMessages);\n }\n };\n\n const extras = useMemo(\n () =>\n langChainExtras.provide({\n interrupt: stream.interrupt,\n interrupts: stream.interrupts,\n toolCalls: stream.toolCalls,\n subagents: stream.subagents,\n subgraphs: stream.subgraphs,\n stream,\n error: stream.error,\n submit: (values, submitOptions) => {\n const isResume = values == null || submitOptions?.command != null;\n return stream.submit(\n values,\n isResume ? withActiveRunConfig(submitOptions) : submitOptions,\n );\n },\n respond: (response, respondOptions) =>\n stream.respond(response, withActiveRunConfig(respondOptions)),\n respondAll: (responsesById, respondOptions) =>\n stream.respondAll(responsesById, withActiveRunConfig(respondOptions)),\n values: stream.values,\n messagesKey,\n }),\n [stream, messagesKey, withActiveRunConfig],\n );\n\n const runtime = useExternalStoreRuntime({\n ...pickExternalStoreSharedOptions(options),\n isRunning: stream.isLoading,\n isLoading: stream.isThreadLoading,\n messages: messagesWithTranscripts,\n adapters,\n extras,\n unstable_enableToolInvocations: true,\n setToolStatuses,\n onNew: async (msg) => {\n if (!(msg.startRun ?? msg.role === \"user\")) {\n stageUserMessage(msg);\n return;\n }\n\n const stagedMessage = stageUserMessage(msg, true);\n const stagedMessageId = stagedMessage.id;\n setActiveRunConfig(msg.runConfig);\n const content = getMessageContent(msg);\n const cancellations =\n autoCancelPendingToolCalls !== false\n ? getPendingToolCalls(\n streamRef.current.messages as readonly LangChainBaseMessage[],\n )\n // LangChain rejects a tool message without a tool_call_id.\n .filter((toolCall) => toolCall.id)\n .map(createToolCallCancellationStub)\n : [];\n // A null threadId is not a no-op for the SDK: it rebinds the controller\n // away from its self-created thread and forces a fresh one, so the\n // submit waits for initialization to produce an identity; core no\n // longer holds appends on that barrier.\n try {\n const { externalId } = await aui.threadListItem.initialize();\n const transcripts = getUnsentTranscripts();\n await submitCarryingTranscripts(transcripts, () =>\n streamRef.current.submit(\n {\n [messagesKey]: [\n ...cancellations,\n ...transcripts.map(toStagedMessageInput),\n {\n id: stagedMessageId,\n type: \"human\",\n content,\n },\n ],\n },\n {\n ...runConfigToSubmitOptions(msg.runConfig),\n ...(externalId != null ? { threadId: externalId } : {}),\n },\n ),\n );\n } catch (error) {\n removeStagedMessage(stagedMessageId);\n throw error;\n }\n },\n onVoiceTranscript: appendVoiceTranscript,\n onAddToolResult: async ({\n messageId,\n toolCallId,\n toolName,\n result,\n isError,\n artifact,\n }) => {\n const runConfig = runConfigByMessageIdRef.current.has(messageId)\n ? runConfigByMessageIdRef.current.get(messageId)\n : activeRunConfigRef.current;\n await stream.submit(\n {\n [messagesKey]: [\n {\n type: \"tool\",\n name: toolName,\n tool_call_id: toolCallId,\n content: JSON.stringify(result),\n ...(artifact !== undefined && { artifact }),\n status: isError ? \"error\" : \"success\",\n },\n ],\n },\n runConfig === undefined ? undefined : { config: runConfig },\n );\n },\n onReload: async (parentId, config) => {\n const stagedRun = getStagedRun(parentId);\n if (stagedRun) {\n if (\n config.sourceId &&\n stagedMessagesRef.current.get(config.sourceId)?.transcriptStatus\n )\n removeStagedMessage(config.sourceId);\n const promotedIds = new Set<string>();\n for (const message of stagedRun.messages) {\n if (\n !message.id ||\n stagedMessagesRef.current.get(message.id)?.transcriptStatus\n )\n continue;\n promotedIds.add(message.id);\n stagedMessagesRef.current.delete(message.id);\n }\n stagedBaseMessagesRef.current = null;\n if (stagedMessagesRef.current.size > 0) {\n const nextMessages = visibleMessagesRef.current.filter(\n (message) => !message.id || !promotedIds.has(message.id),\n );\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n } else {\n setStagedMessages(null);\n }\n const runConfig = config.runConfig ?? stagedRun.runConfig;\n setActiveRunConfig(runConfig);\n await submitCarryingTranscripts(stagedRun.messages, () =>\n stream.submit(\n {\n [messagesKey]: stagedRun.messages.map(toStagedMessageInput),\n },\n runConfigToSubmitOptions(runConfig),\n ),\n );\n return;\n }\n\n const threadId = externalId;\n if (!threadId || parentId == null) return;\n const s = streamRef.current;\n const fork = planForkTranscripts(parentId);\n const checkpointId = await resolveForkCheckpoint(\n s.client,\n threadId,\n s.messages as readonly LangChainBaseMessage[],\n fork.forkParentId,\n config.sourceId,\n s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),\n messagesKey,\n );\n if (!checkpointId) return;\n dropTranscripts(fork.truncated);\n setActiveRunConfig(config.runConfig);\n await submitCarryingTranscripts(fork.transcripts, () =>\n s.submit(\n fork.transcripts.length > 0\n ? { [messagesKey]: fork.transcripts.map(toStagedMessageInput) }\n : null,\n {\n forkFrom: checkpointId,\n ...runConfigToSubmitOptions(config.runConfig),\n },\n ),\n );\n },\n onEdit: async (message) => {\n if (!(message.startRun ?? message.role === \"user\")) {\n const truncated = truncateLangChainBaseMessages(\n threadMessagesRef.current,\n message.parentId,\n );\n const stagedMessage = toStagedHumanMessage(message);\n stagedMessagesRef.current.set(stagedMessage.id, {\n message: stagedMessage,\n runConfig: message.runConfig,\n reconcileOnEcho: false,\n baseMessageCount: 0,\n });\n stagedBaseMessagesRef.current = truncated;\n const nextMessages = [...truncated, stagedMessage];\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n return;\n }\n\n const threadId = externalId;\n if (!threadId) return;\n const s = streamRef.current;\n const fork = planForkTranscripts(message.parentId);\n const checkpointId = await resolveForkCheckpoint(\n s.client,\n threadId,\n s.messages as readonly LangChainBaseMessage[],\n fork.forkParentId,\n message.sourceId,\n s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),\n messagesKey,\n );\n if (!checkpointId) return;\n dropTranscripts(fork.truncated);\n const content = getMessageContent(message);\n setActiveRunConfig(message.runConfig);\n await submitCarryingTranscripts(fork.transcripts, () =>\n s.submit(\n {\n [messagesKey]: [\n ...fork.transcripts.map(toStagedMessageInput),\n { type: \"human\", content },\n ],\n },\n {\n forkFrom: checkpointId,\n ...runConfigToSubmitOptions(message.runConfig),\n },\n ),\n );\n },\n onCancel:\n unstable_allowCancellation !== false\n ? async () => {\n activeRunConfigRef.current = undefined;\n await stream.stop();\n }\n : undefined,\n });\n\n return runtime;\n};\n\n/**\n * Creates an assistant-ui runtime backed by LangChain's `useStream` hook.\n * Accepts the same options as `useStream` from `@langchain/react`, plus\n * `cloud` and `adapters`.\n *\n * @example\n * ```tsx\n * import { useStreamRuntime } from \"@assistant-ui/react-langchain\";\n * import { AssistantRuntimeProvider, Thread } from \"@assistant-ui/react\";\n *\n * function App() {\n * const runtime = useStreamRuntime({\n * assistantId: \"agent\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * return (\n * <AssistantRuntimeProvider runtime={runtime}>\n * <Thread />\n * </AssistantRuntimeProvider>\n * );\n * }\n * ```\n */\nexport const useStreamRuntime = (rawOptions: UseStreamRuntimeOptions) => {\n const {\n cloud,\n unstable_threadListAdapter,\n create,\n delete: deleteFn,\n onThreadIdChange,\n ...options\n } = rawOptions;\n\n const aui = useAui();\n const cloudAdapter = useCloudThreadListAdapter({\n sdk: LANGCHAIN_SDK,\n cloud,\n create: createCloudThreadListAdapterCreateFallback(\n create,\n aui.threadListItem,\n ),\n delete: deleteFn,\n });\n const adapter = unstable_threadListAdapter ?? cloudAdapter;\n\n return useRemoteThreadListRuntime({\n runtimeHook: function RuntimeHook() {\n return useStreamThreadRuntime(options);\n },\n adapter,\n allowNesting: true,\n onThreadIdChange,\n });\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AA+DA,MAAa,4BACX,cAEA,WAAW,SACP,EAAE,QAAQ,EAAE,cAAc,UAAU,OAAO,EAAE,IAC7C,KAAA;AAMN,MAAM,uBACJ,aAEA,qBACE,WACC,YAAY;CACX,MAAM,OAAO,eAAe,OAAO;CACnC,IAAI,SAAS,MAAM,OAAO,EAAE,WAAW,QAAQ,cAAc,CAAC,EAAE;CAChE,IAAI,SAAS,UAAU,QAAQ,cAC7B,OAAO,EAAE,YAAY,QAAQ,aAAa;AAG9C,IACC,aAAa,SAAS,EACzB;AAEF,MAAM,wBACJ,KACA,KAAK,WAAW,OAC2B;CAC3C;CACA,gBAAgB;CAChB,SAAS,kBAAkB,GAAG;AAChC;AAEA,MAAM,wBAAwB,aAAmC;CAC/D,IAAI,QAAQ;CACZ,MAAM,eAAe,OAAO,MAAM,OAAQ,OAAkB;CAC5D,SAAS,QAAQ;CACjB,GAAI,QAAQ,qBAAqB,EAC/B,mBAAmB,QAAQ,kBAC7B;AACF;AAEA,MAAM,oBAAoB,YAA6C;CACrE,IAAI,OAAO,YAAY,UAAU,OAAO;CACxC,IAAI,CAAC,MAAM,QAAQ,OAAO,GAAG,OAAO;CACpC,OAAO,QACJ,QACE,SACC,OAAO,SAAS,YAChB,SAAS,QACT,KAAK,SAAS,UACd,OAAO,KAAK,SAAS,QACzB,CAAC,CACA,KAAK,SAAS,KAAK,IAAI,CAAC,CACxB,KAAK,EAAE;AACZ;AAEA,MAAM,yBACJ,GACA,MACG,iBAAiB,EAAE,OAAO,MAAM,iBAAiB,EAAE,OAAO;AAE/D,MAAM,iCACJ,gBACA,aAC2B;CAC3B,IAAI,aAAa,MAAM,OAAO,CAAC;CAC/B,MAAM,cAAc,eAAe,WAAW,MAAM,EAAE,OAAO,QAAQ;CACrE,IAAI,gBAAgB,IAAI,OAAO,CAAC;CAChC,MAAM,YAAoC,CAAC;CAC3C,KAAK,IAAI,IAAI,GAAG,KAAK,eAAe,IAAI,eAAe,QAAQ,KAC7D,UAAU,KACR,GAAG,yBAA+C,eAAe,EAAG,CACtE;CAEF,OAAO;AACT;AAMA,MAAM,0BACJ,YAIG;CACH,MAAM,EAAE,UAAU,4BAA4B,+BAC5C;CACF,MAAM,MAAM,OAAO;CACnB,MAAM,cAAc,QAAQ,eAAe;CAC3C,MAAM,aAAa,QAAQ,cAAc;CAEzC,MAAM,aAAa,aAAa,MAAM,EAAE,eAAe,UAAU;CAKjE,MAAM,SAAS,UACb,OAAO,OAAO,CAAC,GAAG,SAAS,EAAE,UAAU,WAAW,CAAC,CACrD;CACA,MAAM,CAAC,gBAAgB,qBAAqB,SAE1C,IAAI;CAEN,MAAM,CAAC,cAAc,mBAAmB,SAEtC,CAAC,CAAC;CACJ,MAAM,oBAAoB,OAAO,OAAO,YAAY,CAAC,CAAC,MACnD,MAAM,GAAG,SAAS,WACrB;CACA,MAAM,qBAAqB,OAAO,aAAa;CAE/C,MAAM,CAAC,kBAAkB,SAAS,oBAAoB;CACtD,MAAM,eAAe,oBACnB,OAAO,OAAO,aACd,cACF;CAEA,MAAM,eAAe,WAAW,QAAQ,kBAAkB;CAC1D,MAAM,CAAC,cAAc,SAAS,gBAAgB;CAC9C,MAAM,iBAAiB,cACf,cAAc,cAAc,UAAU,GAC5C,CAAC,cAAc,UAAU,CAC3B;CAEA,MAAM,mBAAmB,cACjB,gBAAgB,gBAAgB,YAAY,GAClD,CAAC,gBAAgB,YAAY,CAC/B;CAEA,MAAM,qBAAqB,cACnB,wBAAmC,gBAAgB,GACzD,CAAC,gBAAgB,CACnB;CAEA,MAAM,kBACJ,kBAAmB,OAAO;CAE5B,MAAM,gBAAgB,4BACpB,iBACA,kBACF;CAEA,MAAM,sBAAsB,uBAC1B,QACA,kBACF;CAEA,MAAM,gBAAgB,eAGb,SAAS,aACd,4BAA4B,SAAS;EACnC,GAAG;EACH;EACA;CACF,CAAC,GACH,CAAC,oBAAoB,aAAa,CACpC;CAEA,MAAM,iBAAiB,4BAA4B;EACjD,UAAU;EACV,UAAU;EACV,WAAW;CACb,CAAC;CACD,MAAM,CAAC,QAAQ,SAAS,gBAAgB;CACxC,MAAM,0BAA0B,cACxB,0BAA0B,gBAAgB,qBAAqB,IAAI,GACzE;EAAC;EAAgB;EAAqB;CAAI,CAC5C;CAEA,MAAM,YAAY,OAAO,MAAM;CAC/B,yBAAyB;EACvB,UAAU,UAAU;CACtB,GAAG,CAAC,MAAM,CAAC;CAEX,MAAM,qBAAqB,OAEzB,KAAA,CAAS;CACX,MAAM,0BAA0B,uBAC9B,IAAI,IAA8D,CACpE;CACA,MAAM,oBAAoB,OAAO,UAAU;CAC3C,MAAM,qBAAqB,aACxB,cAA0C;EACzC,mBAAmB,UAAU,yBAAyB,SAAS,CAAC,EAAE;CACpE,GACA,CAAC,CACH;CACA,MAAM,sBAAsB,aACzB,kBAA4C;EAC3C,IAAI,iBAAiB,YAAY,eAAe,OAAO;EACvD,IAAI,mBAAmB,YAAY,KAAA,GAAW,OAAO;EACrD,OAAO;GAAE,GAAG;GAAe,QAAQ,mBAAmB;EAAQ;CAChE,GACA,CAAC,CACH;CAEA,gBAAgB;EACd,IACE,kBAAkB,YAAY,QAC9B,kBAAkB,YAAY,YAC9B;GACA,mBAAmB,UAAU,KAAA;GAC7B,wBAAwB,QAAQ,MAAM;EACxC;EACA,kBAAkB,UAAU;CAC9B,GAAG,CAAC,UAAU,CAAC;CAEf,gBAAgB;EACd,MAAM,WAAW,OAAO;EACxB,MAAM,QAAQ,wBAAwB;EACtC,KAAK,IAAI,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;GAC7C,MAAM,UAAU,SAAS,GAAG,CAAC;GAC7B,IACE,CAAC,SAAS,MACV,eAAe,OAAO,MAAM,QAC5B,CAAC,QAAQ,YAAY,QAErB;GAEF,IAAI,MAAM,IAAI,QAAQ,EAAE,GAAG;GAC3B;EACF;EACA,KAAK,MAAM,WAAW,UAAU;GAC9B,IACE,CAAC,QAAQ,MACT,eAAe,OAAO,MAAM,QAC5B,CAAC,QAAQ,YAAY,UACrB,MAAM,IAAI,QAAQ,EAAE,GAEpB;GAEF,MAAM,IAAI,QAAQ,IAAI,mBAAmB,OAAO;EAClD;CACF,GAAG,CAAC,OAAO,QAAQ,CAAC;CAEpB,MAAM,qBAAqB,OAAO,eAAe;CACjD,yBAAyB;EACvB,mBAAmB,UAAU;CAC/B,GAAG,CAAC,eAAe,CAAC;CAEpB,MAAM,oBAAoB,OAAO,uBAAuB;CACxD,yBAAyB;EACvB,kBAAkB,UAAU;CAC9B,GAAG,CAAC,uBAAuB,CAAC;CAE5B,MAAM,oBAAoB,uBACxB,IAAI,IASF,CACJ;CACA,MAAM,wBAAwB,OAAsC,IAAI;CACxE,gBAAgB;EACd,IAAI,kBAAkB,QAAQ,SAAS,GAAG;EAG1C,MAAM,eACJ,sBAAsB,WACrB,OAAO;EACV,MAAM,0BAAkD,CAAC;EACzD,MAAM,4CAA4B,IAAI,IAAY;EAClD,MAAM,mBAAmB,IAAI,IAC3B,mBAAmB,QAAQ,SAAS,MAAO,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,CAAE,CAChE;EACA,KAAK,MAAM,CAAC,IAAI,WAAW,kBAAkB,SAAS;GACpD,IAAI,CAAC,iBAAiB,IAAI,EAAE,GAAG;GAkB/B,IAjBe,aAAa,MAAM,SAAS,UAAU;IACnD,IAAI,0BAA0B,IAAI,KAAK,GAAG,OAAO;IACjD,IAAI,QAAQ,OAAO,IAAI;KACrB,0BAA0B,IAAI,KAAK;KACnC,OAAO;IACT;IACA,IACE,CAAC,OAAO,mBACR,QAAQ,OAAO,oBACf,eAAe,OAAO,MAAM,WAC5B,CAAC,sBAAsB,SAAS,OAAO,OAAO,GAE9C,OAAO;IAET,0BAA0B,IAAI,KAAK;IACnC,OAAO;GACT,CACS,GAAG,kBAAkB,QAAQ,OAAO,EAAE;QAC1C,wBAAwB,KAAK,OAAO,OAAO;EAClD;EAEA,IAAI,wBAAwB,WAAW,GAAG;GACxC,sBAAsB,UAAU;GAChC,mBAAmB,UAAU;GAI7B,kBAAkB,IAAI;GACtB;EACF;EAEA,MAAM,eAAe,CAAC,GAAG,cAAc,GAAG,uBAAuB;EACjE,mBAAmB,UAAU;EAC7B,kBAAkB,YAAY;CAChC,GAAG,CAAC,OAAO,QAAQ,CAAC;CAEpB,MAAM,gBAAgB,aAA4B;EAChD,MAAM,SAAS,WACX,kBAAkB,QAAQ,IAAI,QAAQ,IACtC,KAAA;EACJ,IAAI,CAAC,UAAU,OAAO,qBAAqB,QAAQ,OAAO;EAE1D,MAAM,SAAiC,CAAC;EACxC,KAAK,MAAM,WAAW,mBAAmB,SAAS;GAChD,MAAM,QAAQ,QAAQ,KAClB,kBAAkB,QAAQ,IAAI,QAAQ,EAAE,IACxC,KAAA;GACJ,IAAI,SAAS,MAAM,qBAAqB,QACtC,OAAO,KAAK,MAAM,OAAO;GAE3B,IAAI,QAAQ,OAAO,UAAU;EAC/B;EAEA,OAAO;GACL,UAAU;GACV,WAAW,OAAO;EACpB;CACF;CAEA,MAAM,yBAAyB,YAA2B;EACxD,MAAM,aAAa;GACjB,IAAI,QAAQ;GACZ,gBAAiB,QAAQ,SAAS,cAAc,OAAO;GACvD,SAAS,qBAAqB,OAAO;GACrC,GAAI,QAAQ,SAAS,YAAY,EAC/B,mBAAmB,EAAE,UAAU,QAAQ,SAAS,SAAS,EAC3D;EACF;EACA,kBAAkB,QAAQ,IAAI,WAAW,IAAI;GAC3C,SAAS;GACT,WAAW,KAAA;GACX,iBAAiB;GACjB,kBAAkB,UAAU,QAAQ,SAAS;GAC7C,kBAAkB;EACpB,CAAC;EACD,MAAM,eAAe,CAAC,GAAG,mBAAmB,SAAS,UAAU;EAC/D,mBAAmB,UAAU;EAC7B,kBAAkB,YAAY;CAChC;CAEA,MAAM,6BACJ,mBAAmB,QAAQ,QACxB,YACC,QAAQ,OAAO,KAAA,KACf,kBAAkB,QAAQ,IAAI,QAAQ,EAAE,CAAC,EAAE,qBACzC,QACN;CAEF,MAAM,uBACJ,UACA,WACG;EACH,KAAK,MAAM,WAAW,UAAU;GAC9B,MAAM,SAAS,QAAQ,KACnB,kBAAkB,QAAQ,IAAI,QAAQ,EAAE,IACxC,KAAA;GACJ,IAAI,QAAQ,kBAAkB,OAAO,mBAAmB;EAC1D;CACF;CAIA,MAAM,4BAA4B,OAChC,aACA,WACG;EACH,oBAAoB,aAAa,MAAM;EACvC,IAAI;GACF,MAAM,OAAO;EACf,SAAS,OAAO;GACd,oBAAoB,aAAa,QAAQ;GACzC,MAAM;EACR;CACF;CAEA,MAAM,mBAAmB,aAA8C;EACrE,KAAK,MAAM,WAAW,UACpB,IACE,QAAQ,MACR,kBAAkB,QAAQ,IAAI,QAAQ,EAAE,CAAC,EAAE,kBAE3C,oBAAoB,QAAQ,EAAE;CAEpC;CAEA,MAAM,uBAAuB,YAAkC;EAI7D,KAHe,QAAQ,KACnB,kBAAkB,QAAQ,IAAI,QAAQ,EAAE,IACxC,KAAA,EAAA,EACQ,qBAAqB,UAAU,OAAO;EAClD,MAAM,OAAO,eAAe,OAAO;EACnC,QACG,SAAS,WAAW,SAAS,SAC9B,mBAAmB,QAAQ,iBAAiB,MAAM,KAAA;CAEtD;CAIA,MAAM,uBAAuB,aAA4B;EACvD,MAAM,UAAU,mBAAmB;EACnC,MAAM,cACJ,YAAY,OAAO,KAAK,QAAQ,WAAW,MAAM,EAAE,OAAO,QAAQ;EACpE,IAAI,YAAY,QAAQ,gBAAgB,IACtC,OAAO;GAAE,cAAc;GAAU,aAAa,CAAC;GAAG,WAAW,CAAC;EAAE;EAElE,IAAI,QAAQ,cAAc;EAC1B,OAAO,QAAQ,KAAK,oBAAoB,QAAQ,QAAQ,EAAG,GAAG;EAC9D,OAAO;GACL,cAAc,QAAQ,IAAK,QAAQ,QAAQ,EAAE,CAAE,MAAM,OAAQ;GAC7D,aAAa,QAAQ,MAAM,OAAO,cAAc,CAAC;GACjD,WAAW,QAAQ,MAAM,cAAc,CAAC;EAC1C;CACF;CAEA,MAAM,oBAAoB,KAAoB,kBAAkB,UAAU;EACxE,MAAM,gBAAgB,qBAAqB,GAAG;EAC9C,kBAAkB,QAAQ,IAAI,cAAc,IAAI;GAC9C,SAAS;GACT,WAAW,IAAI;GACf;GACA,kBAAkB,UAAU,QAAQ,SAAS;EAC/C,CAAC;EACD,MAAM,eAAe,CAAC,GAAG,mBAAmB,SAAS,aAAa;EAClE,mBAAmB,UAAU;EAC7B,kBAAkB,YAAY;EAC9B,OAAO;CACT;CAEA,MAAM,uBAAuB,OAAe;EAC1C,IAAI,CAAC,kBAAkB,QAAQ,OAAO,EAAE,GAAG;EAC3C,MAAM,eAAe,mBAAmB,QAAQ,QAC7C,YAAY,QAAQ,OAAO,EAC9B;EACA,mBAAmB,UAAU;EAC7B,IAAI,kBAAkB,QAAQ,SAAS,GAAG;GACxC,sBAAsB,UAAU;GAChC,kBAAkB,IAAI;EACxB,OACE,kBAAkB,YAAY;CAElC;CAEA,MAAM,SAAS,cAEX,gBAAgB,QAAQ;EACtB,WAAW,OAAO;EAClB,YAAY,OAAO;EACnB,WAAW,OAAO;EAClB,WAAW,OAAO;EAClB,WAAW,OAAO;EAClB;EACA,OAAO,OAAO;EACd,SAAS,QAAQ,kBAAkB;GACjC,MAAM,WAAW,UAAU,QAAQ,eAAe,WAAW;GAC7D,OAAO,OAAO,OACZ,QACA,WAAW,oBAAoB,aAAa,IAAI,aAClD;EACF;EACA,UAAU,UAAU,mBAClB,OAAO,QAAQ,UAAU,oBAAoB,cAAc,CAAC;EAC9D,aAAa,eAAe,mBAC1B,OAAO,WAAW,eAAe,oBAAoB,cAAc,CAAC;EACtE,QAAQ,OAAO;EACf;CACF,CAAC,GACH;EAAC;EAAQ;EAAa;CAAmB,CAC3C;CA2NA,OAzNgB,wBAAwB;EACtC,GAAG,+BAA+B,OAAO;EACzC,WAAW,OAAO;EAClB,WAAW,OAAO;EAClB,UAAU;EACV;EACA;EACA,gCAAgC;EAChC;EACA,OAAO,OAAO,QAAQ;GACpB,IAAI,EAAE,IAAI,YAAY,IAAI,SAAS,SAAS;IAC1C,iBAAiB,GAAG;IACpB;GACF;GAGA,MAAM,kBADgB,iBAAiB,KAAK,IACR,CAAC,CAAC;GACtC,mBAAmB,IAAI,SAAS;GAChC,MAAM,UAAU,kBAAkB,GAAG;GACrC,MAAM,gBACJ,+BAA+B,QAC3B,oBACE,UAAU,QAAQ,QACpB,CAAC,CAEE,QAAQ,aAAa,SAAS,EAAE,CAAC,CACjC,IAAI,8BAA8B,IACrC,CAAC;GAKP,IAAI;IACF,MAAM,EAAE,eAAe,MAAM,IAAI,eAAe,WAAW;IAC3D,MAAM,cAAc,qBAAqB;IACzC,MAAM,0BAA0B,mBAC9B,UAAU,QAAQ,OAChB,GACG,cAAc;KACb,GAAG;KACH,GAAG,YAAY,IAAI,oBAAoB;KACvC;MACE,IAAI;MACJ,MAAM;MACN;KACF;IACF,EACF,GACA;KACE,GAAG,yBAAyB,IAAI,SAAS;KACzC,GAAI,cAAc,OAAO,EAAE,UAAU,WAAW,IAAI,CAAC;IACvD,CACF,CACF;GACF,SAAS,OAAO;IACd,oBAAoB,eAAe;IACnC,MAAM;GACR;EACF;EACA,mBAAmB;EACnB,iBAAiB,OAAO,EACtB,WACA,YACA,UACA,QACA,SACA,eACI;GACJ,MAAM,YAAY,wBAAwB,QAAQ,IAAI,SAAS,IAC3D,wBAAwB,QAAQ,IAAI,SAAS,IAC7C,mBAAmB;GACvB,MAAM,OAAO,OACX,GACG,cAAc,CACb;IACE,MAAM;IACN,MAAM;IACN,cAAc;IACd,SAAS,KAAK,UAAU,MAAM;IAC9B,GAAI,aAAa,KAAA,KAAa,EAAE,SAAS;IACzC,QAAQ,UAAU,UAAU;GAC9B,CACF,EACF,GACA,cAAc,KAAA,IAAY,KAAA,IAAY,EAAE,QAAQ,UAAU,CAC5D;EACF;EACA,UAAU,OAAO,UAAU,WAAW;GACpC,MAAM,YAAY,aAAa,QAAQ;GACvC,IAAI,WAAW;IACb,IACE,OAAO,YACP,kBAAkB,QAAQ,IAAI,OAAO,QAAQ,CAAC,EAAE,kBAEhD,oBAAoB,OAAO,QAAQ;IACrC,MAAM,8BAAc,IAAI,IAAY;IACpC,KAAK,MAAM,WAAW,UAAU,UAAU;KACxC,IACE,CAAC,QAAQ,MACT,kBAAkB,QAAQ,IAAI,QAAQ,EAAE,CAAC,EAAE,kBAE3C;KACF,YAAY,IAAI,QAAQ,EAAE;KAC1B,kBAAkB,QAAQ,OAAO,QAAQ,EAAE;IAC7C;IACA,sBAAsB,UAAU;IAChC,IAAI,kBAAkB,QAAQ,OAAO,GAAG;KACtC,MAAM,eAAe,mBAAmB,QAAQ,QAC7C,YAAY,CAAC,QAAQ,MAAM,CAAC,YAAY,IAAI,QAAQ,EAAE,CACzD;KACA,mBAAmB,UAAU;KAC7B,kBAAkB,YAAY;IAChC,OACE,kBAAkB,IAAI;IAExB,MAAM,YAAY,OAAO,aAAa,UAAU;IAChD,mBAAmB,SAAS;IAC5B,MAAM,0BAA0B,UAAU,gBACxC,OAAO,OACL,GACG,cAAc,UAAU,SAAS,IAAI,oBAAoB,EAC5D,GACA,yBAAyB,SAAS,CACpC,CACF;IACA;GACF;GAEA,MAAM,WAAW;GACjB,IAAI,CAAC,YAAY,YAAY,MAAM;GACnC,MAAM,IAAI,UAAU;GACpB,MAAM,OAAO,oBAAoB,QAAQ;GACzC,MAAM,eAAe,MAAM,sBACzB,EAAE,QACF,UACA,EAAE,UACF,KAAK,cACL,OAAO,UACP,EAAE,kBAAkB,EAAE,sBAAsB,cAAc,GAC1D,WACF;GACA,IAAI,CAAC,cAAc;GACnB,gBAAgB,KAAK,SAAS;GAC9B,mBAAmB,OAAO,SAAS;GACnC,MAAM,0BAA0B,KAAK,mBACnC,EAAE,OACA,KAAK,YAAY,SAAS,IACtB,GAAG,cAAc,KAAK,YAAY,IAAI,oBAAoB,EAAE,IAC5D,MACJ;IACE,UAAU;IACV,GAAG,yBAAyB,OAAO,SAAS;GAC9C,CACF,CACF;EACF;EACA,QAAQ,OAAO,YAAY;GACzB,IAAI,EAAE,QAAQ,YAAY,QAAQ,SAAS,SAAS;IAClD,MAAM,YAAY,8BAChB,kBAAkB,SAClB,QAAQ,QACV;IACA,MAAM,gBAAgB,qBAAqB,OAAO;IAClD,kBAAkB,QAAQ,IAAI,cAAc,IAAI;KAC9C,SAAS;KACT,WAAW,QAAQ;KACnB,iBAAiB;KACjB,kBAAkB;IACpB,CAAC;IACD,sBAAsB,UAAU;IAChC,MAAM,eAAe,CAAC,GAAG,WAAW,aAAa;IACjD,mBAAmB,UAAU;IAC7B,kBAAkB,YAAY;IAC9B;GACF;GAEA,MAAM,WAAW;GACjB,IAAI,CAAC,UAAU;GACf,MAAM,IAAI,UAAU;GACpB,MAAM,OAAO,oBAAoB,QAAQ,QAAQ;GACjD,MAAM,eAAe,MAAM,sBACzB,EAAE,QACF,UACA,EAAE,UACF,KAAK,cACL,QAAQ,UACR,EAAE,kBAAkB,EAAE,sBAAsB,cAAc,GAC1D,WACF;GACA,IAAI,CAAC,cAAc;GACnB,gBAAgB,KAAK,SAAS;GAC9B,MAAM,UAAU,kBAAkB,OAAO;GACzC,mBAAmB,QAAQ,SAAS;GACpC,MAAM,0BAA0B,KAAK,mBACnC,EAAE,OACA,GACG,cAAc,CACb,GAAG,KAAK,YAAY,IAAI,oBAAoB,GAC5C;IAAE,MAAM;IAAS;GAAQ,CAC3B,EACF,GACA;IACE,UAAU;IACV,GAAG,yBAAyB,QAAQ,SAAS;GAC/C,CACF,CACF;EACF;EACA,UACE,+BAA+B,QAC3B,YAAY;GACV,mBAAmB,UAAU,KAAA;GAC7B,MAAM,OAAO,KAAK;EACpB,IACA,KAAA;CACR,CAEa;AACf;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,oBAAoB,eAAwC;CACvE,MAAM,EACJ,OACA,4BACA,QACA,QAAQ,UACR,kBACA,GAAG,YACD;CAEJ,MAAM,MAAM,OAAO;CACnB,MAAM,eAAe,0BAA0B;EAC7C,KAAK;EACL;EACA,QAAQ,2CACN,QACA,IAAI,cACN;EACA,QAAQ;CACV,CAAC;CAGD,OAAO,2BAA2B;EAChC,aAAa,SAAS,cAAc;GAClC,OAAO,uBAAuB,OAAO;EACvC;EACA,SANc,8BAA8B;EAO5C,cAAc;EACd;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"useStreamRuntime.js","names":[],"sources":["../src/useStreamRuntime.ts"],"sourcesContent":["/// <reference types=\"@assistant-ui/core/store\" preserve=\"true\" />\n\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useInsertionEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { AppendMessage, ToolExecutionStatus } from \"@assistant-ui/core\";\nimport {\n generateId,\n getExternalStoreMessages,\n pickExternalStoreSharedOptions,\n} from \"@assistant-ui/core\";\nimport type { ThreadMessage } from \"@assistant-ui/core\";\nimport {\n createCloudThreadListAdapterCreateFallback,\n createToolCallCancellationStub,\n getThreadMessageText,\n scanPendingToolCalls,\n} from \"@assistant-ui/core/internal\";\nimport {\n useCloudThreadListAdapter,\n useExternalStoreRuntime,\n useExternalMessageConverter,\n useRemoteThreadListRuntime,\n} from \"@assistant-ui/core/react\";\nimport { useAui, useAuiState } from \"@assistant-ui/store\";\nimport { STREAM_CONTROLLER, useChannel, useStream } from \"@langchain/react\";\nimport type {\n LangChainBaseMessage,\n LangChainToolCall,\n UIMessage,\n UseStreamRuntimeOptions,\n} from \"./types\";\nimport { getMessageModality, groupUIMessagesByParent } from \"./converter\";\nexport { groupUIMessagesByParent } from \"./converter\";\nimport {\n createLangChainMetadataKey,\n convertLangChainBaseMessage,\n getMessageContent,\n getMessageType,\n type LangChainMessageConverterMetadata,\n} from \"./convertMessages\";\nimport {\n attachSubagentTranscripts,\n createAttachMemo,\n} from \"./attachSubagentTranscripts\";\nimport { useSubagentTranscripts } from \"./useSubagentTranscripts\";\nimport {\n createUIFoldMemo,\n createUISnapshotMemo,\n foldUIUpdates,\n mergeUIMessages,\n reconcileUISnapshot,\n UI_CUSTOM_CHANNELS,\n} from \"./uiMessages\";\nimport { langChainExtras } from \"./runtimeExtras\";\nimport { resolveForkCheckpoint } from \"./resolveForkCheckpoint\";\nimport { useLangChainStreamingTiming } from \"./streamingTiming\";\nimport { LANGCHAIN_SDK } from \"./sdkIdentity\";\n\nexport const runConfigToSubmitOptions = (\n runConfig: AppendMessage[\"runConfig\"],\n) =>\n runConfig?.custom\n ? { config: { configurable: runConfig.custom } }\n : undefined;\n\ntype NormalizedRunConfigOptions = NonNullable<\n ReturnType<typeof runConfigToSubmitOptions>\n>;\n\nconst getPendingToolCalls = (\n messages: readonly LangChainBaseMessage[],\n): LangChainToolCall[] =>\n scanPendingToolCalls(\n messages,\n (message) => {\n const type = getMessageType(message);\n if (type === \"ai\") return { toolCalls: message.tool_calls ?? [] };\n if (type === \"tool\" && message.tool_call_id) {\n return { toolCallId: message.tool_call_id };\n }\n return undefined;\n },\n (toolCall) => toolCall.id,\n );\n\nconst toStagedHumanMessage = (\n msg: AppendMessage,\n id = generateId(),\n): LangChainBaseMessage & { id: string } => ({\n id,\n _getType: () => \"human\",\n content: getMessageContent(msg),\n});\n\nconst toStagedMessageInput = (message: LangChainBaseMessage) => ({\n id: message.id,\n type: getMessageType(message) === \"ai\" ? (\"ai\" as const) : (\"human\" as const),\n content: message.content,\n ...(message.additional_kwargs && {\n additional_kwargs: message.additional_kwargs,\n }),\n});\n\nconst humanContentText = (content: LangChainBaseMessage[\"content\"]) => {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n return content\n .filter(\n (part): part is { type: \"text\"; text: string } =>\n typeof part === \"object\" &&\n part !== null &&\n part.type === \"text\" &&\n typeof part.text === \"string\",\n )\n .map((part) => part.text)\n .join(\"\");\n};\n\nconst hasSameMessageContent = (\n a: LangChainBaseMessage,\n b: LangChainBaseMessage,\n) => humanContentText(a.content) === humanContentText(b.content);\n\nconst truncateLangChainBaseMessages = (\n threadMessages: readonly ThreadMessage[],\n parentId: string | null,\n): LangChainBaseMessage[] => {\n if (parentId === null) return [];\n const parentIndex = threadMessages.findIndex((m) => m.id === parentId);\n if (parentIndex === -1) return [];\n const truncated: LangChainBaseMessage[] = [];\n for (let i = 0; i <= parentIndex && i < threadMessages.length; i++) {\n truncated.push(\n ...getExternalStoreMessages<LangChainBaseMessage>(threadMessages[i]!),\n );\n }\n return truncated;\n};\n\ntype DistributiveOmit<T, K extends keyof any> = T extends unknown\n ? Omit<T, K>\n : never;\n\nconst useStreamThreadRuntime = (\n options: DistributiveOmit<\n UseStreamRuntimeOptions,\n \"cloud\" | \"unstable_threadListAdapter\" | \"create\" | \"delete\"\n >,\n) => {\n const { adapters, autoCancelPendingToolCalls, unstable_allowCancellation } =\n options;\n const aui = useAui();\n const messagesKey = options.messagesKey ?? \"messages\";\n const uiStateKey = options.uiStateKey ?? \"ui\";\n\n const externalId = useAuiState((s) => s.threadListItem.externalId) as\n | string\n | null;\n // Object.assign preserves the discriminated transport union; object spread\n // collapses its arms and no longer satisfies UseStreamOptions.\n const stream = useStream(\n Object.assign({}, options, { threadId: externalId }),\n );\n const [stagedMessages, setStagedMessages] = useState<\n LangChainBaseMessage[] | null\n >(null);\n\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n const hasExecutingTools = Object.values(toolStatuses).some(\n (s) => s?.type === \"executing\",\n );\n const effectiveIsRunning = stream.isLoading || hasExecutingTools;\n\n const [uiSnapshotMemo] = useState(createUISnapshotMemo);\n const uiStateValue = reconcileUISnapshot(\n stream.values[uiStateKey],\n uiSnapshotMemo,\n );\n\n const customEvents = useChannel(stream, UI_CUSTOM_CHANNELS);\n const [uiFoldMemo] = useState(createUIFoldMemo);\n const liveUiMessages = useMemo(\n () => foldUIUpdates(customEvents, uiFoldMemo),\n [customEvents, uiFoldMemo],\n );\n\n const mergedUiMessages = useMemo(\n () => mergeUIMessages(liveUiMessages, uiStateValue),\n [liveUiMessages, uiStateValue],\n );\n\n const uiMessagesByParent = useMemo(\n () => groupUIMessagesByParent<UIMessage>(mergedUiMessages),\n [mergedUiMessages],\n );\n\n const visibleMessages =\n stagedMessages ?? (stream.messages as LangChainBaseMessage[]);\n\n const messageTiming = useLangChainStreamingTiming(\n visibleMessages,\n effectiveIsRunning,\n );\n\n const subagentTranscripts = useSubagentTranscripts(\n stream,\n uiMessagesByParent,\n );\n\n const [getConverterMetadataKey] = useState(createLangChainMetadataKey);\n const converterMetadata = useMemo<LangChainMessageConverterMetadata>(\n () => ({\n uiMessagesByParent: uiMessagesByParent,\n messageTiming,\n }),\n [uiMessagesByParent, messageTiming],\n );\n\n const threadMessages = useExternalMessageConverter({\n callback: convertLangChainBaseMessage,\n messages: visibleMessages,\n isRunning: effectiveIsRunning,\n metadata: converterMetadata,\n getMetadataKey: getConverterMetadataKey,\n });\n const [memo] = useState(createAttachMemo);\n const messagesWithTranscripts = useMemo(\n () => attachSubagentTranscripts(threadMessages, subagentTranscripts, memo),\n [threadMessages, subagentTranscripts, memo],\n );\n\n const streamRef = useRef(stream);\n useInsertionEffect(() => {\n streamRef.current = stream;\n }, [stream]);\n\n const activeRunConfigRef = useRef<\n NormalizedRunConfigOptions[\"config\"] | undefined\n >(undefined);\n const runConfigByMessageIdRef = useRef(\n new Map<string, NormalizedRunConfigOptions[\"config\"] | undefined>(),\n );\n const activeThreadIdRef = useRef(externalId);\n const setActiveRunConfig = useCallback(\n (runConfig: AppendMessage[\"runConfig\"]) => {\n activeRunConfigRef.current = runConfigToSubmitOptions(runConfig)?.config;\n },\n [],\n );\n const withActiveRunConfig = useCallback(\n (submitOptions?: Record<string, unknown>) => {\n if (submitOptions && \"config\" in submitOptions) return submitOptions;\n if (activeRunConfigRef.current === undefined) return submitOptions;\n return { ...submitOptions, config: activeRunConfigRef.current };\n },\n [],\n );\n\n useEffect(() => {\n if (\n activeThreadIdRef.current !== null &&\n activeThreadIdRef.current !== externalId\n ) {\n activeRunConfigRef.current = undefined;\n runConfigByMessageIdRef.current.clear();\n }\n activeThreadIdRef.current = externalId;\n }, [externalId]);\n\n useEffect(() => {\n const messages = stream.messages as readonly LangChainBaseMessage[];\n const owned = runConfigByMessageIdRef.current;\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages.at(i);\n if (\n !message?.id ||\n getMessageType(message) !== \"ai\" ||\n !message.tool_calls?.length\n ) {\n continue;\n }\n if (owned.has(message.id)) return;\n break;\n }\n for (const message of messages) {\n if (\n !message.id ||\n getMessageType(message) !== \"ai\" ||\n !message.tool_calls?.length ||\n owned.has(message.id)\n ) {\n continue;\n }\n owned.set(message.id, activeRunConfigRef.current);\n }\n }, [stream.messages]);\n\n const visibleMessagesRef = useRef(visibleMessages);\n useInsertionEffect(() => {\n visibleMessagesRef.current = visibleMessages;\n }, [visibleMessages]);\n\n const threadMessagesRef = useRef(messagesWithTranscripts);\n useInsertionEffect(() => {\n threadMessagesRef.current = messagesWithTranscripts;\n }, [messagesWithTranscripts]);\n\n const stagedMessagesRef = useRef(\n new Map<\n string,\n {\n message: LangChainBaseMessage & { id: string };\n runConfig: AppendMessage[\"runConfig\"];\n reconcileOnEcho: boolean;\n baseMessageCount: number;\n transcriptStatus?: \"unsent\" | \"sent\";\n }\n >(),\n );\n const stagedBaseMessagesRef = useRef<LangChainBaseMessage[] | null>(null);\n useEffect(() => {\n if (stagedMessagesRef.current.size === 0) return;\n\n // Staged edits must keep their truncated base while stream updates arrive before promotion.\n const baseMessages =\n stagedBaseMessagesRef.current ??\n (stream.messages as LangChainBaseMessage[]);\n const remainingStagedMessages: LangChainBaseMessage[] = [];\n const matchedBaseMessageIndexes = new Set<number>();\n const visibleStagedIds = new Set(\n visibleMessagesRef.current.flatMap((m) => (m.id ? [m.id] : [])),\n );\n for (const [id, staged] of stagedMessagesRef.current) {\n if (!visibleStagedIds.has(id)) continue;\n const echoed = baseMessages.some((message, index) => {\n if (matchedBaseMessageIndexes.has(index)) return false;\n if (message.id === id) {\n matchedBaseMessageIndexes.add(index);\n return true;\n }\n if (\n !staged.reconcileOnEcho ||\n index < staged.baseMessageCount ||\n getMessageType(message) !== \"human\" ||\n !hasSameMessageContent(message, staged.message)\n ) {\n return false;\n }\n matchedBaseMessageIndexes.add(index);\n return true;\n });\n if (echoed) stagedMessagesRef.current.delete(id);\n else remainingStagedMessages.push(staged.message);\n }\n\n if (remainingStagedMessages.length === 0) {\n stagedBaseMessagesRef.current = null;\n visibleMessagesRef.current = baseMessages;\n // Reconciling against the upstream stream mutates the staged refs above,\n // which cannot happen during render.\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setStagedMessages(null);\n return;\n }\n\n const nextMessages = [...baseMessages, ...remainingStagedMessages];\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n }, [stream.messages]);\n\n const getStagedRun = (parentId: string | null) => {\n const parent = parentId\n ? stagedMessagesRef.current.get(parentId)\n : undefined;\n if (!parent || parent.transcriptStatus === \"sent\") return null;\n\n const staged: LangChainBaseMessage[] = [];\n for (const message of visibleMessagesRef.current) {\n const entry = message.id\n ? stagedMessagesRef.current.get(message.id)\n : undefined;\n if (entry && entry.transcriptStatus !== \"sent\") {\n staged.push(entry.message);\n }\n if (message.id === parentId) break;\n }\n\n return {\n messages: staged,\n runConfig: parent.runConfig,\n };\n };\n\n const appendVoiceTranscript = (message: ThreadMessage) => {\n const transcript = {\n id: message.id,\n _getType: () => (message.role === \"assistant\" ? \"ai\" : \"human\"),\n content: getThreadMessageText(message),\n ...(message.metadata.modality && {\n additional_kwargs: { modality: message.metadata.modality },\n }),\n };\n stagedMessagesRef.current.set(transcript.id, {\n message: transcript,\n runConfig: undefined,\n reconcileOnEcho: false,\n baseMessageCount: streamRef.current.messages.length,\n transcriptStatus: \"unsent\",\n });\n const nextMessages = [...visibleMessagesRef.current, transcript];\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n };\n\n const getUnsentTranscripts = () =>\n visibleMessagesRef.current.filter(\n (message) =>\n message.id !== undefined &&\n stagedMessagesRef.current.get(message.id)?.transcriptStatus ===\n \"unsent\",\n );\n\n const setTranscriptStatus = (\n messages: readonly LangChainBaseMessage[],\n status: \"unsent\" | \"sent\",\n ) => {\n for (const message of messages) {\n const staged = message.id\n ? stagedMessagesRef.current.get(message.id)\n : undefined;\n if (staged?.transcriptStatus) staged.transcriptStatus = status;\n }\n };\n\n // Reserving before the submit keeps an overlapping submit from carrying the\n // same transcript; a failed submit hands it back to the next run.\n const submitCarryingTranscripts = async (\n transcripts: readonly LangChainBaseMessage[],\n submit: () => Promise<void>,\n ) => {\n setTranscriptStatus(transcripts, \"sent\");\n try {\n await submit();\n } catch (error) {\n setTranscriptStatus(transcripts, \"unsent\");\n throw error;\n }\n };\n\n const dropTranscripts = (messages: readonly LangChainBaseMessage[]) => {\n for (const message of messages) {\n if (\n message.id &&\n stagedMessagesRef.current.get(message.id)?.transcriptStatus\n )\n removeStagedMessage(message.id);\n }\n };\n\n const isTranscriptMessage = (message: LangChainBaseMessage) => {\n const staged = message.id\n ? stagedMessagesRef.current.get(message.id)\n : undefined;\n if (staged?.transcriptStatus === \"unsent\") return true;\n const type = getMessageType(message);\n return (\n (type === \"human\" || type === \"ai\") &&\n getMessageModality(message.additional_kwargs) !== undefined\n );\n };\n\n // A transcript reaches the graph in the same input as the message after it, so\n // no checkpoint ends at one; a fork starts before the trailing transcripts.\n const planForkTranscripts = (parentId: string | null) => {\n const visible = visibleMessagesRef.current;\n const parentIndex =\n parentId == null ? -1 : visible.findIndex((m) => m.id === parentId);\n if (parentId != null && parentIndex === -1)\n return { forkParentId: parentId, transcripts: [], truncated: [] };\n\n let start = parentIndex + 1;\n while (start > 0 && isTranscriptMessage(visible[start - 1]!)) start--;\n return {\n forkParentId: start > 0 ? (visible[start - 1]!.id ?? null) : null,\n transcripts: visible.slice(start, parentIndex + 1),\n truncated: visible.slice(parentIndex + 1),\n };\n };\n\n const stageUserMessage = (msg: AppendMessage, reconcileOnEcho = false) => {\n const stagedMessage = toStagedHumanMessage(msg);\n stagedMessagesRef.current.set(stagedMessage.id, {\n message: stagedMessage,\n runConfig: msg.runConfig,\n reconcileOnEcho,\n baseMessageCount: streamRef.current.messages.length,\n });\n const nextMessages = [...visibleMessagesRef.current, stagedMessage];\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n return stagedMessage;\n };\n\n const removeStagedMessage = (id: string) => {\n if (!stagedMessagesRef.current.delete(id)) return;\n const nextMessages = visibleMessagesRef.current.filter(\n (message) => message.id !== id,\n );\n visibleMessagesRef.current = nextMessages;\n if (stagedMessagesRef.current.size === 0) {\n stagedBaseMessagesRef.current = null;\n setStagedMessages(null);\n } else {\n setStagedMessages(nextMessages);\n }\n };\n\n const extras = useMemo(\n () =>\n langChainExtras.provide({\n interrupt: stream.interrupt,\n interrupts: stream.interrupts,\n toolCalls: stream.toolCalls,\n subagents: stream.subagents,\n subgraphs: stream.subgraphs,\n stream,\n error: stream.error,\n submit: (values, submitOptions) => {\n const isResume = values == null || submitOptions?.command != null;\n return stream.submit(\n values,\n isResume ? withActiveRunConfig(submitOptions) : submitOptions,\n );\n },\n respond: (response, respondOptions) =>\n stream.respond(response, withActiveRunConfig(respondOptions)),\n respondAll: (responsesById, respondOptions) =>\n stream.respondAll(responsesById, withActiveRunConfig(respondOptions)),\n values: stream.values,\n messagesKey,\n }),\n [stream, messagesKey, withActiveRunConfig],\n );\n\n const runtime = useExternalStoreRuntime({\n ...pickExternalStoreSharedOptions(options),\n isRunning: stream.isLoading,\n isLoading: stream.isThreadLoading,\n messages: messagesWithTranscripts,\n adapters,\n extras,\n unstable_enableToolInvocations: true,\n setToolStatuses,\n onNew: async (msg) => {\n if (!(msg.startRun ?? msg.role === \"user\")) {\n stageUserMessage(msg);\n return;\n }\n\n const stagedMessage = stageUserMessage(msg, true);\n const stagedMessageId = stagedMessage.id;\n setActiveRunConfig(msg.runConfig);\n const content = getMessageContent(msg);\n const cancellations =\n autoCancelPendingToolCalls !== false\n ? getPendingToolCalls(\n streamRef.current.messages as readonly LangChainBaseMessage[],\n )\n // LangChain rejects a tool message without a tool_call_id.\n .filter((toolCall) => toolCall.id)\n .map(createToolCallCancellationStub)\n : [];\n // A null threadId is not a no-op for the SDK: it rebinds the controller\n // away from its self-created thread and forces a fresh one, so the\n // submit waits for initialization to produce an identity; core no\n // longer holds appends on that barrier.\n try {\n const { externalId } = await aui.threadListItem.initialize();\n const transcripts = getUnsentTranscripts();\n await submitCarryingTranscripts(transcripts, () =>\n streamRef.current.submit(\n {\n [messagesKey]: [\n ...cancellations,\n ...transcripts.map(toStagedMessageInput),\n {\n id: stagedMessageId,\n type: \"human\",\n content,\n },\n ],\n },\n {\n ...runConfigToSubmitOptions(msg.runConfig),\n ...(externalId != null ? { threadId: externalId } : {}),\n },\n ),\n );\n } catch (error) {\n removeStagedMessage(stagedMessageId);\n throw error;\n }\n },\n onVoiceTranscript: appendVoiceTranscript,\n onAddToolResult: async ({\n messageId,\n toolCallId,\n toolName,\n result,\n isError,\n artifact,\n }) => {\n const runConfig = runConfigByMessageIdRef.current.has(messageId)\n ? runConfigByMessageIdRef.current.get(messageId)\n : activeRunConfigRef.current;\n await stream.submit(\n {\n [messagesKey]: [\n {\n type: \"tool\",\n name: toolName,\n tool_call_id: toolCallId,\n content: JSON.stringify(result),\n ...(artifact !== undefined && { artifact }),\n status: isError ? \"error\" : \"success\",\n },\n ],\n },\n runConfig === undefined ? undefined : { config: runConfig },\n );\n },\n onReload: async (parentId, config) => {\n const stagedRun = getStagedRun(parentId);\n if (stagedRun) {\n if (\n config.sourceId &&\n stagedMessagesRef.current.get(config.sourceId)?.transcriptStatus\n )\n removeStagedMessage(config.sourceId);\n const promotedIds = new Set<string>();\n for (const message of stagedRun.messages) {\n if (\n !message.id ||\n stagedMessagesRef.current.get(message.id)?.transcriptStatus\n )\n continue;\n promotedIds.add(message.id);\n stagedMessagesRef.current.delete(message.id);\n }\n stagedBaseMessagesRef.current = null;\n if (stagedMessagesRef.current.size > 0) {\n const nextMessages = visibleMessagesRef.current.filter(\n (message) => !message.id || !promotedIds.has(message.id),\n );\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n } else {\n setStagedMessages(null);\n }\n const runConfig = config.runConfig ?? stagedRun.runConfig;\n setActiveRunConfig(runConfig);\n await submitCarryingTranscripts(stagedRun.messages, () =>\n stream.submit(\n {\n [messagesKey]: stagedRun.messages.map(toStagedMessageInput),\n },\n runConfigToSubmitOptions(runConfig),\n ),\n );\n return;\n }\n\n const threadId = externalId;\n if (!threadId || parentId == null) return;\n const s = streamRef.current;\n const fork = planForkTranscripts(parentId);\n const checkpointId = await resolveForkCheckpoint(\n s.client,\n threadId,\n s.messages as readonly LangChainBaseMessage[],\n fork.forkParentId,\n config.sourceId,\n s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),\n messagesKey,\n );\n if (!checkpointId) return;\n dropTranscripts(fork.truncated);\n setActiveRunConfig(config.runConfig);\n await submitCarryingTranscripts(fork.transcripts, () =>\n s.submit(\n fork.transcripts.length > 0\n ? { [messagesKey]: fork.transcripts.map(toStagedMessageInput) }\n : null,\n {\n forkFrom: checkpointId,\n ...runConfigToSubmitOptions(config.runConfig),\n },\n ),\n );\n },\n onEdit: async (message) => {\n if (!(message.startRun ?? message.role === \"user\")) {\n const truncated = truncateLangChainBaseMessages(\n threadMessagesRef.current,\n message.parentId,\n );\n const stagedMessage = toStagedHumanMessage(message);\n stagedMessagesRef.current.set(stagedMessage.id, {\n message: stagedMessage,\n runConfig: message.runConfig,\n reconcileOnEcho: false,\n baseMessageCount: 0,\n });\n stagedBaseMessagesRef.current = truncated;\n const nextMessages = [...truncated, stagedMessage];\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n return;\n }\n\n const threadId = externalId;\n if (!threadId) return;\n const s = streamRef.current;\n const fork = planForkTranscripts(message.parentId);\n const checkpointId = await resolveForkCheckpoint(\n s.client,\n threadId,\n s.messages as readonly LangChainBaseMessage[],\n fork.forkParentId,\n message.sourceId,\n s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),\n messagesKey,\n );\n if (!checkpointId) return;\n dropTranscripts(fork.truncated);\n const content = getMessageContent(message);\n setActiveRunConfig(message.runConfig);\n await submitCarryingTranscripts(fork.transcripts, () =>\n s.submit(\n {\n [messagesKey]: [\n ...fork.transcripts.map(toStagedMessageInput),\n { type: \"human\", content },\n ],\n },\n {\n forkFrom: checkpointId,\n ...runConfigToSubmitOptions(message.runConfig),\n },\n ),\n );\n },\n onCancel:\n unstable_allowCancellation !== false\n ? async () => {\n activeRunConfigRef.current = undefined;\n await stream.stop();\n }\n : undefined,\n });\n\n return runtime;\n};\n\n/**\n * Creates an assistant-ui runtime backed by LangChain's `useStream` hook.\n * Accepts the same options as `useStream` from `@langchain/react`, plus\n * `cloud` and `adapters`.\n *\n * @example\n * ```tsx\n * import { useStreamRuntime } from \"@assistant-ui/react-langchain\";\n * import { AssistantRuntimeProvider, Thread } from \"@assistant-ui/react\";\n *\n * function App() {\n * const runtime = useStreamRuntime({\n * assistantId: \"agent\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * return (\n * <AssistantRuntimeProvider runtime={runtime}>\n * <Thread />\n * </AssistantRuntimeProvider>\n * );\n * }\n * ```\n */\nexport const useStreamRuntime = (rawOptions: UseStreamRuntimeOptions) => {\n const {\n cloud,\n unstable_threadListAdapter,\n create,\n delete: deleteFn,\n onThreadIdChange,\n ...options\n } = rawOptions;\n\n const aui = useAui();\n const cloudAdapter = useCloudThreadListAdapter({\n sdk: LANGCHAIN_SDK,\n cloud,\n create: createCloudThreadListAdapterCreateFallback(\n create,\n aui.threadListItem,\n ),\n delete: deleteFn,\n });\n const adapter = unstable_threadListAdapter ?? cloudAdapter;\n\n return useRemoteThreadListRuntime({\n runtimeHook: function RuntimeHook() {\n return useStreamThreadRuntime(options);\n },\n adapter,\n allowNesting: true,\n onThreadIdChange,\n });\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react-langchain",
3
- "version": "0.0.34",
3
+ "version": "0.0.35",
4
4
  "description": "LangChain useStream adapter for assistant-ui",
5
5
  "keywords": [
6
6
  "langchain",
@@ -40,10 +40,10 @@
40
40
  ],
41
41
  "sideEffects": false,
42
42
  "dependencies": {
43
- "@assistant-ui/core": "^0.3.23",
43
+ "@assistant-ui/core": "^0.3.24",
44
44
  "@assistant-ui/store": "^0.3.17",
45
45
  "assistant-cloud": "*",
46
- "assistant-stream": "^0.3.47"
46
+ "assistant-stream": "^0.3.48"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "@langchain/core": "^1.0.0",
@@ -94,6 +94,7 @@
94
94
  "scripts": {
95
95
  "build": "aui-build",
96
96
  "test": "vitest run",
97
+ "test:coverage": "vitest run --coverage --coverage.reporter=text --coverage.reporter=html --coverage.reporter=json-summary",
97
98
  "typecheck": "tsc --noEmit",
98
99
  "test:peer-react18": "vitest run --config vitest.peer-react18.config.ts",
99
100
  "test:types:peer-react18": "aui-react18-types",
@@ -2,6 +2,10 @@
2
2
 
3
3
  import type { MessageTiming } from "@assistant-ui/core";
4
4
  import type { useExternalMessageConverter } from "@assistant-ui/core/react";
5
+ import {
6
+ createExternalMessageMetadataKey,
7
+ shallowArrayEqual,
8
+ } from "@assistant-ui/core/internal";
5
9
  import type { ReadonlyJSONObject } from "assistant-stream/utils";
6
10
  import {
7
11
  convertLangChainContentBlock,
@@ -16,12 +20,41 @@ import type {
16
20
  UIMessage,
17
21
  } from "./types";
18
22
 
19
- type LangChainMessageConverterMetadata =
23
+ export type LangChainMessageConverterMetadata =
20
24
  useExternalMessageConverter.Metadata & {
21
25
  uiMessagesByParent?: Map<string, UIMessage[]>;
22
26
  messageTiming?: Record<string, MessageTiming>;
23
27
  };
24
28
 
29
+ export const createLangChainMetadataKey =
30
+ (): useExternalMessageConverter.GetMetadataKey<LangChainBaseMessage> =>
31
+ createExternalMessageMetadataKey<LangChainBaseMessage>([
32
+ {
33
+ select: (message, metadata) =>
34
+ message.id && getMessageType(message) === "ai"
35
+ ? (
36
+ metadata as LangChainMessageConverterMetadata
37
+ ).uiMessagesByParent?.get(message.id)
38
+ : undefined,
39
+ isEqual: (previous, current) =>
40
+ previous === current ||
41
+ (previous !== undefined &&
42
+ current !== undefined &&
43
+ shallowArrayEqual(
44
+ previous as readonly UIMessage[],
45
+ current as readonly UIMessage[],
46
+ )),
47
+ },
48
+ {
49
+ select: (message, metadata) =>
50
+ message.id && getMessageType(message) === "ai"
51
+ ? (metadata as LangChainMessageConverterMetadata).messageTiming?.[
52
+ message.id
53
+ ]
54
+ : undefined,
55
+ },
56
+ ]);
57
+
25
58
  const warnedMalformedMessages = new Set<string>();
26
59
  const warnOnceInDevelopment = (message: string) => {
27
60
  if (
@@ -1,7 +1,7 @@
1
1
  // @vitest-environment jsdom
2
2
 
3
3
  import { act, render, renderHook, waitFor } from "@testing-library/react";
4
- import { afterEach, describe, expect, it, vi } from "vitest";
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
5
  import { AssistantRuntimeProvider } from "@assistant-ui/core/react";
6
6
  import type {
7
7
  AssistantRuntime,
@@ -9,7 +9,11 @@ import type {
9
9
  RemoteThreadListAdapter,
10
10
  } from "@assistant-ui/core";
11
11
  import { useAui } from "@assistant-ui/store";
12
- import type { LangChainBaseMessage, LangChainToolCall } from "./types";
12
+ import type {
13
+ LangChainBaseMessage,
14
+ LangChainToolCall,
15
+ UIMessage,
16
+ } from "./types";
13
17
  import { startTransition, Suspense, type ReactNode } from "react";
14
18
  import {
15
19
  useLangChainRespond,
@@ -19,11 +23,13 @@ import {
19
23
  useLangChainSubmit,
20
24
  } from "./hooks";
21
25
 
22
- const { mockUseChannel, mockUseStream, streamController } = vi.hoisted(() => ({
23
- mockUseChannel: vi.fn((): unknown[] => []),
24
- mockUseStream: vi.fn(),
25
- streamController: Symbol("STREAM_CONTROLLER"),
26
- }));
26
+ const { conversionSpy, mockUseChannel, mockUseStream, streamController } =
27
+ vi.hoisted(() => ({
28
+ conversionSpy: vi.fn(),
29
+ mockUseChannel: vi.fn((): unknown[] => []),
30
+ mockUseStream: vi.fn(),
31
+ streamController: Symbol("STREAM_CONTROLLER"),
32
+ }));
27
33
 
28
34
  vi.mock("@langchain/react", () => ({
29
35
  STREAM_CONTROLLER: streamController,
@@ -31,9 +37,26 @@ vi.mock("@langchain/react", () => ({
31
37
  useStream: mockUseStream,
32
38
  }));
33
39
 
40
+ vi.mock("./convertMessages", async (importOriginal) => {
41
+ const original = await importOriginal<typeof import("./convertMessages")>();
42
+ return {
43
+ ...original,
44
+ convertLangChainBaseMessage: (
45
+ ...args: Parameters<typeof original.convertLangChainBaseMessage>
46
+ ) => {
47
+ conversionSpy(args[0].id);
48
+ return original.convertLangChainBaseMessage(...args);
49
+ },
50
+ };
51
+ });
52
+
34
53
  import { useStreamRuntime } from "./useStreamRuntime";
35
54
  import { settleOutsideAct } from "./tests/settleOutsideAct";
36
55
 
56
+ beforeEach(() => {
57
+ mockUseChannel.mockReturnValue([]);
58
+ });
59
+
37
60
  type MockStream = {
38
61
  messages: LangChainBaseMessage[];
39
62
  isLoading: boolean;
@@ -131,6 +154,45 @@ const getText = (aui: ReturnType<typeof useAui>) =>
131
154
  .join(""),
132
155
  );
133
156
 
157
+ describe("useStreamRuntime metadata cache", () => {
158
+ it("re-converts only the parent when a UI event changes", () => {
159
+ const messageCount = 1_000;
160
+ const messages = Array.from({ length: messageCount }, (_, index) =>
161
+ message(
162
+ `message-${index}`,
163
+ index % 2 === 0 ? "human" : "ai",
164
+ `Message ${index}`,
165
+ ),
166
+ );
167
+ const parentId = `message-${messageCount - 1}`;
168
+ const parentUI: UIMessage = {
169
+ type: "ui",
170
+ id: "ui-1",
171
+ name: "chart",
172
+ props: { value: 1 },
173
+ metadata: { message_id: parentId },
174
+ };
175
+ const stream = createMockStream(messages);
176
+ const { auiResult, rerender } = renderAui(stream);
177
+
178
+ expect(conversionSpy).toHaveBeenCalledTimes(messageCount);
179
+ conversionSpy.mockClear();
180
+
181
+ mockUseChannel.mockReturnValue([{ params: { data: parentUI } }] as never);
182
+ rerender();
183
+
184
+ expect(conversionSpy).toHaveBeenCalledOnce();
185
+ expect(conversionSpy).toHaveBeenCalledWith(parentId);
186
+ expect(
187
+ auiResult.current.thread.getState().messages.at(-1)?.content.at(-1),
188
+ ).toMatchObject({
189
+ type: "data",
190
+ name: "chart",
191
+ data: { value: 1 },
192
+ });
193
+ });
194
+ });
195
+
134
196
  const makeThreadListAdapter = (): RemoteThreadListAdapter => ({
135
197
  list: vi.fn(async () => ({
136
198
  threads: [
@@ -39,9 +39,11 @@ import type {
39
39
  import { getMessageModality, groupUIMessagesByParent } from "./converter";
40
40
  export { groupUIMessagesByParent } from "./converter";
41
41
  import {
42
+ createLangChainMetadataKey,
42
43
  convertLangChainBaseMessage,
43
44
  getMessageContent,
44
45
  getMessageType,
46
+ type LangChainMessageConverterMetadata,
45
47
  } from "./convertMessages";
46
48
  import {
47
49
  attachSubagentTranscripts,
@@ -214,22 +216,21 @@ const useStreamThreadRuntime = (
214
216
  uiMessagesByParent,
215
217
  );
216
218
 
217
- const convertWithUI = useMemo<
218
- useExternalMessageConverter.Callback<LangChainBaseMessage>
219
- >(
220
- () => (message, metadata) =>
221
- convertLangChainBaseMessage(message, {
222
- ...metadata,
223
- uiMessagesByParent,
224
- messageTiming,
225
- }),
219
+ const [getConverterMetadataKey] = useState(createLangChainMetadataKey);
220
+ const converterMetadata = useMemo<LangChainMessageConverterMetadata>(
221
+ () => ({
222
+ uiMessagesByParent: uiMessagesByParent,
223
+ messageTiming,
224
+ }),
226
225
  [uiMessagesByParent, messageTiming],
227
226
  );
228
227
 
229
228
  const threadMessages = useExternalMessageConverter({
230
- callback: convertWithUI,
229
+ callback: convertLangChainBaseMessage,
231
230
  messages: visibleMessages,
232
231
  isRunning: effectiveIsRunning,
232
+ metadata: converterMetadata,
233
+ getMetadataKey: getConverterMetadataKey,
233
234
  });
234
235
  const [memo] = useState(createAttachMemo);
235
236
  const messagesWithTranscripts = useMemo(