@assistant-ui/react-langchain 0.0.8 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,17 @@
1
+ /// <reference types="@assistant-ui/core/store" />
1
2
  import { LangChainBaseMessage } from "./types.js";
3
+ import { AttachmentAdapter, DictationAdapter, ExternalStoreSharedOptions, FeedbackAdapter, RealtimeVoiceAdapter, RemoteThreadListAdapter, SpeechSynthesisAdapter } from "@assistant-ui/core";
2
4
  import { UseStreamOptions } from "@langchain/react";
3
- import { AttachmentAdapter, FeedbackAdapter, RemoteThreadListAdapter, SpeechSynthesisAdapter } from "@assistant-ui/core";
4
5
  import { AssistantCloud } from "assistant-cloud";
5
6
 
6
7
  //#region src/useStreamRuntime.d.ts
7
- type LangChainRuntimeExtraOptions = {
8
+ type LangChainRuntimeExtraOptions = ExternalStoreSharedOptions & {
8
9
  cloud?: AssistantCloud | undefined;
9
10
  adapters?: {
10
11
  attachments?: AttachmentAdapter | undefined;
11
12
  speech?: SpeechSynthesisAdapter | undefined;
13
+ dictation?: DictationAdapter | undefined;
14
+ voice?: RealtimeVoiceAdapter | undefined;
12
15
  feedback?: FeedbackAdapter | undefined;
13
16
  } | undefined;
14
17
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"useStreamRuntime.d.ts","names":[],"sources":["../src/useStreamRuntime.ts"],"mappings":";;;;;;KAkDK,4BAAA;EACH,KAAA,GAAQ,cAAA;EACR,QAAA;IAEM,WAAA,GAAc,iBAAA;IACd,MAAA,GAAS,sBAAA;IACT,QAAA,GAAW,eAAA;EAAA;EADF;;;;;;EAUf,0BAAA;EAdA;;;;EAmBA,0BAAA;EAfM;;;;EAoBN,0BAAA,GAA6B,uBAAA,cAL7B;EAOA,MAAA,UAAgB,OAAA;IAAU,UAAA;EAAA,iBAAV;EAEhB,MAAA,KAAW,QAAA,aAAqB,OAAA;AAAA;AAAA,KAsBtB,uBAAA,GAA0B,gBAAA,mBAClC,CAAA,SAAU,gBAAA,GACR,CAAA,GAAI,4BAAA;;;AAxB+B;AAsBzC;;;;;;;;;;;;;;;;AAEsC;AA4OtC;;;;cAAa,gBAAA,GAAoB,UAAA,EAAY,uBAAuB,kCAAA,gBAAA;;;;cAgCvD,0BAAA;EAMZ,KAAA;AAAA;;;;AAOD;;cAAa,kBAAA,SAGT,MAAA,EAAQ,MAAA,sCACR,OAAA,GAAU,MAAA,sBAAuB,OAAA;;;;;;cAaxB,gBAAA,SAGT,QAAA,WAAmB,oBAAA,IACnB,OAAA,GAAU,MAAA,sBAAuB,OAAA;;;;;;AAjBA;AAarC;cAoBa,uBAAA,SAEH,OAAA,EAAS,MAAA,sBAAuB,OAAA;;;;;;;;;;;;iBAc1B,iBAAA,GAAA,CAAqB,GAAA,WAAc,CAAC;AAAA,iBACpC,iBAAA,GAAA,CAAqB,GAAA,UAAa,YAAA,EAAc,CAAA,GAAI,CAAC"}
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,7 +1,8 @@
1
1
  "use client";
2
2
  import { convertLangChainBaseMessage, getMessageType } from "./convertMessages.js";
3
3
  import { useMemo, useRef, useState } from "react";
4
- import { useCloudThreadListAdapter, useExternalMessageConverter, useExternalStoreRuntime, useRemoteThreadListRuntime, useToolInvocations } from "@assistant-ui/core/react";
4
+ import { pickExternalStoreSharedOptions } from "@assistant-ui/core";
5
+ import { useCloudThreadListAdapter, useExternalMessageConverter, useExternalStoreRuntime, useRemoteThreadListRuntime } from "@assistant-ui/core/react";
5
6
  import { useAui, useAuiState } from "@assistant-ui/store";
6
7
  import { useStream } from "@langchain/react";
7
8
  //#region src/useStreamRuntime.ts
@@ -65,48 +66,31 @@ const useStreamThreadRuntime = (options) => {
65
66
  messages: stream.messages,
66
67
  isRunning: effectiveIsRunning
67
68
  });
