@assistant-ui/react-langchain 0.0.35 → 0.0.36

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.
Files changed (36) hide show
  1. package/dist/LangChainThreadController.d.ts +9 -0
  2. package/dist/LangChainThreadController.d.ts.map +1 -0
  3. package/dist/LangChainThreadController.js +21 -0
  4. package/dist/LangChainThreadController.js.map +1 -0
  5. package/dist/convertMessages.d.ts.map +1 -1
  6. package/dist/convertMessages.js +6 -11
  7. package/dist/convertMessages.js.map +1 -1
  8. package/dist/converter.d.ts +24 -1
  9. package/dist/converter.d.ts.map +1 -1
  10. package/dist/converter.js +84 -18
  11. package/dist/converter.js.map +1 -1
  12. package/dist/langChainThreadState.d.ts +54 -0
  13. package/dist/langChainThreadState.d.ts.map +1 -0
  14. package/dist/langChainThreadState.js +135 -0
  15. package/dist/langChainThreadState.js.map +1 -0
  16. package/dist/sdkIdentity.js +1 -1
  17. package/dist/types.d.ts +9 -0
  18. package/dist/types.d.ts.map +1 -1
  19. package/dist/useStreamRuntime.d.ts +3 -2
  20. package/dist/useStreamRuntime.d.ts.map +1 -1
  21. package/dist/useStreamRuntime.js +152 -139
  22. package/dist/useStreamRuntime.js.map +1 -1
  23. package/dist/useSubagentTranscripts.js +1 -1
  24. package/package.json +8 -8
  25. package/src/LangChainThreadController.ts +24 -0
  26. package/src/convertMessages.test.ts +23 -0
  27. package/src/convertMessages.ts +9 -16
  28. package/src/converter.test.ts +322 -0
  29. package/src/converter.ts +138 -27
  30. package/src/getPendingToolCalls.test.ts +35 -0
  31. package/src/langChainThreadState.test.ts +203 -0
  32. package/src/langChainThreadState.ts +239 -0
  33. package/src/types.ts +9 -0
  34. package/src/useStreamRuntime.cloud-options.test.tsx +61 -0
  35. package/src/useStreamRuntime.test.tsx +227 -1
  36. package/src/useStreamRuntime.ts +210 -239
@@ -0,0 +1,9 @@
1
+ import { type LangChainThreadAction } from "./langChainThreadState.js";
2
+ export declare class LangChainThreadController {
3
+ private state;
4
+ private listeners;
5
+ getState: () => import("./langChainThreadState.js").LangChainThreadState;
6
+ subscribe: (listener: () => void) => () => boolean;
7
+ dispatch: (action: LangChainThreadAction) => void;
8
+ }
9
+ //# sourceMappingURL=LangChainThreadController.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LangChainThreadController.d.ts","sourceRoot":"","sources":["../src/LangChainThreadController.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,qBAAqB,EAC3B,kCAA+B;AAEhC,qBAAa,yBAAyB;IACpC,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,SAAS,CAAyB;IAEnC,QAAQ,iEAAoB;IAE5B,SAAS,GAAI,UAAU,MAAM,IAAI,mBAGtC;IAEK,QAAQ,GAAI,QAAQ,qBAAqB,UAK9C;CACH"}
@@ -0,0 +1,21 @@
1
+ import { createLangChainThreadState, reduceLangChainThreadState } from "./langChainThreadState.js";
2
+ //#region src/LangChainThreadController.ts
3
+ var LangChainThreadController = class {
4
+ state = createLangChainThreadState();
5
+ listeners = /* @__PURE__ */ new Set();
6
+ getState = () => this.state;
7
+ subscribe = (listener) => {
8
+ this.listeners.add(listener);
9
+ return () => this.listeners.delete(listener);
10
+ };
11
+ dispatch = (action) => {
12
+ const nextState = reduceLangChainThreadState(this.state, action);
13
+ if (nextState === this.state) return;
14
+ this.state = nextState;
15
+ for (const listener of this.listeners) listener();
16
+ };
17
+ };
18
+ //#endregion
19
+ export { LangChainThreadController };
20
+
21
+ //# sourceMappingURL=LangChainThreadController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LangChainThreadController.js","names":[],"sources":["../src/LangChainThreadController.ts"],"sourcesContent":["import {\n createLangChainThreadState,\n reduceLangChainThreadState,\n type LangChainThreadAction,\n} from \"./langChainThreadState\";\n\nexport class LangChainThreadController {\n private state = createLangChainThreadState();\n private listeners = new Set<() => void>();\n\n public getState = () => this.state;\n\n public subscribe = (listener: () => void) => {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n public dispatch = (action: LangChainThreadAction) => {\n const nextState = reduceLangChainThreadState(this.state, action);\n if (nextState === this.state) return;\n this.state = nextState;\n for (const listener of this.listeners) listener();\n };\n}\n"],"mappings":";;AAMA,IAAa,4BAAb,MAAuC;CACrC,QAAgB,2BAA2B;CAC3C,4BAAoB,IAAI,IAAgB;CAExC,iBAAwB,KAAK;CAE7B,aAAoB,aAAyB;EAC3C,KAAK,UAAU,IAAI,QAAQ;EAC3B,aAAa,KAAK,UAAU,OAAO,QAAQ;CAC7C;CAEA,YAAmB,WAAkC;EACnD,MAAM,YAAY,2BAA2B,KAAK,OAAO,MAAM;EAC/D,IAAI,cAAc,KAAK,OAAO;EAC9B,KAAK,QAAQ;EACb,KAAK,MAAM,YAAY,KAAK,WAAW,SAAS;CAClD;AACF"}
@@ -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;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
+ {"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;AAc5E,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;AAoDF,eAAO,MAAM,2BAA2B,GACtC,SAAS,oBAAoB,EAC7B,WAAU,iCAAsC,KAC/C,2BAA2B,CAAC,OA4G9B,CAAC;AAEF,OAAO,EAAE,iBAAiB,EAAE,uBAAoB"}
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { convertLangChainContentBlock, getCustomMetadata, getMessageContent, getMessageModality, uiMessageToDataPart, withAudioTranscript } from "./converter.js";
2
+ import { convertLangChainContentBlock, getCustomMetadata, getMessageContent, getMessageModality, normalizeToolCallArgs, uiMessageToDataPart, withAudioTranscript } from "./converter.js";
3
3
  import { createExternalMessageMetadataKey, shallowArrayEqual } from "@assistant-ui/core/internal";
4
4
  //#region src/convertMessages.ts
