@assistant-ui/react-langchain 0.0.32 → 0.0.33

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 (55) hide show
  1. package/dist/attachSubagentTranscripts.d.ts +13 -14
  2. package/dist/attachSubagentTranscripts.d.ts.map +1 -1
  3. package/dist/convertMessages.d.ts +6 -9
  4. package/dist/convertMessages.d.ts.map +1 -1
  5. package/dist/convertMessages.js +53 -15
  6. package/dist/convertMessages.js.map +1 -1
  7. package/dist/converter.d.ts +128 -128
  8. package/dist/converter.d.ts.map +1 -1
  9. package/dist/converter.js +2 -1
  10. package/dist/converter.js.map +1 -1
  11. package/dist/findForkCheckpointInHistory.d.ts +15 -16
  12. package/dist/findForkCheckpointInHistory.d.ts.map +1 -1
  13. package/dist/hooks.d.ts +5 -7
  14. package/dist/hooks.d.ts.map +1 -1
  15. package/dist/index.d.ts +7 -8
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/resolveForkCheckpoint.d.ts +4 -5
  18. package/dist/resolveForkCheckpoint.d.ts.map +1 -1
  19. package/dist/runtimeExtras.d.ts +1 -3
  20. package/dist/runtimeExtras.d.ts.map +1 -1
  21. package/dist/sdkIdentity.d.ts +1 -3
  22. package/dist/sdkIdentity.d.ts.map +1 -1
  23. package/dist/sdkIdentity.js +1 -1
  24. package/dist/streamingTiming.d.ts +2 -4
  25. package/dist/streamingTiming.d.ts.map +1 -1
  26. package/dist/subagentMessagesProjection.d.ts +17 -0
  27. package/dist/subagentMessagesProjection.d.ts.map +1 -0
  28. package/dist/subagentMessagesProjection.js +40 -0
  29. package/dist/subagentMessagesProjection.js.map +1 -0
  30. package/dist/types.d.ts +100 -103
  31. package/dist/types.d.ts.map +1 -1
  32. package/dist/uiMessages.d.ts +18 -6
  33. package/dist/uiMessages.d.ts.map +1 -1
  34. package/dist/uiMessages.js +33 -1
  35. package/dist/uiMessages.js.map +1 -1
  36. package/dist/useStreamRuntime.d.ts +7 -10
  37. package/dist/useStreamRuntime.d.ts.map +1 -1
  38. package/dist/useStreamRuntime.js +99 -25
  39. package/dist/useStreamRuntime.js.map +1 -1
  40. package/dist/useSubagentTranscripts.d.ts +3 -5
  41. package/dist/useSubagentTranscripts.d.ts.map +1 -1
  42. package/dist/useSubagentTranscripts.js +3 -2
  43. package/dist/useSubagentTranscripts.js.map +1 -1
  44. package/package.json +9 -5
  45. package/src/convertMessages.test.ts +69 -0
  46. package/src/convertMessages.ts +46 -8
  47. package/src/converter.ts +6 -0
  48. package/src/subagentMessagesProjection.test.ts +175 -0
  49. package/src/subagentMessagesProjection.ts +50 -0
  50. package/src/uiMessages.test.ts +106 -0
  51. package/src/uiMessages.ts +43 -0
  52. package/src/useStreamRuntime.test.tsx +100 -0
  53. package/src/useStreamRuntime.ts +193 -44
  54. package/src/useStreamRuntime.voice.test.tsx +712 -0
  55. package/src/useSubagentTranscripts.ts +3 -6
@@ -1,24 +1,23 @@
1
- import { ThreadMessage, ToolCallTiming } from "@assistant-ui/core";
2
- //#region src/attachSubagentTranscripts.d.ts
1
+ import { type ThreadMessage, type ToolCallTiming } from "@assistant-ui/core";
3
2
  export type SubagentTranscript = {
4
- readonly messages: readonly ThreadMessage[];
5
- /**
6
- * Only present when the client watched the task run: the discovery
7
- * timestamps are stamped when the task call is first seen, so a thread
8
- * seeded from a checkpoint would report a duration of zero.
9
- */
10
- readonly timing?: ToolCallTiming;
3
+ readonly messages: readonly ThreadMessage[];
4
+ /**
5
+ * Only present when the client watched the task run: the discovery
6
+ * timestamps are stamped when the task call is first seen, so a thread
7
+ * seeded from a checkpoint would report a duration of zero.
8
+ */
9
+ readonly timing?: ToolCallTiming;
11
10
  };