68
- const [runtimeRef] = useState(() => ({ get current() {
69
- return runtime;
70
- } }));
71
69
  const streamRef = useRef(stream);
72
70
  streamRef.current = stream;
73
- const toolInvocations = useToolInvocations({
74
- state: {
75
- messages: threadMessages,
76
- isRunning: effectiveIsRunning
77
- },
78
- getTools: () => runtimeRef.current.thread.getModelContext().tools,
79
- onResult: (command) => {
80
- if (command.type === "add-tool-result") streamRef.current.submit({ [messagesKey]: [{
81
- type: "tool",
82
- name: command.toolName,
83
- tool_call_id: command.toolCallId,
84
- content: JSON.stringify(command.result),
85
- status: command.isError ? "error" : "success"
86
- }] }, {});
87
- },
88
- setToolStatuses
89
- });
90
- const runtime = useExternalStoreRuntime({
71
+ const extras = useMemo(() => ({
72
+ [symbolLangChainRuntimeExtras]: true,
73
+ interrupt: stream.interrupt,
74
+ interrupts: stream.interrupts,
75
+ submit: stream.submit,
76
+ values: stream.values,
77
+ messagesKey
78
+ }), [
79
+ stream.interrupt,
80
+ stream.interrupts,
81
+ stream.submit,
82
+ stream.values,
83
+ messagesKey
84
+ ]);
85
+ return useExternalStoreRuntime({
86
+ ...pickExternalStoreSharedOptions(options),
91
87
  isRunning: effectiveIsRunning,
92
88
  messages: threadMessages,
93
89
  adapters,
94
- extras: useMemo(() => ({
95
- [symbolLangChainRuntimeExtras]: true,
96
- interrupt: stream.interrupt,
97
- interrupts: stream.interrupts,
98
- submit: stream.submit,
99
- values: stream.values,
100
- messagesKey
101
- }), [
102
- stream.interrupt,
103
- stream.interrupts,
104
- stream.submit,
105
- stream.values,
106
- messagesKey
107
- ]),
90
+ extras,
91
+ unstable_enableToolInvocations: true,
92
+ setToolStatuses,
108
93
  onNew: async (msg) => {
109
- await toolInvocations.abort();
110
94
  const content = getMessageContent(msg);
111
95
  const cancellations = autoCancelPendingToolCalls !== false ? getPendingToolCalls(streamRef.current.messages).map((t) => ({
112
96
  type: "tool",
@@ -132,10 +116,8 @@ const useStreamThreadRuntime = (options) => {
132
116
  },
133
117
  onCancel: unstable_allowCancellation !== false ? async () => {
134
118
  await stream.stop();
135
- await toolInvocations.abort();
136
119
  } : void 0
137
120
  });
138
- return runtime;
139
121
  };
140
122
  /**
141
123
  * Creates an assistant-ui runtime backed by LangChain's `useStream` hook.
@@ -1 +1 @@
1
- {"version":3,"file":"useStreamRuntime.js","names":["_exhaustiveCheck"],"sources":["../src/useStreamRuntime.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo, useRef, useState } from \"react\";\nimport type {\n AppendMessage,\n AttachmentAdapter,\n FeedbackAdapter,\n RemoteThreadListAdapter,\n SpeechSynthesisAdapter,\n} from \"@assistant-ui/core\";\nimport {\n useCloudThreadListAdapter,\n useExternalStoreRuntime,\n useExternalMessageConverter,\n useRemoteThreadListRuntime,\n useToolInvocations,\n type ToolExecutionStatus,\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 = {\n cloud?: AssistantCloud | undefined;\n adapters?:\n | {\n attachments?: AttachmentAdapter | undefined;\n speech?: SpeechSynthesisAdapter | 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 // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\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 // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\n const stream = useStream(options);\n\n // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\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 // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\n const threadMessages = useExternalMessageConverter({\n callback: convertLangChainBaseMessage,\n messages: stream.messages as LangChainBaseMessage[],\n isRunning: effectiveIsRunning,\n });\n\n // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\n const [runtimeRef] = useState(() => ({\n get current() {\n return runtime;\n },\n }));\n\n // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\n const streamRef = useRef(stream);\n streamRef.current = stream;\n\n // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\n const toolInvocations = useToolInvocations({\n state: {\n messages: threadMessages,\n isRunning: effectiveIsRunning,\n },\n getTools: () => runtimeRef.current.thread.getModelContext().tools,\n onResult: (command) => {\n if (command.type === \"add-tool-result\") {\n void streamRef.current.submit(\n {\n [messagesKey]: [\n {\n type: \"tool\",\n name: command.toolName,\n tool_call_id: command.toolCallId,\n content: JSON.stringify(command.result),\n status: command.isError ? \"error\" : \"success\",\n },\n ],\n },\n {},\n );\n }\n },\n setToolStatuses,\n });\n\n // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\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 // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\n const runtime = useExternalStoreRuntime({\n isRunning: effectiveIsRunning,\n messages: threadMessages,\n adapters,\n extras,\n onNew: async (msg) => {\n await toolInvocations.abort();\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 await toolInvocations.abort();\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 // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\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":";;;;;;;AAwBA,MAAM,+BAA+B,OAAO,0BAA0B;AActE,MAAM,4BAA4B,WAA4C;CAC5E,IACE,OAAO,WAAW,YAClB,UAAU,QACV,EAAE,gCAAgC,SAElC,MAAM,IAAI,MACR,oEACF;CACF,OAAO;AACT;AAkCA,MAAM,uBACJ,aACwB;CACxB,MAAM,0BAAU,IAAI,IAA+B;CACnD,KAAK,MAAM,KAAK,UAAU;EACxB,MAAM,OAAO,eAAe,CAAC;EAC7B,IAAI,SAAS,MACX,KAAK,MAAM,MAAM,EAAE,cAAc,CAAC,GAAG,QAAQ,IAAI,GAAG,IAAI,EAAE;OACrD,IAAI,SAAS,UAAU,EAAE,cAC9B,QAAQ,OAAO,EAAE,YAAY;CAEjC;CACA,OAAO,CAAC,GAAG,QAAQ,OAAO,CAAC;AAC7B;AAYA,MAAM,qBAAqB,QAAuB;CAChD,MAAM,aAAa,CACjB,GAAG,IAAI,SACP,GAAI,IAAI,aAAa,SAAS,MAAM,EAAE,OAAO,KAAK,CAAC,CACrD;CAEA,MAAM,aAAa,WAAW,MAC3B,SAAS,KAAK,SAAS,UAAU,KAAK,SAAS,OAClD;CACA,MAAM,UAAU,WAAW,MAAM,SAAS,KAAK,SAAS,MAAM;CAC9D,IAAI,cAAc,CAAC,SACjB,WAAW,QAAQ;EAAE,MAAM;EAAQ,MAAM;CAAI,CAAC;CAGhD,MAAM,UAAU,WAAW,KAAK,SAAS;EACvC,MAAM,OAAO,KAAK;EAClB,QAAQ,MAAR;GACE,KAAK,QACH,OAAO;IAAE,MAAM;IAAiB,MAAM,KAAK;GAAK;GAClD,KAAK,SACH,OAAO;IAAE,MAAM;IAAsB,WAAW,EAAE,KAAK,KAAK,MAAM;GAAE;GACtE,KAAK,QACH,OAAO;IACL,MAAM;IACN,MAAM,KAAK;IACX,WAAW,KAAK;IAChB,UAAU,EAAE,UAAU,KAAK,YAAY,OAAO;IAC9C,aAAa;GACf;GACF,KAAK,aACH,MAAM,IAAI,MAAM,sCAAsC;GACxD,SAOE,MAAM,IAAI,MACR,yCAAyCA,MAC3C;EAEJ;CACF,CAAC;CAED,IAAI,QAAQ,WAAW,KAAK,QAAQ,IAAI,SAAS,QAC/C,OAAO,QAAQ,GAAG,QAAQ;CAE5B,OAAO;AACT;AAMA,MAAM,0BACJ,YAIG;CACH,MAAM,EAAE,UAAU,4BAA4B,+BAC5C;CACF,MAAM,cAAc,QAAQ,eAAe;CAS3C,QAAQ,WANW,aAAa,MAAM,EAAE,eAAe,UAM3B;CAG5B,MAAM,SAAS,UAAU,OAAO;CAGhC,MAAM,CAAC,cAAc,mBAAmB,SAEtC,CAAC,CAAC;CACJ,MAAM,oBAAoB,OAAO,OAAO,YAAY,EAAE,MACnD,MAAM,GAAG,SAAS,WACrB;CACA,MAAM,qBAAqB,OAAO,aAAa;CAG/C,MAAM,iBAAiB,4BAA4B;EACjD,UAAU;EACV,UAAU,OAAO;EACjB,WAAW;CACb,CAAC;CAGD,MAAM,CAAC,cAAc,gBAAgB,EACnC,IAAI,UAAU;EACZ,OAAO;CACT,EACF,EAAE;CAGF,MAAM,YAAY,OAAO,MAAM;CAC/B,UAAU,UAAU;CAGpB,MAAM,kBAAkB,mBAAmB;EACzC,OAAO;GACL,UAAU;GACV,WAAW;EACb;EACA,gBAAgB,WAAW,QAAQ,OAAO,gBAAgB,EAAE;EAC5D,WAAW,YAAY;GACrB,IAAI,QAAQ,SAAS,mBACnB,UAAe,QAAQ,OACrB,GACG,cAAc,CACb;IACE,MAAM;IACN,MAAM,QAAQ;IACd,cAAc,QAAQ;IACtB,SAAS,KAAK,UAAU,QAAQ,MAAM;IACtC,QAAQ,QAAQ,UAAU,UAAU;GACtC,CACF,EACF,GACA,CAAC,CACH;EAEJ;EACA;CACF,CAAC;CAsBD,MAAM,UAAU,wBAAwB;EACtC,WAAW;EACX,UAAU;EACV;EACA,QAvBa,eACkB;IAC5B,+BAA+B;GAChC,WAAW,OAAO;GAClB,YAAY,OAAO;GACnB,QAAQ,OAAO;GACf,QAAQ,OAAO;GACf;EACF,IACA;GACE,OAAO;GACP,OAAO;GACP,OAAO;GACP,OAAO;GACP;EACF,CAQK;EACL,OAAO,OAAO,QAAQ;GACpB,MAAM,gBAAgB,MAAM;GAC5B,MAAM,UAAU,kBAAkB,GAAG;GACrC,MAAM,gBACJ,+BAA+B,QAC3B,oBACE,UAAU,QAAQ,QACpB,EAAE,KAAK,OAAO;IACZ,MAAM;IACN,MAAM,EAAE;IACR,cAAc,EAAE;IAChB,SAAS,KAAK,UAAU,EAAE,WAAW,KAAK,CAAC;IAC3C,QAAQ;GACV,EAAE,IACF,CAAC;GACP,MAAM,OAAO,OAAO,GACjB,cAAc,CAAC,GAAG,eAAe;IAAE,MAAM;IAAS;GAAQ,CAAC,EAC9D,CAAC;EACH;EACA,iBAAiB,OAAO,EACtB,YACA,UACA,QACA,SACA,eACI;GACJ,MAAM,OAAO,OAAO,GACjB,cAAc,CACb;IACE,MAAM;IACN,MAAM;IACN,cAAc;IACd,SAAS,KAAK,UAAU,MAAM;IAC9B,GAAI,aAAa,KAAA,KAAa,EAAE,SAAS;IACzC,QAAQ,UAAU,UAAU;GAC9B,CACF,EACF,CAAC;EACH;EACA,UACE,+BAA+B,QAC3B,YAAY;GACV,MAAM,OAAO,KAAK;GAClB,MAAM,gBAAgB,MAAM;EAC9B,IACA,KAAA;CACR,CAAC;CAED,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,oBAAoB,eAAwC;CACvE,MAAM,EACJ,OACA,4BACA,QACA,QAAQ,UACR,GAAG,YACD;CAEJ,MAAM,aAAa,OAAO,OAAO;CACjC,WAAW,UAAU;CAErB,MAAM,eAAe,0BAA0B;EAC7C;EACA;EACA,QAAQ;CACV,CAAC;CAGD,OAAO,2BAA2B;EAChC,aAAa,SAAS,cAAc;GAElC,OAAO,uBAAuB,WAAW,OAAO;EAClD;EACA,SAPc,8BAA8B;EAQ5C,cAAc;CAChB,CAAC;AACH;;;;AAKA,MAAa,mCAAmC;CAC9C,OAAO,aAAa,MAAM;EACxB,MAAM,SAAS,EAAE,OAAO;EACxB,IAAI,CAAC,QAAQ,OAAO,KAAA;EACpB,OAAO,yBAAyB,MAAM,EAAE;CAC1C,CAAC;AACH;;;;;;AAOA,MAAa,2BAA2B;CACtC,MAAM,MAAM,OAAO;CACnB,QACE,QACA,YACG;EACH,MAAM,SAAS,IAAI,OAAO,EAAE,SAAS,EAAE;EACvC,MAAM,EAAE,WAAW,yBAAyB,MAAM;EAClD,OAAO,OAAO,QAAQ,OAAO;CAC/B;AACF;;;;;;AAOA,MAAa,yBAAyB;CACpC,MAAM,MAAM,OAAO;CACnB,QACE,UACA,YACG;EACH,MAAM,EAAE,QAAQ,gBAAgB,yBAC9B,IAAI,OAAO,EAAE,SAAS,EAAE,MAC1B;EACA,OAAO,OAAO,GAAG,cAAc,SAAS,GAAG,OAAO;CACpD;AACF;;;;;;;;AASA,MAAa,gCAAgC;CAC3C,MAAM,SAAS,mBAAmB;CAClC,QAAQ,YAAqC,OAAO,MAAM,EAAE,QAAQ,CAAC;AACvE;AAeA,SAAgB,kBACd,KACA,cACe;CACf,OAAO,aAAa,MAAM;EACxB,MAAM,SAAS,EAAE,OAAO;EACxB,IAAI,CAAC,QAAQ,OAAO;EACpB,MAAM,QAAQ,yBAAyB,MAAM,EAAE,OAAO;EACtD,OAAO,UAAU,KAAA,IAAY,QAAQ;CACvC,CAAC;AACH"}
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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react-langchain",
3
- "version": "0.0.8",
3
+ "version": "0.0.11",
4
4
  "description": "LangChain useStream adapter for assistant-ui",
5
5
  "keywords": [
6
6
  "langchain",
@@ -29,10 +29,10 @@
29
29
  ],
30
30
  "sideEffects": false,
31
31
  "dependencies": {
32
- "@assistant-ui/core": "^0.2.5",
33
- "@assistant-ui/store": "^0.2.12",
32
+ "@assistant-ui/core": "^0.2.8",
33
+ "@assistant-ui/store": "^0.2.13",
34
34
  "assistant-cloud": "*",
35
- "assistant-stream": "^0.3.16"
35
+ "assistant-stream": "^0.3.18"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@langchain/react": "^1.0.2",
@@ -45,15 +45,15 @@
45
45
  }
46
46
  },
47
47
  "devDependencies": {
48
- "@langchain/core": "^1.1.47",
49
- "@langchain/langgraph-sdk": "^1.9.4",
50
- "@langchain/react": "^1.0.4",
48
+ "@langchain/core": "^1.1.48",
49
+ "@langchain/langgraph-sdk": "^1.9.10",
50
+ "@langchain/react": "^1.0.10",
51
51
  "@testing-library/react": "^16.3.2",
52
52
  "@types/react": "^19.2.15",
53
53
  "jsdom": "^29.1.1",
54
54
  "react": "^19.2.6",
55
55
  "vitest": "^4.1.7",
56
- "@assistant-ui/x-buildutils": "0.0.9"
56
+ "@assistant-ui/x-buildutils": "0.0.10"
57
57
  },
58
58
  "publishConfig": {
59
59
  "access": "public",
@@ -1,20 +1,24 @@
1
+ /// <reference types="@assistant-ui/core/store" />
1
2
  "use client";
2
3
 
3
4
  import { useMemo, useRef, useState } from "react";
4
5
  import type {
5
6
  AppendMessage,
6
7
  AttachmentAdapter,
8
+ DictationAdapter,
9
+ ExternalStoreSharedOptions,
7
10
  FeedbackAdapter,
11
+ RealtimeVoiceAdapter,
8
12
  RemoteThreadListAdapter,
9
13
  SpeechSynthesisAdapter,
14
+ ToolExecutionStatus,
10
15
  } from "@assistant-ui/core";
16
+ import { pickExternalStoreSharedOptions } from "@assistant-ui/core";
11
17
  import {
12
18
  useCloudThreadListAdapter,
13
19
  useExternalStoreRuntime,
14
20
  useExternalMessageConverter,
15
21
  useRemoteThreadListRuntime,
16
- useToolInvocations,
17
- type ToolExecutionStatus,
18
22
  } from "@assistant-ui/core/react";
19
23
  import { useAui, useAuiState } from "@assistant-ui/store";
20
24
  import type { AssistantCloud } from "assistant-cloud";
@@ -48,12 +52,14 @@ const asLangChainRuntimeExtras = (extras: unknown): LangChainRuntimeExtras => {
48
52
  return extras as LangChainRuntimeExtras;
49
53
  };
50
54
 
51
- type LangChainRuntimeExtraOptions = {
55
+ type LangChainRuntimeExtraOptions = ExternalStoreSharedOptions & {
52
56
  cloud?: AssistantCloud | undefined;
53
57
  adapters?:
54
58
  | {
55
59
  attachments?: AttachmentAdapter | undefined;
56
60
  speech?: SpeechSynthesisAdapter | undefined;
61
+ dictation?: DictationAdapter | undefined;
62
+ voice?: RealtimeVoiceAdapter | undefined;
57
63
  feedback?: FeedbackAdapter | undefined;
58
64
  }
59
65
  | undefined;
@@ -170,7 +176,6 @@ const useStreamThreadRuntime = (
170
176
  options;
171
177
  const messagesKey = options.messagesKey ?? "messages";
172
178
 
173
- // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
174
179
  const externalId = useAuiState((s) => s.threadListItem.externalId) as
175
180
  | string
176
181
  | null;
@@ -179,10 +184,8 @@ const useStreamThreadRuntime = (
179
184
  // literal merges both arms' transport types, breaking arm assignment.
180
185
  options.threadId = externalId;
181
186
 
182
- // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
183
187
  const stream = useStream(options);
184
188
 
185
- // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
186
189
  const [toolStatuses, setToolStatuses] = useState<
187
190
  Record<string, ToolExecutionStatus>
188
191
  >({});
@@ -191,53 +194,15 @@ const useStreamThreadRuntime = (
191
194
  );
192
195
  const effectiveIsRunning = stream.isLoading || hasExecutingTools;
193
196
 
194
- // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
195
197
  const threadMessages = useExternalMessageConverter({
196
198
  callback: convertLangChainBaseMessage,
197
199
  messages: stream.messages as LangChainBaseMessage[],
198
200
  isRunning: effectiveIsRunning,
199
201
  });
200
202
 
201
- // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
202
- const [runtimeRef] = useState(() => ({
203
- get current() {
204
- return runtime;
205
- },
206
- }));
207
-
208
- // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
209
203
  const streamRef = useRef(stream);
210
204
  streamRef.current = stream;
211
205
 
212
- // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
213
- const toolInvocations = useToolInvocations({
214
- state: {
215
- messages: threadMessages,
216
- isRunning: effectiveIsRunning,
217
- },
218
- getTools: () => runtimeRef.current.thread.getModelContext().tools,
219
- onResult: (command) => {
220
- if (command.type === "add-tool-result") {
221
- void streamRef.current.submit(
222
- {
223
- [messagesKey]: [
224
- {
225
- type: "tool",
226
- name: command.toolName,
227
- tool_call_id: command.toolCallId,
228
- content: JSON.stringify(command.result),
229
- status: command.isError ? "error" : "success",
230
- },
231
- ],
232
- },
233
- {},
234
- );
235
- }
236
- },
237
- setToolStatuses,
238
- });
239
-
240
- // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
241
206
  const extras = useMemo(
242
207
  (): LangChainRuntimeExtras => ({
243
208
  [symbolLangChainRuntimeExtras]: true,
@@ -256,14 +221,15 @@ const useStreamThreadRuntime = (
256
221
  ],
257
222
  );
258
223
 
259
- // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
260
224
  const runtime = useExternalStoreRuntime({
225
+ ...pickExternalStoreSharedOptions(options),
261
226
  isRunning: effectiveIsRunning,
262
227
  messages: threadMessages,
263
228
  adapters,
264
229
  extras,
230
+ unstable_enableToolInvocations: true,
231
+ setToolStatuses,
265
232
  onNew: async (msg) => {
266
- await toolInvocations.abort();
267
233
  const content = getMessageContent(msg);
268
234
  const cancellations =
269
235
  autoCancelPendingToolCalls !== false
@@ -305,7 +271,6 @@ const useStreamThreadRuntime = (
305
271
  unstable_allowCancellation !== false
306
272
  ? async () => {
307
273
  await stream.stop();
308
- await toolInvocations.abort();
309
274
  }
310
275
  : undefined,
311
276
  });
@@ -358,7 +323,6 @@ export const useStreamRuntime = (rawOptions: UseStreamRuntimeOptions) => {
358
323
 
359
324
  return useRemoteThreadListRuntime({
360
325
  runtimeHook: function RuntimeHook() {
361
- // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
362
326
  return useStreamThreadRuntime(optionsRef.current);
363
327
  },
364
328
  adapter,