5
5
  const createLangChainMetadataKey = () => createExternalMessageMetadataKey([{
@@ -25,15 +25,6 @@ const contentBlocks = (content) => {
25
25
  warnOnceInDevelopment(`Ignoring message content that is neither a string nor an array: ${typeof content}`);
26
26
  return [];
27
27
  };
28
- const normalizeToolCallArgs = (args) => {
29
- if (typeof args !== "object" || args === null || Array.isArray(args)) return {};
30
- try {
31
- const prototype = Object.getPrototypeOf(args);
32
- return prototype === Object.prototype || prototype === null ? args : {};
33
- } catch {
34
- return {};
35
- }
36
- };
37
28
  const toolCallArgs = (value) => {
38
29
  const args = normalizeToolCallArgs(value);
39
30
  try {
@@ -57,7 +48,11 @@ const contentToParts = (content) => {
57
48
  type: "text",
58
49
  text: content
59
50
  }];
60
- return contentBlocks(content).map(convertLangChainContentBlock).filter((part) => part !== null && part !== void 0);
51
+ return contentBlocks(content).map((block) => {
52
+ const part = convertLangChainContentBlock(block);
53
+ if (part === void 0) warnOnceInDevelopment(`Dropped an unrepresentable message part of type: ${block.type}`);
54
+ return part;
55
+ }).filter((part) => part !== null && part !== void 0);
61
56
  };
62
57
  const getStringContent = (content) => {
63
58
  if (typeof content === "string") return content;
@@ -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 {\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":"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"}
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 normalizeToolCallArgs,\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 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((block) => {\n const part = convertLangChainContentBlock(block);\n if (part === undefined)\n warnOnceInDevelopment(\n `Dropped an unrepresentable message part of type: ${block.type}`,\n );\n return part;\n })\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":"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"}
@@ -1,5 +1,6 @@
1
1
  import type { AppendMessage, DataMessagePart, MessageModality, ThreadAssistantMessage, ThreadUserMessage } from "@assistant-ui/core";
2
2
  import type { StreamingTimingAccessors } from "@assistant-ui/core/react";
3
+ import type { ReadonlyJSONObject } from "assistant-stream/utils";
3
4
  /** Known content block types from @langchain/core messages. */
4
5
  export type LangChainContentBlock = {
5
6
  type: "text";
@@ -54,8 +55,30 @@ export type LangChainContentBlock = {
54
55
  } | {
55
56
  type: "tool_use" | "input_json_delta";
56
57
  };
58
+ /**
59
+ * One block covers both multimodal vocabularies: the standard content blocks
60
+ * key their payload off a camelCase JavaScript field or snake_case Python
61
+ * field, while the legacy data content blocks use a `source_type` discriminator.
62
+ */
63
+ type LangChainMediaBlock = {
64
+ type: "image" | "video" | "audio" | "file" | "text-plain";
65
+ mimeType?: string;
66
+ mime_type?: string;
67
+ data?: string | Uint8Array;
68
+ base64?: string | Uint8Array;
69
+ url?: string;
70
+ fileId?: string;
71
+ file_id?: string;
72
+ id?: string;
73
+ text?: string;
74
+ source_type?: "base64" | "url" | "id" | "text";
75
+ metadata?: {
76
+ filename?: string;
77
+ };
78
+ };
57
79
  type ConvertedContentPart = ThreadUserMessage["content"][number] | ThreadAssistantMessage["content"][number];
58
- export declare const convertLangChainContentBlock: (part: LangChainContentBlock) => ConvertedContentPart | null | undefined;
80
+ export declare const normalizeToolCallArgs: (args: unknown) => ReadonlyJSONObject;
81
+ export declare const convertLangChainContentBlock: (part: LangChainContentBlock | LangChainMediaBlock) => ConvertedContentPart | null | undefined;
59
82
  /**
60
83
  * Audio output arrives outside the content array: providers leave `content`
61
84
  * empty and put the spoken text in `additional_kwargs.audio.transcript`. The
@@ -1 +1 @@
1
- {"version":3,"file":"converter.d.ts","sourceRoot":"","sources":["../src/converter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"converter.d.ts","sourceRoot":"","sources":["../src/converter.ts"],"names":[],"mappings":"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"}
package/dist/converter.js CHANGED
@@ -1,5 +1,83 @@
1
1
  import { parseDataUrl, resolveFilePartSource } from "@assistant-ui/core/internal";
2
2
  //#region src/converter.ts
3
+ /**
4
+ * `id` names the block, not the payload, in the standard content blocks, so it
5
+ * only identifies a file under the legacy `source_type: "id"` discriminator.
6
+ */
7
+ const resolveMediaSource = (part) => {
8
+ switch (part.source_type) {
9
+ case "url": return typeof part.url === "string" ? {
10
+ data: part.url,
11
+ sourceType: "url"
12
+ } : void 0;
13
+ case "id": return typeof part.id === "string" ? {
14
+ data: part.id,
15
+ sourceType: "id"
16
+ } : void 0;
17
+ case "base64": {
18
+ const data = part.data ?? part.base64;
19
+ return typeof data === "string" ? { data } : void 0;
20
+ }
21
+ }
22
+ if (typeof part.url === "string") return {
23
+ data: part.url,
24
+ sourceType: "url"
25
+ };
26
+ const fileId = part.fileId ?? part.file_id;
27
+ if (typeof fileId === "string") return {
28
+ data: fileId,
29
+ sourceType: "id"
30
+ };
31
+ const data = part.data ?? part.base64;
32
+ if (typeof data === "string") return { data };
33
+ if (part.type === "text-plain" && typeof part.text === "string") return { data: textToBase64(part.text) };
34
+ };
35
+ const textToBase64 = (text) => {
36
+ const bytes = new TextEncoder().encode(text);
37
+ let binary = "";
38
+ for (let i = 0; i < bytes.length; i += 32768) binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
39
+ return btoa(binary);
40
+ };
41
+ const resolveMediaMimeType = (part) => typeof part.mimeType === "string" ? part.mimeType : typeof part.mime_type === "string" ? part.mime_type : void 0;
42
+ const defaultMediaFilename = (type, mimeType) => {
43
+ if (type !== "audio" && type !== "video") return "file";
44
+ const subtype = mimeType.startsWith(`${type}/`) ? mimeType.slice(type.length + 1) : void 0;
45
+ return subtype ? `${type}.${subtype}` : type;
46
+ };
47
+ const resolveImage = (source, mimeType) => {
48
+ if (source.sourceType === "id") return void 0;
49
+ if (source.sourceType === "url" || /^data:/i.test(source.data)) return source.data;
50
+ return mimeType ? `data:${mimeType};base64,${source.data}` : void 0;
51
+ };
52
+ const convertMediaBlock = (part) => {
53
+ const source = resolveMediaSource(part);
54
+ if (!source) return void 0;
55
+ const mimeType = resolveMediaMimeType(part);
56
+ if (part.type === "image") {
57
+ const image = resolveImage(source, mimeType);
58
+ if (image) return {
59
+ type: "image",
60
+ image
61
+ };
62
+ }
63
+ const resolvedMimeType = mimeType ?? (part.type === "text-plain" ? "text/plain" : "application/octet-stream");
64
+ return {
65
+ type: "file",
66
+ filename: part.metadata?.filename ?? defaultMediaFilename(part.type, resolvedMimeType),
67
+ data: source.data,
68
+ mimeType: resolvedMimeType,
69
+ ...source.sourceType && { sourceType: source.sourceType }
70
+ };
71
+ };
72
+ const normalizeToolCallArgs = (args) => {
73
+ if (typeof args !== "object" || args === null || Array.isArray(args)) return {};
74
+ try {
75
+ const prototype = Object.getPrototypeOf(args);
76
+ return prototype === Object.prototype || prototype === null ? args : {};
77
+ } catch {
78
+ return {};
79
+ }
80
+ };
3
81
  const convertLangChainContentBlock = (part) => {
4
82
  switch (part.type) {
5
83
  case "text":
@@ -15,23 +93,11 @@ const convertLangChainContentBlock = (part) => {
15
93
  image
16
94
  };
17
95
  }
18
- case "file": return {
19
- type: "file",
20
- filename: part.metadata?.filename ?? "file",
21
- data: part.source_type === "url" ? part.url : part.source_type === "id" ? part.id : part.data,
22
- mimeType: part.mime_type ?? "application/octet-stream",
23
- ...(part.source_type === "url" || part.source_type === "id") && { sourceType: part.source_type }
24
- };
25
- case "audio": {
26
- const mimeType = part.mime_type ?? "application/octet-stream";
27
- const subtype = mimeType.startsWith("audio/") ? mimeType.slice(6) : void 0;
28
- return {
29
- type: "file",
30
- filename: subtype ? `audio.${subtype}` : "audio",
31
- data: part.data,
32
- mimeType
33
- };
34
- }
96
+ case "image":
97
+ case "video":
98
+ case "audio":
99
+ case "file":
100
+ case "text-plain": return convertMediaBlock(part);
35
101
  case "thinking": return hasVisibleText(part.thinking) ? {
36
102
  type: "reasoning",
37
103
  text: part.thinking
@@ -217,6 +283,6 @@ const groupUIMessagesByParent = (value) => {
217
283
  return map;
218
284
  };
219
285
  //#endregion
220
- export { convertLangChainContentBlock, createLangChainStreamingTimingAccessors, getCustomMetadata, getMessageContent, getMessageModality, getUIMessageParentId, groupUIMessagesByParent, uiMessageToDataPart, withAudioTranscript };
286
+ export { convertLangChainContentBlock, createLangChainStreamingTimingAccessors, getCustomMetadata, getMessageContent, getMessageModality, getUIMessageParentId, groupUIMessagesByParent, normalizeToolCallArgs, uiMessageToDataPart, withAudioTranscript };
221
287
 
222
288
  //# sourceMappingURL=converter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"converter.js","names":["_exhaustiveCheck"],"sources":["../src/converter.ts"],"sourcesContent":["import type {\n AppendMessage,\n DataMessagePart,\n MessageModality,\n ThreadAssistantMessage,\n ThreadUserMessage,\n} from \"@assistant-ui/core\";\nimport {\n parseDataUrl,\n resolveFilePartSource,\n} from \"@assistant-ui/core/internal\";\nimport type { StreamingTimingAccessors } from \"@assistant-ui/core/react\";\n\n/** Known content block types from @langchain/core messages. */\nexport type LangChainContentBlock =\n | { type: \"text\"; text: string }\n | { type: \"text_delta\"; text: string }\n | { type: \"image_url\"; image_url: string | { url?: string } }\n | { type: \"thinking\"; thinking: string }\n | {\n type: \"reasoning\";\n summary?: Array<{ type: \"summary_text\"; text?: string }>;\n reasoning?: string;\n }\n | {\n type: \"file\";\n data: string;\n mime_type: string;\n source_type?: \"base64\";\n metadata?: { filename?: string };\n }\n | {\n type: \"file\";\n url: string;\n mime_type?: string;\n source_type: \"url\";\n metadata?: { filename?: string };\n }\n | {\n type: \"file\";\n id: string;\n mime_type?: string;\n source_type: \"id\";\n metadata?: { filename?: string };\n }\n | {\n type: \"audio\";\n data: string;\n mime_type: string;\n source_type: \"base64\";\n }\n | { type: \"tool_use\" | \"input_json_delta\" };\n\ntype ConvertedContentPart =\n | ThreadUserMessage[\"content\"][number]\n | ThreadAssistantMessage[\"content\"][number];\n\nexport const convertLangChainContentBlock = (\n part: LangChainContentBlock,\n): ConvertedContentPart | null | undefined => {\n const type = part.type;\n switch (type) {\n case \"text\":\n case \"text_delta\":\n return {\n type: \"text\" as const,\n text: typeof part.text === \"string\" ? part.text : \"\",\n };\n case \"image_url\": {\n const image =\n typeof part.image_url === \"string\"\n ? part.image_url\n : part.image_url?.url;\n if (!image) return null;\n return { type: \"image\" as const, image };\n }\n case \"file\":\n return {\n type: \"file\" as const,\n filename: part.metadata?.filename ?? \"file\",\n data:\n part.source_type === \"url\"\n ? part.url\n : part.source_type === \"id\"\n ? part.id\n : part.data,\n mimeType: part.mime_type ?? \"application/octet-stream\",\n ...((part.source_type === \"url\" || part.source_type === \"id\") && {\n sourceType: part.source_type,\n }),\n };\n case \"audio\": {\n const mimeType = part.mime_type ?? \"application/octet-stream\";\n const subtype = mimeType.startsWith(\"audio/\")\n ? mimeType.slice(\"audio/\".length)\n : undefined;\n return {\n type: \"file\" as const,\n filename: subtype ? `audio.${subtype}` : \"audio\",\n data: part.data,\n mimeType,\n };\n }\n case \"thinking\":\n return hasVisibleText(part.thinking)\n ? { type: \"reasoning\" as const, text: part.thinking }\n : null;\n case \"reasoning\": {\n const text = getReasoningText(part);\n return text ? { type: \"reasoning\" as const, text } : null;\n }\n case \"tool_use\":\n case \"input_json_delta\":\n return null;\n default:\n return undefined;\n }\n};\n\nconst hasVisibleText = (text: unknown): boolean =>\n typeof text === \"string\" && text.trim() !== \"\";\n\n/**\n * Audio output arrives outside the content array: providers leave `content`\n * empty and put the spoken text in `additional_kwargs.audio.transcript`. The\n * audio bytes stay behind because no provider reports their media type, and a\n * streamed response carries raw PCM rather than a playable file.\n */\nexport const withAudioTranscript = <T extends { type: string; text?: unknown }>(\n parts: readonly T[],\n additionalKwargs: Record<string, unknown> | undefined,\n): readonly (T | { type: \"text\"; text: string })[] => {\n const audio = additionalKwargs?.audio as { transcript?: unknown } | undefined;\n const transcript = audio?.transcript;\n if (typeof transcript !== \"string\" || !hasVisibleText(transcript))\n return parts;\n if (parts.some((part) => part.type === \"text\" && hasVisibleText(part.text)))\n return parts;\n return [\n ...parts.filter((part) => part.type !== \"text\"),\n { type: \"text\" as const, text: transcript },\n ];\n};\n\nexport const getCustomMetadata = (\n additionalKwargs: Record<string, unknown> | undefined,\n): Record<string, unknown> =>\n (additionalKwargs?.metadata as Record<string, unknown>) ?? {};\n\nexport const getMessageModality = (\n additionalKwargs: Record<string, unknown> | undefined,\n): MessageModality | undefined =>\n additionalKwargs?.modality === \"voice\" ? \"voice\" : undefined;\n\nexport const uiMessageToDataPart = <\n TUIMessage extends { name: string; props: Record<string, unknown> },\n>(\n ui: TUIMessage,\n): DataMessagePart => ({\n type: \"data\",\n name: ui.name,\n data: ui.props,\n});\n\n/**\n * Audio media types that reach a provider's audio input through the LangChain\n * `audio` block. langchain-core derives OpenAI's `input_audio.format` by\n * splitting `mime_type` on `/`, and that format is a wav-or-mp3 enum, so\n * `audio/mpeg` passes the converter and is rejected at the provider.\n */\nconst audioBlockMimeTypes = new Map<string, \"audio/mp3\" | \"audio/wav\">([\n [\"audio/mp3\", \"audio/mp3\"],\n [\"audio/mpeg\", \"audio/mp3\"],\n [\"audio/wav\", \"audio/wav\"],\n [\"audio/wave\", \"audio/wav\"],\n [\"audio/x-wav\", \"audio/wav\"],\n]);\n\nexport const getMessageContent = (msg: AppendMessage) => {\n const allContent = [\n ...msg.content,\n ...(msg.attachments?.flatMap((a) => a.content) ?? []),\n ];\n\n const hasNonText = allContent.some(\n (part) =>\n part.type === \"file\" || part.type === \"image\" || part.type === \"audio\",\n );\n const hasText = allContent.some((part) => part.type === \"text\");\n if (hasNonText && !hasText) {\n allContent.unshift({ type: \"text\", text: \" \" });\n }\n\n const content = allContent.flatMap((part) => {\n const type = part.type;\n switch (type) {\n case \"text\":\n return { type: \"text\" as const, text: part.text };\n case \"image\":\n return { type: \"image_url\" as const, image_url: { url: part.image } };\n case \"file\": {\n const metadata = { filename: part.filename ?? \"file\" };\n if (part.sourceType === \"id\") {\n return {\n type: \"file\" as const,\n id: part.data,\n mime_type: part.mimeType,\n filename: metadata.filename,\n metadata,\n source_type: \"id\" as const,\n };\n }\n const source = resolveFilePartSource(part);\n if (source.kind === \"url\") {\n return {\n type: \"file\" as const,\n url: source.url,\n mime_type: part.mimeType,\n filename: metadata.filename,\n metadata,\n source_type: \"url\" as const,\n };\n }\n const audioMimeType = audioBlockMimeTypes.get(\n source.mimeType.toLowerCase(),\n );\n if (audioMimeType) {\n return {\n type: \"audio\" as const,\n data: source.data,\n mime_type: audioMimeType,\n source_type: \"base64\" as const,\n };\n }\n return {\n type: \"file\" as const,\n data: source.data,\n mime_type: source.mimeType,\n filename: metadata.filename,\n metadata,\n source_type: \"base64\" as const,\n };\n }\n case \"audio\": {\n const parsed = parseDataUrl(part.audio.data);\n return {\n type: \"audio\" as const,\n data: parsed?.data ?? part.audio.data,\n mime_type: `audio/${part.audio.format}`,\n source_type: \"base64\" as const,\n };\n }\n case \"data\":\n return [];\n case \"tool-call\":\n throw new Error(\"Tool call appends are not supported.\");\n default: {\n const _exhaustiveCheck: \"reasoning\" | \"source\" | \"generative-ui\" = type;\n throw new Error(\n `Unsupported append message part type: ${_exhaustiveCheck}`,\n );\n }\n }\n });\n\n if (content.length === 1 && content[0]?.type === \"text\") {\n return content[0].text ?? \"\";\n }\n return content;\n};\n\nconst getReasoningText = (part: {\n readonly summary?: ReadonlyArray<{ readonly text?: string }>;\n readonly reasoning?: string;\n}): string => {\n const summary = part.summary?.map((s) => s?.text ?? \"\").join(\"\\n\\n\\n\") ?? \"\";\n if (hasVisibleText(summary)) return summary;\n const reasoning = part.reasoning ?? \"\";\n return hasVisibleText(reasoning) ? reasoning : \"\";\n};\n\nexport const createLangChainStreamingTimingAccessors = <\n TMessage extends {\n id?: string | undefined;\n content?: unknown;\n tool_calls?: readonly unknown[] | undefined;\n },\n>(\n getType: (message: TMessage) => string,\n): StreamingTimingAccessors<TMessage> => {\n const findAiMessage = (\n messages: readonly TMessage[],\n messageId: string,\n ): TMessage | undefined =>\n messages.find(\n (message) => getType(message) === \"ai\" && message.id === messageId,\n );\n\n const getTextLength = (\n messages: readonly TMessage[],\n messageId: string,\n ): number => {\n const message = findAiMessage(messages, messageId);\n if (!message) return 0;\n const content = message.content;\n if (typeof content === \"string\") return content.length;\n if (!Array.isArray(content)) return 0;\n let len = 0;\n for (const part of content as readonly LangChainContentBlock[]) {\n if (typeof part !== \"object\" || part === null) continue;\n switch (part.type) {\n case \"text\":\n case \"text_delta\":\n if (typeof part.text === \"string\") len += part.text.length;\n break;\n case \"thinking\":\n if (hasVisibleText(part.thinking)) len += part.thinking.length;\n break;\n case \"reasoning\":\n len += getReasoningText(part).length;\n break;\n }\n }\n return len;\n };\n\n const getToolCallCount = (\n messages: readonly TMessage[],\n messageId: string,\n ): number => findAiMessage(messages, messageId)?.tool_calls?.length ?? 0;\n\n const getAssistantMessageId = (\n messages: readonly TMessage[],\n ): string | undefined => {\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages[i];\n if (message && getType(message) === \"ai\" && message.id) return message.id;\n }\n return undefined;\n };\n\n return {\n getAssistantMessageId,\n getTextLength,\n getToolCallCount,\n };\n};\n\n/**\n * Resolve the assistant message a `UIMessage` belongs to: the parent id comes\n * from `metadata.message_id` (Python SDK) or `metadata.id` (JS SDK).\n */\nexport const getUIMessageParentId = (ui: {\n metadata?: { message_id?: string; id?: string } | undefined;\n}): string | undefined => ui.metadata?.message_id ?? ui.metadata?.id;\n\n/**\n * Group the graph's accumulated `UIMessage`s by the assistant message they\n * belong to. Non-array state and entries without a parent link are dropped.\n */\nexport const groupUIMessagesByParent = <\n T extends {\n metadata?: { message_id?: string; id?: string } | undefined;\n },\n>(\n value: unknown,\n): Map<string, T[]> => {\n const map = new Map<string, T[]>();\n if (!Array.isArray(value)) return map;\n for (const ui of value as T[]) {\n const parentId = getUIMessageParentId(ui);\n if (!parentId) continue;\n const existing = map.get(parentId);\n if (existing) {\n existing.push(ui);\n } else {\n map.set(parentId, [ui]);\n }\n }\n return map;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"converter.js","names":["_exhaustiveCheck"],"sources":["../src/converter.ts"],"sourcesContent":["import type {\n AppendMessage,\n DataMessagePart,\n MessageModality,\n ThreadAssistantMessage,\n ThreadUserMessage,\n} from \"@assistant-ui/core\";\nimport {\n parseDataUrl,\n resolveFilePartSource,\n} from \"@assistant-ui/core/internal\";\nimport type { StreamingTimingAccessors } from \"@assistant-ui/core/react\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\n\n/** Known content block types from @langchain/core messages. */\nexport type LangChainContentBlock =\n | { type: \"text\"; text: string }\n | { type: \"text_delta\"; text: string }\n | { type: \"image_url\"; image_url: string | { url?: string } }\n | { type: \"thinking\"; thinking: string }\n | {\n type: \"reasoning\";\n summary?: Array<{ type: \"summary_text\"; text?: string }>;\n reasoning?: string;\n }\n | {\n type: \"file\";\n data: string;\n mime_type: string;\n source_type?: \"base64\";\n metadata?: { filename?: string };\n }\n | {\n type: \"file\";\n url: string;\n mime_type?: string;\n source_type: \"url\";\n metadata?: { filename?: string };\n }\n | {\n type: \"file\";\n id: string;\n mime_type?: string;\n source_type: \"id\";\n metadata?: { filename?: string };\n }\n | {\n type: \"audio\";\n data: string;\n mime_type: string;\n source_type: \"base64\";\n }\n | { type: \"tool_use\" | \"input_json_delta\" };\n\n/**\n * One block covers both multimodal vocabularies: the standard content blocks\n * key their payload off a camelCase JavaScript field or snake_case Python\n * field, while the legacy data content blocks use a `source_type` discriminator.\n */\ntype LangChainMediaBlock = {\n type: \"image\" | \"video\" | \"audio\" | \"file\" | \"text-plain\";\n mimeType?: string;\n mime_type?: string;\n data?: string | Uint8Array;\n base64?: string | Uint8Array;\n url?: string;\n fileId?: string;\n file_id?: string;\n id?: string;\n text?: string;\n source_type?: \"base64\" | \"url\" | \"id\" | \"text\";\n metadata?: { filename?: string };\n};\n\ntype ConvertedContentPart =\n | ThreadUserMessage[\"content\"][number]\n | ThreadAssistantMessage[\"content\"][number];\n\ntype MediaSource = { data: string; sourceType?: \"url\" | \"id\" };\n\n/**\n * `id` names the block, not the payload, in the standard content blocks, so it\n * only identifies a file under the legacy `source_type: \"id\"` discriminator.\n */\nconst resolveMediaSource = (\n part: LangChainMediaBlock,\n): MediaSource | undefined => {\n switch (part.source_type) {\n case \"url\":\n return typeof part.url === \"string\"\n ? { data: part.url, sourceType: \"url\" }\n : undefined;\n case \"id\":\n return typeof part.id === \"string\"\n ? { data: part.id, sourceType: \"id\" }\n : undefined;\n case \"base64\": {\n const data = part.data ?? part.base64;\n return typeof data === \"string\" ? { data } : undefined;\n }\n }\n if (typeof part.url === \"string\")\n return { data: part.url, sourceType: \"url\" };\n const fileId = part.fileId ?? part.file_id;\n if (typeof fileId === \"string\") return { data: fileId, sourceType: \"id\" };\n const data = part.data ?? part.base64;\n if (typeof data === \"string\") return { data };\n if (part.type === \"text-plain\" && typeof part.text === \"string\")\n return { data: textToBase64(part.text) };\n return undefined;\n};\n\nconst textToBase64 = (text: string): string => {\n const bytes = new TextEncoder().encode(text);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i += 0x8000) {\n binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000));\n }\n return btoa(binary);\n};\n\nconst resolveMediaMimeType = (part: LangChainMediaBlock): string | undefined =>\n typeof part.mimeType === \"string\"\n ? part.mimeType\n : typeof part.mime_type === \"string\"\n ? part.mime_type\n : undefined;\n\nconst defaultMediaFilename = (type: string, mimeType: string): string => {\n if (type !== \"audio\" && type !== \"video\") return \"file\";\n const subtype = mimeType.startsWith(`${type}/`)\n ? mimeType.slice(type.length + 1)\n : undefined;\n return subtype ? `${type}.${subtype}` : type;\n};\n\nconst resolveImage = (\n source: MediaSource,\n mimeType: string | undefined,\n): string | undefined => {\n if (source.sourceType === \"id\") return undefined;\n // `parseDataUrl` only matches the base64 form, so a percent-encoded data URL\n // would otherwise be wrapped in a second base64 envelope.\n if (source.sourceType === \"url\" || /^data:/i.test(source.data))\n return source.data;\n return mimeType ? `data:${mimeType};base64,${source.data}` : undefined;\n};\n\nconst convertMediaBlock = (\n part: LangChainMediaBlock,\n): ConvertedContentPart | undefined => {\n const source = resolveMediaSource(part);\n if (!source) return undefined;\n\n const mimeType = resolveMediaMimeType(part);\n if (part.type === \"image\") {\n const image = resolveImage(source, mimeType);\n if (image) return { type: \"image\" as const, image };\n }\n\n const resolvedMimeType =\n mimeType ??\n (part.type === \"text-plain\" ? \"text/plain\" : \"application/octet-stream\");\n return {\n type: \"file\" as const,\n filename:\n part.metadata?.filename ??\n defaultMediaFilename(part.type, resolvedMimeType),\n data: source.data,\n mimeType: resolvedMimeType,\n ...(source.sourceType && { sourceType: source.sourceType }),\n };\n};\n\nexport const 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\nexport const convertLangChainContentBlock = (\n part: LangChainContentBlock | LangChainMediaBlock,\n): ConvertedContentPart | null | undefined => {\n const type = part.type;\n switch (type) {\n case \"text\":\n case \"text_delta\":\n return {\n type: \"text\" as const,\n text: typeof part.text === \"string\" ? part.text : \"\",\n };\n case \"image_url\": {\n const image =\n typeof part.image_url === \"string\"\n ? part.image_url\n : part.image_url?.url;\n if (!image) return null;\n return { type: \"image\" as const, image };\n }\n case \"image\":\n case \"video\":\n case \"audio\":\n case \"file\":\n case \"text-plain\":\n return convertMediaBlock(part);\n case \"thinking\":\n return hasVisibleText(part.thinking)\n ? { type: \"reasoning\" as const, text: part.thinking }\n : null;\n case \"reasoning\": {\n const text = getReasoningText(part);\n return text ? { type: \"reasoning\" as const, text } : null;\n }\n case \"tool_use\":\n case \"input_json_delta\":\n return null;\n default:\n return undefined;\n }\n};\n\nconst hasVisibleText = (text: unknown): boolean =>\n typeof text === \"string\" && text.trim() !== \"\";\n\n/**\n * Audio output arrives outside the content array: providers leave `content`\n * empty and put the spoken text in `additional_kwargs.audio.transcript`. The\n * audio bytes stay behind because no provider reports their media type, and a\n * streamed response carries raw PCM rather than a playable file.\n */\nexport const withAudioTranscript = <T extends { type: string; text?: unknown }>(\n parts: readonly T[],\n additionalKwargs: Record<string, unknown> | undefined,\n): readonly (T | { type: \"text\"; text: string })[] => {\n const audio = additionalKwargs?.audio as { transcript?: unknown } | undefined;\n const transcript = audio?.transcript;\n if (typeof transcript !== \"string\" || !hasVisibleText(transcript))\n return parts;\n if (parts.some((part) => part.type === \"text\" && hasVisibleText(part.text)))\n return parts;\n return [\n ...parts.filter((part) => part.type !== \"text\"),\n { type: \"text\" as const, text: transcript },\n ];\n};\n\nexport const getCustomMetadata = (\n additionalKwargs: Record<string, unknown> | undefined,\n): Record<string, unknown> =>\n (additionalKwargs?.metadata as Record<string, unknown>) ?? {};\n\nexport const getMessageModality = (\n additionalKwargs: Record<string, unknown> | undefined,\n): MessageModality | undefined =>\n additionalKwargs?.modality === \"voice\" ? \"voice\" : undefined;\n\nexport const uiMessageToDataPart = <\n TUIMessage extends { name: string; props: Record<string, unknown> },\n>(\n ui: TUIMessage,\n): DataMessagePart => ({\n type: \"data\",\n name: ui.name,\n data: ui.props,\n});\n\n/**\n * Audio media types that reach a provider's audio input through the LangChain\n * `audio` block. langchain-core derives OpenAI's `input_audio.format` by\n * splitting `mime_type` on `/`, and that format is a wav-or-mp3 enum, so\n * `audio/mpeg` passes the converter and is rejected at the provider.\n */\nconst audioBlockMimeTypes = new Map<string, \"audio/mp3\" | \"audio/wav\">([\n [\"audio/mp3\", \"audio/mp3\"],\n [\"audio/mpeg\", \"audio/mp3\"],\n [\"audio/wav\", \"audio/wav\"],\n [\"audio/wave\", \"audio/wav\"],\n [\"audio/x-wav\", \"audio/wav\"],\n]);\n\nexport const getMessageContent = (msg: AppendMessage) => {\n const allContent = [\n ...msg.content,\n ...(msg.attachments?.flatMap((a) => a.content) ?? []),\n ];\n\n const hasNonText = allContent.some(\n (part) =>\n part.type === \"file\" || part.type === \"image\" || part.type === \"audio\",\n );\n const hasText = allContent.some((part) => part.type === \"text\");\n if (hasNonText && !hasText) {\n allContent.unshift({ type: \"text\", text: \" \" });\n }\n\n const content = allContent.flatMap((part) => {\n const type = part.type;\n switch (type) {\n case \"text\":\n return { type: \"text\" as const, text: part.text };\n case \"image\":\n return { type: \"image_url\" as const, image_url: { url: part.image } };\n case \"file\": {\n const metadata = { filename: part.filename ?? \"file\" };\n if (part.sourceType === \"id\") {\n return {\n type: \"file\" as const,\n id: part.data,\n mime_type: part.mimeType,\n filename: metadata.filename,\n metadata,\n source_type: \"id\" as const,\n };\n }\n const source = resolveFilePartSource(part);\n if (source.kind === \"url\") {\n return {\n type: \"file\" as const,\n url: source.url,\n mime_type: part.mimeType,\n filename: metadata.filename,\n metadata,\n source_type: \"url\" as const,\n };\n }\n const audioMimeType = audioBlockMimeTypes.get(\n source.mimeType.toLowerCase(),\n );\n if (audioMimeType) {\n return {\n type: \"audio\" as const,\n data: source.data,\n mime_type: audioMimeType,\n source_type: \"base64\" as const,\n };\n }\n return {\n type: \"file\" as const,\n data: source.data,\n mime_type: source.mimeType,\n filename: metadata.filename,\n metadata,\n source_type: \"base64\" as const,\n };\n }\n case \"audio\": {\n const parsed = parseDataUrl(part.audio.data);\n return {\n type: \"audio\" as const,\n data: parsed?.data ?? part.audio.data,\n mime_type: `audio/${part.audio.format}`,\n source_type: \"base64\" as const,\n };\n }\n case \"data\":\n return [];\n case \"tool-call\":\n throw new Error(\"Tool call appends are not supported.\");\n default: {\n const _exhaustiveCheck: \"reasoning\" | \"source\" | \"generative-ui\" = type;\n throw new Error(\n `Unsupported append message part type: ${_exhaustiveCheck}`,\n );\n }\n }\n });\n\n if (content.length === 1 && content[0]?.type === \"text\") {\n return content[0].text ?? \"\";\n }\n return content;\n};\n\nconst getReasoningText = (part: {\n readonly summary?: ReadonlyArray<{ readonly text?: string }>;\n readonly reasoning?: string;\n}): string => {\n const summary = part.summary?.map((s) => s?.text ?? \"\").join(\"\\n\\n\\n\") ?? \"\";\n if (hasVisibleText(summary)) return summary;\n const reasoning = part.reasoning ?? \"\";\n return hasVisibleText(reasoning) ? reasoning : \"\";\n};\n\nexport const createLangChainStreamingTimingAccessors = <\n TMessage extends {\n id?: string | undefined;\n content?: unknown;\n tool_calls?: readonly unknown[] | undefined;\n },\n>(\n getType: (message: TMessage) => string,\n): StreamingTimingAccessors<TMessage> => {\n const findAiMessage = (\n messages: readonly TMessage[],\n messageId: string,\n ): TMessage | undefined =>\n messages.find(\n (message) => getType(message) === \"ai\" && message.id === messageId,\n );\n\n const getTextLength = (\n messages: readonly TMessage[],\n messageId: string,\n ): number => {\n const message = findAiMessage(messages, messageId);\n if (!message) return 0;\n const content = message.content;\n if (typeof content === \"string\") return content.length;\n if (!Array.isArray(content)) return 0;\n let len = 0;\n for (const part of content as readonly LangChainContentBlock[]) {\n if (typeof part !== \"object\" || part === null) continue;\n switch (part.type) {\n case \"text\":\n case \"text_delta\":\n if (typeof part.text === \"string\") len += part.text.length;\n break;\n case \"thinking\":\n if (hasVisibleText(part.thinking)) len += part.thinking.length;\n break;\n case \"reasoning\":\n len += getReasoningText(part).length;\n break;\n }\n }\n return len;\n };\n\n const getToolCallCount = (\n messages: readonly TMessage[],\n messageId: string,\n ): number => findAiMessage(messages, messageId)?.tool_calls?.length ?? 0;\n\n const getAssistantMessageId = (\n messages: readonly TMessage[],\n ): string | undefined => {\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages[i];\n if (message && getType(message) === \"ai\" && message.id) return message.id;\n }\n return undefined;\n };\n\n return {\n getAssistantMessageId,\n getTextLength,\n getToolCallCount,\n };\n};\n\n/**\n * Resolve the assistant message a `UIMessage` belongs to: the parent id comes\n * from `metadata.message_id` (Python SDK) or `metadata.id` (JS SDK).\n */\nexport const getUIMessageParentId = (ui: {\n metadata?: { message_id?: string; id?: string } | undefined;\n}): string | undefined => ui.metadata?.message_id ?? ui.metadata?.id;\n\n/**\n * Group the graph's accumulated `UIMessage`s by the assistant message they\n * belong to. Non-array state and entries without a parent link are dropped.\n */\nexport const groupUIMessagesByParent = <\n T extends {\n metadata?: { message_id?: string; id?: string } | undefined;\n },\n>(\n value: unknown,\n): Map<string, T[]> => {\n const map = new Map<string, T[]>();\n if (!Array.isArray(value)) return map;\n for (const ui of value as T[]) {\n const parentId = getUIMessageParentId(ui);\n if (!parentId) continue;\n const existing = map.get(parentId);\n if (existing) {\n existing.push(ui);\n } else {\n map.set(parentId, [ui]);\n }\n }\n return map;\n};\n"],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import type { AppendMessage } from "@assistant-ui/core";
2
+ import type { LangChainBaseMessage } from "./types.js";
3
+ export type StagedMessage = LangChainBaseMessage & {
4
+ id: string;
5
+ };
6
+ export type StagedEntry = {
7
+ message: StagedMessage;
8
+ runConfig: AppendMessage["runConfig"];
9
+ reconcileOnEcho: boolean;
10
+ baseMessageCount: number;
11
+ transcriptStatus?: "unsent" | "sent";
12
+ };
13
+ export type LangChainThreadState = {
14
+ forkGeneration: number;
15
+ forkPending: boolean;
16
+ stagedEntries: ReadonlyMap<string, StagedEntry>;
17
+ stagedBaseMessages: LangChainBaseMessage[] | null;
18
+ visibleStagedMessages: LangChainBaseMessage[] | null;
19
+ };
20
+ export type LangChainThreadAction = {
21
+ type: "supersedeFork";
22
+ } | {
23
+ type: "finishFork";
24
+ generation: number;
25
+ } | {
26
+ type: "cancelFork";
27
+ } | {
28
+ type: "stage";
29
+ entry: StagedEntry;
30
+ visibleMessages: readonly LangChainBaseMessage[];
31
+ } | {
32
+ type: "stageEdit";
33
+ entry: StagedEntry;
34
+ baseMessages: LangChainBaseMessage[];
35
+ } | {
36
+ type: "reconcile";
37
+ messages: LangChainBaseMessage[];
38
+ visibleMessages: readonly LangChainBaseMessage[];
39
+ } | {
40
+ type: "markTranscript";
41
+ messages: readonly LangChainBaseMessage[];
42
+ status: "unsent" | "sent";
43
+ } | {
44
+ type: "remove";
45
+ id: string;
46
+ visibleMessages: readonly LangChainBaseMessage[];
47
+ } | {
48
+ type: "promote";
49
+ messages: readonly LangChainBaseMessage[];
50
+ visibleMessages: readonly LangChainBaseMessage[];
51
+ };
52
+ export declare const createLangChainThreadState: () => LangChainThreadState;
53
+ export declare const reduceLangChainThreadState: (state: LangChainThreadState, action: LangChainThreadAction) => LangChainThreadState;
54
+ //# sourceMappingURL=langChainThreadState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"langChainThreadState.d.ts","sourceRoot":"","sources":["../src/langChainThreadState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAgB;AAGpD,MAAM,MAAM,aAAa,GAAG,oBAAoB,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAElE,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,aAAa,CAAC;IACvB,SAAS,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IACtC,eAAe,EAAE,OAAO,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAChD,kBAAkB,EAAE,oBAAoB,EAAE,GAAG,IAAI,CAAC;IAClD,qBAAqB,EAAE,oBAAoB,EAAE,GAAG,IAAI,CAAC;CACtD,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAC7B;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,GACzB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IACE,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,WAAW,CAAC;IACnB,eAAe,EAAE,SAAS,oBAAoB,EAAE,CAAC;CAClD,GACD;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,WAAW,CAAC;IACnB,YAAY,EAAE,oBAAoB,EAAE,CAAC;CACtC,GACD;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,oBAAoB,EAAE,CAAC;IACjC,eAAe,EAAE,SAAS,oBAAoB,EAAE,CAAC;CAClD,GACD;IACE,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAC1C,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;CAC3B,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,eAAe,EAAE,SAAS,oBAAoB,EAAE,CAAC;CAClD,GACD;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAC1C,eAAe,EAAE,SAAS,oBAAoB,EAAE,CAAC;CAClD,CAAC;AAEN,eAAO,MAAM,0BAA0B,QAAO,oBAM5C,CAAC;AAsBH,eAAO,MAAM,0BAA0B,GACrC,OAAO,oBAAoB,EAC3B,QAAQ,qBAAqB,KAC5B,oBAsJF,CAAC"}
@@ -0,0 +1,135 @@
1
+ import { getMessageType } from "./convertMessages.js";
2
+ //#region src/langChainThreadState.ts
3
+ const createLangChainThreadState = () => ({
4
+ forkGeneration: 0,
5
+ forkPending: false,
6
+ stagedEntries: /* @__PURE__ */ new Map(),
7
+ stagedBaseMessages: null,
8
+ visibleStagedMessages: null
9
+ });
10
+ const humanContentText = (content) => {
11
+ if (typeof content === "string") return content;
12
+ if (!Array.isArray(content)) return "";
13
+ return content.filter((part) => typeof part === "object" && part !== null && part.type === "text" && typeof part.text === "string").map((part) => part.text).join("");
14
+ };
15
+ const hasSameMessageContent = (a, b) => humanContentText(a.content) === humanContentText(b.content);
16
+ const reduceLangChainThreadState = (state, action) => {
17
+ switch (action.type) {
18
+ case "supersedeFork": return {
19
+ ...state,
20
+ forkGeneration: state.forkGeneration + 1,
21
+ forkPending: true
22
+ };
23
+ case "finishFork": return state.forkGeneration === action.generation ? {
24
+ ...state,
25
+ forkPending: false
26
+ } : state;
27
+ case "cancelFork": return {
28
+ ...state,
29
+ forkGeneration: state.forkGeneration + 1,
30
+ forkPending: false
31
+ };
32
+ case "stage": {
33
+ const stagedEntries = new Map(state.stagedEntries);
34
+ stagedEntries.set(action.entry.message.id, action.entry);
35
+ return {
36
+ ...state,
37
+ stagedEntries,
38
+ visibleStagedMessages: [...action.visibleMessages, action.entry.message]
39
+ };
40
+ }
41
+ case "stageEdit": {
42
+ const stagedEntries = new Map(state.stagedEntries);
43
+ stagedEntries.set(action.entry.message.id, action.entry);
44
+ return {
45
+ ...state,
46
+ stagedEntries,
47
+ stagedBaseMessages: action.baseMessages,
48
+ visibleStagedMessages: [...action.baseMessages, action.entry.message]
49
+ };
50
+ }
51
+ case "reconcile": {
52
+ if (state.stagedEntries.size === 0) return state;
53
+ const baseMessages = state.stagedBaseMessages ?? action.messages;
54
+ const stagedEntries = new Map(state.stagedEntries);
55
+ const remainingStagedMessages = [];
56
+ const matchedBaseMessageIndexes = /* @__PURE__ */ new Set();
57
+ const visibleStagedIds = new Set(action.visibleMessages.flatMap((message) => message.id ? [message.id] : []));
58
+ for (const [id, staged] of state.stagedEntries) {
59
+ if (!visibleStagedIds.has(id)) continue;
60
+ if (baseMessages.some((message, index) => {
61
+ if (matchedBaseMessageIndexes.has(index)) return false;
62
+ if (message.id === id) {
63
+ matchedBaseMessageIndexes.add(index);
64
+ return true;
65
+ }
66
+ if (!staged.reconcileOnEcho || index < staged.baseMessageCount || getMessageType(message) !== "human" || !hasSameMessageContent(message, staged.message)) return false;
67
+ matchedBaseMessageIndexes.add(index);
68
+ return true;
69
+ })) stagedEntries.delete(id);
70
+ else remainingStagedMessages.push(staged.message);
71
+ }
72
+ if (remainingStagedMessages.length === 0) {
73
+ if (stagedEntries.size === state.stagedEntries.size && state.stagedBaseMessages === null && state.visibleStagedMessages === null) return state;
74
+ return {
75
+ ...state,
76
+ stagedEntries,
77
+ stagedBaseMessages: null,
78
+ visibleStagedMessages: null
79
+ };
80
+ }
81
+ return {
82
+ ...state,
83
+ stagedEntries,
84
+ visibleStagedMessages: [...baseMessages, ...remainingStagedMessages]
85
+ };
86
+ }
87
+ case "markTranscript": {
88
+ let stagedEntries = null;
89
+ for (const message of action.messages) {
90
+ const entry = message.id ? (stagedEntries ?? state.stagedEntries).get(message.id) : void 0;
91
+ if (entry?.transcriptStatus) {
92
+ stagedEntries ??= new Map(state.stagedEntries);
93
+ stagedEntries.set(message.id, {
94
+ ...entry,
95
+ transcriptStatus: action.status
96
+ });
97
+ }
98
+ }
99
+ return stagedEntries ? {
100
+ ...state,
101
+ stagedEntries
102
+ } : state;
103
+ }
104
+ case "remove": {
105
+ if (!state.stagedEntries.has(action.id)) return state;
106
+ const stagedEntries = new Map(state.stagedEntries);
107
+ stagedEntries.delete(action.id);
108
+ return {
109
+ ...state,
110
+ stagedEntries,
111
+ stagedBaseMessages: stagedEntries.size === 0 ? null : state.stagedBaseMessages,
112
+ visibleStagedMessages: stagedEntries.size === 0 ? null : action.visibleMessages.filter((message) => message.id !== action.id)
113
+ };
114
+ }
115
+ case "promote": {
116
+ const stagedEntries = new Map(state.stagedEntries);
117
+ const promotedIds = /* @__PURE__ */ new Set();
118
+ for (const message of action.messages) {
119
+ if (!message.id || stagedEntries.get(message.id)?.transcriptStatus) continue;
120
+ promotedIds.add(message.id);
121
+ stagedEntries.delete(message.id);
122
+ }
123
+ return {
124
+ ...state,
125
+ stagedEntries,
126
+ stagedBaseMessages: null,
127
+ visibleStagedMessages: stagedEntries.size > 0 ? action.visibleMessages.filter((message) => !message.id || !promotedIds.has(message.id)) : null
128
+ };
129
+ }
130
+ }
131
+ };
132
+ //#endregion
133
+ export { createLangChainThreadState, reduceLangChainThreadState };
134
+
135
+ //# sourceMappingURL=langChainThreadState.js.map