12
11
  type AttachedTranscript = readonly [string, SubagentTranscript];
13
12
  type AttachedMessage = {
14
- transcripts: readonly AttachedTranscript[];
15
- attached: ThreadMessage;
13
+ transcripts: readonly AttachedTranscript[];
14
+ attached: ThreadMessage;
16
15
  };
17
16
  export type AttachMemo = {
18
- messages: WeakMap<ThreadMessage, AttachedMessage>;
19
- results: WeakMap<readonly ThreadMessage[], readonly ThreadMessage[]>;
17
+ messages: WeakMap<ThreadMessage, AttachedMessage>;
18
+ results: WeakMap<readonly ThreadMessage[], readonly ThreadMessage[]>;
20
19
  };
21
20
  export declare const createAttachMemo: () => AttachMemo;
22
21
  export declare const attachSubagentTranscripts: (messages: readonly ThreadMessage[], transcripts: ReadonlyMap<string, SubagentTranscript>, memo: AttachMemo) => readonly ThreadMessage[];
23
- //#endregion
22
+ export {};
24
23
  //# sourceMappingURL=attachSubagentTranscripts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"attachSubagentTranscripts.d.ts","names":[],"sources":["../src/attachSubagentTranscripts.ts"],"mappings":";;YAOY;WACD,mBAAmB;;;;;;WAMnB,SAAS;;KAGf,uCAAuC;KAEvC;EACH,sBAAsB;EACtB,UAAU;;YAGA;EACV,UAAU,QAAQ,eAAe;EACjC,SAAS,iBAAiB,0BAA0B;;qBAGzC,wBAAuB;qBAevB,4BACX,mBAAmB,iBACnB,aAAa,oBAAoB,qBACjC,MAAM,wBACI"}
