@assistant-ui/react-ai-sdk 1.1.17 → 1.1.19

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 +1 @@
1
- {"version":3,"file":"useAISDKRuntime.d.ts","sourceRoot":"","sources":["../../../src/ui/use-chat/useAISDKRuntime.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACzE,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EAMrB,KAAK,aAAa,EACnB,MAAM,qBAAqB,CAAC;AAI7B,MAAM,MAAM,6BAA6B,GAAG,CAC1C,UAAU,SAAS,SAAS,GAAG,SAAS,EAExC,OAAO,EAAE,aAAa,KACnB,eAAe,CAAC,UAAU,CAAC,CAAC;AAejC,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,EACL,CAAC,WAAW,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC,GAAG;QAC/C,OAAO,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC;KAC5C,CAAC,GACF,SAAS,CAAC;IACd,eAAe,CAAC,EAAE,6BAA6B,CAAC;IAChD;;;;;;;OAOG;IACH,4BAA4B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACpD,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,UAAU,SAAS,SAAS,GAAG,SAAS,EACtE,aAAa,UAAU,CAAC,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC,EACnD,sFAIG,mBAAwB,qBAkM5B,CAAC"}
1
+ {"version":3,"file":"useAISDKRuntime.d.ts","sourceRoot":"","sources":["../../../src/ui/use-chat/useAISDKRuntime.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEzE,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EAMrB,KAAK,aAAa,EACnB,MAAM,qBAAqB,CAAC;AAY7B,MAAM,MAAM,6BAA6B,GAAG,CAC1C,UAAU,SAAS,SAAS,GAAG,SAAS,EAExC,OAAO,EAAE,aAAa,KACnB,eAAe,CAAC,UAAU,CAAC,CAAC;AAEjC,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,EACL,CAAC,WAAW,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC,GAAG;QAC/C,OAAO,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC;KAC5C,CAAC,GACF,SAAS,CAAC;IACd,eAAe,CAAC,EAAE,6BAA6B,CAAC;IAChD;;;;;;;OAOG;IACH,4BAA4B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACpD,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,UAAU,SAAS,SAAS,GAAG,SAAS,EACtE,aAAa,UAAU,CAAC,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC,EACnD,sFAIG,mBAAwB,qBA+K5B,CAAC"}
@@ -2,6 +2,7 @@
2
2
 
3
3
  // src/ui/use-chat/useAISDKRuntime.tsx
4
4
  import { useState, useMemo } from "react";
5
+ import { isToolUIPart } from "ai";
5
6
  import {
6
7
  useExternalStoreRuntime,
7
8
  useRuntimeAdapters,
@@ -22,8 +23,11 @@ var useAISDKRuntime = (chatHelpers, {
22
23
  cancelPendingToolCallsOnSend = true
23
24
  } = {}) => {
24
25
  const contextAdapters = useRuntimeAdapters();
25
- const isRunning = chatHelpers.status === "submitted" || chatHelpers.status === "streaming";
26
26
  const [toolStatuses, setToolStatuses] = useState({});
27
+ const hasExecutingTools = Object.values(toolStatuses).some(
28
+ (s) => s?.type === "executing"
29
+ );
30
+ const isRunning = chatHelpers.status === "submitted" || chatHelpers.status === "streaming" || hasExecutingTools;
27
31
  const messages = AISDKMessageConverter.useThreadMessages({
28
32
  isRunning,
29
33
  messages: chatHelpers.messages,
@@ -66,39 +70,26 @@ var useAISDKRuntime = (chatHelpers, {
66
70
  chatHelpers.setMessages(messages2);
67
71
  }
68
72
  );
69
- const completePendingToolCalls = () => {
73
+ const completePendingToolCalls = async () => {
70
74
  if (!cancelPendingToolCallsOnSend) return;
71
- const pendingHumanTools = Object.entries(toolStatuses).filter(
72
- (entry) => entry[1]?.type === "interrupt"
73
- ).map(([toolCallId]) => ({ toolCallId }));
74
- if (pendingHumanTools.length === 0) return;
75
- setToolStatuses((prev) => {
76
- const next = { ...prev };
77
- pendingHumanTools.forEach(({ toolCallId }) => {
78
- next[toolCallId] = {
79
- type: "cancelled",
80
- reason: "User cancelled tool call by sending a new message."
75
+ await toolInvocations.abort();
76
+ chatHelpers.setMessages((messages2) => {
77
+ const lastMessage = messages2.at(-1);
78
+ if (lastMessage?.role !== "assistant") return messages2;
79
+ let hasChanges = false;
80
+ const parts = lastMessage.parts?.map((part) => {
81
+ if (!isToolUIPart(part)) return part;
82
+ if (part.state === "output-available" || part.state === "output-error")
83
+ return part;
84
+ hasChanges = true;
85
+ return {
86
+ ...part,
87
+ state: "output-error",
88
+ errorText: "User cancelled tool call by sending a new message."
81
89
  };
82
90
  });
83
- return next;
84
- });
85
- pendingHumanTools.forEach(({ toolCallId }) => {
86
- chatHelpers.setMessages(
87
- chatHelpers.messages.map((message) => {
88
- if (message.id === toolCallId) {
89
- return {
90
- ...message,
91
- content: [
92
- {
93
- type: "text",
94
- text: "User cancelled tool call by sending a new message."
95
- }
96
- ]
97
- };
98
- }
99
- return message;
100
- })
101
- );
91
+ if (!hasChanges) return messages2;
92
+ return [...messages2.slice(0, -1), { ...lastMessage, parts }];
102
93
  });
103
94
  };
104
95
  const runtime = useExternalStoreRuntime({
@@ -112,17 +103,16 @@ var useAISDKRuntime = (chatHelpers, {
112
103
  ),
113
104
  onCancel: async () => {
114
105
  chatHelpers.stop();
115
- toolInvocations.abort();
106
+ await toolInvocations.abort();
116
107
  },
117
108
  onNew: async (message) => {
118
- completePendingToolCalls();
109
+ await completePendingToolCalls();
119
110
  const createMessage = (customToCreateMessage ?? toCreateMessage)(message);
120
111
  await chatHelpers.sendMessage(createMessage, {
121
112
  metadata: message.runConfig
122
113
  });
123
114
  },
124
115
  onEdit: async (message) => {
125
- completePendingToolCalls();
126
116
  const newMessages = sliceMessagesUntil(
127
117
  chatHelpers.messages,
128
118
  message.parentId
@@ -134,7 +124,6 @@ var useAISDKRuntime = (chatHelpers, {
134
124
  });
135
125
  },
136
126
  onReload: async (parentId, config) => {
137
- completePendingToolCalls();
138
127
  const newMessages = sliceMessagesUntil(chatHelpers.messages, parentId);
139
128
  chatHelpers.setMessages(newMessages);
140
129
  await chatHelpers.regenerate({ metadata: config.runConfig });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/ui/use-chat/useAISDKRuntime.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useMemo } from \"react\";\nimport type { UIMessage, useChat, CreateUIMessage } from \"@ai-sdk/react\";\nimport {\n useExternalStoreRuntime,\n type ExternalStoreAdapter,\n type ThreadHistoryAdapter,\n type AssistantRuntime,\n type ThreadMessage,\n type MessageFormatAdapter,\n useRuntimeAdapters,\n INTERNAL,\n type ToolExecutionStatus,\n type AppendMessage,\n} from \"@assistant-ui/react\";\nimport { sliceMessagesUntil } from \"../utils/sliceMessagesUntil\";\nimport { toCreateMessage } from \"../utils/toCreateMessage\";\n\nexport type CustomToCreateMessageFunction = <\n UI_MESSAGE extends UIMessage = UIMessage,\n>(\n message: AppendMessage,\n) => CreateUIMessage<UI_MESSAGE>;\n\nimport { vercelAttachmentAdapter } from \"../utils/vercelAttachmentAdapter\";\nimport { getVercelAIMessages } from \"../getVercelAIMessages\";\nimport { AISDKMessageConverter } from \"../utils/convertMessage\";\nimport {\n type AISDKStorageFormat,\n aiSDKV5FormatAdapter,\n} from \"../adapters/aiSDKFormatAdapter\";\nimport { useExternalHistory } from \"./useExternalHistory\";\n\ntype PendingHumanTool = {\n readonly toolCallId: string;\n};\n\nexport type AISDKRuntimeAdapter = {\n adapters?:\n | (NonNullable<ExternalStoreAdapter[\"adapters\"]> & {\n history?: ThreadHistoryAdapter | undefined;\n })\n | undefined;\n toCreateMessage?: CustomToCreateMessageFunction;\n /**\n * Whether to automatically cancel pending interactive tool calls when the user sends a new message.\n *\n * When enabled (default), the pending tool calls will be marked as failed with an error message\n * indicating the user cancelled the tool call by sending a new message.\n *\n * @default true\n */\n cancelPendingToolCallsOnSend?: boolean | undefined;\n};\n\nexport const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(\n chatHelpers: ReturnType<typeof useChat<UI_MESSAGE>>,\n {\n adapters,\n toCreateMessage: customToCreateMessage,\n cancelPendingToolCallsOnSend = true,\n }: AISDKRuntimeAdapter = {},\n) => {\n const contextAdapters = useRuntimeAdapters();\n const isRunning =\n chatHelpers.status === \"submitted\" || chatHelpers.status === \"streaming\";\n\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n\n const messages = AISDKMessageConverter.useThreadMessages({\n isRunning,\n messages: chatHelpers.messages,\n metadata: useMemo(\n () => ({\n toolStatuses,\n ...(chatHelpers.error && { error: chatHelpers.error.message }),\n }),\n [toolStatuses, chatHelpers.error],\n ),\n });\n\n const [runtimeRef] = useState(() => ({\n get current(): AssistantRuntime {\n return runtime;\n },\n }));\n\n const toolInvocations = INTERNAL.useToolInvocations({\n state: {\n messages,\n isRunning,\n },\n getTools: () => runtimeRef.current.thread.getModelContext().tools,\n onResult: (command) => {\n if (command.type === \"add-tool-result\") {\n chatHelpers.addToolResult({\n tool: command.toolName,\n toolCallId: command.toolCallId,\n output: command.result,\n });\n }\n },\n setToolStatuses,\n });\n\n const isLoading = useExternalHistory(\n runtimeRef,\n adapters?.history ?? contextAdapters?.history,\n AISDKMessageConverter.toThreadMessages as (\n messages: UI_MESSAGE[],\n ) => ThreadMessage[],\n aiSDKV5FormatAdapter as MessageFormatAdapter<\n UI_MESSAGE,\n AISDKStorageFormat\n >,\n (messages) => {\n chatHelpers.setMessages(messages);\n },\n );\n\n const completePendingToolCalls = () => {\n if (!cancelPendingToolCallsOnSend) return;\n\n const pendingHumanTools: PendingHumanTool[] = Object.entries(toolStatuses)\n .filter(\n (\n entry,\n ): entry is [\n string,\n Extract<ToolExecutionStatus, { type: \"interrupt\" }>,\n ] => entry[1]?.type === \"interrupt\",\n )\n .map(([toolCallId]) => ({ toolCallId }));\n\n if (pendingHumanTools.length === 0) return;\n\n // Set tool statuses to cancelled so UI can show special state\n setToolStatuses((prev) => {\n const next = { ...prev };\n pendingHumanTools.forEach(({ toolCallId }) => {\n next[toolCallId] = {\n type: \"cancelled\",\n reason: \"User cancelled tool call by sending a new message.\",\n };\n });\n return next;\n });\n\n // Mark tools as errored in the message history\n pendingHumanTools.forEach(({ toolCallId }) => {\n chatHelpers.setMessages(\n chatHelpers.messages.map((message) => {\n if (message.id === toolCallId) {\n return {\n ...message,\n content: [\n {\n type: \"text\",\n text: \"User cancelled tool call by sending a new message.\",\n },\n ],\n };\n }\n return message;\n }),\n );\n });\n };\n\n const runtime = useExternalStoreRuntime({\n isRunning,\n messages,\n setMessages: (messages) =>\n chatHelpers.setMessages(\n messages\n .map(getVercelAIMessages<UI_MESSAGE>)\n .filter(Boolean)\n .flat(),\n ),\n onImport: (messages) =>\n chatHelpers.setMessages(\n messages\n .map(getVercelAIMessages<UI_MESSAGE>)\n .filter(Boolean)\n .flat(),\n ),\n onCancel: async () => {\n chatHelpers.stop();\n toolInvocations.abort();\n },\n onNew: async (message) => {\n completePendingToolCalls();\n\n const createMessage = (\n customToCreateMessage ?? toCreateMessage\n )<UI_MESSAGE>(message);\n await chatHelpers.sendMessage(createMessage, {\n metadata: message.runConfig,\n });\n },\n onEdit: async (message) => {\n completePendingToolCalls();\n\n const newMessages = sliceMessagesUntil(\n chatHelpers.messages,\n message.parentId,\n );\n chatHelpers.setMessages(newMessages);\n\n const createMessage = (\n customToCreateMessage ?? toCreateMessage\n )<UI_MESSAGE>(message);\n await chatHelpers.sendMessage(createMessage, {\n metadata: message.runConfig,\n });\n },\n onReload: async (parentId: string | null, config) => {\n completePendingToolCalls();\n\n const newMessages = sliceMessagesUntil(chatHelpers.messages, parentId);\n chatHelpers.setMessages(newMessages);\n\n await chatHelpers.regenerate({ metadata: config.runConfig });\n },\n onAddToolResult: ({ toolCallId, result, isError }) => {\n if (isError) {\n chatHelpers.addToolOutput({\n state: \"output-error\",\n tool: toolCallId,\n toolCallId,\n errorText:\n typeof result === \"string\" ? result : JSON.stringify(result),\n });\n } else {\n chatHelpers.addToolOutput({\n state: \"output-available\",\n tool: toolCallId,\n toolCallId,\n output: result,\n });\n }\n },\n onResumeToolCall: (options) =>\n toolInvocations.resume(options.toolCallId, options.payload),\n adapters: {\n attachments: vercelAttachmentAdapter,\n ...contextAdapters,\n ...adapters,\n },\n isLoading,\n });\n\n return runtime;\n};\n"],"mappings":"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","names":["messages"]}
1
+ {"version":3,"sources":["../../../src/ui/use-chat/useAISDKRuntime.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useMemo } from \"react\";\nimport type { UIMessage, useChat, CreateUIMessage } from \"@ai-sdk/react\";\nimport { isToolUIPart } from \"ai\";\nimport {\n useExternalStoreRuntime,\n type ExternalStoreAdapter,\n type ThreadHistoryAdapter,\n type AssistantRuntime,\n type ThreadMessage,\n type MessageFormatAdapter,\n useRuntimeAdapters,\n INTERNAL,\n type ToolExecutionStatus,\n type AppendMessage,\n} from \"@assistant-ui/react\";\nimport { sliceMessagesUntil } from \"../utils/sliceMessagesUntil\";\nimport { toCreateMessage } from \"../utils/toCreateMessage\";\nimport { vercelAttachmentAdapter } from \"../utils/vercelAttachmentAdapter\";\nimport { getVercelAIMessages } from \"../getVercelAIMessages\";\nimport { AISDKMessageConverter } from \"../utils/convertMessage\";\nimport {\n type AISDKStorageFormat,\n aiSDKV5FormatAdapter,\n} from \"../adapters/aiSDKFormatAdapter\";\nimport { useExternalHistory } from \"./useExternalHistory\";\n\nexport type CustomToCreateMessageFunction = <\n UI_MESSAGE extends UIMessage = UIMessage,\n>(\n message: AppendMessage,\n) => CreateUIMessage<UI_MESSAGE>;\n\nexport type AISDKRuntimeAdapter = {\n adapters?:\n | (NonNullable<ExternalStoreAdapter[\"adapters\"]> & {\n history?: ThreadHistoryAdapter | undefined;\n })\n | undefined;\n toCreateMessage?: CustomToCreateMessageFunction;\n /**\n * Whether to automatically cancel pending interactive tool calls when the user sends a new message.\n *\n * When enabled (default), the pending tool calls will be marked as failed with an error message\n * indicating the user cancelled the tool call by sending a new message.\n *\n * @default true\n */\n cancelPendingToolCallsOnSend?: boolean | undefined;\n};\n\nexport const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(\n chatHelpers: ReturnType<typeof useChat<UI_MESSAGE>>,\n {\n adapters,\n toCreateMessage: customToCreateMessage,\n cancelPendingToolCallsOnSend = true,\n }: AISDKRuntimeAdapter = {},\n) => {\n const contextAdapters = useRuntimeAdapters();\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n\n const hasExecutingTools = Object.values(toolStatuses).some(\n (s) => s?.type === \"executing\",\n );\n const isRunning =\n chatHelpers.status === \"submitted\" ||\n chatHelpers.status === \"streaming\" ||\n hasExecutingTools;\n\n const messages = AISDKMessageConverter.useThreadMessages({\n isRunning,\n messages: chatHelpers.messages,\n metadata: useMemo(\n () => ({\n toolStatuses,\n ...(chatHelpers.error && { error: chatHelpers.error.message }),\n }),\n [toolStatuses, chatHelpers.error],\n ),\n });\n\n const [runtimeRef] = useState(() => ({\n get current(): AssistantRuntime {\n return runtime;\n },\n }));\n\n const toolInvocations = INTERNAL.useToolInvocations({\n state: {\n messages,\n isRunning,\n },\n getTools: () => runtimeRef.current.thread.getModelContext().tools,\n onResult: (command) => {\n if (command.type === \"add-tool-result\") {\n chatHelpers.addToolResult({\n tool: command.toolName,\n toolCallId: command.toolCallId,\n output: command.result,\n });\n }\n },\n setToolStatuses,\n });\n\n const isLoading = useExternalHistory(\n runtimeRef,\n adapters?.history ?? contextAdapters?.history,\n AISDKMessageConverter.toThreadMessages as (\n messages: UI_MESSAGE[],\n ) => ThreadMessage[],\n aiSDKV5FormatAdapter as MessageFormatAdapter<\n UI_MESSAGE,\n AISDKStorageFormat\n >,\n (messages) => {\n chatHelpers.setMessages(messages);\n },\n );\n\n const completePendingToolCalls = async () => {\n if (!cancelPendingToolCallsOnSend) return;\n\n await toolInvocations.abort();\n\n // Mark any tool without a result as cancelled (uses setMessages to avoid triggering sendAutomaticallyWhen)\n chatHelpers.setMessages((messages) => {\n const lastMessage = messages.at(-1);\n if (lastMessage?.role !== \"assistant\") return messages;\n\n let hasChanges = false;\n const parts = lastMessage.parts?.map((part) => {\n if (!isToolUIPart(part)) return part;\n if (part.state === \"output-available\" || part.state === \"output-error\")\n return part;\n\n hasChanges = true;\n return {\n ...part,\n state: \"output-error\" as const,\n errorText: \"User cancelled tool call by sending a new message.\",\n };\n });\n\n if (!hasChanges) return messages;\n return [...messages.slice(0, -1), { ...lastMessage, parts }];\n });\n };\n\n const runtime = useExternalStoreRuntime({\n isRunning,\n messages,\n setMessages: (messages) =>\n chatHelpers.setMessages(\n messages\n .map(getVercelAIMessages<UI_MESSAGE>)\n .filter(Boolean)\n .flat(),\n ),\n onImport: (messages) =>\n chatHelpers.setMessages(\n messages\n .map(getVercelAIMessages<UI_MESSAGE>)\n .filter(Boolean)\n .flat(),\n ),\n onCancel: async () => {\n chatHelpers.stop();\n await toolInvocations.abort();\n },\n onNew: async (message) => {\n await completePendingToolCalls();\n\n const createMessage = (\n customToCreateMessage ?? toCreateMessage\n )<UI_MESSAGE>(message);\n await chatHelpers.sendMessage(createMessage, {\n metadata: message.runConfig,\n });\n },\n onEdit: async (message) => {\n const newMessages = sliceMessagesUntil(\n chatHelpers.messages,\n message.parentId,\n );\n chatHelpers.setMessages(newMessages);\n\n const createMessage = (\n customToCreateMessage ?? toCreateMessage\n )<UI_MESSAGE>(message);\n await chatHelpers.sendMessage(createMessage, {\n metadata: message.runConfig,\n });\n },\n onReload: async (parentId: string | null, config) => {\n const newMessages = sliceMessagesUntil(chatHelpers.messages, parentId);\n chatHelpers.setMessages(newMessages);\n\n await chatHelpers.regenerate({ metadata: config.runConfig });\n },\n onAddToolResult: ({ toolCallId, result, isError }) => {\n if (isError) {\n chatHelpers.addToolOutput({\n state: \"output-error\",\n tool: toolCallId,\n toolCallId,\n errorText:\n typeof result === \"string\" ? result : JSON.stringify(result),\n });\n } else {\n chatHelpers.addToolOutput({\n state: \"output-available\",\n tool: toolCallId,\n toolCallId,\n output: result,\n });\n }\n },\n onResumeToolCall: (options) =>\n toolInvocations.resume(options.toolCallId, options.payload),\n adapters: {\n attachments: vercelAttachmentAdapter,\n ...contextAdapters,\n ...adapters,\n },\n isLoading,\n });\n\n return runtime;\n};\n"],"mappings":"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","names":["messages"]}
@@ -1 +1 @@
1
- {"version":3,"file":"convertMessage.d.ts","sourceRoot":"","sources":["../../../src/ui/utils/convertMessage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,IAAI,CAAC;AAClD,OAAO,EAOL,KAAK,2BAA2B,EACjC,MAAM,qBAAqB,CAAC;AAkL7B,eAAO,MAAM,qBAAqB;;;;;;;;0HA9JT,sDAErB;yHAGE,sDAEU;;;CA2Nf,CAAC"}
1
+ {"version":3,"file":"convertMessage.d.ts","sourceRoot":"","sources":["../../../src/ui/utils/convertMessage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,IAAI,CAAC;AAClD,OAAO,EAOL,KAAK,2BAA2B,EACjC,MAAM,qBAAqB,CAAC;AAoK7B,eAAO,MAAM,qBAAqB;;;;;;;;0HAhJT,sDAErB;yHAGE,sDAEU;;;CA6Mf,CAAC"}
@@ -59,13 +59,6 @@ var convertParts = (message, metadata) => {
59
59
  type: "requires-action",
60
60
  reason: "interrupt"
61
61
  }
62
- },
63
- ...toolStatus?.type === "cancelled" && {
64
- status: {
65
- type: "incomplete",
66
- reason: "cancelled",
67
- error: toolStatus.reason
68
- }
69
62
  }
70
63
  };
71
64
  }
@@ -100,13 +93,6 @@ var convertParts = (message, metadata) => {
100
93
  type: "requires-action",
101
94
  reason: "interrupt"
102
95
  }
103
- },
104
- ...toolStatus?.type === "cancelled" && {
105
- status: {
106
- type: "incomplete",
107
- reason: "cancelled",
108
- error: toolStatus.reason
109
- }
110
96
  }
111
97
  };
112
98
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/ui/utils/convertMessage.ts"],"sourcesContent":["import { isToolUIPart, type UIMessage } from \"ai\";\nimport {\n unstable_createMessageConverter,\n type ReasoningMessagePart,\n type ToolCallMessagePart,\n type TextMessagePart,\n type DataMessagePart,\n type SourceMessagePart,\n type useExternalMessageConverter,\n} from \"@assistant-ui/react\";\n\nfunction stripClosingDelimiters(json: string) {\n return json.replace(/[}\\]\"]+$/, \"\");\n}\n\nconst convertParts = (\n message: UIMessage,\n metadata: useExternalMessageConverter.Metadata,\n) => {\n if (!message.parts || message.parts.length === 0) {\n return [];\n }\n\n return message.parts\n .filter((p) => p.type !== \"step-start\" && p.type !== \"file\")\n .map((part) => {\n const type = part.type;\n\n // Handle text parts\n if (type === \"text\") {\n return {\n type: \"text\",\n text: part.text,\n } satisfies TextMessagePart;\n }\n\n // Handle reasoning parts\n if (type === \"reasoning\") {\n return {\n type: \"reasoning\",\n text: part.text,\n } satisfies ReasoningMessagePart;\n }\n\n // Handle tool-* parts (AI SDK v5 tool calls)\n if (isToolUIPart(part)) {\n const toolName = type.replace(\"tool-\", \"\");\n const toolCallId = part.toolCallId;\n\n // Extract args and result based on state\n let args: any = {};\n let result: any;\n let isError = false;\n\n if (\n part.state === \"input-streaming\" ||\n part.state === \"input-available\"\n ) {\n args = part.input || {};\n } else if (part.state === \"output-available\") {\n args = part.input || {};\n result = part.output;\n } else if (part.state === \"output-error\") {\n args = part.input || {};\n isError = true;\n result = { error: part.errorText };\n }\n\n let argsText = JSON.stringify(args);\n if (part.state === \"input-streaming\") {\n // the argsText is not complete, so we need to strip the closing delimiters\n // these are added by the AI SDK in fix-json\n argsText = stripClosingDelimiters(argsText);\n }\n\n const toolStatus = metadata.toolStatuses?.[toolCallId];\n return {\n type: \"tool-call\",\n toolName,\n toolCallId,\n argsText,\n args,\n result,\n isError,\n ...(toolStatus?.type === \"interrupt\" && {\n interrupt: toolStatus.payload,\n status: {\n type: \"requires-action\" as const,\n reason: \"interrupt\",\n },\n }),\n ...(toolStatus?.type === \"cancelled\" && {\n status: {\n type: \"incomplete\" as const,\n reason: \"cancelled\",\n error: toolStatus.reason,\n },\n }),\n } satisfies ToolCallMessagePart;\n }\n\n // Handle dynamic-tool parts\n if (type === \"dynamic-tool\") {\n const toolName = part.toolName;\n const toolCallId = part.toolCallId;\n\n // Extract args and result based on state\n let args: any = {};\n let result: any;\n let isError = false;\n\n if (\n part.state === \"input-streaming\" ||\n part.state === \"input-available\"\n ) {\n args = part.input || {};\n } else if (part.state === \"output-available\") {\n args = part.input || {};\n result = part.output;\n } else if (part.state === \"output-error\") {\n args = part.input || {};\n isError = true;\n result = { error: part.errorText };\n }\n\n const toolStatus = metadata.toolStatuses?.[toolCallId];\n return {\n type: \"tool-call\",\n toolName,\n toolCallId,\n argsText: JSON.stringify(args),\n args,\n result,\n isError,\n ...(toolStatus?.type === \"interrupt\" && {\n interrupt: toolStatus.payload,\n status: {\n type: \"requires-action\" as const,\n reason: \"interrupt\",\n },\n }),\n ...(toolStatus?.type === \"cancelled\" && {\n status: {\n type: \"incomplete\" as const,\n reason: \"cancelled\",\n error: toolStatus.reason,\n },\n }),\n } satisfies ToolCallMessagePart;\n }\n\n // Handle source-url parts\n if (type === \"source-url\") {\n return {\n type: \"source\",\n sourceType: \"url\",\n id: part.sourceId,\n url: part.url,\n title: part.title || \"\",\n } satisfies SourceMessagePart;\n }\n\n // Handle source-document parts\n if (type === \"source-document\") {\n console.warn(\n `Source document part type ${type} is not yet supported in conversion`,\n );\n return null;\n }\n\n // Handle data-* parts (AI SDK v5 data parts)\n if (type.startsWith(\"data-\")) {\n const name = type.substring(5);\n return {\n type: \"data\",\n name,\n data: (part as any).data,\n } satisfies DataMessagePart;\n }\n\n // For unsupported types, we'll skip them instead of throwing\n console.warn(`Unsupported message part type: ${type}`);\n return null;\n })\n .filter(Boolean) as any[];\n};\n\nexport const AISDKMessageConverter = unstable_createMessageConverter(\n (message: UIMessage, metadata: useExternalMessageConverter.Metadata) => {\n // UIMessage doesn't have createdAt, so we'll use current date or undefined\n const createdAt = new Date();\n switch (message.role) {\n case \"user\":\n return {\n role: \"user\",\n id: message.id,\n createdAt,\n content: convertParts(message, metadata),\n attachments: message.parts\n ?.filter((p) => p.type === \"file\")\n .map((part, idx) => {\n return {\n id: idx.toString(),\n type: part.mediaType.startsWith(\"image/\") ? \"image\" : \"file\",\n name: part.filename ?? \"file\",\n content: [\n part.mediaType.startsWith(\"image/\")\n ? {\n type: \"image\",\n image: part.url,\n filename: part.filename!,\n }\n : {\n type: \"file\",\n filename: part.filename!,\n data: part.url,\n mimeType: part.mediaType,\n },\n ],\n contentType: part.mediaType ?? \"unknown/unknown\",\n status: { type: \"complete\" as const },\n };\n }),\n };\n\n case \"system\":\n return {\n role: \"system\",\n id: message.id,\n createdAt,\n content: convertParts(message, metadata),\n };\n\n case \"assistant\":\n return {\n role: \"assistant\",\n id: message.id,\n createdAt,\n content: convertParts(message, metadata),\n metadata: {\n unstable_annotations: (message as any).annotations,\n unstable_data: Array.isArray((message as any).data)\n ? (message as any).data\n : (message as any).data\n ? [(message as any).data]\n : undefined,\n custom: {},\n },\n };\n\n default:\n console.warn(`Unsupported message role: ${message.role}`);\n return [];\n }\n },\n);\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/ui/utils/convertMessage.ts"],"sourcesContent":["import { isToolUIPart, type UIMessage } from \"ai\";\nimport {\n unstable_createMessageConverter,\n type ReasoningMessagePart,\n type ToolCallMessagePart,\n type TextMessagePart,\n type DataMessagePart,\n type SourceMessagePart,\n type useExternalMessageConverter,\n} from \"@assistant-ui/react\";\n\nfunction stripClosingDelimiters(json: string) {\n return json.replace(/[}\\]\"]+$/, \"\");\n}\n\nconst convertParts = (\n message: UIMessage,\n metadata: useExternalMessageConverter.Metadata,\n) => {\n if (!message.parts || message.parts.length === 0) {\n return [];\n }\n\n return message.parts\n .filter((p) => p.type !== \"step-start\" && p.type !== \"file\")\n .map((part) => {\n const type = part.type;\n\n // Handle text parts\n if (type === \"text\") {\n return {\n type: \"text\",\n text: part.text,\n } satisfies TextMessagePart;\n }\n\n // Handle reasoning parts\n if (type === \"reasoning\") {\n return {\n type: \"reasoning\",\n text: part.text,\n } satisfies ReasoningMessagePart;\n }\n\n // Handle tool-* parts (AI SDK v5 tool calls)\n if (isToolUIPart(part)) {\n const toolName = type.replace(\"tool-\", \"\");\n const toolCallId = part.toolCallId;\n\n // Extract args and result based on state\n let args: any = {};\n let result: any;\n let isError = false;\n\n if (\n part.state === \"input-streaming\" ||\n part.state === \"input-available\"\n ) {\n args = part.input || {};\n } else if (part.state === \"output-available\") {\n args = part.input || {};\n result = part.output;\n } else if (part.state === \"output-error\") {\n args = part.input || {};\n isError = true;\n result = { error: part.errorText };\n }\n\n let argsText = JSON.stringify(args);\n if (part.state === \"input-streaming\") {\n // the argsText is not complete, so we need to strip the closing delimiters\n // these are added by the AI SDK in fix-json\n argsText = stripClosingDelimiters(argsText);\n }\n\n const toolStatus = metadata.toolStatuses?.[toolCallId];\n return {\n type: \"tool-call\",\n toolName,\n toolCallId,\n argsText,\n args,\n result,\n isError,\n ...(toolStatus?.type === \"interrupt\" && {\n interrupt: toolStatus.payload,\n status: {\n type: \"requires-action\" as const,\n reason: \"interrupt\",\n },\n }),\n } satisfies ToolCallMessagePart;\n }\n\n // Handle dynamic-tool parts\n if (type === \"dynamic-tool\") {\n const toolName = part.toolName;\n const toolCallId = part.toolCallId;\n\n // Extract args and result based on state\n let args: any = {};\n let result: any;\n let isError = false;\n\n if (\n part.state === \"input-streaming\" ||\n part.state === \"input-available\"\n ) {\n args = part.input || {};\n } else if (part.state === \"output-available\") {\n args = part.input || {};\n result = part.output;\n } else if (part.state === \"output-error\") {\n args = part.input || {};\n isError = true;\n result = { error: part.errorText };\n }\n\n const toolStatus = metadata.toolStatuses?.[toolCallId];\n return {\n type: \"tool-call\",\n toolName,\n toolCallId,\n argsText: JSON.stringify(args),\n args,\n result,\n isError,\n ...(toolStatus?.type === \"interrupt\" && {\n interrupt: toolStatus.payload,\n status: {\n type: \"requires-action\" as const,\n reason: \"interrupt\",\n },\n }),\n } satisfies ToolCallMessagePart;\n }\n\n // Handle source-url parts\n if (type === \"source-url\") {\n return {\n type: \"source\",\n sourceType: \"url\",\n id: part.sourceId,\n url: part.url,\n title: part.title || \"\",\n } satisfies SourceMessagePart;\n }\n\n // Handle source-document parts\n if (type === \"source-document\") {\n console.warn(\n `Source document part type ${type} is not yet supported in conversion`,\n );\n return null;\n }\n\n // Handle data-* parts (AI SDK v5 data parts)\n if (type.startsWith(\"data-\")) {\n const name = type.substring(5);\n return {\n type: \"data\",\n name,\n data: (part as any).data,\n } satisfies DataMessagePart;\n }\n\n // For unsupported types, we'll skip them instead of throwing\n console.warn(`Unsupported message part type: ${type}`);\n return null;\n })\n .filter(Boolean) as any[];\n};\n\nexport const AISDKMessageConverter = unstable_createMessageConverter(\n (message: UIMessage, metadata: useExternalMessageConverter.Metadata) => {\n // UIMessage doesn't have createdAt, so we'll use current date or undefined\n const createdAt = new Date();\n switch (message.role) {\n case \"user\":\n return {\n role: \"user\",\n id: message.id,\n createdAt,\n content: convertParts(message, metadata),\n attachments: message.parts\n ?.filter((p) => p.type === \"file\")\n .map((part, idx) => {\n return {\n id: idx.toString(),\n type: part.mediaType.startsWith(\"image/\") ? \"image\" : \"file\",\n name: part.filename ?? \"file\",\n content: [\n part.mediaType.startsWith(\"image/\")\n ? {\n type: \"image\",\n image: part.url,\n filename: part.filename!,\n }\n : {\n type: \"file\",\n filename: part.filename!,\n data: part.url,\n mimeType: part.mediaType,\n },\n ],\n contentType: part.mediaType ?? \"unknown/unknown\",\n status: { type: \"complete\" as const },\n };\n }),\n };\n\n case \"system\":\n return {\n role: \"system\",\n id: message.id,\n createdAt,\n content: convertParts(message, metadata),\n };\n\n case \"assistant\":\n return {\n role: \"assistant\",\n id: message.id,\n createdAt,\n content: convertParts(message, metadata),\n metadata: {\n unstable_annotations: (message as any).annotations,\n unstable_data: Array.isArray((message as any).data)\n ? (message as any).data\n : (message as any).data\n ? [(message as any).data]\n : undefined,\n custom: {},\n },\n };\n\n default:\n console.warn(`Unsupported message role: ${message.role}`);\n return [];\n }\n },\n);\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react-ai-sdk",
3
- "version": "1.1.17",
3
+ "version": "1.1.19",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -19,16 +19,16 @@
19
19
  "sideEffects": false,
20
20
  "dependencies": {
21
21
  "@ai-sdk/provider": "^2.0.0",
22
- "@ai-sdk/react": "^2.0.107",
22
+ "@ai-sdk/react": "^2.0.114",
23
23
  "@radix-ui/react-use-callback-ref": "^1.1.1",
24
24
  "@types/json-schema": "^7.0.15",
25
- "ai": "^5.0.107",
26
- "assistant-stream": "^0.2.43",
25
+ "ai": "^5.0.112",
26
+ "assistant-stream": "^0.2.45",
27
27
  "zod": "^4.1.13",
28
28
  "zustand": "^5.0.9"
29
29
  },
30
30
  "peerDependencies": {
31
- "@assistant-ui/react": "^0.11.48",
31
+ "@assistant-ui/react": "^0.11.50",
32
32
  "@types/react": "*",
33
33
  "assistant-cloud": "*",
34
34
  "react": "^18 || ^19 || ^19.0.0-rc"
@@ -42,11 +42,11 @@
42
42
  }
43
43
  },
44
44
  "devDependencies": {
45
- "@types/node": "^24.10.1",
45
+ "@types/node": "^25.0.0",
46
46
  "@types/react": "^19.2.7",
47
- "react": "19.2.1",
47
+ "react": "19.2.3",
48
48
  "tsx": "^4.21.0",
49
- "@assistant-ui/react": "0.11.48",
49
+ "@assistant-ui/react": "0.11.50",
50
50
  "@assistant-ui/x-buildutils": "0.0.1"
51
51
  },
52
52
  "publishConfig": {
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useState, useMemo } from "react";
4
4
  import type { UIMessage, useChat, CreateUIMessage } from "@ai-sdk/react";
5
+ import { isToolUIPart } from "ai";
5
6
  import {
6
7
  useExternalStoreRuntime,
7
8
  type ExternalStoreAdapter,
@@ -16,13 +17,6 @@ import {
16
17
  } from "@assistant-ui/react";
17
18
  import { sliceMessagesUntil } from "../utils/sliceMessagesUntil";
18
19
  import { toCreateMessage } from "../utils/toCreateMessage";
19
-
20
- export type CustomToCreateMessageFunction = <
21
- UI_MESSAGE extends UIMessage = UIMessage,
22
- >(
23
- message: AppendMessage,
24
- ) => CreateUIMessage<UI_MESSAGE>;
25
-
26
20
  import { vercelAttachmentAdapter } from "../utils/vercelAttachmentAdapter";
27
21
  import { getVercelAIMessages } from "../getVercelAIMessages";
28
22
  import { AISDKMessageConverter } from "../utils/convertMessage";
@@ -32,9 +26,11 @@ import {
32
26
  } from "../adapters/aiSDKFormatAdapter";
33
27
  import { useExternalHistory } from "./useExternalHistory";
34
28
 
35
- type PendingHumanTool = {
36
- readonly toolCallId: string;
37
- };
29
+ export type CustomToCreateMessageFunction = <
30
+ UI_MESSAGE extends UIMessage = UIMessage,
31
+ >(
32
+ message: AppendMessage,
33
+ ) => CreateUIMessage<UI_MESSAGE>;
38
34
 
39
35
  export type AISDKRuntimeAdapter = {
40
36
  adapters?:
@@ -63,13 +59,18 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
63
59
  }: AISDKRuntimeAdapter = {},
64
60
  ) => {
65
61
  const contextAdapters = useRuntimeAdapters();
66
- const isRunning =
67
- chatHelpers.status === "submitted" || chatHelpers.status === "streaming";
68
-
69
62
  const [toolStatuses, setToolStatuses] = useState<
70
63
  Record<string, ToolExecutionStatus>
71
64
  >({});
72
65
 
66
+ const hasExecutingTools = Object.values(toolStatuses).some(
67
+ (s) => s?.type === "executing",
68
+ );
69
+ const isRunning =
70
+ chatHelpers.status === "submitted" ||
71
+ chatHelpers.status === "streaming" ||
72
+ hasExecutingTools;
73
+
73
74
  const messages = AISDKMessageConverter.useThreadMessages({
74
75
  isRunning,
75
76
  messages: chatHelpers.messages,
@@ -121,52 +122,32 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
121
122
  },
122
123
  );
123
124
 
124
- const completePendingToolCalls = () => {
125
+ const completePendingToolCalls = async () => {
125
126
  if (!cancelPendingToolCallsOnSend) return;
126
127
 
127
- const pendingHumanTools: PendingHumanTool[] = Object.entries(toolStatuses)
128
- .filter(
129
- (
130
- entry,
131
- ): entry is [
132
- string,
133
- Extract<ToolExecutionStatus, { type: "interrupt" }>,
134
- ] => entry[1]?.type === "interrupt",
135
- )
136
- .map(([toolCallId]) => ({ toolCallId }));
128
+ await toolInvocations.abort();
137
129
 
138
- if (pendingHumanTools.length === 0) return;
130
+ // Mark any tool without a result as cancelled (uses setMessages to avoid triggering sendAutomaticallyWhen)
131
+ chatHelpers.setMessages((messages) => {
132
+ const lastMessage = messages.at(-1);
133
+ if (lastMessage?.role !== "assistant") return messages;
139
134
 
140
- // Set tool statuses to cancelled so UI can show special state
141
- setToolStatuses((prev) => {
142
- const next = { ...prev };
143
- pendingHumanTools.forEach(({ toolCallId }) => {
144
- next[toolCallId] = {
145
- type: "cancelled",
146
- reason: "User cancelled tool call by sending a new message.",
135
+ let hasChanges = false;
136
+ const parts = lastMessage.parts?.map((part) => {
137
+ if (!isToolUIPart(part)) return part;
138
+ if (part.state === "output-available" || part.state === "output-error")
139
+ return part;
140
+
141
+ hasChanges = true;
142
+ return {
143
+ ...part,
144
+ state: "output-error" as const,
145
+ errorText: "User cancelled tool call by sending a new message.",
147
146
  };
148
147
  });
149
- return next;
150
- });
151
148
 
152
- // Mark tools as errored in the message history
153
- pendingHumanTools.forEach(({ toolCallId }) => {
154
- chatHelpers.setMessages(
155
- chatHelpers.messages.map((message) => {
156
- if (message.id === toolCallId) {
157
- return {
158
- ...message,
159
- content: [
160
- {
161
- type: "text",
162
- text: "User cancelled tool call by sending a new message.",
163
- },
164
- ],
165
- };
166
- }
167
- return message;
168
- }),
169
- );
149
+ if (!hasChanges) return messages;
150
+ return [...messages.slice(0, -1), { ...lastMessage, parts }];
170
151
  });
171
152
  };
172
153
 
@@ -189,10 +170,10 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
189
170
  ),
190
171
  onCancel: async () => {
191
172
  chatHelpers.stop();
192
- toolInvocations.abort();
173
+ await toolInvocations.abort();
193
174
  },
194
175
  onNew: async (message) => {
195
- completePendingToolCalls();
176
+ await completePendingToolCalls();
196
177
 
197
178
  const createMessage = (
198
179
  customToCreateMessage ?? toCreateMessage
@@ -202,8 +183,6 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
202
183
  });
203
184
  },
204
185
  onEdit: async (message) => {
205
- completePendingToolCalls();
206
-
207
186
  const newMessages = sliceMessagesUntil(
208
187
  chatHelpers.messages,
209
188
  message.parentId,
@@ -218,8 +197,6 @@ export const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(
218
197
  });
219
198
  },
220
199
  onReload: async (parentId: string | null, config) => {
221
- completePendingToolCalls();
222
-
223
200
  const newMessages = sliceMessagesUntil(chatHelpers.messages, parentId);
224
201
  chatHelpers.setMessages(newMessages);
225
202
 
@@ -89,13 +89,6 @@ const convertParts = (
89
89
  reason: "interrupt",
90
90
  },
91
91
  }),
92
- ...(toolStatus?.type === "cancelled" && {
93
- status: {
94
- type: "incomplete" as const,
95
- reason: "cancelled",
96
- error: toolStatus.reason,
97
- },
98
- }),
99
92
  } satisfies ToolCallMessagePart;
100
93
  }
101
94
 
@@ -139,13 +132,6 @@ const convertParts = (
139
132
  reason: "interrupt",
140
133
  },
141
134
  }),
142
- ...(toolStatus?.type === "cancelled" && {
143
- status: {
144
- type: "incomplete" as const,
145
- reason: "cancelled",
146
- error: toolStatus.reason,
147
- },
148
- }),
149
135
  } satisfies ToolCallMessagePart;
150
136
  }
151
137