@assistant-ui/react-langchain 0.0.14 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,4 +35,4 @@ export function App() {
35
35
 
36
36
  `useStreamRuntime` accepts every option `@langchain/react`'s `useStream` does, plus `cloud` for thread persistence, an `adapters` bag, and a `messagesKey` override.
37
37
 
38
- Full reference for `useLangChainState`, `useLangChainInterruptState`, `useLangChainSubmit`, and `convertLangChainBaseMessage` at [assistant-ui.com/docs/runtimes/langchain](https://www.assistant-ui.com/docs/runtimes/langchain).
38
+ Full reference for `useLangChainState`, `useLangChainInterruptState`, `useLangChainError`, `useLangChainSubmit`, and `convertLangChainBaseMessage` at [assistant-ui.com/docs/runtimes/langchain](https://www.assistant-ui.com/docs/runtimes/langchain).
@@ -1 +1 @@
1
- {"version":3,"file":"convertMessages.d.ts","names":[],"sources":["../src/convertMessages.ts"],"mappings":";;;;cAMa,cAAA,GAAkB,OAA6B,EAApB,oBAAoB;AAAA,cAuD/C,2BAAA,EAA6B,2BAAA,CAA4B,QAAQ,CAC5E,oBAAA"}
1
+ {"version":3,"file":"convertMessages.d.ts","names":[],"sources":["../src/convertMessages.ts"],"mappings":";;;;cAMa,cAAA,GAAkB,OAA6B,EAApB,oBAAoB;AAAA,cA8D/C,2BAAA,EAA6B,2BAAA,CAA4B,QAAQ,CAC5E,oBAAA"}
@@ -26,6 +26,12 @@ const contentToParts = (content) => {
26
26
  type: "image",
27
27
  image: part.image_url.url
28
28
  };
29
+ case "file": return {
30
+ type: "file",
31
+ filename: part.metadata?.filename ?? "file",
32
+ data: part.data,
33
+ mimeType: part.mime_type
34
+ };
29
35
  case "thinking": return {
30
36
  type: "reasoning",
31
37
  text: part.thinking
@@ -1 +1 @@
1
- {"version":3,"file":"convertMessages.js","names":["parts"],"sources":["../src/convertMessages.ts"],"sourcesContent":["\"use client\";\n\nimport type { useExternalMessageConverter } from \"@assistant-ui/core/react\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\nimport type { LangChainBaseMessage, LangChainContentBlock } from \"./types\";\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 throw new Error(\"Cannot determine message type\");\n};\n\nconst contentToParts = (content: unknown) => {\n if (typeof content === \"string\")\n return [{ type: \"text\" as const, text: content }];\n\n const parts = content as readonly LangChainContentBlock[];\n return parts\n .map((part) => {\n const type = part.type;\n switch (type) {\n case \"text\":\n case \"text_delta\":\n return { type: \"text\" as const, text: part.text };\n case \"image_url\":\n if (typeof part.image_url === \"string\") {\n return { type: \"image\" as const, image: part.image_url };\n }\n return { type: \"image\" as const, image: part.image_url.url };\n case \"thinking\":\n return { type: \"reasoning\" as const, text: part.thinking };\n case \"reasoning\":\n return {\n type: \"reasoning\" as const,\n text: part.summary.map((s) => s.text).join(\"\\n\\n\\n\"),\n };\n case \"tool_use\":\n case \"input_json_delta\":\n return null;\n default:\n return null;\n }\n })\n .filter((p) => p !== null);\n};\n\nconst getCustomMetadata = (\n additionalKwargs: Record<string, unknown> | undefined,\n): Record<string, unknown> =>\n (additionalKwargs?.metadata as Record<string, unknown>) ?? {};\n\nconst getStringContent = (content: unknown): string => {\n if (typeof content === \"string\") return content;\n const parts = content as readonly LangChainContentBlock[];\n return parts\n .filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n .map((c) => c.text)\n .join(\"\");\n};\n\nexport const convertLangChainBaseMessage: useExternalMessageConverter.Callback<\n LangChainBaseMessage\n> = (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 return {\n role: \"assistant\",\n id: message.id,\n content: [...contentToParts(message.content), ...toolCallParts],\n metadata: {\n custom: getCustomMetadata(message.additional_kwargs),\n },\n ...(assistantStatus && { status: assistantStatus }),\n };\n }\n\n case \"tool\":\n return {\n role: \"tool\",\n toolName: message.name ?? \"\",\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"],"mappings":";;AAMA,MAAa,kBAAkB,YAA0C;CACvE,IAAI,OAAO,QAAQ,aAAa,YAAY,OAAO,QAAQ,SAAS;CACpE,IAAI,UAAU,SACZ,OAAQ,QAAoC;CAC9C,MAAM,IAAI,MAAM,+BAA+B;AACjD;AAEA,MAAM,kBAAkB,YAAqB;CAC3C,IAAI,OAAO,YAAY,UACrB,OAAO,CAAC;EAAE,MAAM;EAAiB,MAAM;CAAQ,CAAC;CAGlD,OAAOA,QACJ,KAAK,SAAS;EAEb,QADa,KAAK,MAClB;GACE,KAAK;GACL,KAAK,cACH,OAAO;IAAE,MAAM;IAAiB,MAAM,KAAK;GAAK;GAClD,KAAK;IACH,IAAI,OAAO,KAAK,cAAc,UAC5B,OAAO;KAAE,MAAM;KAAkB,OAAO,KAAK;IAAU;IAEzD,OAAO;KAAE,MAAM;KAAkB,OAAO,KAAK,UAAU;IAAI;GAC7D,KAAK,YACH,OAAO;IAAE,MAAM;IAAsB,MAAM,KAAK;GAAS;GAC3D,KAAK,aACH,OAAO;IACL,MAAM;IACN,MAAM,KAAK,QAAQ,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,KAAK,QAAQ;GACrD;GACF,KAAK;GACL,KAAK,oBACH,OAAO;GACT,SACE,OAAO;EACX;CACF,CAAC,CAAC,CACD,QAAQ,MAAM,MAAM,IAAI;AAC7B;AAEA,MAAM,qBACJ,qBAEC,kBAAkB,YAAwC,CAAC;AAE9D,MAAM,oBAAoB,YAA6B;CACrD,IAAI,OAAO,YAAY,UAAU,OAAO;CAExC,OAAOA,QACJ,QAAQ,MAA2C,EAAE,SAAS,MAAM,CAAC,CACrE,KAAK,MAAM,EAAE,IAAI,CAAC,CAClB,KAAK,EAAE;AACZ;AAEA,MAAa,+BAER,YAAY;CAGf,QAFa,eAAe,OAEjB,GAAX;EACE,KAAK,UACH,OAAO;GACL,MAAM;GACN,IAAI,QAAQ;GACZ,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,iBAAiB,QAAQ,OAAO;GAAE,CAAC;GACnE,UAAU,EACR,QAAQ,kBAAkB,QAAQ,iBAAiB,EACrD;EACF;EAEF,KAAK,SACH,OAAO;GACL,MAAM;GACN,IAAI,QAAQ;GACZ,SAAS,eAAe,QAAQ,OAAO;GACvC,UAAU,EACR,QAAQ,kBAAkB,QAAQ,iBAAiB,EACrD;EACF;EAEF,KAAK,MAAM;GACT,MAAM,gBACJ,QAAQ,YAAY,KAAK,QAAQ;IAC/B,MAAM;IACN,YAAY,GAAG;IACf,UAAU,GAAG;IACb,MAAM,GAAG;IACT,UAAU,KAAK,UAAU,GAAG,IAAI;GAClC,EAAE,KAAK,CAAC;GAEV,MAAM,kBACJ,OAAO,QAAQ,WAAW,WAAW,QAAQ,SAAS,KAAA;GAExD,OAAO;IACL,MAAM;IACN,IAAI,QAAQ;IACZ,SAAS,CAAC,GAAG,eAAe,QAAQ,OAAO,GAAG,GAAG,aAAa;IAC9D,UAAU,EACR,QAAQ,kBAAkB,QAAQ,iBAAiB,EACrD;IACA,GAAI,mBAAmB,EAAE,QAAQ,gBAAgB;GACnD;EACF;EAEA,KAAK,QACH,OAAO;GACL,MAAM;GACN,UAAU,QAAQ,QAAQ;GAC1B,YAAY,QAAQ,gBAAgB;GACpC,QAAQ,QAAQ;GAChB,UAAU,QAAQ;GAClB,SAAS,QAAQ,WAAW;EAC9B;EAEF,SACE,OAAO;GACL,MAAM;GACN,IAAI,QAAQ;GACZ,SAAS,CACP;IACE,MAAM;IACN,MACE,OAAO,QAAQ,YAAY,WACvB,QAAQ,UACR,KAAK,UAAU,QAAQ,OAAO;GACtC,CACF;EACF;CACJ;AACF"}
1
+ {"version":3,"file":"convertMessages.js","names":["parts"],"sources":["../src/convertMessages.ts"],"sourcesContent":["\"use client\";\n\nimport type { useExternalMessageConverter } from \"@assistant-ui/core/react\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\nimport type { LangChainBaseMessage, LangChainContentBlock } from \"./types\";\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 throw new Error(\"Cannot determine message type\");\n};\n\nconst contentToParts = (content: unknown) => {\n if (typeof content === \"string\")\n return [{ type: \"text\" as const, text: content }];\n\n const parts = content as readonly LangChainContentBlock[];\n return parts\n .map((part) => {\n const type = part.type;\n switch (type) {\n case \"text\":\n case \"text_delta\":\n return { type: \"text\" as const, text: part.text };\n case \"image_url\":\n if (typeof part.image_url === \"string\") {\n return { type: \"image\" as const, image: part.image_url };\n }\n return { type: \"image\" as const, image: part.image_url.url };\n case \"file\":\n return {\n type: \"file\" as const,\n filename: part.metadata?.filename ?? \"file\",\n data: part.data,\n mimeType: part.mime_type,\n };\n case \"thinking\":\n return { type: \"reasoning\" as const, text: part.thinking };\n case \"reasoning\":\n return {\n type: \"reasoning\" as const,\n text: part.summary.map((s) => s.text).join(\"\\n\\n\\n\"),\n };\n case \"tool_use\":\n case \"input_json_delta\":\n return null;\n default:\n return null;\n }\n })\n .filter((p) => p !== null);\n};\n\nconst getCustomMetadata = (\n additionalKwargs: Record<string, unknown> | undefined,\n): Record<string, unknown> =>\n (additionalKwargs?.metadata as Record<string, unknown>) ?? {};\n\nconst getStringContent = (content: unknown): string => {\n if (typeof content === \"string\") return content;\n const parts = content as readonly LangChainContentBlock[];\n return parts\n .filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n .map((c) => c.text)\n .join(\"\");\n};\n\nexport const convertLangChainBaseMessage: useExternalMessageConverter.Callback<\n LangChainBaseMessage\n> = (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 return {\n role: \"assistant\",\n id: message.id,\n content: [...contentToParts(message.content), ...toolCallParts],\n metadata: {\n custom: getCustomMetadata(message.additional_kwargs),\n },\n ...(assistantStatus && { status: assistantStatus }),\n };\n }\n\n case \"tool\":\n return {\n role: \"tool\",\n toolName: message.name ?? \"\",\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"],"mappings":";;AAMA,MAAa,kBAAkB,YAA0C;CACvE,IAAI,OAAO,QAAQ,aAAa,YAAY,OAAO,QAAQ,SAAS;CACpE,IAAI,UAAU,SACZ,OAAQ,QAAoC;CAC9C,MAAM,IAAI,MAAM,+BAA+B;AACjD;AAEA,MAAM,kBAAkB,YAAqB;CAC3C,IAAI,OAAO,YAAY,UACrB,OAAO,CAAC;EAAE,MAAM;EAAiB,MAAM;CAAQ,CAAC;CAGlD,OAAOA,QACJ,KAAK,SAAS;EAEb,QADa,KAAK,MAClB;GACE,KAAK;GACL,KAAK,cACH,OAAO;IAAE,MAAM;IAAiB,MAAM,KAAK;GAAK;GAClD,KAAK;IACH,IAAI,OAAO,KAAK,cAAc,UAC5B,OAAO;KAAE,MAAM;KAAkB,OAAO,KAAK;IAAU;IAEzD,OAAO;KAAE,MAAM;KAAkB,OAAO,KAAK,UAAU;IAAI;GAC7D,KAAK,QACH,OAAO;IACL,MAAM;IACN,UAAU,KAAK,UAAU,YAAY;IACrC,MAAM,KAAK;IACX,UAAU,KAAK;GACjB;GACF,KAAK,YACH,OAAO;IAAE,MAAM;IAAsB,MAAM,KAAK;GAAS;GAC3D,KAAK,aACH,OAAO;IACL,MAAM;IACN,MAAM,KAAK,QAAQ,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,KAAK,QAAQ;GACrD;GACF,KAAK;GACL,KAAK,oBACH,OAAO;GACT,SACE,OAAO;EACX;CACF,CAAC,CAAC,CACD,QAAQ,MAAM,MAAM,IAAI;AAC7B;AAEA,MAAM,qBACJ,qBAEC,kBAAkB,YAAwC,CAAC;AAE9D,MAAM,oBAAoB,YAA6B;CACrD,IAAI,OAAO,YAAY,UAAU,OAAO;CAExC,OAAOA,QACJ,QAAQ,MAA2C,EAAE,SAAS,MAAM,CAAC,CACrE,KAAK,MAAM,EAAE,IAAI,CAAC,CAClB,KAAK,EAAE;AACZ;AAEA,MAAa,+BAER,YAAY;CAGf,QAFa,eAAe,OAEjB,GAAX;EACE,KAAK,UACH,OAAO;GACL,MAAM;GACN,IAAI,QAAQ;GACZ,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,iBAAiB,QAAQ,OAAO;GAAE,CAAC;GACnE,UAAU,EACR,QAAQ,kBAAkB,QAAQ,iBAAiB,EACrD;EACF;EAEF,KAAK,SACH,OAAO;GACL,MAAM;GACN,IAAI,QAAQ;GACZ,SAAS,eAAe,QAAQ,OAAO;GACvC,UAAU,EACR,QAAQ,kBAAkB,QAAQ,iBAAiB,EACrD;EACF;EAEF,KAAK,MAAM;GACT,MAAM,gBACJ,QAAQ,YAAY,KAAK,QAAQ;IAC/B,MAAM;IACN,YAAY,GAAG;IACf,UAAU,GAAG;IACb,MAAM,GAAG;IACT,UAAU,KAAK,UAAU,GAAG,IAAI;GAClC,EAAE,KAAK,CAAC;GAEV,MAAM,kBACJ,OAAO,QAAQ,WAAW,WAAW,QAAQ,SAAS,KAAA;GAExD,OAAO;IACL,MAAM;IACN,IAAI,QAAQ;IACZ,SAAS,CAAC,GAAG,eAAe,QAAQ,OAAO,GAAG,GAAG,aAAa;IAC9D,UAAU,EACR,QAAQ,kBAAkB,QAAQ,iBAAiB,EACrD;IACA,GAAI,mBAAmB,EAAE,QAAQ,gBAAgB;GACnD;EACF;EAEA,KAAK,QACH,OAAO;GACL,MAAM;GACN,UAAU,QAAQ,QAAQ;GAC1B,YAAY,QAAQ,gBAAgB;GACpC,QAAQ,QAAQ;GAChB,UAAU,QAAQ;GAClB,SAAS,QAAQ,WAAW;EAC9B;EAEF,SACE,OAAO;GACL,MAAM;GACN,IAAI,QAAQ;GACZ,SAAS,CACP;IACE,MAAM;IACN,MACE,OAAO,QAAQ,YAAY,WACvB,QAAQ,UACR,KAAK,UAAU,QAAQ,OAAO;GACtC,CACF;EACF;CACJ;AACF"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import { LangChainBaseMessage, LangChainContentBlock, LangChainToolCall } from "./types.js";
2
2
  import { convertLangChainBaseMessage } from "./convertMessages.js";
3
- import { UseStreamRuntimeOptions, useLangChainInterruptState, useLangChainSend, useLangChainSendCommand, useLangChainState, useLangChainSubmit, useStreamRuntime } from "./useStreamRuntime.js";
4
- export { type LangChainBaseMessage, type LangChainContentBlock, type LangChainToolCall, type UseStreamRuntimeOptions, convertLangChainBaseMessage, useLangChainInterruptState, useLangChainSend, useLangChainSendCommand, useLangChainState, useLangChainSubmit, useStreamRuntime };
3
+ import { UseStreamRuntimeOptions, useLangChainError, useLangChainInterruptState, useLangChainSend, useLangChainSendCommand, useLangChainState, useLangChainSubmit, useLangChainToolCalls, useStreamRuntime } from "./useStreamRuntime.js";
4
+ export { type LangChainBaseMessage, type LangChainContentBlock, type LangChainToolCall, type UseStreamRuntimeOptions, convertLangChainBaseMessage, useLangChainError, useLangChainInterruptState, useLangChainSend, useLangChainSendCommand, useLangChainState, useLangChainSubmit, useLangChainToolCalls, useStreamRuntime };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import { convertLangChainBaseMessage } from "./convertMessages.js";
2
- import { useLangChainInterruptState, useLangChainSend, useLangChainSendCommand, useLangChainState, useLangChainSubmit, useStreamRuntime } from "./useStreamRuntime.js";
3
- export { convertLangChainBaseMessage, useLangChainInterruptState, useLangChainSend, useLangChainSendCommand, useLangChainState, useLangChainSubmit, useStreamRuntime };
2
+ import { useLangChainError, useLangChainInterruptState, useLangChainSend, useLangChainSendCommand, useLangChainState, useLangChainSubmit, useLangChainToolCalls, useStreamRuntime } from "./useStreamRuntime.js";
3
+ export { convertLangChainBaseMessage, useLangChainError, useLangChainInterruptState, useLangChainSend, useLangChainSendCommand, useLangChainState, useLangChainSubmit, useLangChainToolCalls, useStreamRuntime };
package/dist/types.d.ts CHANGED
@@ -22,6 +22,14 @@ type LangChainContentBlock = {
22
22
  type: "summary_text";
23
23
  text: string;
24
24
  }>;
25
+ } | {
26
+ type: "file";
27
+ data: string;
28
+ mime_type: string;
29
+ source_type?: "base64";
30
+ metadata?: {
31
+ filename?: string;
32
+ };
25
33
  } | {
26
34
  type: "tool_use" | "input_json_delta";
27
35
  };
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;KAGY,qBAAA;EACN,IAAA;EAAc,IAAA;AAAA;EACd,IAAA;EAAoB,IAAA;AAAA;EACpB,IAAA;EAAmB,SAAA;IAAsB,GAAA;EAAA;AAAA;EACzC,IAAA;EAAkB,QAAA;AAAA;EAElB,IAAA;EACA,OAAA,EAAS,KAAK;IAAG,IAAA;IAAsB,IAAA;EAAA;AAAA;EAEvC,IAAA;AAAA;AAAA,KAEM,iBAAA;EACV,EAAA;EACA,IAAA;EACA,IAAA,EAAM,MAAM;AAAA;;;;AAAA;AAUd;;;KAAY,oBAAA;EACV,QAAA;EACA,OAAA;EACA,EAAA;EACA,IAAA;EACA,iBAAA,GAAoB,MAAA,+BAHpB;EAKA,UAAA,YAAsB,iBAAA,gBAHtB;EAKA,YAAA,uBAJoB;EAMpB,MAAA,GAAS,aAAA,oCAJa;EAMtB,QAAA;AAAA"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;KAGY,qBAAA;EACN,IAAA;EAAc,IAAA;AAAA;EACd,IAAA;EAAoB,IAAA;AAAA;EACpB,IAAA;EAAmB,SAAA;IAAsB,GAAA;EAAA;AAAA;EACzC,IAAA;EAAkB,QAAA;AAAA;EAElB,IAAA;EACA,OAAA,EAAS,KAAK;IAAG,IAAA;IAAsB,IAAA;EAAA;AAAA;EAGvC,IAAA;EACA,IAAA;EACA,SAAA;EACA,WAAA;EACA,QAAA;IAAa,QAAA;EAAA;AAAA;EAEb,IAAA;AAAA;AAAA,KAEM,iBAAA;EACV,EAAA;EACA,IAAA;EACA,IAAA,EAAM,MAAM;AAAA;;AAAA;AAUd;;;;;KAAY,oBAAA;EACV,QAAA;EACA,OAAA;EACA,EAAA;EACA,IAAA;EACA,iBAAA,GAAoB,MAAA,+BADpB;EAGA,UAAA,YAAsB,iBAAA,gBAFF;EAIpB,YAAA,uBAFsB;EAItB,MAAA,GAAS,aAAA,oCAAT;EAEA,QAAA;AAAA"}
@@ -1,7 +1,7 @@
1
1
  /// <reference types="@assistant-ui/core/store" />
2
2
  import { LangChainBaseMessage } from "./types.js";
3
- import { AttachmentAdapter, DictationAdapter, ExternalStoreSharedOptions, FeedbackAdapter, RealtimeVoiceAdapter, RemoteThreadListAdapter, SpeechSynthesisAdapter } from "@assistant-ui/core";
4
- import { UseStreamOptions } from "@langchain/react";
3
+ import { AppendMessage, AttachmentAdapter, DictationAdapter, ExternalStoreSharedOptions, FeedbackAdapter, RealtimeVoiceAdapter, RemoteThreadListAdapter, SpeechSynthesisAdapter } from "@assistant-ui/core";
4
+ import { AssembledToolCall, UseStreamOptions } from "@langchain/react";
5
5
  import { AssistantCloud } from "assistant-cloud";
6
6
 
7
7
  //#region src/useStreamRuntime.d.ts
@@ -36,6 +36,11 @@ type LangChainRuntimeExtraOptions = ExternalStoreSharedOptions & {
36
36
  }>) | undefined; /** Custom thread-deletion hook, forwarded to the cloud adapter. */
37
37
  delete?: ((threadId: string) => Promise<void>) | undefined;
38
38
  };
39
+ declare const runConfigToSubmitOptions: (runConfig: AppendMessage["runConfig"]) => {
40
+ config: {
41
+ configurable: Record<string, unknown>;
42
+ };
43
+ } | undefined;
39
44
  type UseStreamRuntimeOptions = UseStreamOptions extends infer O ? O extends UseStreamOptions ? O & LangChainRuntimeExtraOptions : never : never;
40
45
  /**
41
46
  * Creates an assistant-ui runtime backed by LangChain's `useStream` hook.
@@ -68,6 +73,15 @@ declare const useStreamRuntime: (rawOptions: UseStreamRuntimeOptions) => import(
68
73
  declare const useLangChainInterruptState: () => {
69
74
  value?: unknown;
70
75
  } | undefined;
76
+ /** Read the last run/hydration error from the runtime extras. */
77
+ declare const useLangChainError: () => unknown;
78
+ /**
79
+ * Read the root tool calls assembled by `useStream` from the `tools`
80
+ * channel. Defaults to an empty array, so consumers can `.map` without
81
+ * a guard. Useful for rendering pending/streamed tool calls and
82
+ * approval UIs.
83
+ */
84
+ declare const useLangChainToolCalls: () => readonly AssembledToolCall<string, unknown, unknown>[];
71
85
  /**
72
86
  * Returns a function to submit raw state updates to the LangGraph agent,
73
87
  * bypassing the normal message flow. Useful for sending interrupt resume
@@ -102,5 +116,5 @@ declare const useLangChainSendCommand: () => (command: Record<string, unknown>)
102
116
  declare function useLangChainState<T>(key: string): T | undefined;
103
117
  declare function useLangChainState<T>(key: string, defaultValue: T): T;
104
118
  //#endregion
105
- export { UseStreamRuntimeOptions, useLangChainInterruptState, useLangChainSend, useLangChainSendCommand, useLangChainState, useLangChainSubmit, useStreamRuntime };
119
+ export { UseStreamRuntimeOptions, runConfigToSubmitOptions, useLangChainError, useLangChainInterruptState, useLangChainSend, useLangChainSendCommand, useLangChainState, useLangChainSubmit, useLangChainToolCalls, useStreamRuntime };
106
120
  //# sourceMappingURL=useStreamRuntime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useStreamRuntime.d.ts","names":[],"sources":["../src/useStreamRuntime.ts"],"mappings":";;;;;;KAsDK,4BAAA,GAA+B,0BAAA;EAClC,KAAA,GAAQ,cAAA;EACR,QAAA;IAEM,WAAA,GAAc,iBAAA;IACd,MAAA,GAAS,sBAAA;IACT,SAAA,GAAY,gBAAA;IACZ,KAAA,GAAQ,oBAAA;IACR,QAAA,GAAW,eAAA;EAAA;EAFC;;;;;;EAWlB,0BAAA;EAcuC;;;;EATvC,0BAAA;EAlBM;;;;EAuBN,0BAAA,GAA6B,uBAAA,cArBX;EAuBlB,MAAA,UAAgB,OAAA;IAAU,UAAA;EAAA,iBArBT;EAuBjB,MAAA,KAAW,QAAA,aAAqB,OAAA;AAAA;AAAA,KAsBtB,uBAAA,GAA0B,gBAAA,mBAClC,CAAA,SAAU,gBAAA,GACR,CAAA,GAAI,4BAAA;;;;;;;;;AAxB+B;AAsBzC;;;;;;;;;;;;;;;cAqMa,gBAAA,GAAoB,UAAA,EAAY,uBAAuB,kCAAA,gBAAA;AAnM9B;AAmMtC;;AAnMsC,cAkOzB,0BAAA;EAMZ,KAAA;AAAA;;;;AAXA;AAKD;cAaa,kBAAA,SAGT,MAAA,EAAQ,MAAA,sCACR,OAAA,GAAU,MAAA,sBAAuB,OAAA;;;;AAJrC;;cAiBa,gBAAA,SAGT,QAAA,WAAmB,oBAAA,IACnB,OAAA,GAAU,MAAA,sBAAuB,OAAA;;;;;;;;cAgBxB,uBAAA,SAEH,OAAA,EAAS,MAAA,sBAAuB,OAAA;;;;AAnCL;AAarC;;;;;;;iBAoCgB,iBAAA,IAAqB,GAAA,WAAc,CAAC;AAAA,iBACpC,iBAAA,IAAqB,GAAA,UAAa,YAAA,EAAc,CAAA,GAAI,CAAC"}
1
+ {"version":3,"file":"useStreamRuntime.d.ts","names":[],"sources":["../src/useStreamRuntime.ts"],"mappings":";;;;;;KA8DK,4BAAA,GAA+B,0BAAA;EAClC,KAAA,GAAQ,cAAA;EACR,QAAA;IAEM,WAAA,GAAc,iBAAA;IACd,MAAA,GAAS,sBAAA;IACT,SAAA,GAAY,gBAAA;IACZ,KAAA,GAAQ,oBAAA;IACR,QAAA,GAAW,eAAA;EAAA;EAFC;;;;;;EAWlB,0BAAA;EAcuC;;;;EATvC,0BAAA;EAlBM;;;;EAuBN,0BAAA,GAA6B,uBAAA,cArBX;EAuBlB,MAAA,UAAgB,OAAA;IAAU,UAAA;EAAA,iBArBT;EAuBjB,MAAA,KAAW,QAAA,aAAqB,OAAA;AAAA;AAAA,cAGrB,wBAAA,GACX,SAAA,EAAW,aAAA;;kBAAA,MAAA;EAAA;AAAA;AAAA,KAyBD,uBAAA,GAA0B,gBAAA,mBAClC,CAAA,SAAU,gBAAA,GACR,CAAA,GAAI,4BAAA;;;;AA/B+B;AAGzC;;;;;;;;;;;AA0BA;;;;;;;;;cA2Ma,gBAAA,GAAoB,UAAA,EAAY,uBAAuB,kCAAA,gBAAA;;;;cA+BvD,0BAAA;EAMZ,KAAA;AAAA;AA9OqC;AAAA,cAiPzB,iBAAA;;;;;;;cAcA,qBAAA,iBAAqB,iBAAA;AA5BjC;AAKD;;;;AALC,cAyCY,kBAAA,SAGT,MAAA,EAAQ,MAAA,sCACR,OAAA,GAAU,MAAA,sBAAuB,OAAA;AA/BrC;;;;AAMC;AAND,cA4Ca,gBAAA,SAGT,QAAA,WAAmB,oBAAA,IACnB,OAAA,GAAU,MAAA,sBAAuB,OAAA;;;;AAlCH;AAalC;;;cAqCa,uBAAA,SAEH,OAAA,EAAS,MAAA,sBAAuB,OAAA;;;;;;;;;;;AAnCL;iBAiDrB,iBAAA,IAAqB,GAAA,WAAc,CAAC;AAAA,iBACpC,iBAAA,IAAqB,GAAA,UAAa,YAAA,EAAc,CAAA,GAAI,CAAC"}
@@ -7,10 +7,12 @@ import { useAui, useAuiState } from "@assistant-ui/store";
7
7
  import { useStream } from "@langchain/react";
8
8
  //#region src/useStreamRuntime.ts
9
9
  const symbolLangChainRuntimeExtras = Symbol("langchain-runtime-extras");
10
+ const EMPTY_TOOL_CALLS = [];
10
11
  const asLangChainRuntimeExtras = (extras) => {
11
12
  if (typeof extras !== "object" || extras == null || !(symbolLangChainRuntimeExtras in extras)) throw new Error("This method can only be called when you are using useStreamRuntime");
12
13
  return extras;
13
14
  };
15
+ const runConfigToSubmitOptions = (runConfig) => runConfig?.custom ? { config: { configurable: runConfig.custom } } : void 0;
14
16
  const getPendingToolCalls = (messages) => {
15
17
  const pending = /* @__PURE__ */ new Map();
16
18
  for (const m of messages) {
@@ -72,12 +74,16 @@ const useStreamThreadRuntime = (options) => {
72
74
  [symbolLangChainRuntimeExtras]: true,
73
75
  interrupt: stream.interrupt,
74
76
  interrupts: stream.interrupts,
77
+ toolCalls: stream.toolCalls,
78
+ error: stream.error,
75
79
  submit: stream.submit,
76
80
  values: stream.values,
77
81
  messagesKey
78
82
  }), [
79
83
  stream.interrupt,
80
84
  stream.interrupts,
85
+ stream.toolCalls,
86
+ stream.error,
81
87
  stream.submit,
82
88
  stream.values,
83
89
  messagesKey
@@ -85,6 +91,7 @@ const useStreamThreadRuntime = (options) => {
85
91
  return useExternalStoreRuntime({
86
92
  ...pickExternalStoreSharedOptions(options),
87
93
  isRunning: effectiveIsRunning,
94
+ isLoading: stream.isThreadLoading,
88
95
  messages: threadMessages,
89
96
  adapters,
90
97
  extras,
@@ -102,7 +109,7 @@ const useStreamThreadRuntime = (options) => {
102
109
  await stream.submit({ [messagesKey]: [...cancellations, {
103
110
  type: "human",
104
111
  content
105
- }] });
112
+ }] }, runConfigToSubmitOptions(msg.runConfig));
106
113
  },
107
114
  onAddToolResult: async ({ toolCallId, toolName, result, isError, artifact }) => {
108
115
  await stream.submit({ [messagesKey]: [{
@@ -170,6 +177,27 @@ const useLangChainInterruptState = () => {
170
177
  return asLangChainRuntimeExtras(extras).interrupt;
171
178
  });
172
179
  };
180
+ /** Read the last run/hydration error from the runtime extras. */
181
+ const useLangChainError = () => {
182
+ return useAuiState((s) => {
183
+ const extras = s.thread.extras;
184
+ if (!extras) return void 0;
185
+ return asLangChainRuntimeExtras(extras).error;
186
+ });
187
+ };
188
+ /**
189
+ * Read the root tool calls assembled by `useStream` from the `tools`
190
+ * channel. Defaults to an empty array, so consumers can `.map` without
191
+ * a guard. Useful for rendering pending/streamed tool calls and
192
+ * approval UIs.
193
+ */
194
+ const useLangChainToolCalls = () => {
195
+ return useAuiState((s) => {
196
+ const extras = s.thread.extras;
197
+ if (!extras) return EMPTY_TOOL_CALLS;
198
+ return asLangChainRuntimeExtras(extras).toolCalls ?? EMPTY_TOOL_CALLS;
199
+ });
200
+ };
173
201
  /**
174
202
  * Returns a function to submit raw state updates to the LangGraph agent,
175
203
  * bypassing the normal message flow. Useful for sending interrupt resume
@@ -215,6 +243,6 @@ function useLangChainState(key, defaultValue) {
215
243
  });
216
244
  }
217
245
  //#endregion
218
- export { useLangChainInterruptState, useLangChainSend, useLangChainSendCommand, useLangChainState, useLangChainSubmit, useStreamRuntime };
246
+ export { runConfigToSubmitOptions, useLangChainError, useLangChainInterruptState, useLangChainSend, useLangChainSendCommand, useLangChainState, useLangChainSubmit, useLangChainToolCalls, useStreamRuntime };
219
247
 
220
248
  //# sourceMappingURL=useStreamRuntime.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useStreamRuntime.js","names":["_exhaustiveCheck"],"sources":["../src/useStreamRuntime.ts"],"sourcesContent":["/// <reference types=\"@assistant-ui/core/store\" />\n\"use client\";\n\nimport { useMemo, useRef, useState } from \"react\";\nimport type {\n AppendMessage,\n AttachmentAdapter,\n DictationAdapter,\n ExternalStoreSharedOptions,\n FeedbackAdapter,\n RealtimeVoiceAdapter,\n RemoteThreadListAdapter,\n SpeechSynthesisAdapter,\n ToolExecutionStatus,\n} from \"@assistant-ui/core\";\nimport { pickExternalStoreSharedOptions } from \"@assistant-ui/core\";\nimport {\n useCloudThreadListAdapter,\n useExternalStoreRuntime,\n useExternalMessageConverter,\n useRemoteThreadListRuntime,\n} from \"@assistant-ui/core/react\";\nimport { useAui, useAuiState } from \"@assistant-ui/store\";\nimport type { AssistantCloud } from \"assistant-cloud\";\nimport { useStream, type UseStreamOptions } from \"@langchain/react\";\nimport type { LangChainBaseMessage, LangChainToolCall } from \"./types\";\nimport { convertLangChainBaseMessage, getMessageType } from \"./convertMessages\";\n\nconst symbolLangChainRuntimeExtras = Symbol(\"langchain-runtime-extras\");\n\ntype LangChainRuntimeExtras = {\n [symbolLangChainRuntimeExtras]: true;\n interrupt: { value?: unknown } | undefined;\n interrupts: readonly { value?: unknown }[];\n submit: (\n values: Record<string, unknown> | null | undefined,\n options?: Record<string, unknown>,\n ) => Promise<void>;\n values: Record<string, unknown>;\n messagesKey: string;\n};\n\nconst asLangChainRuntimeExtras = (extras: unknown): LangChainRuntimeExtras => {\n if (\n typeof extras !== \"object\" ||\n extras == null ||\n !(symbolLangChainRuntimeExtras in extras)\n )\n throw new Error(\n \"This method can only be called when you are using useStreamRuntime\",\n );\n return extras as LangChainRuntimeExtras;\n};\n\ntype LangChainRuntimeExtraOptions = ExternalStoreSharedOptions & {\n cloud?: AssistantCloud | undefined;\n adapters?:\n | {\n attachments?: AttachmentAdapter | undefined;\n speech?: SpeechSynthesisAdapter | undefined;\n dictation?: DictationAdapter | undefined;\n voice?: RealtimeVoiceAdapter | undefined;\n feedback?: FeedbackAdapter | undefined;\n }\n | undefined;\n /**\n * When the user sends a new message while previous tool calls are\n * still pending, automatically submit `tool` messages that cancel\n * them so the agent's tool-call accounting stays consistent.\n * Defaults to `true`.\n */\n autoCancelPendingToolCalls?: boolean | undefined;\n /**\n * Routes the Cancel button's click to `useStream().stop()`. On by\n * default. Pass `false` to disable the Cancel button.\n */\n unstable_allowCancellation?: boolean | undefined;\n /**\n * Custom `RemoteThreadListAdapter`. When provided, replaces the\n * cloud-backed thread list adapter.\n */\n unstable_threadListAdapter?: RemoteThreadListAdapter | undefined;\n /** Custom thread-creation hook, forwarded to the cloud adapter. */\n create?: (() => Promise<{ externalId: string | undefined }>) | undefined;\n /** Custom thread-deletion hook, forwarded to the cloud adapter. */\n delete?: ((threadId: string) => Promise<void>) | undefined;\n};\n\nconst getPendingToolCalls = (\n messages: readonly LangChainBaseMessage[],\n): LangChainToolCall[] => {\n const pending = new Map<string, LangChainToolCall>();\n for (const m of messages) {\n const type = getMessageType(m);\n if (type === \"ai\") {\n for (const tc of m.tool_calls ?? []) pending.set(tc.id, tc);\n } else if (type === \"tool\" && m.tool_call_id) {\n pending.delete(m.tool_call_id);\n }\n }\n return [...pending.values()];\n};\n\n// Distribute the intersection through the union arms of `UseStreamOptions`\n// (`AgentServerOptions | CustomAdapterOptions`). Writing `UseStreamOptions & X`\n// directly collapses arm tracking, so `Omit<…, \"cloud\">` and the like would\n// produce a flattened structural type that no longer matches either arm.\nexport type UseStreamRuntimeOptions = UseStreamOptions extends infer O\n ? O extends UseStreamOptions\n ? O & LangChainRuntimeExtraOptions\n : never\n : never;\n\nconst 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) => part.type === \"file\" || part.type === \"image\",\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.map((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 return {\n type: \"file\" as const,\n data: part.data,\n mime_type: part.mimeType,\n metadata: { filename: part.filename ?? \"file\" },\n source_type: \"base64\" as const,\n };\n case \"tool-call\":\n throw new Error(\"Tool call appends are not supported.\");\n default: {\n const _exhaustiveCheck:\n | \"reasoning\"\n | \"source\"\n | \"audio\"\n | \"data\"\n | \"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\ntype DistributiveOmit<T, K extends keyof any> = T extends unknown\n ? Omit<T, K>\n : never;\n\nconst useStreamThreadRuntime = (\n options: DistributiveOmit<\n UseStreamRuntimeOptions,\n \"cloud\" | \"unstable_threadListAdapter\" | \"create\" | \"delete\"\n >,\n) => {\n const { adapters, autoCancelPendingToolCalls, unstable_allowCancellation } =\n options;\n const messagesKey = options.messagesKey ?? \"messages\";\n\n const externalId = useAuiState((s) => s.threadListItem.externalId) as\n | string\n | null;\n // Mutate in place rather than `{ ...options, threadId }`: spreading\n // `UseStreamOptions` (a discriminated union on `transport`) into an object\n // literal merges both arms' transport types, breaking arm assignment.\n options.threadId = externalId;\n\n const stream = useStream(options);\n\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n const hasExecutingTools = Object.values(toolStatuses).some(\n (s) => s?.type === \"executing\",\n );\n const effectiveIsRunning = stream.isLoading || hasExecutingTools;\n\n const threadMessages = useExternalMessageConverter({\n callback: convertLangChainBaseMessage,\n messages: stream.messages as LangChainBaseMessage[],\n isRunning: effectiveIsRunning,\n });\n\n const streamRef = useRef(stream);\n streamRef.current = stream;\n\n const extras = useMemo(\n (): LangChainRuntimeExtras => ({\n [symbolLangChainRuntimeExtras]: true,\n interrupt: stream.interrupt,\n interrupts: stream.interrupts,\n submit: stream.submit,\n values: stream.values,\n messagesKey,\n }),\n [\n stream.interrupt,\n stream.interrupts,\n stream.submit,\n stream.values,\n messagesKey,\n ],\n );\n\n const runtime = useExternalStoreRuntime({\n ...pickExternalStoreSharedOptions(options),\n isRunning: effectiveIsRunning,\n messages: threadMessages,\n adapters,\n extras,\n unstable_enableToolInvocations: true,\n setToolStatuses,\n onNew: async (msg) => {\n const content = getMessageContent(msg);\n const cancellations =\n autoCancelPendingToolCalls !== false\n ? getPendingToolCalls(\n streamRef.current.messages as readonly LangChainBaseMessage[],\n ).map((t) => ({\n type: \"tool\" as const,\n name: t.name,\n tool_call_id: t.id,\n content: JSON.stringify({ cancelled: true }),\n status: \"error\" as const,\n }))\n : [];\n await stream.submit({\n [messagesKey]: [...cancellations, { type: \"human\", content }],\n });\n },\n onAddToolResult: async ({\n toolCallId,\n toolName,\n result,\n isError,\n artifact,\n }) => {\n await stream.submit({\n [messagesKey]: [\n {\n type: \"tool\",\n name: toolName,\n tool_call_id: toolCallId,\n content: JSON.stringify(result),\n ...(artifact !== undefined && { artifact }),\n status: isError ? \"error\" : \"success\",\n },\n ],\n });\n },\n onCancel:\n unstable_allowCancellation !== false\n ? async () => {\n await stream.stop();\n }\n : undefined,\n });\n\n return runtime;\n};\n\n/**\n * Creates an assistant-ui runtime backed by LangChain's `useStream` hook.\n * Accepts the same options as `useStream` from `@langchain/react`, plus\n * `cloud` and `adapters`.\n *\n * @example\n * ```tsx\n * import { useStreamRuntime } from \"@assistant-ui/react-langchain\";\n * import { AssistantRuntimeProvider, Thread } from \"@assistant-ui/react\";\n *\n * function App() {\n * const runtime = useStreamRuntime({\n * assistantId: \"agent\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * return (\n * <AssistantRuntimeProvider runtime={runtime}>\n * <Thread />\n * </AssistantRuntimeProvider>\n * );\n * }\n * ```\n */\nexport const useStreamRuntime = (rawOptions: UseStreamRuntimeOptions) => {\n const {\n cloud,\n unstable_threadListAdapter,\n create,\n delete: deleteFn,\n ...options\n } = rawOptions;\n\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const cloudAdapter = useCloudThreadListAdapter({\n cloud,\n create,\n delete: deleteFn,\n });\n const adapter = unstable_threadListAdapter ?? cloudAdapter;\n\n return useRemoteThreadListRuntime({\n runtimeHook: function RuntimeHook() {\n return useStreamThreadRuntime(optionsRef.current);\n },\n adapter,\n allowNesting: true,\n });\n};\n\n/**\n * Read the current LangGraph interrupt state from the runtime extras.\n */\nexport const useLangChainInterruptState = () => {\n return useAuiState((s) => {\n const extras = s.thread.extras;\n if (!extras) return undefined;\n return asLangChainRuntimeExtras(extras).interrupt;\n });\n};\n\n/**\n * Returns a function to submit raw state updates to the LangGraph agent,\n * bypassing the normal message flow. Useful for sending interrupt resume\n * commands.\n */\nexport const useLangChainSubmit = () => {\n const aui = useAui();\n return (\n values: Record<string, unknown> | null | undefined,\n options?: Record<string, unknown>,\n ) => {\n const extras = aui.thread().getState().extras;\n const { submit } = asLangChainRuntimeExtras(extras);\n return submit(values, options);\n };\n};\n\n/**\n * Submit a list of LangChain-shaped messages on the current thread.\n * Parity helper for migrating from `useLangGraphSend`. Routes to\n * `useStream().submit({ [messagesKey]: messages }, options)`.\n */\nexport const useLangChainSend = () => {\n const aui = useAui();\n return (\n messages: readonly LangChainBaseMessage[],\n options?: Record<string, unknown>,\n ) => {\n const { submit, messagesKey } = asLangChainRuntimeExtras(\n aui.thread().getState().extras,\n );\n return submit({ [messagesKey]: messages }, options);\n };\n};\n\n/**\n * Submit a `useStream` command (e.g. interrupt resume). Parity helper\n * for migrating from `useLangGraphSendCommand`. Note that v1's command\n * shape (`{ resume?, goto?, update? }`) differs from the legacy\n * `{ resume: string }` form — to carry a payload, use the input or\n * `stream.respond` instead.\n */\nexport const useLangChainSendCommand = () => {\n const submit = useLangChainSubmit();\n return (command: Record<string, unknown>) => submit(null, { command });\n};\n\n/**\n * Read a custom LangGraph state key from the current thread. Mirrors\n * `useStream().values[key]` from `@langchain/react` and updates when the\n * stream emits new state.\n *\n * @example\n * ```tsx\n * const todos = useLangChainState<Todo[]>(\"todos\");\n * const files = useLangChainState<Record<string, string>>(\"files\", {});\n * ```\n */\nexport function useLangChainState<T>(key: string): T | undefined;\nexport function useLangChainState<T>(key: string, defaultValue: T): T;\nexport function useLangChainState<T>(\n key: string,\n defaultValue?: T,\n): T | undefined {\n return useAuiState((s) => {\n const extras = s.thread.extras;\n if (!extras) return defaultValue;\n const value = asLangChainRuntimeExtras(extras).values[key] as T | undefined;\n return value !== undefined ? value : defaultValue;\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useStreamRuntime.js","names":["_exhaustiveCheck"],"sources":["../src/useStreamRuntime.ts"],"sourcesContent":["/// <reference types=\"@assistant-ui/core/store\" />\n\"use client\";\n\nimport { useMemo, useRef, useState } from \"react\";\nimport type {\n AppendMessage,\n AttachmentAdapter,\n DictationAdapter,\n ExternalStoreSharedOptions,\n FeedbackAdapter,\n RealtimeVoiceAdapter,\n RemoteThreadListAdapter,\n SpeechSynthesisAdapter,\n ToolExecutionStatus,\n} from \"@assistant-ui/core\";\nimport { pickExternalStoreSharedOptions } from \"@assistant-ui/core\";\nimport {\n useCloudThreadListAdapter,\n useExternalStoreRuntime,\n useExternalMessageConverter,\n useRemoteThreadListRuntime,\n} from \"@assistant-ui/core/react\";\nimport { useAui, useAuiState } from \"@assistant-ui/store\";\nimport type { AssistantCloud } from \"assistant-cloud\";\nimport {\n useStream,\n type UseStreamOptions,\n type AssembledToolCall,\n} from \"@langchain/react\";\nimport type { LangChainBaseMessage, LangChainToolCall } from \"./types\";\nimport { convertLangChainBaseMessage, getMessageType } from \"./convertMessages\";\n\nconst symbolLangChainRuntimeExtras = Symbol(\"langchain-runtime-extras\");\n\nconst EMPTY_TOOL_CALLS: readonly AssembledToolCall[] = [];\n\ntype LangChainRuntimeExtras = {\n [symbolLangChainRuntimeExtras]: true;\n interrupt: { value?: unknown } | undefined;\n interrupts: readonly { value?: unknown }[];\n toolCalls: readonly AssembledToolCall[];\n error: unknown;\n submit: (\n values: Record<string, unknown> | null | undefined,\n options?: Record<string, unknown>,\n ) => Promise<void>;\n values: Record<string, unknown>;\n messagesKey: string;\n};\n\nconst asLangChainRuntimeExtras = (extras: unknown): LangChainRuntimeExtras => {\n if (\n typeof extras !== \"object\" ||\n extras == null ||\n !(symbolLangChainRuntimeExtras in extras)\n )\n throw new Error(\n \"This method can only be called when you are using useStreamRuntime\",\n );\n return extras as LangChainRuntimeExtras;\n};\n\ntype LangChainRuntimeExtraOptions = ExternalStoreSharedOptions & {\n cloud?: AssistantCloud | undefined;\n adapters?:\n | {\n attachments?: AttachmentAdapter | undefined;\n speech?: SpeechSynthesisAdapter | undefined;\n dictation?: DictationAdapter | undefined;\n voice?: RealtimeVoiceAdapter | undefined;\n feedback?: FeedbackAdapter | undefined;\n }\n | undefined;\n /**\n * When the user sends a new message while previous tool calls are\n * still pending, automatically submit `tool` messages that cancel\n * them so the agent's tool-call accounting stays consistent.\n * Defaults to `true`.\n */\n autoCancelPendingToolCalls?: boolean | undefined;\n /**\n * Routes the Cancel button's click to `useStream().stop()`. On by\n * default. Pass `false` to disable the Cancel button.\n */\n unstable_allowCancellation?: boolean | undefined;\n /**\n * Custom `RemoteThreadListAdapter`. When provided, replaces the\n * cloud-backed thread list adapter.\n */\n unstable_threadListAdapter?: RemoteThreadListAdapter | undefined;\n /** Custom thread-creation hook, forwarded to the cloud adapter. */\n create?: (() => Promise<{ externalId: string | undefined }>) | undefined;\n /** Custom thread-deletion hook, forwarded to the cloud adapter. */\n delete?: ((threadId: string) => Promise<void>) | undefined;\n};\n\nexport const runConfigToSubmitOptions = (\n runConfig: AppendMessage[\"runConfig\"],\n) =>\n runConfig?.custom\n ? { config: { configurable: runConfig.custom } }\n : undefined;\n\nconst getPendingToolCalls = (\n messages: readonly LangChainBaseMessage[],\n): LangChainToolCall[] => {\n const pending = new Map<string, LangChainToolCall>();\n for (const m of messages) {\n const type = getMessageType(m);\n if (type === \"ai\") {\n for (const tc of m.tool_calls ?? []) pending.set(tc.id, tc);\n } else if (type === \"tool\" && m.tool_call_id) {\n pending.delete(m.tool_call_id);\n }\n }\n return [...pending.values()];\n};\n\n// Distribute the intersection through the union arms of `UseStreamOptions`\n// (`AgentServerOptions | CustomAdapterOptions`). Writing `UseStreamOptions & X`\n// directly collapses arm tracking, so `Omit<…, \"cloud\">` and the like would\n// produce a flattened structural type that no longer matches either arm.\nexport type UseStreamRuntimeOptions = UseStreamOptions extends infer O\n ? O extends UseStreamOptions\n ? O & LangChainRuntimeExtraOptions\n : never\n : never;\n\nconst 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) => part.type === \"file\" || part.type === \"image\",\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.map((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 return {\n type: \"file\" as const,\n data: part.data,\n mime_type: part.mimeType,\n metadata: { filename: part.filename ?? \"file\" },\n source_type: \"base64\" as const,\n };\n case \"tool-call\":\n throw new Error(\"Tool call appends are not supported.\");\n default: {\n const _exhaustiveCheck:\n | \"reasoning\"\n | \"source\"\n | \"audio\"\n | \"data\"\n | \"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\ntype DistributiveOmit<T, K extends keyof any> = T extends unknown\n ? Omit<T, K>\n : never;\n\nconst useStreamThreadRuntime = (\n options: DistributiveOmit<\n UseStreamRuntimeOptions,\n \"cloud\" | \"unstable_threadListAdapter\" | \"create\" | \"delete\"\n >,\n) => {\n const { adapters, autoCancelPendingToolCalls, unstable_allowCancellation } =\n options;\n const messagesKey = options.messagesKey ?? \"messages\";\n\n const externalId = useAuiState((s) => s.threadListItem.externalId) as\n | string\n | null;\n // Mutate in place rather than `{ ...options, threadId }`: spreading\n // `UseStreamOptions` (a discriminated union on `transport`) into an object\n // literal merges both arms' transport types, breaking arm assignment.\n options.threadId = externalId;\n\n const stream = useStream(options);\n\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n const hasExecutingTools = Object.values(toolStatuses).some(\n (s) => s?.type === \"executing\",\n );\n const effectiveIsRunning = stream.isLoading || hasExecutingTools;\n\n const threadMessages = useExternalMessageConverter({\n callback: convertLangChainBaseMessage,\n messages: stream.messages as LangChainBaseMessage[],\n isRunning: effectiveIsRunning,\n });\n\n const streamRef = useRef(stream);\n streamRef.current = stream;\n\n const extras = useMemo(\n (): LangChainRuntimeExtras => ({\n [symbolLangChainRuntimeExtras]: true,\n interrupt: stream.interrupt,\n interrupts: stream.interrupts,\n toolCalls: stream.toolCalls,\n error: stream.error,\n submit: stream.submit,\n values: stream.values,\n messagesKey,\n }),\n [\n stream.interrupt,\n stream.interrupts,\n stream.toolCalls,\n stream.error,\n stream.submit,\n stream.values,\n messagesKey,\n ],\n );\n\n const runtime = useExternalStoreRuntime({\n ...pickExternalStoreSharedOptions(options),\n isRunning: effectiveIsRunning,\n isLoading: stream.isThreadLoading,\n messages: threadMessages,\n adapters,\n extras,\n unstable_enableToolInvocations: true,\n setToolStatuses,\n onNew: async (msg) => {\n const content = getMessageContent(msg);\n const cancellations =\n autoCancelPendingToolCalls !== false\n ? getPendingToolCalls(\n streamRef.current.messages as readonly LangChainBaseMessage[],\n ).map((t) => ({\n type: \"tool\" as const,\n name: t.name,\n tool_call_id: t.id,\n content: JSON.stringify({ cancelled: true }),\n status: \"error\" as const,\n }))\n : [];\n await stream.submit(\n { [messagesKey]: [...cancellations, { type: \"human\", content }] },\n runConfigToSubmitOptions(msg.runConfig),\n );\n },\n onAddToolResult: async ({\n toolCallId,\n toolName,\n result,\n isError,\n artifact,\n }) => {\n await stream.submit({\n [messagesKey]: [\n {\n type: \"tool\",\n name: toolName,\n tool_call_id: toolCallId,\n content: JSON.stringify(result),\n ...(artifact !== undefined && { artifact }),\n status: isError ? \"error\" : \"success\",\n },\n ],\n });\n },\n onCancel:\n unstable_allowCancellation !== false\n ? async () => {\n await stream.stop();\n }\n : undefined,\n });\n\n return runtime;\n};\n\n/**\n * Creates an assistant-ui runtime backed by LangChain's `useStream` hook.\n * Accepts the same options as `useStream` from `@langchain/react`, plus\n * `cloud` and `adapters`.\n *\n * @example\n * ```tsx\n * import { useStreamRuntime } from \"@assistant-ui/react-langchain\";\n * import { AssistantRuntimeProvider, Thread } from \"@assistant-ui/react\";\n *\n * function App() {\n * const runtime = useStreamRuntime({\n * assistantId: \"agent\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * return (\n * <AssistantRuntimeProvider runtime={runtime}>\n * <Thread />\n * </AssistantRuntimeProvider>\n * );\n * }\n * ```\n */\nexport const useStreamRuntime = (rawOptions: UseStreamRuntimeOptions) => {\n const {\n cloud,\n unstable_threadListAdapter,\n create,\n delete: deleteFn,\n ...options\n } = rawOptions;\n\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const cloudAdapter = useCloudThreadListAdapter({\n cloud,\n create,\n delete: deleteFn,\n });\n const adapter = unstable_threadListAdapter ?? cloudAdapter;\n\n return useRemoteThreadListRuntime({\n runtimeHook: function RuntimeHook() {\n return useStreamThreadRuntime(optionsRef.current);\n },\n adapter,\n allowNesting: true,\n });\n};\n\n/**\n * Read the current LangGraph interrupt state from the runtime extras.\n */\nexport const useLangChainInterruptState = () => {\n return useAuiState((s) => {\n const extras = s.thread.extras;\n if (!extras) return undefined;\n return asLangChainRuntimeExtras(extras).interrupt;\n });\n};\n\n/** Read the last run/hydration error from the runtime extras. */\nexport const useLangChainError = () => {\n return useAuiState((s) => {\n const extras = s.thread.extras;\n if (!extras) return undefined;\n return asLangChainRuntimeExtras(extras).error;\n });\n};\n\n/**\n * Read the root tool calls assembled by `useStream` from the `tools`\n * channel. Defaults to an empty array, so consumers can `.map` without\n * a guard. Useful for rendering pending/streamed tool calls and\n * approval UIs.\n */\nexport const useLangChainToolCalls = () => {\n return useAuiState((s) => {\n const extras = s.thread.extras;\n if (!extras) return EMPTY_TOOL_CALLS;\n return asLangChainRuntimeExtras(extras).toolCalls ?? EMPTY_TOOL_CALLS;\n });\n};\n\n/**\n * Returns a function to submit raw state updates to the LangGraph agent,\n * bypassing the normal message flow. Useful for sending interrupt resume\n * commands.\n */\nexport const useLangChainSubmit = () => {\n const aui = useAui();\n return (\n values: Record<string, unknown> | null | undefined,\n options?: Record<string, unknown>,\n ) => {\n const extras = aui.thread().getState().extras;\n const { submit } = asLangChainRuntimeExtras(extras);\n return submit(values, options);\n };\n};\n\n/**\n * Submit a list of LangChain-shaped messages on the current thread.\n * Parity helper for migrating from `useLangGraphSend`. Routes to\n * `useStream().submit({ [messagesKey]: messages }, options)`.\n */\nexport const useLangChainSend = () => {\n const aui = useAui();\n return (\n messages: readonly LangChainBaseMessage[],\n options?: Record<string, unknown>,\n ) => {\n const { submit, messagesKey } = asLangChainRuntimeExtras(\n aui.thread().getState().extras,\n );\n return submit({ [messagesKey]: messages }, options);\n };\n};\n\n/**\n * Submit a `useStream` command (e.g. interrupt resume). Parity helper\n * for migrating from `useLangGraphSendCommand`. Note that v1's command\n * shape (`{ resume?, goto?, update? }`) differs from the legacy\n * `{ resume: string }` form — to carry a payload, use the input or\n * `stream.respond` instead.\n */\nexport const useLangChainSendCommand = () => {\n const submit = useLangChainSubmit();\n return (command: Record<string, unknown>) => submit(null, { command });\n};\n\n/**\n * Read a custom LangGraph state key from the current thread. Mirrors\n * `useStream().values[key]` from `@langchain/react` and updates when the\n * stream emits new state.\n *\n * @example\n * ```tsx\n * const todos = useLangChainState<Todo[]>(\"todos\");\n * const files = useLangChainState<Record<string, string>>(\"files\", {});\n * ```\n */\nexport function useLangChainState<T>(key: string): T | undefined;\nexport function useLangChainState<T>(key: string, defaultValue: T): T;\nexport function useLangChainState<T>(\n key: string,\n defaultValue?: T,\n): T | undefined {\n return useAuiState((s) => {\n const extras = s.thread.extras;\n if (!extras) return defaultValue;\n const value = asLangChainRuntimeExtras(extras).values[key] as T | undefined;\n return value !== undefined ? value : defaultValue;\n });\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react-langchain",
3
- "version": "0.0.14",
3
+ "version": "0.0.15",
4
4
  "description": "LangChain useStream adapter for assistant-ui",
5
5
  "keywords": [
6
6
  "langchain",
@@ -29,7 +29,7 @@
29
29
  ],
30
30
  "sideEffects": false,
31
31
  "dependencies": {
32
- "@assistant-ui/core": "^0.2.16",
32
+ "@assistant-ui/core": "^0.2.18",
33
33
  "@assistant-ui/store": "^0.2.18",
34
34
  "assistant-cloud": "*",
35
35
  "assistant-stream": "^0.3.23"
@@ -53,7 +53,7 @@
53
53
  "jsdom": "^29.1.1",
54
54
  "react": "^19.2.7",
55
55
  "vitest": "^4.1.8",
56
- "@assistant-ui/x-buildutils": "0.0.14"
56
+ "@assistant-ui/x-buildutils": "0.0.15"
57
57
  },
58
58
  "publishConfig": {
59
59
  "access": "public",
@@ -0,0 +1,22 @@
1
+ import type { Mock } from "vitest";
2
+
3
+ export type Selector = (state: unknown) => unknown;
4
+
5
+ // The real guard symbol in useStreamRuntime is module-private. To stand in for
6
+ // runtime-produced extras, wrap a plain object in a Proxy whose `has` trap
7
+ // claims any symbol described "langchain-runtime-extras" is present.
8
+ export const makeExtras = (extras: Record<string, unknown>) =>
9
+ new Proxy(extras, {
10
+ has: (target, key) =>
11
+ (typeof key === "symbol" &&
12
+ key.description === "langchain-runtime-extras") ||
13
+ Reflect.has(target, key),
14
+ });
15
+
16
+ export const createRunSelectorAgainst =
17
+ (mock: Mock) =>
18
+ (extras: unknown): void => {
19
+ mock.mockImplementationOnce((selector: Selector) =>
20
+ selector({ thread: { extras } }),
21
+ );
22
+ };
@@ -0,0 +1,53 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { convertLangChainBaseMessage } from "./convertMessages";
3
+ import type { LangChainBaseMessage } from "./types";
4
+
5
+ const humanMessage = (content: unknown): LangChainBaseMessage => ({
6
+ _getType: () => "human",
7
+ id: "msg-1",
8
+ content,
9
+ });
10
+
11
+ describe("convertLangChainBaseMessage file content parts", () => {
12
+ it("converts a base64 file block", () => {
13
+ const result = convertLangChainBaseMessage(
14
+ humanMessage([
15
+ {
16
+ type: "file",
17
+ data: "ZmFrZQ==",
18
+ mime_type: "application/pdf",
19
+ source_type: "base64",
20
+ metadata: { filename: "a.pdf" },
21
+ },
22
+ ]),
23
+ {},
24
+ );
25
+
26
+ expect(result.content).toEqual([
27
+ {
28
+ type: "file",
29
+ filename: "a.pdf",
30
+ data: "ZmFrZQ==",
31
+ mimeType: "application/pdf",
32
+ },
33
+ ]);
34
+ });
35
+
36
+ it("falls back to a default filename when metadata is absent", () => {
37
+ const result = convertLangChainBaseMessage(
38
+ humanMessage([
39
+ { type: "file", data: "ZmFrZQ==", mime_type: "application/pdf" },
40
+ ]),
41
+ {},
42
+ );
43
+
44
+ expect(result.content).toEqual([
45
+ {
46
+ type: "file",
47
+ filename: "file",
48
+ data: "ZmFrZQ==",
49
+ mimeType: "application/pdf",
50
+ },
51
+ ]);
52
+ });
53
+ });
@@ -28,6 +28,13 @@ const contentToParts = (content: unknown) => {
28
28
  return { type: "image" as const, image: part.image_url };
29
29
  }
30
30
  return { type: "image" as const, image: part.image_url.url };
31
+ case "file":
32
+ return {
33
+ type: "file" as const,
34
+ filename: part.metadata?.filename ?? "file",
35
+ data: part.data,
36
+ mimeType: part.mime_type,
37
+ };
31
38
  case "thinking":
32
39
  return { type: "reasoning" as const, text: part.thinking };
33
40
  case "reasoning":
package/src/index.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  export {
2
2
  useStreamRuntime,
3
+ useLangChainError,
3
4
  useLangChainInterruptState,
4
5
  useLangChainSend,
5
6
  useLangChainSendCommand,
6
7
  useLangChainState,
7
8
  useLangChainSubmit,
9
+ useLangChainToolCalls,
8
10
  } from "./useStreamRuntime";
9
11
  export type { UseStreamRuntimeOptions } from "./useStreamRuntime";
10
12
 
@@ -0,0 +1,24 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { AppendMessage } from "@assistant-ui/core";
3
+ import { runConfigToSubmitOptions } from "./useStreamRuntime";
4
+
5
+ type RunConfig = AppendMessage["runConfig"];
6
+
7
+ describe("runConfigToSubmitOptions", () => {
8
+ it("returns undefined when runConfig is undefined", () => {
9
+ expect(runConfigToSubmitOptions(undefined)).toBeUndefined();
10
+ });
11
+
12
+ it("returns undefined when custom is undefined", () => {
13
+ expect(
14
+ runConfigToSubmitOptions({ custom: undefined } as unknown as RunConfig),
15
+ ).toBeUndefined();
16
+ });
17
+
18
+ it("maps custom to config.configurable", () => {
19
+ const runConfig = { custom: { mode: "plan" } } as RunConfig;
20
+ expect(runConfigToSubmitOptions(runConfig)).toEqual({
21
+ config: { configurable: { mode: "plan" } },
22
+ });
23
+ });
24
+ });
package/src/types.ts CHANGED
@@ -10,6 +10,13 @@ export type LangChainContentBlock =
10
10
  type: "reasoning";
11
11
  summary: Array<{ type: "summary_text"; text: string }>;
12
12
  }
13
+ | {
14
+ type: "file";
15
+ data: string;
16
+ mime_type: string;
17
+ source_type?: "base64";
18
+ metadata?: { filename?: string };
19
+ }
13
20
  | { type: "tool_use" | "input_json_delta" };
14
21
 
15
22
  export type LangChainToolCall = {
@@ -0,0 +1,48 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { renderHook } from "@testing-library/react";
5
+ import {
6
+ createRunSelectorAgainst,
7
+ makeExtras,
8
+ type Selector,
9
+ } from "./__tests__/langChainTestUtils";
10
+
11
+ const { mockUseAuiState } = vi.hoisted(() => ({
12
+ mockUseAuiState: vi.fn(),
13
+ }));
14
+
15
+ vi.mock(import("@assistant-ui/store"), async (importOriginal) => {
16
+ const actual = await importOriginal();
17
+ return {
18
+ ...actual,
19
+ useAuiState: ((selector: Selector) =>
20
+ mockUseAuiState(selector)) as typeof actual.useAuiState,
21
+ useAui: (() => ({})) as unknown as typeof actual.useAui,
22
+ };
23
+ });
24
+
25
+ import { useLangChainError } from "./useStreamRuntime";
26
+
27
+ const runSelectorAgainst = createRunSelectorAgainst(mockUseAuiState);
28
+
29
+ describe("useLangChainError", () => {
30
+ it("returns undefined when extras are absent", () => {
31
+ runSelectorAgainst(undefined);
32
+ const { result } = renderHook(() => useLangChainError());
33
+ expect(result.current).toBeUndefined();
34
+ });
35
+
36
+ it("returns the error value when extras carry error", () => {
37
+ const error = new Error("boom");
38
+ runSelectorAgainst(makeExtras({ error }));
39
+ const { result } = renderHook(() => useLangChainError());
40
+ expect(result.current).toBe(error);
41
+ });
42
+
43
+ it("returns undefined when extras carry no error", () => {
44
+ runSelectorAgainst(makeExtras({ error: undefined }));
45
+ const { result } = renderHook(() => useLangChainError());
46
+ expect(result.current).toBeUndefined();
47
+ });
48
+ });
@@ -2,8 +2,12 @@
2
2
 
3
3
  import { describe, expect, it, vi } from "vitest";
4
4
  import { renderHook } from "@testing-library/react";
5
+ import {
6
+ createRunSelectorAgainst,
7
+ makeExtras,
8
+ type Selector,
9
+ } from "./__tests__/langChainTestUtils";
5
10
 
6
- type Selector = (state: unknown) => unknown;
7
11
  const { mockUseAuiState } = vi.hoisted(() => ({
8
12
  mockUseAuiState: vi.fn(),
9
13
  }));
@@ -20,25 +24,7 @@ vi.mock(import("@assistant-ui/store"), async (importOriginal) => {
20
24
 
21
25
  import { useLangChainState } from "./useStreamRuntime";
22
26
 
23
- // The real guard symbol in useStreamRuntime is module-private. To stand in for
24
- // runtime-produced extras, we use a Proxy whose `has` trap claims any symbol
25
- // with the description "langchain-runtime-extras" is present.
26
- const makeExtras = (values: Record<string, unknown>) =>
27
- new Proxy(
28
- { values },
29
- {
30
- has: (target, key) =>
31
- (typeof key === "symbol" &&
32
- key.description === "langchain-runtime-extras") ||
33
- Reflect.has(target, key),
34
- },
35
- );
36
-
37
- const runSelectorAgainst = (extras: unknown) => {
38
- mockUseAuiState.mockImplementationOnce((selector: Selector) =>
39
- selector({ thread: { extras } }),
40
- );
41
- };
27
+ const runSelectorAgainst = createRunSelectorAgainst(mockUseAuiState);
42
28
 
43
29
  describe("useLangChainState", () => {
44
30
  it("returns undefined when extras are absent", () => {
@@ -54,7 +40,9 @@ describe("useLangChainState", () => {
54
40
  });
55
41
 
56
42
  it("reads the value for the given key from extras.values", () => {
57
- runSelectorAgainst(makeExtras({ todos: [{ id: "a" }], count: 7 }));
43
+ runSelectorAgainst(
44
+ makeExtras({ values: { todos: [{ id: "a" }], count: 7 } }),
45
+ );
58
46
  const { result } = renderHook(() =>
59
47
  useLangChainState<Array<{ id: string }>>("todos"),
60
48
  );
@@ -62,26 +50,26 @@ describe("useLangChainState", () => {
62
50
  });
63
51
 
64
52
  it("falls back to defaultValue when the key is missing from values", () => {
65
- runSelectorAgainst(makeExtras({ unrelated: "x" }));
53
+ runSelectorAgainst(makeExtras({ values: { unrelated: "x" } }));
66
54
  const { result } = renderHook(() => useLangChainState<number>("count", 99));
67
55
  expect(result.current).toBe(99);
68
56
  });
69
57
 
70
58
  it("returns undefined when the key is missing and no default is given", () => {
71
- runSelectorAgainst(makeExtras({ unrelated: "x" }));
59
+ runSelectorAgainst(makeExtras({ values: { unrelated: "x" } }));
72
60
  const { result } = renderHook(() => useLangChainState<number>("count"));
73
61
  expect(result.current).toBeUndefined();
74
62
  });
75
63
 
76
64
  it("returns the stored value (not default) when both are present", () => {
77
- runSelectorAgainst(makeExtras({ count: 0 }));
65
+ runSelectorAgainst(makeExtras({ values: { count: 0 } }));
78
66
  const { result } = renderHook(() => useLangChainState<number>("count", 99));
79
67
  // 0 is a valid value; must be preserved over the default.
80
68
  expect(result.current).toBe(0);
81
69
  });
82
70
 
83
71
  it("preserves explicit null over defaultValue", () => {
84
- runSelectorAgainst(makeExtras({ flag: null }));
72
+ runSelectorAgainst(makeExtras({ values: { flag: null } }));
85
73
  const { result } = renderHook(() =>
86
74
  useLangChainState<string | null>("flag", "default"),
87
75
  );
@@ -0,0 +1,48 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { renderHook } from "@testing-library/react";
5
+ import {
6
+ createRunSelectorAgainst,
7
+ makeExtras,
8
+ type Selector,
9
+ } from "./__tests__/langChainTestUtils";
10
+
11
+ const { mockUseAuiState } = vi.hoisted(() => ({
12
+ mockUseAuiState: vi.fn(),
13
+ }));
14
+
15
+ vi.mock(import("@assistant-ui/store"), async (importOriginal) => {
16
+ const actual = await importOriginal();
17
+ return {
18
+ ...actual,
19
+ useAuiState: ((selector: Selector) =>
20
+ mockUseAuiState(selector)) as typeof actual.useAuiState,
21
+ useAui: (() => ({})) as unknown as typeof actual.useAui,
22
+ };
23
+ });
24
+
25
+ import { useLangChainToolCalls } from "./useStreamRuntime";
26
+
27
+ const runSelectorAgainst = createRunSelectorAgainst(mockUseAuiState);
28
+
29
+ describe("useLangChainToolCalls", () => {
30
+ it("returns an empty array when extras are absent", () => {
31
+ runSelectorAgainst(undefined);
32
+ const { result } = renderHook(() => useLangChainToolCalls());
33
+ expect(result.current).toEqual([]);
34
+ });
35
+
36
+ it("returns the assembled tool calls from extras", () => {
37
+ const toolCalls = [{ id: "a", name: "search", args: { q: "x" } }];
38
+ runSelectorAgainst(makeExtras({ toolCalls }));
39
+ const { result } = renderHook(() => useLangChainToolCalls());
40
+ expect(result.current).toBe(toolCalls);
41
+ });
42
+
43
+ it("returns an empty array when extras carry no tool calls", () => {
44
+ runSelectorAgainst(makeExtras({ toolCalls: undefined }));
45
+ const { result } = renderHook(() => useLangChainToolCalls());
46
+ expect(result.current).toEqual([]);
47
+ });
48
+ });
@@ -22,16 +22,24 @@ import {
22
22
  } from "@assistant-ui/core/react";
23
23
  import { useAui, useAuiState } from "@assistant-ui/store";
24
24
  import type { AssistantCloud } from "assistant-cloud";
25
- import { useStream, type UseStreamOptions } from "@langchain/react";
25
+ import {
26
+ useStream,
27
+ type UseStreamOptions,
28
+ type AssembledToolCall,
29
+ } from "@langchain/react";
26
30
  import type { LangChainBaseMessage, LangChainToolCall } from "./types";
27
31
  import { convertLangChainBaseMessage, getMessageType } from "./convertMessages";
28
32
 
29
33
  const symbolLangChainRuntimeExtras = Symbol("langchain-runtime-extras");
30
34
 
35
+ const EMPTY_TOOL_CALLS: readonly AssembledToolCall[] = [];
36
+
31
37
  type LangChainRuntimeExtras = {
32
38
  [symbolLangChainRuntimeExtras]: true;
33
39
  interrupt: { value?: unknown } | undefined;
34
40
  interrupts: readonly { value?: unknown }[];
41
+ toolCalls: readonly AssembledToolCall[];
42
+ error: unknown;
35
43
  submit: (
36
44
  values: Record<string, unknown> | null | undefined,
37
45
  options?: Record<string, unknown>,
@@ -86,6 +94,13 @@ type LangChainRuntimeExtraOptions = ExternalStoreSharedOptions & {
86
94
  delete?: ((threadId: string) => Promise<void>) | undefined;
87
95
  };
88
96
 
97
+ export const runConfigToSubmitOptions = (
98
+ runConfig: AppendMessage["runConfig"],
99
+ ) =>
100
+ runConfig?.custom
101
+ ? { config: { configurable: runConfig.custom } }
102
+ : undefined;
103
+
89
104
  const getPendingToolCalls = (
90
105
  messages: readonly LangChainBaseMessage[],
91
106
  ): LangChainToolCall[] => {
@@ -208,6 +223,8 @@ const useStreamThreadRuntime = (
208
223
  [symbolLangChainRuntimeExtras]: true,
209
224
  interrupt: stream.interrupt,
210
225
  interrupts: stream.interrupts,
226
+ toolCalls: stream.toolCalls,
227
+ error: stream.error,
211
228
  submit: stream.submit,
212
229
  values: stream.values,
213
230
  messagesKey,
@@ -215,6 +232,8 @@ const useStreamThreadRuntime = (
215
232
  [
216
233
  stream.interrupt,
217
234
  stream.interrupts,
235
+ stream.toolCalls,
236
+ stream.error,
218
237
  stream.submit,
219
238
  stream.values,
220
239
  messagesKey,
@@ -224,6 +243,7 @@ const useStreamThreadRuntime = (
224
243
  const runtime = useExternalStoreRuntime({
225
244
  ...pickExternalStoreSharedOptions(options),
226
245
  isRunning: effectiveIsRunning,
246
+ isLoading: stream.isThreadLoading,
227
247
  messages: threadMessages,
228
248
  adapters,
229
249
  extras,
@@ -243,9 +263,10 @@ const useStreamThreadRuntime = (
243
263
  status: "error" as const,
244
264
  }))
245
265
  : [];
246
- await stream.submit({
247
- [messagesKey]: [...cancellations, { type: "human", content }],
248
- });
266
+ await stream.submit(
267
+ { [messagesKey]: [...cancellations, { type: "human", content }] },
268
+ runConfigToSubmitOptions(msg.runConfig),
269
+ );
249
270
  },
250
271
  onAddToolResult: async ({
251
272
  toolCallId,
@@ -341,6 +362,29 @@ export const useLangChainInterruptState = () => {
341
362
  });
342
363
  };
343
364
 
365
+ /** Read the last run/hydration error from the runtime extras. */
366
+ export const useLangChainError = () => {
367
+ return useAuiState((s) => {
368
+ const extras = s.thread.extras;
369
+ if (!extras) return undefined;
370
+ return asLangChainRuntimeExtras(extras).error;
371
+ });
372
+ };
373
+
374
+ /**
375
+ * Read the root tool calls assembled by `useStream` from the `tools`
376
+ * channel. Defaults to an empty array, so consumers can `.map` without
377
+ * a guard. Useful for rendering pending/streamed tool calls and
378
+ * approval UIs.
379
+ */
380
+ export const useLangChainToolCalls = () => {
381
+ return useAuiState((s) => {
382
+ const extras = s.thread.extras;
383
+ if (!extras) return EMPTY_TOOL_CALLS;
384
+ return asLangChainRuntimeExtras(extras).toolCalls ?? EMPTY_TOOL_CALLS;
385
+ });
386
+ };
387
+
344
388
  /**
345
389
  * Returns a function to submit raw state updates to the LangGraph agent,
346
390
  * bypassing the normal message flow. Useful for sending interrupt resume