1
+ {"version":3,"file":"attachSubagentTranscripts.d.ts","sourceRoot":"","sources":["../src/attachSubagentTranscripts.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,cAAc,EACpB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;IAC5C;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;CAClC,CAAC;AAEF,KAAK,kBAAkB,GAAG,SAAS,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;AAEhE,KAAK,eAAe,GAAG;IACrB,WAAW,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAC3C,QAAQ,EAAE,aAAa,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,OAAO,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;IAClD,OAAO,EAAE,OAAO,CAAC,SAAS,aAAa,EAAE,EAAE,SAAS,aAAa,EAAE,CAAC,CAAC;CACtE,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAO,UAGlC,CAAC;AAYH,eAAO,MAAM,yBAAyB,GACpC,UAAU,SAAS,aAAa,EAAE,EAClC,aAAa,WAAW,CAAC,MAAM,EAAE,kBAAkB,CAAC,EACpD,MAAM,UAAU,KACf,SAAS,aAAa,EA0CxB,CAAC"}
@@ -1,14 +1,11 @@
1
- import { getMessageContent } from "./converter.js";
2
- import { LangChainBaseMessage, UIMessage } from "./types.js";
3
- import { MessageTiming } from "@assistant-ui/core";
4
- import { useExternalMessageConverter } from "@assistant-ui/core/react";
5
- //#region src/convertMessages.d.ts
1
+ import type { MessageTiming } from "@assistant-ui/core";
2
+ import type { useExternalMessageConverter } from "@assistant-ui/core/react";
3
+ import type { LangChainBaseMessage, UIMessage } from "./types.js";
6
4
  type LangChainMessageConverterMetadata = useExternalMessageConverter.Metadata & {
7
- uiMessagesByParent?: Map<string, UIMessage[]>;
8
- messageTiming?: Record<string, MessageTiming>;
5
+ uiMessagesByParent?: Map<string, UIMessage[]>;
6
+ messageTiming?: Record<string, MessageTiming>;
9
7
  };
10
8
  export declare const getMessageType: (message: LangChainBaseMessage) => string;
11
9
  export declare const convertLangChainBaseMessage: (message: LangChainBaseMessage, metadata?: LangChainMessageConverterMetadata) => useExternalMessageConverter.Message;
12
- //#endregion
13
- export { getMessageContent };
10
+ export { getMessageContent } from "./converter.js";
14
11
  //# sourceMappingURL=convertMessages.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"convertMessages.d.ts","names":[],"sources":["../src/convertMessages.ts"],"mappings":";;;;;KAiBK,oCACH,4BAA4B;EAC1B,qBAAqB,YAAY;EACjC,gBAAgB,eAAe;;qBAetB,iBAAkB,SAAS;qBAuC3B,8BACX,SAAS,sBACT,WAAU,sCACT,4BAA4B"}
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,OAoG9B,CAAC;AAEF,OAAO,EAAE,iBAAiB,EAAE,uBAAoB"}
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { convertLangChainContentBlock, getCustomMetadata, getMessageContent, uiMessageToDataPart, withAudioTranscript } from "./converter.js";
2
+ import { convertLangChainContentBlock, getCustomMetadata, getMessageContent, getMessageModality, uiMessageToDataPart, withAudioTranscript } from "./converter.js";
3
3
  //#region src/convertMessages.ts
4
4
  const warnedMalformedMessages = /* @__PURE__ */ new Set();
5
5
  const warnOnceInDevelopment = (message) => {
@@ -20,6 +20,33 @@ const contentBlocks = (content) => {
20
20
  warnOnceInDevelopment(`Ignoring message content that is neither a string nor an array: ${typeof content}`);
21
21
  return [];
22
22
  };
23
+ const normalizeToolCallArgs = (args) => {
24
+ if (typeof args !== "object" || args === null || Array.isArray(args)) return {};
25
+ try {
26
+ const prototype = Object.getPrototypeOf(args);
27
+ return prototype === Object.prototype || prototype === null ? args : {};
28
+ } catch {
29
+ return {};
30
+ }
31
+ };
32
+ const toolCallArgs = (value) => {
33
+ const args = normalizeToolCallArgs(value);
34
+ try {
35
+ const argsText = JSON.stringify(args);
36
+ return typeof argsText === "string" ? {
37
+ args,
38
+ argsText
39
+ } : {
40
+ args: {},
41
+ argsText: "{}"
42
+ };
43
+ } catch {
44
+ return {
45
+ args: {},
46
+ argsText: "{}"
47
+ };
48
+ }
49
+ };
23
50
  const contentToParts = (content) => {
24
51
  if (typeof content === "string") return [{
25
52
  type: "text",
@@ -42,23 +69,33 @@ const convertLangChainBaseMessage = (message, metadata = {}) => {
42
69
  }],
43
70
  metadata: { custom: getCustomMetadata(message.additional_kwargs) }
44
71
  };
45
- case "human": return {
46
- role: "user",
47
- id: message.id,
48
- content: contentToParts(message.content),
49
- metadata: { custom: getCustomMetadata(message.additional_kwargs) }
50
- };
72
+ case "human": {
73
+ const modality = getMessageModality(message.additional_kwargs);
74
+ return {
75
+ role: "user",
76
+ id: message.id,
77
+ content: contentToParts(message.content),
78
+ metadata: {
79
+ custom: getCustomMetadata(message.additional_kwargs),
80
+ ...modality && { modality }
81
+ }
82
+ };
83
+ }
51
84
  case "ai": {
52
- const toolCallParts = message.tool_calls?.map((tc) => ({
53
- type: "tool-call",
54
- toolCallId: tc.id,
55
- toolName: tc.name,
56
- args: tc.args,
57
- argsText: JSON.stringify(tc.args)
58
- })) ?? [];
85
+ const toolCallParts = message.tool_calls?.map((tc) => {
86
+ const { args, argsText } = toolCallArgs(tc.args);
87
+ return {
88
+ type: "tool-call",
89
+ toolCallId: tc.id,
90
+ toolName: tc.name,
91
+ args,
92
+ argsText
93
+ };
94
+ }) ?? [];
59
95
  const assistantStatus = typeof message.status === "object" ? message.status : void 0;
60
96
  const uiDataParts = (message.id ? metadata.uiMessagesByParent?.get(message.id)?.map(uiMessageToDataPart) : void 0) ?? [];
61
97
  const timing = metadata.messageTiming?.[message.id ?? ""];
98
+ const modality = getMessageModality(message.additional_kwargs);
62
99
  return {
63
100
  role: "assistant",
64
101
  id: message.id,
@@ -69,7 +106,8 @@ const convertLangChainBaseMessage = (message, metadata = {}) => {
69
106
  ],
70
107
  metadata: {
71
108
  custom: getCustomMetadata(message.additional_kwargs),
72
- ...timing && { timing }
109
+ ...timing && { timing },
110
+ ...modality && { modality }
73
111
  },
74
112
  ...assistantStatus && { status: assistantStatus }
75
113
  };
@@ -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 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 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 return {\n role: \"user\",\n id: message.id,\n content: contentToParts(message.content),\n metadata: {\n custom: getCustomMetadata(message.additional_kwargs),\n },\n };\n\n case \"ai\": {\n const toolCallParts =\n message.tool_calls?.map((tc) => ({\n type: \"tool-call\" as const,\n toolCallId: tc.id,\n toolName: tc.name,\n args: tc.args as ReadonlyJSONObject,\n argsText: JSON.stringify(tc.args),\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\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 },\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 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?.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,59 +1,58 @@
1
- import { AppendMessage, DataMessagePart, ThreadAssistantMessage, ThreadUserMessage } from "@assistant-ui/core";
2
- import { StreamingTimingAccessors } from "@assistant-ui/core/react";
3
- //#region src/converter.d.ts
1
+ import type { AppendMessage, DataMessagePart, MessageModality, ThreadAssistantMessage, ThreadUserMessage } from "@assistant-ui/core";
2
+ import type { StreamingTimingAccessors } from "@assistant-ui/core/react";
4
3
  /** Known content block types from @langchain/core messages. */
5
4
  export type LangChainContentBlock = {
6
- type: "text";
7
- text: string;
5
+ type: "text";
6
+ text: string;
8
7
  } | {
9
- type: "text_delta";
10
- text: string;
8
+ type: "text_delta";
9
+ text: string;
11
10
  } | {
12
- type: "image_url";
13
- image_url: string | {
14
- url?: string;
15
- };
11
+ type: "image_url";
12
+ image_url: string | {
13
+ url?: string;
14
+ };
16
15
  } | {
17
- type: "thinking";
18
- thinking: string;
16
+ type: "thinking";
17
+ thinking: string;
19
18
  } | {
20
- type: "reasoning";
21
- summary?: Array<{
22
- type: "summary_text";
23
- text?: string;
24
- }>;
25
- reasoning?: string;
19
+ type: "reasoning";
20
+ summary?: Array<{
21
+ type: "summary_text";
22
+ text?: string;
23
+ }>;
24
+ reasoning?: string;
26
25
  } | {
27
- type: "file";
28
- data: string;
29
- mime_type: string;
30
- source_type?: "base64";
31
- metadata?: {
32
- filename?: string;
33
- };
26
+ type: "file";
27
+ data: string;
28
+ mime_type: string;
29
+ source_type?: "base64";
30
+ metadata?: {
31
+ filename?: string;
32
+ };
34
33
  } | {
35
- type: "file";
36
- url: string;
37
- mime_type?: string;
38
- source_type: "url";
39
- metadata?: {
40
- filename?: string;
41
- };
34
+ type: "file";
35
+ url: string;
36
+ mime_type?: string;
37
+ source_type: "url";
38
+ metadata?: {
39
+ filename?: string;
40
+ };
42
41
  } | {
43
- type: "file";
44
- id: string;
45
- mime_type?: string;
46
- source_type: "id";
47
- metadata?: {
48
- filename?: string;
49
- };
42
+ type: "file";
43
+ id: string;
44
+ mime_type?: string;
45
+ source_type: "id";
46
+ metadata?: {
47
+ filename?: string;
48
+ };
50
49
  } | {
51
- type: "audio";
52
- data: string;
53
- mime_type: string;
54
- source_type: "base64";
50
+ type: "audio";
51
+ data: string;
52
+ mime_type: string;
53
+ source_type: "base64";
55
54
  } | {
56
- type: "tool_use" | "input_json_delta";
55
+ type: "tool_use" | "input_json_delta";
57
56
  };
58
57
  type ConvertedContentPart = ThreadUserMessage["content"][number] | ThreadAssistantMessage["content"][number];
59
58
  export declare const convertLangChainContentBlock: (part: LangChainContentBlock) => ConvertedContentPart | null | undefined;
@@ -64,116 +63,117 @@ export declare const convertLangChainContentBlock: (part: LangChainContentBlock)
64
63
  * streamed response carries raw PCM rather than a playable file.
65
64
  */
66
65
  export declare const withAudioTranscript: <T extends {
67
- type: string;
68
- text?: unknown;
66
+ type: string;
67
+ text?: unknown;
69
68
  }>(parts: readonly T[], additionalKwargs: Record<string, unknown> | undefined) => readonly (T | {
70
- type: "text";
71
- text: string;
69
+ type: "text";
70
+ text: string;
72
71
  })[];
73
72
  export declare const getCustomMetadata: (additionalKwargs: Record<string, unknown> | undefined) => Record<string, unknown>;
73
+ export declare const getMessageModality: (additionalKwargs: Record<string, unknown> | undefined) => MessageModality | undefined;
74
74
  export declare const uiMessageToDataPart: <TUIMessage extends {
75
- name: string;
76
- props: Record<string, unknown>;
75
+ name: string;
76
+ props: Record<string, unknown>;
77
77
  }>(ui: TUIMessage) => DataMessagePart;
78
78
  export declare const getMessageContent: (msg: AppendMessage) => string | ({
79
- type: "text";
80
- text: string;
81
- image_url?: never;
82
- id?: never;
83
- mime_type?: never;
84
- filename?: never;
85
- metadata?: never;
86
- source_type?: never;
87
- url?: never;
88
- data?: never;
79
+ type: "text";
80
+ text: string;
81
+ image_url?: never;
82
+ id?: never;
83
+ mime_type?: never;
84
+ filename?: never;
85
+ metadata?: never;
86
+ source_type?: never;
87
+ url?: never;
88
+ data?: never;
89
89
  } | {
90
- type: "image_url";
91
- image_url: {
92
- url: string;
93
- };
94
- text?: never;
95
- id?: never;
96
- mime_type?: never;
97
- filename?: never;
98
- metadata?: never;
99
- source_type?: never;
100
- url?: never;
101
- data?: never;
90
+ type: "image_url";
91
+ image_url: {
92
+ url: string;
93
+ };
94
+ text?: never;
95
+ id?: never;
96
+ mime_type?: never;
97
+ filename?: never;
98
+ metadata?: never;
99
+ source_type?: never;
100
+ url?: never;
101
+ data?: never;
102
102
  } | {
103
- type: "file";
104
- id: string;
105
- mime_type: string;
106
- filename: string;
107
- metadata: {
103
+ type: "file";
104
+ id: string;
105
+ mime_type: string;
108
106
  filename: string;
109
- };
110
- source_type: "id";
111
- text?: never;
112
- image_url?: never;
113
- url?: never;
114
- data?: never;
107
+ metadata: {
108
+ filename: string;
109
+ };
110
+ source_type: "id";
111
+ text?: never;
112
+ image_url?: never;
113
+ url?: never;
114
+ data?: never;
115
115
  } | {
116
- type: "file";
117
- url: string;
118
- mime_type: string;
119
- filename: string;
120
- metadata: {
116
+ type: "file";
117
+ url: string;
118
+ mime_type: string;
121
119
  filename: string;
122
- };
123
- source_type: "url";
124
- text?: never;
125
- image_url?: never;
126
- id?: never;
127
- data?: never;
120
+ metadata: {
121
+ filename: string;
122
+ };
123
+ source_type: "url";
124
+ text?: never;
125
+ image_url?: never;
126
+ id?: never;
127
+ data?: never;
128
128
  } | {
129
- type: "file";
130
- data: string;
131
- mime_type: string;
132
- filename: string;
133
- metadata: {
129
+ type: "file";
130
+ data: string;
131
+ mime_type: string;
134
132
  filename: string;
135
- };
136
- source_type: "base64";
137
- text?: never;
138
- image_url?: never;
139
- id?: never;
140
- url?: never;
133
+ metadata: {
134
+ filename: string;
135
+ };
136
+ source_type: "base64";
137
+ text?: never;
138
+ image_url?: never;
139
+ id?: never;
140
+ url?: never;
141
141
  } | {
142
- type: "audio";
143
- data: string;
144
- mime_type: string;
145
- source_type: "base64";
146
- text?: never;
147
- image_url?: never;
148
- id?: never;
149
- filename?: never;
150
- metadata?: never;
151
- url?: never;
142
+ type: "audio";
143
+ data: string;
144
+ mime_type: string;
145
+ source_type: "base64";
146
+ text?: never;
147
+ image_url?: never;
148
+ id?: never;
149
+ filename?: never;
150
+ metadata?: never;
151
+ url?: never;
152
152
  })[];
153
153
  export declare const createLangChainStreamingTimingAccessors: <TMessage extends {
154
- id?: string | undefined;
155
- content?: unknown;
156
- tool_calls?: readonly unknown[] | undefined;
154
+ id?: string | undefined;
155
+ content?: unknown;
156
+ tool_calls?: readonly unknown[] | undefined;
157
157
  }>(getType: (message: TMessage) => string) => StreamingTimingAccessors<TMessage>;
158
158
  /**
159
159
  * Resolve the assistant message a `UIMessage` belongs to: the parent id comes
160
160
  * from `metadata.message_id` (Python SDK) or `metadata.id` (JS SDK).
161
161
  */
162
162
  export declare const getUIMessageParentId: (ui: {
163
- metadata?: {
164
- message_id?: string;
165
- id?: string;
166
- } | undefined;
163
+ metadata?: {
164
+ message_id?: string;
165
+ id?: string;
166
+ } | undefined;
167
167
  }) => string | undefined;
168
168
  /**
169
169
  * Group the graph's accumulated `UIMessage`s by the assistant message they
170
170
  * belong to. Non-array state and entries without a parent link are dropped.
171
171
  */
172
172
  export declare const groupUIMessagesByParent: <T extends {
173
- metadata?: {
174
- message_id?: string;
175
- id?: string;
176
- } | undefined;
173
+ metadata?: {
174
+ message_id?: string;
175
+ id?: string;
176
+ } | undefined;
177
177
  }>(value: unknown) => Map<string, T[]>;
178
- //#endregion
178
+ export {};
179
179
  //# sourceMappingURL=converter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"converter.d.ts","names":[],"sources":["../src/converter.ts"],"mappings":";;;;YAaY;EACN;EAAc;;EACd;EAAoB;;EACpB;EAAmB;IAAsB;;;EACzC;EAAkB;;EAElB;EACA,UAAU;IAAQ;IAAsB;;EACxC;;EAGA;EACA;EACA;EACA;EACA;IAAa;;;EAGb;EACA;EACA;EACA;EACA;IAAa;;;EAGb;EACA;EACA;EACA;EACA;IAAa;;;EAGb;EACA;EACA;EACA;;EAEA;;KAED,uBACD,uCACA;qBAES,+BACX,MAAM,0BACL;;;;;;;qBAqEU,sBAAuB;EAAY;EAAc;GAC5D,gBAAgB,KAChB,kBAAkB,kDACP;EAAM;EAAc;;qBAapB,oBACX,kBAAkB,wCACjB;qBAGU,sBACX;EAAqB;EAAc,OAAO;GAE1C,IAAI,eACH;qBAoBU,oBAAqB,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAuG1B,0CACX;EACE;EACA;EACA;GAGF,UAAU,SAAS,wBAClB,yBAAyB;;;;;qBA+Df,uBAAwB;EACnC;IAAa;IAAqB;;;;;;;qBAOvB,0BACX;EACE;IAAa;IAAqB;;GAGpC,mBACC,YAAY"}
1
+ {"version":3,"file":"converter.d.ts","sourceRoot":"","sources":["../src/converter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AAK5B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAEzE,+DAA+D;AAC/D,MAAM,MAAM,qBAAqB,GAC7B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAC3D;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACtC;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,cAAc,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,QAAQ,CAAC;IACvB,QAAQ,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAClC,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAClC,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAClC,GACD;IACE,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,QAAQ,CAAC;CACvB,GACD;IAAE,IAAI,EAAE,UAAU,GAAG,kBAAkB,CAAA;CAAE,CAAC;AAE9C,KAAK,oBAAoB,GACrB,iBAAiB,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,GACpC,sBAAsB,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9C,eAAO,MAAM,4BAA4B,GACvC,MAAM,qBAAqB,KAC1B,oBAAoB,GAAG,IAAI,GAAG,SA0DhC,CAAC;AAKF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,EAC5E,OAAO,SAAS,CAAC,EAAE,EACnB,kBAAkB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,KACpD,SAAS,CAAC,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EAW/C,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,kBAAkB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,KACpD,MAAM,CAAC,MAAM,EAAE,OAAO,CACsC,CAAC;AAEhE,eAAO,MAAM,kBAAkB,GAC7B,kBAAkB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,KACpD,eAAe,GAAG,SACyC,CAAC;AAE/D,eAAO,MAAM,mBAAmB,GAC9B,UAAU,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,EAEnE,IAAI,UAAU,KACb,eAID,CAAC;AAgBH,eAAO,MAAM,iBAAiB,GAAI,KAAK,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA2FnD,CAAC;AAYF,eAAO,MAAM,uCAAuC,GAClD,QAAQ,SAAS;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,CAAC;CAC7C,EAED,SAAS,CAAC,OAAO,EAAE,QAAQ,KAAK,MAAM,KACrC,wBAAwB,CAAC,QAAQ,CAyDnC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAAI,IAAI;IACvC,QAAQ,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;CAC7D,KAAG,MAAM,GAAG,SAAuD,CAAC;AAErE;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GAClC,CAAC,SAAS;IACR,QAAQ,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;CAC7D,EAED,OAAO,OAAO,KACb,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,CAcjB,CAAC"}
package/dist/converter.js CHANGED
@@ -65,6 +65,7 @@ const withAudioTranscript = (parts, additionalKwargs) => {
65
65
  }];
66
66
  };
67
67
  const getCustomMetadata = (additionalKwargs) => additionalKwargs?.metadata ?? {};
68
+ const getMessageModality = (additionalKwargs) => additionalKwargs?.modality === "voice" ? "voice" : void 0;
68
69
  const uiMessageToDataPart = (ui) => ({
69
70
  type: "data",
70
71
  name: ui.name,
@@ -216,6 +217,6 @@ const groupUIMessagesByParent = (value) => {
216
217
  return map;
217
218
  };
218
219
  //#endregion
219
- export { convertLangChainContentBlock, createLangChainStreamingTimingAccessors, getCustomMetadata, getMessageContent, getUIMessageParentId, groupUIMessagesByParent, uiMessageToDataPart, withAudioTranscript };
220
+ export { convertLangChainContentBlock, createLangChainStreamingTimingAccessors, getCustomMetadata, getMessageContent, getMessageModality, getUIMessageParentId, groupUIMessagesByParent, uiMessageToDataPart, withAudioTranscript };
220
221
 
221
222
  //# 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 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 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\";\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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"}