@assistant-ui/react-ai-sdk 1.3.28 → 1.3.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/assistant-stream/dist/core/tool/schema-utils.d.ts +15 -0
  2. package/dist/assistant-stream/dist/core/tool/schema-utils.d.ts.map +1 -0
  3. package/dist/{packages/assistant-stream → assistant-stream}/dist/core/tool/schema-utils.js +9 -3
  4. package/dist/assistant-stream/dist/core/tool/schema-utils.js.map +1 -0
  5. package/dist/{packages/assistant-stream → assistant-stream}/dist/core/tool/tool-types.d.ts +27 -1
  6. package/dist/assistant-stream/dist/core/tool/tool-types.d.ts.map +1 -0
  7. package/dist/assistant-stream/dist/index.d.ts +2 -0
  8. package/dist/{node_modules → assistant-stream/dist/node_modules}/.pnpm/@types_json-schema@7.0.15/node_modules/@types/json-schema/index.d.ts +4 -5
  9. package/dist/assistant-stream/dist/node_modules/.pnpm/@types_json-schema@7.0.15/node_modules/@types/json-schema/index.d.ts.map +1 -0
  10. package/dist/assistant-stream/dist/resumable/createResumableAssistantStreamResponse.d.ts.map +1 -0
  11. package/dist/assistant-stream/dist/resumable/createResumableAssistantStreamResponse.js.map +1 -0
  12. package/dist/assistant-stream/dist/utils/json/json-value.d.ts.map +1 -0
  13. package/dist/frontendTools.d.ts +33 -6
  14. package/dist/frontendTools.d.ts.map +1 -1
  15. package/dist/frontendTools.js +3 -2
  16. package/dist/frontendTools.js.map +1 -1
  17. package/dist/generativeTools.d.ts +44 -0
  18. package/dist/generativeTools.d.ts.map +1 -0
  19. package/dist/generativeTools.js +55 -0
  20. package/dist/generativeTools.js.map +1 -0
  21. package/dist/index.d.ts +3 -3
  22. package/dist/index.js +3 -2
  23. package/dist/modelContentEnvelope.d.ts +1 -1
  24. package/dist/modelContentEnvelope.d.ts.map +1 -1
  25. package/dist/ui/resumable.d.ts +1 -1
  26. package/dist/ui/resumable.js +1 -1
  27. package/dist/ui/use-chat/AssistantChatTransport.d.ts.map +1 -1
  28. package/dist/ui/use-chat/AssistantChatTransport.js +2 -2
  29. package/dist/ui/use-chat/useAISDKRuntime.d.ts +3 -16
  30. package/dist/ui/use-chat/useAISDKRuntime.d.ts.map +1 -1
  31. package/dist/ui/use-chat/useAISDKRuntime.js +22 -35
  32. package/dist/ui/use-chat/useAISDKRuntime.js.map +1 -1
  33. package/dist/ui/use-chat/useChatRuntime.d.ts +2 -3
  34. package/dist/ui/use-chat/useChatRuntime.d.ts.map +1 -1
  35. package/dist/ui/use-chat/useChatRuntime.js +4 -3
  36. package/dist/ui/use-chat/useChatRuntime.js.map +1 -1
  37. package/dist/ui/use-chat/useExternalHistory.d.ts.map +1 -1
  38. package/dist/ui/use-chat/useExternalHistory.js +16 -8
  39. package/dist/ui/use-chat/useExternalHistory.js.map +1 -1
  40. package/dist/ui/utils/convertMessage.d.ts +3 -2
  41. package/dist/ui/utils/convertMessage.d.ts.map +1 -1
  42. package/dist/ui/utils/convertMessage.js +3 -1
  43. package/dist/ui/utils/convertMessage.js.map +1 -1
  44. package/dist/usage.d.ts.map +1 -1
  45. package/package.json +7 -7
  46. package/src/frontendTools.test.ts +24 -0
  47. package/src/frontendTools.ts +4 -6
  48. package/src/generativeTools.ts +90 -0
  49. package/src/index.ts +4 -1
  50. package/src/ui/use-chat/useAISDKRuntime.test.ts +36 -0
  51. package/src/ui/use-chat/useAISDKRuntime.ts +41 -52
  52. package/src/ui/use-chat/useChatRuntime.ts +22 -21
  53. package/src/ui/use-chat/useExternalHistory.test.ts +60 -1
  54. package/src/ui/use-chat/useExternalHistory.ts +17 -8
  55. package/src/ui/utils/convertMessage.test.ts +34 -6
  56. package/src/ui/utils/convertMessage.ts +6 -0
  57. package/dist/node_modules/.pnpm/@types_json-schema@7.0.15/node_modules/@types/json-schema/index.d.ts.map +0 -1
  58. package/dist/packages/assistant-stream/dist/core/tool/schema-utils.d.ts +0 -1
  59. package/dist/packages/assistant-stream/dist/core/tool/schema-utils.js.map +0 -1
  60. package/dist/packages/assistant-stream/dist/core/tool/tool-types.d.ts.map +0 -1
  61. package/dist/packages/assistant-stream/dist/index.d.ts +0 -1
  62. package/dist/packages/assistant-stream/dist/resumable/createResumableAssistantStreamResponse.d.ts.map +0 -1
  63. package/dist/packages/assistant-stream/dist/resumable/createResumableAssistantStreamResponse.js.map +0 -1
  64. package/dist/packages/assistant-stream/dist/utils/json/json-value.d.ts.map +0 -1
  65. /package/dist/{packages/assistant-stream → assistant-stream}/dist/core/AssistantStream.d.ts +0 -0
  66. /package/dist/{packages/assistant-stream → assistant-stream}/dist/core/AssistantStreamChunk.d.ts +0 -0
  67. /package/dist/{packages/assistant-stream → assistant-stream}/dist/core/accumulators/AssistantMessageStream.d.ts +0 -0
  68. /package/dist/{packages/assistant-stream → assistant-stream}/dist/core/accumulators/assistant-message-accumulator.d.ts +0 -0
  69. /package/dist/{packages/assistant-stream → assistant-stream}/dist/core/modules/assistant-stream.d.ts +0 -0
  70. /package/dist/{packages/assistant-stream → assistant-stream}/dist/core/modules/text.d.ts +0 -0
  71. /package/dist/{packages/assistant-stream → assistant-stream}/dist/core/modules/tool-call.d.ts +0 -0
  72. /package/dist/{packages/assistant-stream → assistant-stream}/dist/core/serialization/PlainText.d.ts +0 -0
  73. /package/dist/{packages/assistant-stream → assistant-stream}/dist/core/serialization/assistant-transport/AssistantTransport.d.ts +0 -0
  74. /package/dist/{packages/assistant-stream → assistant-stream}/dist/core/serialization/data-stream/DataStream.d.ts +0 -0
  75. /package/dist/{packages/assistant-stream → assistant-stream}/dist/core/serialization/ui-message-stream/UIMessageStream.d.ts +0 -0
  76. /package/dist/{packages/assistant-stream → assistant-stream}/dist/core/tool/ToolExecutionStream.d.ts +0 -0
  77. /package/dist/{packages/assistant-stream → assistant-stream}/dist/core/tool/toolResultStream.d.ts +0 -0
  78. /package/dist/{packages/assistant-stream → assistant-stream}/dist/core/utils/stream/AssistantMetaTransformStream.d.ts +0 -0
  79. /package/dist/{packages/assistant-stream → assistant-stream}/dist/core/utils/stream/AssistantTransformStream.d.ts +0 -0
  80. /package/dist/{packages/assistant-stream → assistant-stream}/dist/core/utils/types.d.ts +0 -0
  81. /package/dist/{packages/assistant-stream → assistant-stream}/dist/resumable/createResumableAssistantStreamResponse.d.ts +0 -0
  82. /package/dist/{packages/assistant-stream → assistant-stream}/dist/resumable/createResumableAssistantStreamResponse.js +0 -0
  83. /package/dist/{packages/assistant-stream → assistant-stream}/dist/resumable/index.d.ts +0 -0
  84. /package/dist/{packages/assistant-stream → assistant-stream}/dist/utils/json/json-value.d.ts +0 -0
  85. /package/dist/{packages/assistant-stream → assistant-stream}/dist/utils.d.ts +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"useAISDKRuntime.js","names":[],"sources":["../../../src/ui/use-chat/useAISDKRuntime.ts"],"sourcesContent":["\"use client\";\n\nimport { useState, useMemo, useRef } from \"react\";\nimport type { UIMessage, useChat, CreateUIMessage } from \"@ai-sdk/react\";\nimport { isToolUIPart, generateId } from \"ai\";\nimport {\n useExternalStoreRuntime,\n useRuntimeAdapters,\n useToolInvocations,\n type ToolExecutionStatus,\n} from \"@assistant-ui/core/react\";\nimport type {\n ExternalStoreAdapter,\n ThreadHistoryAdapter,\n AssistantRuntime,\n ThreadMessage,\n ThreadSuggestion,\n MessageFormatAdapter,\n MessageFormatItem,\n MessageFormatRepository,\n AppendMessage,\n RunConfig,\n McpAppMetadata,\n} from \"@assistant-ui/core\";\nimport { getExternalStoreMessages } from \"@assistant-ui/core\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\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 { wrapModelContentEnvelope } from \"../../modelContentEnvelope\";\nimport {\n type AISDKStorageFormat,\n aiSDKV6FormatAdapter,\n} from \"../adapters/aiSDKFormatAdapter\";\nimport {\n useExternalHistory,\n toExportedMessageRepository,\n} from \"./useExternalHistory\";\nimport { useStreamingTiming } from \"./useStreamingTiming\";\n\nexport type CustomToCreateMessageFunction = <\n UI_MESSAGE extends UIMessage = UIMessage,\n>(\n message: AppendMessage,\n) => CreateUIMessage<UI_MESSAGE>;\n\nconst toUIMessage = <UI_MESSAGE extends UIMessage>(\n createMessage: CreateUIMessage<UI_MESSAGE>,\n fallbackRole: UI_MESSAGE[\"role\"],\n): UI_MESSAGE =>\n ({\n ...createMessage,\n id: createMessage.id ?? generateId(),\n role: createMessage.role ?? fallbackRole,\n }) as 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 * Called when `runtime.thread.resumeRun(config)` is invoked.\n *\n * When omitted, `resumeRun` throws `\"Runtime does not support resuming runs.\"`.\n * Provide this to bridge resume invocations into a custom replay channel\n * (for example, an SSE reconnect endpoint keyed by turn id).\n */\n onResume?: ExternalStoreAdapter[\"onResume\"];\n /**\n * Follow up suggestions to surface on the thread. Use this to drive\n * dynamic suggestions from application state, tool results, or backend\n * responses; flows into `thread.suggestions` and is rendered by\n * components that read it (such as the shadcn `ThreadFollowupSuggestions`).\n */\n suggestions?: readonly ThreadSuggestion[] | 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 onResume,\n suggestions,\n }: AISDKRuntimeAdapter = {},\n) => {\n const contextAdapters = useRuntimeAdapters();\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n const toolArgsKeyOrderCacheRef = useRef<Map<string, Map<string, string[]>>>(\n new Map(),\n );\n const toolLastInputCacheRef = useRef<Map<string, ReadonlyJSONObject>>(\n new Map(),\n );\n const mcpAppMetadataCacheRef = useRef<Map<string, McpAppMetadata>>(new Map());\n const lastRunConfigRef = useRef<RunConfig | undefined>(undefined);\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 messageTiming = useStreamingTiming(chatHelpers.messages, isRunning);\n\n const messages = AISDKMessageConverter.useThreadMessages({\n isRunning,\n messages: chatHelpers.messages,\n metadata: useMemo(\n () => ({\n toolStatuses,\n messageTiming,\n toolArgsKeyOrderCache: toolArgsKeyOrderCacheRef.current,\n toolLastInputCache: toolLastInputCacheRef.current,\n mcpAppMetadataCache: mcpAppMetadataCacheRef.current,\n ...(chatHelpers.error && { error: chatHelpers.error.message }),\n }),\n [toolStatuses, messageTiming, chatHelpers.error],\n ),\n });\n\n const [runtimeRef] = useState(() => ({\n get current(): AssistantRuntime {\n return runtime;\n },\n }));\n\n const toolInvocations = 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 const output =\n command.modelContent !== undefined\n ? wrapModelContentEnvelope(command.result, command.modelContent)\n : command.result;\n chatHelpers.addToolResult({\n tool: command.toolName,\n toolCallId: command.toolCallId,\n output,\n options: { metadata: lastRunConfigRef.current },\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 aiSDKV6FormatAdapter 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 onExportExternalState: (): MessageFormatRepository<UI_MESSAGE> => {\n const exported = runtimeRef.current.thread.export();\n\n const expandedMessages: MessageFormatItem<UI_MESSAGE>[] = [];\n const lastInnerIdMap = new Map<string, string>();\n\n for (const item of exported.messages) {\n const innerMessages = getExternalStoreMessages<UI_MESSAGE>(\n item.message,\n );\n let parentId =\n item.parentId != null\n ? (lastInnerIdMap.get(item.parentId) ?? item.parentId)\n : null;\n for (const innerMessage of innerMessages) {\n expandedMessages.push({ parentId, message: innerMessage });\n parentId = aiSDKV6FormatAdapter.getId(innerMessage as UIMessage);\n }\n if (innerMessages.length > 0) {\n lastInnerIdMap.set(\n item.message.id,\n aiSDKV6FormatAdapter.getId(\n innerMessages[innerMessages.length - 1]! as UIMessage,\n ),\n );\n }\n }\n\n const result: MessageFormatRepository<UI_MESSAGE> = {\n messages: expandedMessages,\n };\n\n if (exported.headId != null) {\n result.headId = lastInnerIdMap.get(exported.headId) ?? exported.headId;\n }\n\n return result;\n },\n onLoadExternalState: (repo: MessageFormatRepository<UI_MESSAGE>) => {\n // Convert MessageFormatRepository to ExportedMessageRepository\n const exportedRepo = toExportedMessageRepository(\n AISDKMessageConverter.toThreadMessages,\n repo,\n );\n\n // Import into the thread's MessageRepository\n runtimeRef.current.thread.import(exportedRepo);\n },\n onCancel: async () => {\n chatHelpers.stop();\n await toolInvocations.abort();\n },\n onNew: async (message) => {\n const createMessage = (\n customToCreateMessage ?? toCreateMessage\n )<UI_MESSAGE>(message);\n\n if (!(message.startRun ?? message.role === \"user\")) {\n chatHelpers.setMessages((current) => [\n ...current,\n toUIMessage<UI_MESSAGE>(createMessage, message.role),\n ]);\n return;\n }\n\n lastRunConfigRef.current = message.runConfig;\n await completePendingToolCalls();\n await chatHelpers.sendMessage(createMessage, {\n metadata: message.runConfig,\n });\n },\n onEdit: async (message) => {\n const createMessage = (\n customToCreateMessage ?? toCreateMessage\n )<UI_MESSAGE>(message);\n\n if (!(message.startRun ?? message.role === \"user\")) {\n chatHelpers.setMessages((current) => [\n ...sliceMessagesUntil(current, message.parentId),\n toUIMessage<UI_MESSAGE>(createMessage, message.role),\n ]);\n return;\n }\n\n lastRunConfigRef.current = message.runConfig;\n chatHelpers.setMessages((current) =>\n sliceMessagesUntil(current, message.parentId),\n );\n await chatHelpers.sendMessage(createMessage, {\n metadata: message.runConfig,\n });\n },\n onReload: async (parentId: string | null, config) => {\n lastRunConfigRef.current = config.runConfig;\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 const options = { metadata: lastRunConfigRef.current };\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 options,\n });\n } else {\n chatHelpers.addToolOutput({\n state: \"output-available\",\n tool: toolCallId,\n toolCallId,\n output: result,\n options,\n });\n }\n },\n onResumeToolCall: (options) =>\n toolInvocations.resume(options.toolCallId, options.payload),\n onRespondToToolApproval: ({ approvalId, approved, reason }) => {\n void chatHelpers.addToolApprovalResponse({\n id: approvalId,\n approved,\n ...(reason != null && { reason }),\n options: { metadata: lastRunConfigRef.current },\n });\n },\n ...(onResume && { onResume }),\n ...(suggestions && { suggestions }),\n adapters: {\n attachments: vercelAttachmentAdapter,\n ...contextAdapters,\n ...adapters,\n },\n isLoading,\n });\n\n return runtime;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"useAISDKRuntime.js","names":[],"sources":["../../../src/ui/use-chat/useAISDKRuntime.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo, useRef, useState } from \"react\";\nimport type { UIMessage, useChat, CreateUIMessage } from \"@ai-sdk/react\";\nimport { isToolUIPart, generateId } from \"ai\";\nimport {\n useExternalStoreRuntime,\n useRuntimeAdapters,\n} from \"@assistant-ui/core/react\";\nimport type { ToolExecutionStatus } from \"@assistant-ui/core\";\nimport type {\n ExternalStoreAdapter,\n ExternalStoreSharedOptions,\n ThreadHistoryAdapter,\n AssistantRuntime,\n ThreadMessage,\n MessageFormatAdapter,\n MessageFormatItem,\n MessageFormatRepository,\n AppendMessage,\n RunConfig,\n McpAppMetadata,\n} from \"@assistant-ui/core\";\nimport {\n getExternalStoreMessages,\n pickExternalStoreSharedOptions,\n} from \"@assistant-ui/core\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\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 { wrapModelContentEnvelope } from \"../../modelContentEnvelope\";\nimport {\n type AISDKStorageFormat,\n aiSDKV6FormatAdapter,\n} from \"../adapters/aiSDKFormatAdapter\";\nimport {\n useExternalHistory,\n toExportedMessageRepository,\n} from \"./useExternalHistory\";\nimport { useStreamingTiming } from \"./useStreamingTiming\";\n\nexport type CustomToCreateMessageFunction = <\n UI_MESSAGE extends UIMessage = UIMessage,\n>(\n message: AppendMessage,\n) => CreateUIMessage<UI_MESSAGE>;\n\nconst toUIMessage = <UI_MESSAGE extends UIMessage>(\n createMessage: CreateUIMessage<UI_MESSAGE>,\n fallbackRole: UI_MESSAGE[\"role\"],\n): UI_MESSAGE =>\n ({\n ...createMessage,\n id: createMessage.id ?? generateId(),\n role: createMessage.role ?? fallbackRole,\n }) as UI_MESSAGE;\n\nexport type AISDKRuntimeAdapter = ExternalStoreSharedOptions & {\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 * Called when `runtime.thread.resumeRun(config)` is invoked.\n *\n * When omitted, `resumeRun` throws `\"Runtime does not support resuming runs.\"`.\n * Provide this to bridge resume invocations into a custom replay channel\n * (for example, an SSE reconnect endpoint keyed by turn id).\n */\n onResume?: ExternalStoreAdapter[\"onResume\"];\n};\n\nexport const useAISDKRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(\n chatHelpers: ReturnType<typeof useChat<UI_MESSAGE>>,\n adapter: AISDKRuntimeAdapter = {},\n) => {\n const {\n adapters,\n toCreateMessage: customToCreateMessage,\n cancelPendingToolCallsOnSend = true,\n onResume,\n } = adapter;\n const contextAdapters = useRuntimeAdapters();\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n const toolArgsKeyOrderCacheRef = useRef<Map<string, Map<string, string[]>>>(\n new Map(),\n );\n const toolLastInputCacheRef = useRef<Map<string, ReadonlyJSONObject>>(\n new Map(),\n );\n const mcpAppMetadataCacheRef = useRef<Map<string, McpAppMetadata>>(new Map());\n const lastRunConfigRef = useRef<RunConfig | undefined>(undefined);\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 messageTiming = useStreamingTiming(chatHelpers.messages, isRunning);\n\n // Flag the streaming message optimistic: its id can be swapped for a server\n // id mid-run, and the repository then drops the orphaned pre-swap id (#4037).\n const lastMessage = chatHelpers.messages.at(-1);\n const optimisticMessageId =\n isRunning && lastMessage?.role === \"assistant\" ? lastMessage.id : undefined;\n\n const messages = AISDKMessageConverter.useThreadMessages({\n isRunning,\n messages: chatHelpers.messages,\n metadata: useMemo(\n () => ({\n toolStatuses,\n messageTiming,\n toolArgsKeyOrderCache: toolArgsKeyOrderCacheRef.current,\n toolLastInputCache: toolLastInputCacheRef.current,\n mcpAppMetadataCache: mcpAppMetadataCacheRef.current,\n ...(optimisticMessageId && { optimisticMessageId }),\n ...(chatHelpers.error && { error: chatHelpers.error.message }),\n }),\n [toolStatuses, messageTiming, optimisticMessageId, chatHelpers.error],\n ),\n });\n\n const [runtimeRef] = useState(() => ({\n get current(): AssistantRuntime {\n return runtime;\n },\n }));\n\n const isLoading = useExternalHistory(\n runtimeRef,\n adapters?.history ?? contextAdapters?.history,\n AISDKMessageConverter.toThreadMessages as (\n messages: UI_MESSAGE[],\n ) => ThreadMessage[],\n aiSDKV6FormatAdapter 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 // The runtime auto-aborts in-flight tool invocations when a new run\n // is dispatched (append() / startRun()). All we need to do here is\n // mark any tool without a result as cancelled in the UI message list.\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 unstable_enableToolInvocations: true,\n setToolStatuses,\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 onExportExternalState: (): MessageFormatRepository<UI_MESSAGE> => {\n const exported = runtimeRef.current.thread.export();\n\n const expandedMessages: MessageFormatItem<UI_MESSAGE>[] = [];\n const lastInnerIdMap = new Map<string, string>();\n\n for (const item of exported.messages) {\n const innerMessages = getExternalStoreMessages<UI_MESSAGE>(\n item.message,\n );\n let parentId =\n item.parentId != null\n ? (lastInnerIdMap.get(item.parentId) ?? item.parentId)\n : null;\n for (const innerMessage of innerMessages) {\n expandedMessages.push({ parentId, message: innerMessage });\n parentId = aiSDKV6FormatAdapter.getId(innerMessage as UIMessage);\n }\n if (innerMessages.length > 0) {\n lastInnerIdMap.set(\n item.message.id,\n aiSDKV6FormatAdapter.getId(\n innerMessages[innerMessages.length - 1]! as UIMessage,\n ),\n );\n }\n }\n\n const result: MessageFormatRepository<UI_MESSAGE> = {\n messages: expandedMessages,\n };\n\n if (exported.headId != null) {\n result.headId = lastInnerIdMap.get(exported.headId) ?? exported.headId;\n }\n\n return result;\n },\n onLoadExternalState: (repo: MessageFormatRepository<UI_MESSAGE>) => {\n // Convert MessageFormatRepository to ExportedMessageRepository\n const exportedRepo = toExportedMessageRepository(\n AISDKMessageConverter.toThreadMessages,\n repo,\n );\n\n // Import into the thread's MessageRepository\n runtimeRef.current.thread.import(exportedRepo);\n },\n onCancel: async () => {\n chatHelpers.stop();\n },\n onNew: async (message) => {\n const createMessage = (\n customToCreateMessage ?? toCreateMessage\n )<UI_MESSAGE>(message);\n\n if (!(message.startRun ?? message.role === \"user\")) {\n chatHelpers.setMessages((current) => [\n ...current,\n toUIMessage<UI_MESSAGE>(createMessage, message.role),\n ]);\n return;\n }\n\n lastRunConfigRef.current = message.runConfig;\n await completePendingToolCalls();\n await chatHelpers.sendMessage(createMessage, {\n metadata: message.runConfig,\n });\n },\n onEdit: async (message) => {\n const createMessage = (\n customToCreateMessage ?? toCreateMessage\n )<UI_MESSAGE>(message);\n\n if (!(message.startRun ?? message.role === \"user\")) {\n chatHelpers.setMessages((current) => [\n ...sliceMessagesUntil(current, message.parentId),\n toUIMessage<UI_MESSAGE>(createMessage, message.role),\n ]);\n return;\n }\n\n lastRunConfigRef.current = message.runConfig;\n chatHelpers.setMessages((current) =>\n sliceMessagesUntil(current, message.parentId),\n );\n await chatHelpers.sendMessage(createMessage, {\n metadata: message.runConfig,\n });\n },\n onReload: async (parentId: string | null, config) => {\n lastRunConfigRef.current = config.runConfig;\n const newMessages = sliceMessagesUntil(chatHelpers.messages, parentId);\n chatHelpers.setMessages(newMessages);\n\n await chatHelpers.regenerate({ metadata: config.runConfig });\n },\n onAddToolResult: ({\n toolCallId,\n toolName,\n result,\n isError,\n modelContent,\n }) => {\n const options = { metadata: lastRunConfigRef.current };\n if (isError) {\n chatHelpers.addToolOutput({\n state: \"output-error\",\n tool: toolName ?? toolCallId,\n toolCallId,\n errorText:\n typeof result === \"string\" ? result : JSON.stringify(result),\n options,\n });\n } else {\n const output =\n modelContent !== undefined\n ? wrapModelContentEnvelope(result, modelContent)\n : result;\n chatHelpers.addToolResult({\n tool: toolName,\n toolCallId,\n output,\n options,\n });\n }\n },\n onRespondToToolApproval: ({ approvalId, approved, reason }) => {\n void chatHelpers.addToolApprovalResponse({\n id: approvalId,\n approved,\n ...(reason != null && { reason }),\n options: { metadata: lastRunConfigRef.current },\n });\n },\n ...pickExternalStoreSharedOptions(adapter),\n ...(onResume && { onResume }),\n adapters: {\n attachments: vercelAttachmentAdapter,\n ...contextAdapters,\n ...adapters,\n },\n isLoading,\n });\n\n return runtime;\n};\n"],"mappings":"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"}
@@ -1,16 +1,15 @@
1
1
  import { AISDKRuntimeAdapter, CustomToCreateMessageFunction } from "./useAISDKRuntime.js";
2
2
  import { ChatInit } from "ai";
3
- import { AssistantRuntime } from "@assistant-ui/core";
3
+ import { AssistantRuntime, ExternalStoreSharedOptions } from "@assistant-ui/core";
4
4
  import { UIMessage as UIMessage$1 } from "@ai-sdk/react";
5
5
  import { AssistantCloud } from "assistant-cloud";
6
6
 
7
7
  //#region src/ui/use-chat/useChatRuntime.d.ts
8
- type UseChatRuntimeOptions<UI_MESSAGE extends UIMessage$1 = UIMessage$1> = ChatInit<UI_MESSAGE> & {
8
+ type UseChatRuntimeOptions<UI_MESSAGE extends UIMessage$1 = UIMessage$1> = ChatInit<UI_MESSAGE> & ExternalStoreSharedOptions & {
9
9
  cloud?: AssistantCloud | undefined;
10
10
  adapters?: AISDKRuntimeAdapter["adapters"] | undefined;
11
11
  toCreateMessage?: CustomToCreateMessageFunction;
12
12
  onResume?: AISDKRuntimeAdapter["onResume"];
13
- suggestions?: AISDKRuntimeAdapter["suggestions"];
14
13
  };
15
14
  declare const useChatRuntime: <UI_MESSAGE extends UIMessage$1 = UIMessage$1>({
16
15
  cloud,
@@ -1 +1 @@
1
- {"version":3,"file":"useChatRuntime.d.ts","names":[],"sources":["../../../src/ui/use-chat/useChatRuntime.ts"],"mappings":";;;;;;;KAoBY,qBAAA,oBAAyC,WAAA,GAAY,WAAA,IAC/D,QAAA,CAAS,UAAA;EACP,KAAA,GAAQ,cAAA;EACR,QAAA,GAAW,mBAAA;EACX,eAAA,GAAkB,6BAAA;EAClB,QAAA,GAAW,mBAAA;EACX,WAAA,GAAc,mBAAA;AAAA;AAAA,cA0GL,cAAA,sBAAqC,WAAA,GAAY,WAAA;EAAW,KAAA;EAAA,GAAA;AAAA,IAGtE,qBAAA,CAAsB,UAAA,MAAmB,gBAAA"}
1
+ {"version":3,"file":"useChatRuntime.d.ts","names":[],"sources":["../../../src/ui/use-chat/useChatRuntime.ts"],"mappings":";;;;;;;KAwBY,qBAAA,oBAAyC,WAAA,GAAY,WAAA,IAC/D,QAAA,CAAS,UAAA,IACP,0BAAA;EACE,KAAA,GAAQ,cAAA;EACR,QAAA,GAAW,mBAAA;EACX,eAAA,GAAkB,6BAAA;EAClB,QAAA,GAAW,mBAAA;AAAA;AAAA,cAwGJ,cAAA,sBAAqC,WAAA,GAAY,WAAA;EAAW,KAAA;EAAA,GAAA;AAAA,IAGtE,qBAAA,CAAsB,UAAA,MAAmB,gBAAA"}
@@ -3,6 +3,7 @@ import { useAISDKRuntime } from "./useAISDKRuntime.js";
3
3
  import { AssistantChatTransport } from "./AssistantChatTransport.js";
4
4
  import { useEffect, useMemo, useRef } from "react";
5
5
  import { useCloudThreadListAdapter, useRemoteThreadListRuntime } from "@assistant-ui/core/react";
6
+ import { pickExternalStoreSharedOptions } from "@assistant-ui/core";
6
7
  import { useAui, useAuiState } from "@assistant-ui/store";
7
8
  import { useChat } from "@ai-sdk/react";
8
9
  //#region src/ui/use-chat/useChatRuntime.ts
@@ -23,7 +24,7 @@ const getResumableAdapter = (transport) => {
23
24
  return candidate.call(transport);
24
25
  };
25
26
  const useChatThreadRuntime = (options) => {
26
- const { adapters, transport: transportOptions, toCreateMessage, onResume, suggestions, ...chatOptions } = options ?? {};
27
+ const { adapters, transport: transportOptions, toCreateMessage, isDisabled: _isDisabled, isSendDisabled: _isSendDisabled, unstable_capabilities: _unstable_capabilities, suggestions: _suggestions, onResume, ...chatOptions } = options ?? {};
27
28
  const transport = useDynamicChatTransport(transportOptions ?? new AssistantChatTransport());
28
29
  const id = useAuiState((s) => s.threadListItem.id);
29
30
  const aui = useAui();
@@ -34,9 +35,9 @@ const useChatThreadRuntime = (options) => {
34
35
  });
35
36
  const runtime = useAISDKRuntime(chat, {
36
37
  adapters,
38
+ ...pickExternalStoreSharedOptions(options ?? {}),
37
39
  ...toCreateMessage && { toCreateMessage },
38
- ...onResume && { onResume },
39
- ...suggestions && { suggestions }
40
+ ...onResume && { onResume }
40
41
  });
41
42
  if (transport instanceof AssistantChatTransport) {
42
43
  transport.setRuntime(runtime);
@@ -1 +1 @@
1
- {"version":3,"file":"useChatRuntime.js","names":[],"sources":["../../../src/ui/use-chat/useChatRuntime.ts"],"sourcesContent":["\"use client\";\n\nimport { useChat, type UIMessage } from \"@ai-sdk/react\";\nimport type { AssistantCloud } from \"assistant-cloud\";\nimport type { AssistantRuntime } from \"@assistant-ui/core\";\nimport {\n useCloudThreadListAdapter,\n useRemoteThreadListRuntime,\n} from \"@assistant-ui/core/react\";\nimport { useAui, useAuiState } from \"@assistant-ui/store\";\nimport {\n useAISDKRuntime,\n type AISDKRuntimeAdapter,\n type CustomToCreateMessageFunction,\n} from \"./useAISDKRuntime\";\nimport type { ChatInit, ChatTransport } from \"ai\";\nimport { AssistantChatTransport } from \"./AssistantChatTransport\";\nimport type { AssistantChatResumableOptions } from \"../resumable\";\nimport { useEffect, useMemo, useRef } from \"react\";\n\nexport type UseChatRuntimeOptions<UI_MESSAGE extends UIMessage = UIMessage> =\n ChatInit<UI_MESSAGE> & {\n cloud?: AssistantCloud | undefined;\n adapters?: AISDKRuntimeAdapter[\"adapters\"] | undefined;\n toCreateMessage?: CustomToCreateMessageFunction;\n onResume?: AISDKRuntimeAdapter[\"onResume\"];\n suggestions?: AISDKRuntimeAdapter[\"suggestions\"];\n };\n\nconst useDynamicChatTransport = <UI_MESSAGE extends UIMessage = UIMessage>(\n transport: ChatTransport<UI_MESSAGE>,\n): ChatTransport<UI_MESSAGE> => {\n // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\n const transportRef = useRef<ChatTransport<UI_MESSAGE>>(transport);\n // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\n useEffect(() => {\n transportRef.current = transport;\n });\n // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\n const dynamicTransport = useMemo(\n () =>\n new Proxy(transportRef.current, {\n get(_, prop) {\n const res =\n transportRef.current[prop as keyof ChatTransport<UI_MESSAGE>];\n return typeof res === \"function\"\n ? res.bind(transportRef.current)\n : res;\n },\n }),\n [],\n );\n return dynamicTransport;\n};\n\nconst getResumableAdapter = <UI_MESSAGE extends UIMessage>(\n transport: ChatTransport<UI_MESSAGE>,\n): AssistantChatResumableOptions | undefined => {\n if (transport instanceof AssistantChatTransport) {\n return transport.getResumableAdapter();\n }\n const candidate = (transport as { getResumableAdapter?: () => unknown })\n .getResumableAdapter;\n if (typeof candidate !== \"function\") return undefined;\n return candidate.call(transport) as AssistantChatResumableOptions | undefined;\n};\n\nconst useChatThreadRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(\n options?: UseChatRuntimeOptions<UI_MESSAGE>,\n): AssistantRuntime => {\n const {\n adapters,\n transport: transportOptions,\n toCreateMessage,\n onResume,\n suggestions,\n ...chatOptions\n } = options ?? {};\n\n // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\n const transport = useDynamicChatTransport(\n transportOptions ?? new AssistantChatTransport(),\n );\n\n // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\n const id = useAuiState((s) => s.threadListItem.id);\n // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\n const aui = useAui();\n // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\n const chat = useChat({\n ...chatOptions,\n id,\n transport,\n });\n\n // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\n const runtime = useAISDKRuntime(chat, {\n adapters,\n ...(toCreateMessage && { toCreateMessage }),\n ...(onResume && { onResume }),\n ...(suggestions && { suggestions }),\n });\n\n if (transport instanceof AssistantChatTransport) {\n transport.setRuntime(runtime);\n transport.__internal_setGetThreadListItem(() =>\n aui.threadListItem.source ? aui.threadListItem() : undefined,\n );\n }\n\n // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\n const resumeFiredRef = useRef(false);\n // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\n useEffect(() => {\n if (resumeFiredRef.current) return;\n const adapter = getResumableAdapter(transport);\n if (!adapter) return;\n const pending = adapter.storage.getStreamId();\n if (!pending) return;\n resumeFiredRef.current = true;\n chat.resumeStream().catch((err: unknown) => {\n console.warn(\n \"[assistant-ui] resumable: resume failed; clearing stored stream id\",\n err,\n );\n adapter.storage.clear();\n });\n }, [transport, chat]);\n\n return runtime;\n};\n\nexport const useChatRuntime = <UI_MESSAGE extends UIMessage = UIMessage>({\n cloud,\n ...options\n}: UseChatRuntimeOptions<UI_MESSAGE> = {}): AssistantRuntime => {\n const cloudAdapter = useCloudThreadListAdapter({ cloud });\n return useRemoteThreadListRuntime({\n runtimeHook: function RuntimeHook() {\n // biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage\n return useChatThreadRuntime(options);\n },\n adapter: cloudAdapter,\n allowNesting: true,\n });\n};\n"],"mappings":";;;;;;;;AA6BA,MAAM,2BACJ,cAC8B;CAE9B,MAAM,eAAe,OAAkC,SAAS;CAEhE,gBAAgB;EACd,aAAa,UAAU;CACzB,CAAC;CAeD,OAbyB,cAErB,IAAI,MAAM,aAAa,SAAS,EAC9B,IAAI,GAAG,MAAM;EACX,MAAM,MACJ,aAAa,QAAQ;EACvB,OAAO,OAAO,QAAQ,aAClB,IAAI,KAAK,aAAa,OAAO,IAC7B;CACN,EACF,CAAC,GACH,CAAC,CAEmB;AACxB;AAEA,MAAM,uBACJ,cAC8C;CAC9C,IAAI,qBAAqB,wBACvB,OAAO,UAAU,oBAAoB;CAEvC,MAAM,YAAa,UAChB;CACH,IAAI,OAAO,cAAc,YAAY,OAAO,KAAA;CAC5C,OAAO,UAAU,KAAK,SAAS;AACjC;AAEA,MAAM,wBACJ,YACqB;CACrB,MAAM,EACJ,UACA,WAAW,kBACX,iBACA,UACA,aACA,GAAG,gBACD,WAAW,CAAC;CAGhB,MAAM,YAAY,wBAChB,oBAAoB,IAAI,uBAAuB,CACjD;CAGA,MAAM,KAAK,aAAa,MAAM,EAAE,eAAe,EAAE;CAEjD,MAAM,MAAM,OAAO;CAEnB,MAAM,OAAO,QAAQ;EACnB,GAAG;EACH;EACA;CACF,CAAC;CAGD,MAAM,UAAU,gBAAgB,MAAM;EACpC;EACA,GAAI,mBAAmB,EAAE,gBAAgB;EACzC,GAAI,YAAY,EAAE,SAAS;EAC3B,GAAI,eAAe,EAAE,YAAY;CACnC,CAAC;CAED,IAAI,qBAAqB,wBAAwB;EAC/C,UAAU,WAAW,OAAO;EAC5B,UAAU,sCACR,IAAI,eAAe,SAAS,IAAI,eAAe,IAAI,KAAA,CACrD;CACF;CAGA,MAAM,iBAAiB,OAAO,KAAK;CAEnC,gBAAgB;EACd,IAAI,eAAe,SAAS;EAC5B,MAAM,UAAU,oBAAoB,SAAS;EAC7C,IAAI,CAAC,SAAS;EAEd,IAAI,CADY,QAAQ,QAAQ,YACrB,GAAG;EACd,eAAe,UAAU;EACzB,KAAK,aAAa,EAAE,OAAO,QAAiB;GAC1C,QAAQ,KACN,sEACA,GACF;GACA,QAAQ,QAAQ,MAAM;EACxB,CAAC;CACH,GAAG,CAAC,WAAW,IAAI,CAAC;CAEpB,OAAO;AACT;AAEA,MAAa,kBAA4D,EACvE,OACA,GAAG,YACkC,CAAC,MAAwB;CAE9D,OAAO,2BAA2B;EAChC,aAAa,SAAS,cAAc;GAElC,OAAO,qBAAqB,OAAO;EACrC;EACA,SANmB,0BAA0B,EAAE,MAAM,CAMjC;EACpB,cAAc;CAChB,CAAC;AACH"}
1
+ {"version":3,"file":"useChatRuntime.js","names":[],"sources":["../../../src/ui/use-chat/useChatRuntime.ts"],"sourcesContent":["\"use client\";\n\nimport { useChat, type UIMessage } from \"@ai-sdk/react\";\nimport type { AssistantCloud } from \"assistant-cloud\";\nimport {\n pickExternalStoreSharedOptions,\n type AssistantRuntime,\n type ExternalStoreSharedOptions,\n} from \"@assistant-ui/core\";\nimport {\n useCloudThreadListAdapter,\n useRemoteThreadListRuntime,\n} from \"@assistant-ui/core/react\";\nimport { useAui, useAuiState } from \"@assistant-ui/store\";\nimport {\n useAISDKRuntime,\n type AISDKRuntimeAdapter,\n type CustomToCreateMessageFunction,\n} from \"./useAISDKRuntime\";\nimport type { ChatInit, ChatTransport } from \"ai\";\nimport { AssistantChatTransport } from \"./AssistantChatTransport\";\nimport type { AssistantChatResumableOptions } from \"../resumable\";\nimport { useEffect, useMemo, useRef } from \"react\";\n\nexport type UseChatRuntimeOptions<UI_MESSAGE extends UIMessage = UIMessage> =\n ChatInit<UI_MESSAGE> &\n ExternalStoreSharedOptions & {\n cloud?: AssistantCloud | undefined;\n adapters?: AISDKRuntimeAdapter[\"adapters\"] | undefined;\n toCreateMessage?: CustomToCreateMessageFunction;\n onResume?: AISDKRuntimeAdapter[\"onResume\"];\n };\n\nconst useDynamicChatTransport = <UI_MESSAGE extends UIMessage = UIMessage>(\n transport: ChatTransport<UI_MESSAGE>,\n): ChatTransport<UI_MESSAGE> => {\n const transportRef = useRef<ChatTransport<UI_MESSAGE>>(transport);\n useEffect(() => {\n transportRef.current = transport;\n });\n const dynamicTransport = useMemo(\n () =>\n new Proxy(transportRef.current, {\n get(_, prop) {\n const res =\n transportRef.current[prop as keyof ChatTransport<UI_MESSAGE>];\n return typeof res === \"function\"\n ? res.bind(transportRef.current)\n : res;\n },\n }),\n [],\n );\n return dynamicTransport;\n};\n\nconst getResumableAdapter = <UI_MESSAGE extends UIMessage>(\n transport: ChatTransport<UI_MESSAGE>,\n): AssistantChatResumableOptions | undefined => {\n if (transport instanceof AssistantChatTransport) {\n return transport.getResumableAdapter();\n }\n const candidate = (transport as { getResumableAdapter?: () => unknown })\n .getResumableAdapter;\n if (typeof candidate !== \"function\") return undefined;\n return candidate.call(transport) as AssistantChatResumableOptions | undefined;\n};\n\nconst useChatThreadRuntime = <UI_MESSAGE extends UIMessage = UIMessage>(\n options?: UseChatRuntimeOptions<UI_MESSAGE>,\n): AssistantRuntime => {\n const {\n adapters,\n transport: transportOptions,\n toCreateMessage,\n isDisabled: _isDisabled,\n isSendDisabled: _isSendDisabled,\n unstable_capabilities: _unstable_capabilities,\n suggestions: _suggestions,\n onResume,\n ...chatOptions\n } = options ?? {};\n // peel guard: any shared key left in `chatOptions` collapses this to `never`\n true satisfies keyof typeof chatOptions &\n keyof ExternalStoreSharedOptions extends never\n ? true\n : never;\n\n const transport = useDynamicChatTransport(\n transportOptions ?? new AssistantChatTransport(),\n );\n\n const id = useAuiState((s) => s.threadListItem.id);\n const aui = useAui();\n const chat = useChat({\n ...chatOptions,\n id,\n transport,\n });\n\n const runtime = useAISDKRuntime(chat, {\n adapters,\n ...pickExternalStoreSharedOptions(options ?? {}),\n ...(toCreateMessage && { toCreateMessage }),\n ...(onResume && { onResume }),\n });\n\n if (transport instanceof AssistantChatTransport) {\n transport.setRuntime(runtime);\n transport.__internal_setGetThreadListItem(() =>\n aui.threadListItem.source ? aui.threadListItem() : undefined,\n );\n }\n\n const resumeFiredRef = useRef(false);\n useEffect(() => {\n if (resumeFiredRef.current) return;\n const adapter = getResumableAdapter(transport);\n if (!adapter) return;\n const pending = adapter.storage.getStreamId();\n if (!pending) return;\n resumeFiredRef.current = true;\n chat.resumeStream().catch((err: unknown) => {\n console.warn(\n \"[assistant-ui] resumable: resume failed; clearing stored stream id\",\n err,\n );\n adapter.storage.clear();\n });\n }, [transport, chat]);\n\n return runtime;\n};\n\nexport const useChatRuntime = <UI_MESSAGE extends UIMessage = UIMessage>({\n cloud,\n ...options\n}: UseChatRuntimeOptions<UI_MESSAGE> = {}): AssistantRuntime => {\n const cloudAdapter = useCloudThreadListAdapter({ cloud });\n return useRemoteThreadListRuntime({\n runtimeHook: function RuntimeHook() {\n return useChatThreadRuntime(options);\n },\n adapter: cloudAdapter,\n allowNesting: true,\n });\n};\n"],"mappings":";;;;;;;;;AAiCA,MAAM,2BACJ,cAC8B;CAC9B,MAAM,eAAe,OAAkC,SAAS;CAChE,gBAAgB;EACd,aAAa,UAAU;CACzB,CAAC;CAcD,OAbyB,cAErB,IAAI,MAAM,aAAa,SAAS,EAC9B,IAAI,GAAG,MAAM;EACX,MAAM,MACJ,aAAa,QAAQ;EACvB,OAAO,OAAO,QAAQ,aAClB,IAAI,KAAK,aAAa,OAAO,IAC7B;CACN,EACF,CAAC,GACH,CAAC,CAEmB;AACxB;AAEA,MAAM,uBACJ,cAC8C;CAC9C,IAAI,qBAAqB,wBACvB,OAAO,UAAU,oBAAoB;CAEvC,MAAM,YAAa,UAChB;CACH,IAAI,OAAO,cAAc,YAAY,OAAO,KAAA;CAC5C,OAAO,UAAU,KAAK,SAAS;AACjC;AAEA,MAAM,wBACJ,YACqB;CACrB,MAAM,EACJ,UACA,WAAW,kBACX,iBACA,YAAY,aACZ,gBAAgB,iBAChB,uBAAuB,wBACvB,aAAa,cACb,UACA,GAAG,gBACD,WAAW,CAAC;CAOhB,MAAM,YAAY,wBAChB,oBAAoB,IAAI,uBAAuB,CACjD;CAEA,MAAM,KAAK,aAAa,MAAM,EAAE,eAAe,EAAE;CACjD,MAAM,MAAM,OAAO;CACnB,MAAM,OAAO,QAAQ;EACnB,GAAG;EACH;EACA;CACF,CAAC;CAED,MAAM,UAAU,gBAAgB,MAAM;EACpC;EACA,GAAG,+BAA+B,WAAW,CAAC,CAAC;EAC/C,GAAI,mBAAmB,EAAE,gBAAgB;EACzC,GAAI,YAAY,EAAE,SAAS;CAC7B,CAAC;CAED,IAAI,qBAAqB,wBAAwB;EAC/C,UAAU,WAAW,OAAO;EAC5B,UAAU,sCACR,IAAI,eAAe,SAAS,IAAI,eAAe,IAAI,KAAA,CACrD;CACF;CAEA,MAAM,iBAAiB,OAAO,KAAK;CACnC,gBAAgB;EACd,IAAI,eAAe,SAAS;EAC5B,MAAM,UAAU,oBAAoB,SAAS;EAC7C,IAAI,CAAC,SAAS;EAEd,IAAI,CADY,QAAQ,QAAQ,YACrB,GAAG;EACd,eAAe,UAAU;EACzB,KAAK,aAAa,EAAE,OAAO,QAAiB;GAC1C,QAAQ,KACN,sEACA,GACF;GACA,QAAQ,QAAQ,MAAM;EACxB,CAAC;CACH,GAAG,CAAC,WAAW,IAAI,CAAC;CAEpB,OAAO;AACT;AAEA,MAAa,kBAA4D,EACvE,OACA,GAAG,YACkC,CAAC,MAAwB;CAE9D,OAAO,2BAA2B;EAChC,aAAa,SAAS,cAAc;GAClC,OAAO,qBAAqB,OAAO;EACrC;EACA,SALmB,0BAA0B,EAAE,MAAM,CAKjC;EACpB,cAAc;CAChB,CAAC;AACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"useExternalHistory.d.ts","names":[],"sources":["../../../src/ui/use-chat/useExternalHistory.ts"],"mappings":";;;;cAsBa,2BAAA,aACX,gBAAA,GAAmB,QAAA,EAAU,QAAA,OAAe,aAAA,IAC5C,QAAA,EAAU,uBAAA,CAAwB,QAAA,MACjC,yBAAA;AAAA,cAaU,kBAAA,aACX,UAAA,EAAY,SAAA,CAAU,gBAAA,GACtB,cAAA,EAAgB,oBAAA,cAChB,gBAAA,GAAmB,QAAA,EAAU,QAAA,OAAe,aAAA,IAC5C,oBAAA,EAAsB,oBAAA,CAAqB,QAAA,QAC3C,aAAA,GAAgB,QAAA,EAAU,QAAA"}
1
+ {"version":3,"file":"useExternalHistory.d.ts","names":[],"sources":["../../../src/ui/use-chat/useExternalHistory.ts"],"mappings":";;;;cAsBa,2BAAA,aACX,gBAAA,GAAmB,QAAA,EAAU,QAAA,OAAe,aAAA,IAC5C,QAAA,EAAU,uBAAA,CAAwB,QAAA,MACjC,yBAAA;AAAA,cAsBU,kBAAA,aACX,UAAA,EAAY,SAAA,CAAU,gBAAA,GACtB,cAAA,EAAgB,oBAAA,cAChB,gBAAA,GAAmB,QAAA,EAAU,QAAA,OAAe,aAAA,IAC5C,oBAAA,EAAsB,oBAAA,CAAqB,QAAA,QAC3C,aAAA,GAAgB,QAAA,EAAU,QAAA"}
@@ -5,15 +5,23 @@ import { MessageRepository } from "@assistant-ui/core/internal";
5
5
  import { useAui } from "@assistant-ui/store";
6
6
  //#region src/ui/use-chat/useExternalHistory.ts
7
7
  const toExportedMessageRepository = (toThreadMessages, messages) => {
8
+ const survivingIds = /* @__PURE__ */ new Set();
9
+ const survivors = messages.messages.flatMap((m) => {
10
+ const message = toThreadMessages([m.message])[0];
11
+ if (!message) {
12
+ console.warn("Skipping a stored message that could not be loaded.");
13
+ return [];
14
+ }
15
+ if (m.parentId && !survivingIds.has(m.parentId)) return [];
16
+ survivingIds.add(message.id);
17
+ return [{
18
+ ...m,
19
+ message
20
+ }];
21
+ });
8
22
  return {
9
- headId: messages.headId,
10
- messages: messages.messages.map((m) => {
11
- const message = toThreadMessages([m.message])[0];
12
- return {
13
- ...m,
14
- message
15
- };
16
- })
23
+ headId: messages.headId && survivingIds.has(messages.headId) ? messages.headId : null,
24
+ messages: survivors
17
25
  };
18
26
  };
19
27
  const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storageFormatAdapter, onSetMessages) => {
@@ -1 +1 @@
1
- {"version":3,"file":"useExternalHistory.js","names":[],"sources":["../../../src/ui/use-chat/useExternalHistory.ts"],"sourcesContent":["\"use client\";\n\nimport type {\n AssistantRuntime,\n ThreadHistoryAdapter,\n ThreadMessage,\n MessageFormatAdapter,\n MessageFormatRepository,\n ExportedMessageRepository,\n} from \"@assistant-ui/core\";\nimport { getExternalStoreMessages } from \"@assistant-ui/core\";\nimport { MessageRepository } from \"@assistant-ui/core/internal\";\nimport { useAui } from \"@assistant-ui/store\";\nimport {\n useRef,\n useEffect,\n useState,\n type RefObject,\n useCallback,\n useMemo,\n} from \"react\";\n\nexport const toExportedMessageRepository = <TMessage>(\n toThreadMessages: (messages: TMessage[]) => ThreadMessage[],\n messages: MessageFormatRepository<TMessage>,\n): ExportedMessageRepository => {\n return {\n headId: messages.headId!,\n messages: messages.messages.map((m) => {\n const message = toThreadMessages([m.message])[0]!;\n return {\n ...m,\n message,\n };\n }),\n };\n};\n\nexport const useExternalHistory = <TMessage>(\n runtimeRef: RefObject<AssistantRuntime>,\n historyAdapter: ThreadHistoryAdapter | undefined,\n toThreadMessages: (messages: TMessage[]) => ThreadMessage[],\n storageFormatAdapter: MessageFormatAdapter<TMessage, any>,\n onSetMessages: (messages: TMessage[]) => void,\n) => {\n const loadedRef = useRef(false);\n\n const aui = useAui();\n const optionalThreadListItem = useCallback(\n () => (aui.threadListItem.source ? aui.threadListItem() : null),\n [aui],\n );\n\n const [isLoading, setIsLoading] = useState(false);\n\n const historyIds = useRef(new Set<string>());\n\n const onSetMessagesRef = useRef(onSetMessages);\n useEffect(() => {\n onSetMessagesRef.current = onSetMessages;\n });\n\n const formatAdapter = useMemo(() => {\n if (!historyAdapter) return undefined;\n if (!historyAdapter.withFormat) {\n throw new Error(\n \"useAISDKRuntime: ThreadHistoryAdapter is missing the required `withFormat` method.\",\n );\n }\n return historyAdapter.withFormat<TMessage, any>(storageFormatAdapter);\n }, [historyAdapter, storageFormatAdapter]);\n\n useEffect(() => {\n if (!formatAdapter || loadedRef.current) return;\n\n const loadHistory = async () => {\n setIsLoading(true);\n try {\n const repo = await formatAdapter.load();\n if (repo && repo.messages.length > 0) {\n const converted = toExportedMessageRepository(toThreadMessages, repo);\n runtimeRef.current.thread.import(converted);\n\n const tempRepo = new MessageRepository();\n tempRepo.import(converted);\n const messages = tempRepo.getMessages();\n\n onSetMessagesRef.current(\n messages.flatMap(getExternalStoreMessages<TMessage>),\n );\n\n historyIds.current = new Set(\n converted.messages.map((m) => m.message.id),\n );\n }\n } catch (error) {\n console.error(\"Failed to load message history:\", error);\n } finally {\n setIsLoading(false);\n }\n };\n\n loadedRef.current = true;\n\n if (!optionalThreadListItem()?.getState().remoteId) {\n setIsLoading(false);\n return;\n }\n\n loadHistory();\n }, [formatAdapter, toThreadMessages, runtimeRef, optionalThreadListItem]);\n\n const runStartRef = useRef<number | null>(null);\n const persistTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const stepBoundariesRef = useRef<number[]>([]);\n const wasRunningRef = useRef(false);\n const toolCallCountRef = useRef(0);\n\n useEffect(() => {\n if (!formatAdapter) return;\n\n const unsubscribe = runtimeRef.current.thread.subscribe(() => {\n const { isRunning } = runtimeRef.current.thread.getState();\n const wasRunning = wasRunningRef.current;\n wasRunningRef.current = isRunning;\n\n // Track step boundaries by content changes (more reliable than isRunning)\n if (runStartRef.current != null) {\n const lastMsg = runtimeRef.current.thread.getState().messages.at(-1);\n if (lastMsg?.role === \"assistant\") {\n const currentToolCallCount = lastMsg.content.filter(\n (p) => p.type === \"tool-call\",\n ).length;\n while (toolCallCountRef.current < currentToolCallCount) {\n stepBoundariesRef.current.push(Date.now() - runStartRef.current);\n toolCallCountRef.current++;\n }\n }\n }\n\n if (isRunning) {\n if (runStartRef.current == null) {\n runStartRef.current = Date.now();\n stepBoundariesRef.current = [];\n toolCallCountRef.current = 0;\n }\n // Cancel any pending persist — isRunning went back to true\n if (persistTimerRef.current) {\n clearTimeout(persistTimerRef.current);\n persistTimerRef.current = null;\n }\n return;\n }\n\n // Only act on the true→false transition\n if (!wasRunning) return;\n\n // Record step boundary offset (synchronous for accuracy)\n if (runStartRef.current != null) {\n stepBoundariesRef.current.push(Date.now() - runStartRef.current);\n }\n\n // Debounce: wait one macrotask so agentic step flickers are absorbed\n if (persistTimerRef.current) clearTimeout(persistTimerRef.current);\n persistTimerRef.current = setTimeout(async () => {\n persistTimerRef.current = null;\n\n // Re-read latest state — may have changed since the timeout was scheduled\n const latest = runtimeRef.current.thread.getState();\n if (latest.isRunning) return; // was just a flicker\n\n // Derive durationMs from the last boundary (covers all steps)\n const boundaries = stepBoundariesRef.current;\n const durationMs =\n boundaries.length > 0 ? boundaries.at(-1) : undefined;\n\n // Fallback: if only 1 boundary but message has multiple steps, distribute evenly\n if (boundaries.length === 1 && durationMs != null) {\n const lastAssistant = latest.messages.findLast(\n (m) => m.role === \"assistant\",\n );\n if (lastAssistant) {\n const tcCount = lastAssistant.content.filter(\n (p) => p.type === \"tool-call\",\n ).length;\n if (tcCount > 0) {\n const totalSteps = tcCount + 1;\n const stepDur = durationMs / totalSteps;\n boundaries.length = 0;\n for (let i = 0; i < totalSteps; i++) {\n boundaries.push(Math.round((i + 1) * stepDur));\n }\n }\n }\n }\n\n // Build per-step timestamps when there are multiple steps\n const stepTimestamps =\n boundaries.length > 1\n ? boundaries.map((endMs, i) => ({\n start_ms: i === 0 ? 0 : boundaries[i - 1]!,\n end_ms: endMs,\n }))\n : undefined;\n\n runStartRef.current = null;\n stepBoundariesRef.current = [];\n\n const telemetryOptions = {\n ...(durationMs != null ? { durationMs } : undefined),\n ...(stepTimestamps != null ? { stepTimestamps } : undefined),\n };\n\n const { messages } = latest;\n let lastInnerMessageId: string | null = null;\n\n const getLastInnerId = (msgs: TMessage[]): string | null =>\n msgs.length > 0 ? storageFormatAdapter.getId(msgs.at(-1)!) : null;\n\n const toBatchItems = (msgs: TMessage[]) =>\n msgs.map((msg, idx) => ({\n parentId:\n idx === 0\n ? lastInnerMessageId\n : storageFormatAdapter.getId(msgs[idx - 1]!),\n message: msg,\n }));\n\n for (const message of messages) {\n const innerMessages = getExternalStoreMessages<TMessage>(message);\n\n const isReady =\n message.status === undefined ||\n message.status.type === \"complete\" ||\n message.status.type === \"incomplete\";\n\n if (!isReady) {\n lastInnerMessageId =\n getLastInnerId(innerMessages) ?? lastInnerMessageId;\n continue;\n }\n\n if (historyIds.current.has(message.id)) {\n if (durationMs !== undefined) {\n let parentId = lastInnerMessageId;\n for (const innerMessage of innerMessages) {\n try {\n await formatAdapter.update?.(\n { parentId, message: innerMessage },\n storageFormatAdapter.getId(innerMessage),\n );\n } catch {\n // ignore update failures to avoid breaking the message processing loop\n }\n parentId = storageFormatAdapter.getId(innerMessage);\n }\n }\n lastInnerMessageId =\n getLastInnerId(innerMessages) ?? lastInnerMessageId;\n continue;\n }\n historyIds.current.add(message.id);\n\n const batchItems = toBatchItems(innerMessages);\n for (const item of batchItems) {\n await formatAdapter.append(item);\n }\n\n lastInnerMessageId =\n getLastInnerId(innerMessages) ?? lastInnerMessageId;\n\n formatAdapter.reportTelemetry?.(batchItems, telemetryOptions);\n }\n }, 0);\n });\n\n return () => {\n unsubscribe();\n if (persistTimerRef.current) {\n clearTimeout(persistTimerRef.current);\n persistTimerRef.current = null;\n }\n };\n }, [formatAdapter, storageFormatAdapter, runtimeRef]);\n\n return isLoading;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"useExternalHistory.js","names":[],"sources":["../../../src/ui/use-chat/useExternalHistory.ts"],"sourcesContent":["\"use client\";\n\nimport type {\n AssistantRuntime,\n ThreadHistoryAdapter,\n ThreadMessage,\n MessageFormatAdapter,\n MessageFormatRepository,\n ExportedMessageRepository,\n} from \"@assistant-ui/core\";\nimport { getExternalStoreMessages } from \"@assistant-ui/core\";\nimport { MessageRepository } from \"@assistant-ui/core/internal\";\nimport { useAui } from \"@assistant-ui/store\";\nimport {\n useRef,\n useEffect,\n useState,\n type RefObject,\n useCallback,\n useMemo,\n} from \"react\";\n\nexport const toExportedMessageRepository = <TMessage>(\n toThreadMessages: (messages: TMessage[]) => ThreadMessage[],\n messages: MessageFormatRepository<TMessage>,\n): ExportedMessageRepository => {\n const survivingIds = new Set<string>();\n const survivors = messages.messages.flatMap((m) => {\n const message = toThreadMessages([m.message])[0];\n if (!message) {\n console.warn(\"Skipping a stored message that could not be loaded.\");\n return [];\n }\n if (m.parentId && !survivingIds.has(m.parentId)) return [];\n survivingIds.add(message.id);\n return [{ ...m, message }];\n });\n\n return {\n headId:\n messages.headId && survivingIds.has(messages.headId)\n ? messages.headId\n : null,\n messages: survivors,\n };\n};\n\nexport const useExternalHistory = <TMessage>(\n runtimeRef: RefObject<AssistantRuntime>,\n historyAdapter: ThreadHistoryAdapter | undefined,\n toThreadMessages: (messages: TMessage[]) => ThreadMessage[],\n storageFormatAdapter: MessageFormatAdapter<TMessage, any>,\n onSetMessages: (messages: TMessage[]) => void,\n) => {\n const loadedRef = useRef(false);\n\n const aui = useAui();\n const optionalThreadListItem = useCallback(\n () => (aui.threadListItem.source ? aui.threadListItem() : null),\n [aui],\n );\n\n const [isLoading, setIsLoading] = useState(false);\n\n const historyIds = useRef(new Set<string>());\n\n const onSetMessagesRef = useRef(onSetMessages);\n useEffect(() => {\n onSetMessagesRef.current = onSetMessages;\n });\n\n const formatAdapter = useMemo(() => {\n if (!historyAdapter) return undefined;\n if (!historyAdapter.withFormat) {\n throw new Error(\n \"useAISDKRuntime: ThreadHistoryAdapter is missing the required `withFormat` method.\",\n );\n }\n return historyAdapter.withFormat<TMessage, any>(storageFormatAdapter);\n }, [historyAdapter, storageFormatAdapter]);\n\n useEffect(() => {\n if (!formatAdapter || loadedRef.current) return;\n\n const loadHistory = async () => {\n setIsLoading(true);\n try {\n const repo = await formatAdapter.load();\n if (repo && repo.messages.length > 0) {\n const converted = toExportedMessageRepository(toThreadMessages, repo);\n runtimeRef.current.thread.import(converted);\n\n const tempRepo = new MessageRepository();\n tempRepo.import(converted);\n const messages = tempRepo.getMessages();\n\n onSetMessagesRef.current(\n messages.flatMap(getExternalStoreMessages<TMessage>),\n );\n\n historyIds.current = new Set(\n converted.messages.map((m) => m.message.id),\n );\n }\n } catch (error) {\n console.error(\"Failed to load message history:\", error);\n } finally {\n setIsLoading(false);\n }\n };\n\n loadedRef.current = true;\n\n if (!optionalThreadListItem()?.getState().remoteId) {\n setIsLoading(false);\n return;\n }\n\n loadHistory();\n }, [formatAdapter, toThreadMessages, runtimeRef, optionalThreadListItem]);\n\n const runStartRef = useRef<number | null>(null);\n const persistTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const stepBoundariesRef = useRef<number[]>([]);\n const wasRunningRef = useRef(false);\n const toolCallCountRef = useRef(0);\n\n useEffect(() => {\n if (!formatAdapter) return;\n\n const unsubscribe = runtimeRef.current.thread.subscribe(() => {\n const { isRunning } = runtimeRef.current.thread.getState();\n const wasRunning = wasRunningRef.current;\n wasRunningRef.current = isRunning;\n\n // Track step boundaries by content changes (more reliable than isRunning)\n if (runStartRef.current != null) {\n const lastMsg = runtimeRef.current.thread.getState().messages.at(-1);\n if (lastMsg?.role === \"assistant\") {\n const currentToolCallCount = lastMsg.content.filter(\n (p) => p.type === \"tool-call\",\n ).length;\n while (toolCallCountRef.current < currentToolCallCount) {\n stepBoundariesRef.current.push(Date.now() - runStartRef.current);\n toolCallCountRef.current++;\n }\n }\n }\n\n if (isRunning) {\n if (runStartRef.current == null) {\n runStartRef.current = Date.now();\n stepBoundariesRef.current = [];\n toolCallCountRef.current = 0;\n }\n // Cancel any pending persist — isRunning went back to true\n if (persistTimerRef.current) {\n clearTimeout(persistTimerRef.current);\n persistTimerRef.current = null;\n }\n return;\n }\n\n // Only act on the true→false transition\n if (!wasRunning) return;\n\n // Record step boundary offset (synchronous for accuracy)\n if (runStartRef.current != null) {\n stepBoundariesRef.current.push(Date.now() - runStartRef.current);\n }\n\n // Debounce: wait one macrotask so agentic step flickers are absorbed\n if (persistTimerRef.current) clearTimeout(persistTimerRef.current);\n persistTimerRef.current = setTimeout(async () => {\n persistTimerRef.current = null;\n\n // Re-read latest state — may have changed since the timeout was scheduled\n const latest = runtimeRef.current.thread.getState();\n if (latest.isRunning) return; // was just a flicker\n\n // Derive durationMs from the last boundary (covers all steps)\n const boundaries = stepBoundariesRef.current;\n const durationMs =\n boundaries.length > 0 ? boundaries.at(-1) : undefined;\n\n // Fallback: if only 1 boundary but message has multiple steps, distribute evenly\n if (boundaries.length === 1 && durationMs != null) {\n const lastAssistant = latest.messages.findLast(\n (m) => m.role === \"assistant\",\n );\n if (lastAssistant) {\n const tcCount = lastAssistant.content.filter(\n (p) => p.type === \"tool-call\",\n ).length;\n if (tcCount > 0) {\n const totalSteps = tcCount + 1;\n const stepDur = durationMs / totalSteps;\n boundaries.length = 0;\n for (let i = 0; i < totalSteps; i++) {\n boundaries.push(Math.round((i + 1) * stepDur));\n }\n }\n }\n }\n\n // Build per-step timestamps when there are multiple steps\n const stepTimestamps =\n boundaries.length > 1\n ? boundaries.map((endMs, i) => ({\n start_ms: i === 0 ? 0 : boundaries[i - 1]!,\n end_ms: endMs,\n }))\n : undefined;\n\n runStartRef.current = null;\n stepBoundariesRef.current = [];\n\n const telemetryOptions = {\n ...(durationMs != null ? { durationMs } : undefined),\n ...(stepTimestamps != null ? { stepTimestamps } : undefined),\n };\n\n const { messages } = latest;\n let lastInnerMessageId: string | null = null;\n\n const getLastInnerId = (msgs: TMessage[]): string | null =>\n msgs.length > 0 ? storageFormatAdapter.getId(msgs.at(-1)!) : null;\n\n const toBatchItems = (msgs: TMessage[]) =>\n msgs.map((msg, idx) => ({\n parentId:\n idx === 0\n ? lastInnerMessageId\n : storageFormatAdapter.getId(msgs[idx - 1]!),\n message: msg,\n }));\n\n for (const message of messages) {\n const innerMessages = getExternalStoreMessages<TMessage>(message);\n\n const isReady =\n message.status === undefined ||\n message.status.type === \"complete\" ||\n message.status.type === \"incomplete\";\n\n if (!isReady) {\n lastInnerMessageId =\n getLastInnerId(innerMessages) ?? lastInnerMessageId;\n continue;\n }\n\n if (historyIds.current.has(message.id)) {\n if (durationMs !== undefined) {\n let parentId = lastInnerMessageId;\n for (const innerMessage of innerMessages) {\n try {\n await formatAdapter.update?.(\n { parentId, message: innerMessage },\n storageFormatAdapter.getId(innerMessage),\n );\n } catch {\n // ignore update failures to avoid breaking the message processing loop\n }\n parentId = storageFormatAdapter.getId(innerMessage);\n }\n }\n lastInnerMessageId =\n getLastInnerId(innerMessages) ?? lastInnerMessageId;\n continue;\n }\n historyIds.current.add(message.id);\n\n const batchItems = toBatchItems(innerMessages);\n for (const item of batchItems) {\n await formatAdapter.append(item);\n }\n\n lastInnerMessageId =\n getLastInnerId(innerMessages) ?? lastInnerMessageId;\n\n formatAdapter.reportTelemetry?.(batchItems, telemetryOptions);\n }\n }, 0);\n });\n\n return () => {\n unsubscribe();\n if (persistTimerRef.current) {\n clearTimeout(persistTimerRef.current);\n persistTimerRef.current = null;\n }\n };\n }, [formatAdapter, storageFormatAdapter, runtimeRef]);\n\n return isLoading;\n};\n"],"mappings":";;;;;;AAsBA,MAAa,+BACX,kBACA,aAC8B;CAC9B,MAAM,+BAAe,IAAI,IAAY;CACrC,MAAM,YAAY,SAAS,SAAS,SAAS,MAAM;EACjD,MAAM,UAAU,iBAAiB,CAAC,EAAE,OAAO,CAAC,EAAE;EAC9C,IAAI,CAAC,SAAS;GACZ,QAAQ,KAAK,qDAAqD;GAClE,OAAO,CAAC;EACV;EACA,IAAI,EAAE,YAAY,CAAC,aAAa,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC;EACzD,aAAa,IAAI,QAAQ,EAAE;EAC3B,OAAO,CAAC;GAAE,GAAG;GAAG;EAAQ,CAAC;CAC3B,CAAC;CAED,OAAO;EACL,QACE,SAAS,UAAU,aAAa,IAAI,SAAS,MAAM,IAC/C,SAAS,SACT;EACN,UAAU;CACZ;AACF;AAEA,MAAa,sBACX,YACA,gBACA,kBACA,sBACA,kBACG;CACH,MAAM,YAAY,OAAO,KAAK;CAE9B,MAAM,MAAM,OAAO;CACnB,MAAM,yBAAyB,kBACtB,IAAI,eAAe,SAAS,IAAI,eAAe,IAAI,MAC1D,CAAC,GAAG,CACN;CAEA,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAEhD,MAAM,aAAa,uBAAO,IAAI,IAAY,CAAC;CAE3C,MAAM,mBAAmB,OAAO,aAAa;CAC7C,gBAAgB;EACd,iBAAiB,UAAU;CAC7B,CAAC;CAED,MAAM,gBAAgB,cAAc;EAClC,IAAI,CAAC,gBAAgB,OAAO,KAAA;EAC5B,IAAI,CAAC,eAAe,YAClB,MAAM,IAAI,MACR,oFACF;EAEF,OAAO,eAAe,WAA0B,oBAAoB;CACtE,GAAG,CAAC,gBAAgB,oBAAoB,CAAC;CAEzC,gBAAgB;EACd,IAAI,CAAC,iBAAiB,UAAU,SAAS;EAEzC,MAAM,cAAc,YAAY;GAC9B,aAAa,IAAI;GACjB,IAAI;IACF,MAAM,OAAO,MAAM,cAAc,KAAK;IACtC,IAAI,QAAQ,KAAK,SAAS,SAAS,GAAG;KACpC,MAAM,YAAY,4BAA4B,kBAAkB,IAAI;KACpE,WAAW,QAAQ,OAAO,OAAO,SAAS;KAE1C,MAAM,WAAW,IAAI,kBAAkB;KACvC,SAAS,OAAO,SAAS;KACzB,MAAM,WAAW,SAAS,YAAY;KAEtC,iBAAiB,QACf,SAAS,QAAQ,wBAAkC,CACrD;KAEA,WAAW,UAAU,IAAI,IACvB,UAAU,SAAS,KAAK,MAAM,EAAE,QAAQ,EAAE,CAC5C;IACF;GACF,SAAS,OAAO;IACd,QAAQ,MAAM,mCAAmC,KAAK;GACxD,UAAU;IACR,aAAa,KAAK;GACpB;EACF;EAEA,UAAU,UAAU;EAEpB,IAAI,CAAC,uBAAuB,GAAG,SAAS,EAAE,UAAU;GAClD,aAAa,KAAK;GAClB;EACF;EAEA,YAAY;CACd,GAAG;EAAC;EAAe;EAAkB;EAAY;CAAsB,CAAC;CAExE,MAAM,cAAc,OAAsB,IAAI;CAC9C,MAAM,kBAAkB,OAA6C,IAAI;CACzE,MAAM,oBAAoB,OAAiB,CAAC,CAAC;CAC7C,MAAM,gBAAgB,OAAO,KAAK;CAClC,MAAM,mBAAmB,OAAO,CAAC;CAEjC,gBAAgB;EACd,IAAI,CAAC,eAAe;EAEpB,MAAM,cAAc,WAAW,QAAQ,OAAO,gBAAgB;GAC5D,MAAM,EAAE,cAAc,WAAW,QAAQ,OAAO,SAAS;GACzD,MAAM,aAAa,cAAc;GACjC,cAAc,UAAU;GAGxB,IAAI,YAAY,WAAW,MAAM;IAC/B,MAAM,UAAU,WAAW,QAAQ,OAAO,SAAS,EAAE,SAAS,GAAG,EAAE;IACnE,IAAI,SAAS,SAAS,aAAa;KACjC,MAAM,uBAAuB,QAAQ,QAAQ,QAC1C,MAAM,EAAE,SAAS,WACpB,EAAE;KACF,OAAO,iBAAiB,UAAU,sBAAsB;MACtD,kBAAkB,QAAQ,KAAK,KAAK,IAAI,IAAI,YAAY,OAAO;MAC/D,iBAAiB;KACnB;IACF;GACF;GAEA,IAAI,WAAW;IACb,IAAI,YAAY,WAAW,MAAM;KAC/B,YAAY,UAAU,KAAK,IAAI;KAC/B,kBAAkB,UAAU,CAAC;KAC7B,iBAAiB,UAAU;IAC7B;IAEA,IAAI,gBAAgB,SAAS;KAC3B,aAAa,gBAAgB,OAAO;KACpC,gBAAgB,UAAU;IAC5B;IACA;GACF;GAGA,IAAI,CAAC,YAAY;GAGjB,IAAI,YAAY,WAAW,MACzB,kBAAkB,QAAQ,KAAK,KAAK,IAAI,IAAI,YAAY,OAAO;GAIjE,IAAI,gBAAgB,SAAS,aAAa,gBAAgB,OAAO;GACjE,gBAAgB,UAAU,WAAW,YAAY;IAC/C,gBAAgB,UAAU;IAG1B,MAAM,SAAS,WAAW,QAAQ,OAAO,SAAS;IAClD,IAAI,OAAO,WAAW;IAGtB,MAAM,aAAa,kBAAkB;IACrC,MAAM,aACJ,WAAW,SAAS,IAAI,WAAW,GAAG,EAAE,IAAI,KAAA;IAG9C,IAAI,WAAW,WAAW,KAAK,cAAc,MAAM;KACjD,MAAM,gBAAgB,OAAO,SAAS,UACnC,MAAM,EAAE,SAAS,WACpB;KACA,IAAI,eAAe;MACjB,MAAM,UAAU,cAAc,QAAQ,QACnC,MAAM,EAAE,SAAS,WACpB,EAAE;MACF,IAAI,UAAU,GAAG;OACf,MAAM,aAAa,UAAU;OAC7B,MAAM,UAAU,aAAa;OAC7B,WAAW,SAAS;OACpB,KAAK,IAAI,IAAI,GAAG,IAAI,YAAY,KAC9B,WAAW,KAAK,KAAK,OAAO,IAAI,KAAK,OAAO,CAAC;MAEjD;KACF;IACF;IAGA,MAAM,iBACJ,WAAW,SAAS,IAChB,WAAW,KAAK,OAAO,OAAO;KAC5B,UAAU,MAAM,IAAI,IAAI,WAAW,IAAI;KACvC,QAAQ;IACV,EAAE,IACF,KAAA;IAEN,YAAY,UAAU;IACtB,kBAAkB,UAAU,CAAC;IAE7B,MAAM,mBAAmB;KACvB,GAAI,cAAc,OAAO,EAAE,WAAW,IAAI,KAAA;KAC1C,GAAI,kBAAkB,OAAO,EAAE,eAAe,IAAI,KAAA;IACpD;IAEA,MAAM,EAAE,aAAa;IACrB,IAAI,qBAAoC;IAExC,MAAM,kBAAkB,SACtB,KAAK,SAAS,IAAI,qBAAqB,MAAM,KAAK,GAAG,EAAE,CAAE,IAAI;IAE/D,MAAM,gBAAgB,SACpB,KAAK,KAAK,KAAK,SAAS;KACtB,UACE,QAAQ,IACJ,qBACA,qBAAqB,MAAM,KAAK,MAAM,EAAG;KAC/C,SAAS;IACX,EAAE;IAEJ,KAAK,MAAM,WAAW,UAAU;KAC9B,MAAM,gBAAgB,yBAAmC,OAAO;KAOhE,IAAI,EAJF,QAAQ,WAAW,KAAA,KACnB,QAAQ,OAAO,SAAS,cACxB,QAAQ,OAAO,SAAS,eAEZ;MACZ,qBACE,eAAe,aAAa,KAAK;MACnC;KACF;KAEA,IAAI,WAAW,QAAQ,IAAI,QAAQ,EAAE,GAAG;MACtC,IAAI,eAAe,KAAA,GAAW;OAC5B,IAAI,WAAW;OACf,KAAK,MAAM,gBAAgB,eAAe;QACxC,IAAI;SACF,MAAM,cAAc,SAClB;UAAE;UAAU,SAAS;SAAa,GAClC,qBAAqB,MAAM,YAAY,CACzC;QACF,QAAQ,CAER;QACA,WAAW,qBAAqB,MAAM,YAAY;OACpD;MACF;MACA,qBACE,eAAe,aAAa,KAAK;MACnC;KACF;KACA,WAAW,QAAQ,IAAI,QAAQ,EAAE;KAEjC,MAAM,aAAa,aAAa,aAAa;KAC7C,KAAK,MAAM,QAAQ,YACjB,MAAM,cAAc,OAAO,IAAI;KAGjC,qBACE,eAAe,aAAa,KAAK;KAEnC,cAAc,kBAAkB,YAAY,gBAAgB;IAC9D;GACF,GAAG,CAAC;EACN,CAAC;EAED,aAAa;GACX,YAAY;GACZ,IAAI,gBAAgB,SAAS;IAC3B,aAAa,gBAAgB,OAAO;IACpC,gBAAgB,UAAU;GAC5B;EACF;CACF,GAAG;EAAC;EAAe;EAAsB;CAAU,CAAC;CAEpD,OAAO;AACT"}
@@ -1,4 +1,4 @@
1
- import { ReadonlyJSONObject } from "../../packages/assistant-stream/dist/utils/json/json-value.js";
1
+ import { ReadonlyJSONObject } from "../../assistant-stream/dist/utils/json/json-value.js";
2
2
  import { UIMessage } from "ai";
3
3
  import { useExternalMessageConverter } from "@assistant-ui/core/react";
4
4
  import { McpAppMetadata } from "@assistant-ui/core";
@@ -7,7 +7,8 @@ import { McpAppMetadata } from "@assistant-ui/core";
7
7
  type AISDKMessageConverterMetadata = useExternalMessageConverter.Metadata & {
8
8
  toolArgsKeyOrderCache?: Map<string, Map<string, string[]>>;
9
9
  toolLastInputCache?: Map<string, ReadonlyJSONObject>;
10
- mcpAppMetadataCache?: Map<string, McpAppMetadata>;
10
+ mcpAppMetadataCache?: Map<string, McpAppMetadata>; /** Id of the currently-streaming message, flagged optimistic (#4037). */
11
+ optimisticMessageId?: string | undefined;
11
12
  };
12
13
  declare const AISDKMessageConverter: {
13
14
  useThreadMessages: ({
@@ -1 +1 @@
1
- {"version":3,"file":"convertMessage.d.ts","names":[],"sources":["../../../src/ui/utils/convertMessage.ts"],"mappings":";;;;;;KAqBY,6BAAA,GACV,2BAAA,CAA4B,QAAA;EAC1B,qBAAA,GAAwB,GAAA,SAAY,GAAA;EACpC,kBAAA,GAAqB,GAAA,SAAY,kBAAA;EACjC,mBAAA,GAAsB,GAAA,SAAY,cAAA;AAAA;AAAA,cAyUzB,qBAAA;;;;;;;;;;;;;mJA3UyC,aAAA;kJAEf,aAAA"}
1
+ {"version":3,"file":"convertMessage.d.ts","names":[],"sources":["../../../src/ui/utils/convertMessage.ts"],"mappings":";;;;;;KAqBY,6BAAA,GACV,2BAAA,CAA4B,QAAA;EAC1B,qBAAA,GAAwB,GAAA,SAAY,GAAA;EACpC,kBAAA,GAAqB,GAAA,SAAY,kBAAA;EACjC,mBAAA,GAAsB,GAAA,SAAY,cAAA,GAJG;EAMrC,mBAAA;AAAA;AAAA,cAyUS,qBAAA;;;;;;;;;;;;;mJA7UyC,aAAA;kJAEf,aAAA"}
@@ -201,6 +201,7 @@ const AISDKMessageConverter = createMessageConverter((message, metadata) => {
201
201
  case "system":
202
202
  case "assistant": {
203
203
  const timing = metadata.messageTiming?.[message.id];
204
+ const isOptimistic = message.role === "assistant" && message.id === metadata.optimisticMessageId;
204
205
  return {
205
206
  role: message.role,
206
207
  id: message.id,
@@ -208,7 +209,8 @@ const AISDKMessageConverter = createMessageConverter((message, metadata) => {
208
209
  content,
209
210
  metadata: {
210
211
  ...message.metadata,
211
- ...timing && { timing }
212
+ ...timing && { timing },
213
+ ...isOptimistic && { isOptimistic: true }
212
214
  }
213
215
  };
214
216
  }
@@ -1 +1 @@
1
- {"version":3,"file":"convertMessage.js","names":["unstable_createMessageConverter"],"sources":["../../../src/ui/utils/convertMessage.ts"],"sourcesContent":["import { isToolUIPart, getToolName, type UIMessage } from \"ai\";\nimport {\n createMessageConverter as unstable_createMessageConverter,\n type useExternalMessageConverter,\n} from \"@assistant-ui/core/react\";\nimport {\n isMcpAppUri,\n type ReasoningMessagePart,\n type ToolCallMessagePart,\n type TextMessagePart,\n type DataMessagePart,\n type SourceMessagePart,\n type SourceProviderMetadata,\n type FileMessagePart,\n type ThreadMessageLike,\n type McpAppMetadata,\n} from \"@assistant-ui/core\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\nimport { unwrapModelContentEnvelope } from \"../../modelContentEnvelope\";\n\ntype MessageMetadata = ThreadMessageLike[\"metadata\"];\nexport type AISDKMessageConverterMetadata =\n useExternalMessageConverter.Metadata & {\n toolArgsKeyOrderCache?: Map<string, Map<string, string[]>>;\n toolLastInputCache?: Map<string, ReadonlyJSONObject>;\n mcpAppMetadataCache?: Map<string, McpAppMetadata>;\n };\n\nfunction stripClosingDelimiters(json: string): string {\n return json.replace(/[}\\]\"]+$/, \"\");\n}\n\nconst MCP_APP_METADATA_CACHE_MAX = 100;\n\nfunction extractMcpAppMetadata(\n part: unknown,\n cache: Map<string, McpAppMetadata> | undefined,\n): McpAppMetadata | undefined {\n if (!part || typeof part !== \"object\") return undefined;\n const meta = (part as { callProviderMetadata?: unknown })\n .callProviderMetadata;\n const mcp =\n meta && typeof meta === \"object\"\n ? (meta as { mcp?: unknown }).mcp\n : undefined;\n const app =\n mcp && typeof mcp === \"object\" ? (mcp as { app?: unknown }).app : undefined;\n let a: Record<string, unknown>;\n if (app && typeof app === \"object\") {\n a = app as Record<string, unknown>;\n } else {\n // MCP-UI tools (e.g. xmcp) surface the UI pointer as\n // result._meta[\"ui/resourceUri\"] rather than in callProviderMetadata.\n const output = (part as { output?: unknown }).output;\n const outMeta =\n output && typeof output === \"object\"\n ? (output as { _meta?: unknown })._meta\n : undefined;\n const uiResourceUri =\n outMeta && typeof outMeta === \"object\"\n ? (outMeta as Record<string, unknown>)[\"ui/resourceUri\"]\n : undefined;\n if (typeof uiResourceUri !== \"string\") return undefined;\n a = { resourceUri: uiResourceUri };\n }\n if (typeof a[\"resourceUri\"] !== \"string\") return undefined;\n if (!isMcpAppUri(a[\"resourceUri\"])) return undefined;\n const cached = cache?.get(a[\"resourceUri\"]);\n if (cached) {\n cache!.delete(a[\"resourceUri\"]);\n cache!.set(a[\"resourceUri\"], cached);\n return cached;\n }\n const out: { -readonly [K in keyof McpAppMetadata]: McpAppMetadata[K] } = {\n resourceUri: a[\"resourceUri\"],\n };\n if (typeof a[\"mimeType\"] === \"string\") out.mimeType = a[\"mimeType\"];\n if (Array.isArray(a[\"visibility\"])) {\n out.visibility = a[\"visibility\"].filter(\n (v): v is \"model\" | \"app\" => v === \"model\" || v === \"app\",\n );\n }\n if (cache) {\n if (cache.size >= MCP_APP_METADATA_CACHE_MAX) {\n const oldest = cache.keys().next().value;\n if (oldest !== undefined) cache.delete(oldest);\n }\n cache.set(a[\"resourceUri\"], out);\n }\n return out;\n}\n\nconst hasOwn = (value: object, key: string) => Object.hasOwn(value, key);\n\nconst stabilizeToolArgsValue = (\n value: unknown,\n path: string,\n keyOrderByPath: Map<string, string[]>,\n): unknown => {\n if (Array.isArray(value)) {\n return value.map((item, idx) =>\n stabilizeToolArgsValue(item, `${path}[${idx}]`, keyOrderByPath),\n );\n }\n\n if (value && typeof value === \"object\") {\n const record = value as Record<string, unknown>;\n const currentKeys = Object.keys(record);\n const previousOrder = keyOrderByPath.get(path) ?? [];\n const previousOrderSet = new Set(previousOrder);\n const nextOrder = [\n ...previousOrder.filter((key) => hasOwn(record, key)),\n ...currentKeys.filter((key) => !previousOrderSet.has(key)),\n ];\n keyOrderByPath.set(path, nextOrder);\n\n return Object.fromEntries(\n nextOrder.map((key) => [\n key,\n stabilizeToolArgsValue(record[key], `${path}.${key}`, keyOrderByPath),\n ]),\n );\n }\n\n return value;\n};\n\nfunction stableStringifyToolArgs(\n keyOrderCache: Map<string, Map<string, string[]>> | undefined,\n cacheKey: string,\n args: ReadonlyJSONObject,\n): string {\n const keyOrderByPath = keyOrderCache?.get(cacheKey) ?? new Map();\n keyOrderCache?.set(cacheKey, keyOrderByPath);\n\n const stableArgs = stabilizeToolArgsValue(\n args,\n \"$\",\n keyOrderByPath,\n ) as ReadonlyJSONObject;\n return JSON.stringify(stableArgs);\n}\n\nfunction getToolApprovalAndInterrupt(\n part: {\n approval?:\n | {\n id: string;\n approved?: boolean;\n reason?: string;\n isAutomatic?: boolean;\n }\n | undefined;\n },\n toolStatus: { type: string; payload?: unknown } | undefined,\n): {\n approval?: NonNullable<ToolCallMessagePart[\"approval\"]>;\n interrupt?: NonNullable<ToolCallMessagePart[\"interrupt\"]>;\n} {\n if (part.approval && typeof part.approval.id === \"string\") {\n const { id, approved, reason, isAutomatic } = part.approval;\n return {\n approval: {\n id,\n ...(typeof approved === \"boolean\" && { approved }),\n ...(typeof reason === \"string\" && { reason }),\n ...(isAutomatic === true && { isAutomatic: true }),\n },\n };\n }\n\n if (toolStatus?.type === \"interrupt\") {\n return {\n interrupt: toolStatus.payload as NonNullable<\n ToolCallMessagePart[\"interrupt\"]\n >,\n };\n }\n\n return {};\n}\n\ntype MessageContent = Exclude<ThreadMessageLike[\"content\"], string>;\n\nfunction convertParts(\n message: UIMessage,\n metadata: AISDKMessageConverterMetadata,\n): MessageContent {\n if (!message.parts || message.parts.length === 0) {\n return [];\n }\n\n const converted = message.parts\n .filter(\n (p) =>\n p.type !== \"step-start\" &&\n (message.role !== \"user\" || p.type !== \"file\"),\n )\n .map((part) => {\n if (part.type === \"text\") {\n return {\n type: \"text\",\n text: part.text,\n } satisfies TextMessagePart;\n }\n\n if (part.type === \"reasoning\") {\n return {\n type: \"reasoning\",\n text: part.text,\n } satisfies ReasoningMessagePart;\n }\n\n if (isToolUIPart(part)) {\n const toolName = getToolName(part);\n const toolCallId = part.toolCallId;\n const argsKeyOrderCacheKey = `${message.id}:${toolCallId}`;\n\n const rawInput = part.input as ReadonlyJSONObject | null | undefined;\n let args: ReadonlyJSONObject;\n if (\n rawInput != null &&\n typeof rawInput === \"object\" &&\n !Array.isArray(rawInput)\n ) {\n args = rawInput;\n metadata.toolLastInputCache?.set(argsKeyOrderCacheKey, args);\n } else {\n args = metadata.toolLastInputCache?.get(argsKeyOrderCacheKey) ?? {};\n }\n\n let result: unknown;\n let modelContent: ToolCallMessagePart[\"modelContent\"];\n let isError = false;\n\n if (part.state === \"output-available\") {\n const unwrapped = unwrapModelContentEnvelope(part.output);\n result = unwrapped.result;\n modelContent = unwrapped.modelContent;\n } else if (part.state === \"output-error\") {\n isError = true;\n result = { error: part.errorText };\n } else if (part.state === \"output-denied\") {\n isError = true;\n result = {\n error:\n (part as { approval?: { reason?: string } }).approval?.reason ||\n \"Tool approval denied\",\n };\n }\n\n let argsText = stableStringifyToolArgs(\n metadata.toolArgsKeyOrderCache,\n argsKeyOrderCacheKey,\n args,\n );\n if (part.state === \"input-streaming\") {\n // strip closing delimiters added by the AI SDK's fix-json\n argsText = stripClosingDelimiters(argsText);\n } else {\n metadata.toolArgsKeyOrderCache?.delete(argsKeyOrderCacheKey);\n if (\n part.state === \"output-available\" ||\n part.state === \"output-error\" ||\n part.state === \"output-denied\"\n ) {\n metadata.toolLastInputCache?.delete(argsKeyOrderCacheKey);\n }\n }\n\n const toolStatus = metadata.toolStatuses?.[toolCallId];\n const mcpApp = extractMcpAppMetadata(\n part,\n metadata.mcpAppMetadataCache,\n );\n return {\n type: \"tool-call\",\n toolName,\n toolCallId,\n argsText,\n args,\n result,\n isError,\n ...(modelContent !== undefined && { modelContent }),\n ...(mcpApp && { mcp: { app: mcpApp } }),\n ...getToolApprovalAndInterrupt(part, toolStatus),\n } satisfies ToolCallMessagePart;\n }\n\n if (part.type === \"source-url\") {\n return {\n type: \"source\",\n sourceType: \"url\",\n id: part.sourceId,\n url: part.url,\n ...(part.title != null ? { title: part.title } : undefined),\n ...(part.providerMetadata != null\n ? {\n providerMetadata:\n part.providerMetadata as SourceProviderMetadata,\n }\n : undefined),\n } satisfies SourceMessagePart;\n }\n\n if (part.type === \"file\") {\n return {\n type: \"file\",\n data: part.url,\n mimeType: part.mediaType,\n ...(part.filename != null && { filename: part.filename }),\n } satisfies FileMessagePart;\n }\n\n if (part.type === \"source-document\") {\n return {\n type: \"source\",\n sourceType: \"document\",\n id: part.sourceId,\n title: part.title,\n mediaType: part.mediaType,\n ...(part.filename != null ? { filename: part.filename } : undefined),\n ...(part.providerMetadata != null\n ? {\n providerMetadata:\n part.providerMetadata as SourceProviderMetadata,\n }\n : undefined),\n } satisfies SourceMessagePart;\n }\n\n if (part.type.startsWith(\"data-\")) {\n return {\n type: \"data\",\n name: part.type.substring(5),\n data: (part as any).data,\n } satisfies DataMessagePart;\n }\n\n console.warn(`Unsupported message part type: ${part.type}`);\n return null;\n })\n .filter(Boolean) as MessageContent[number][];\n\n const seenToolCallIds = new Set<string>();\n return converted.filter((part) => {\n if (part.type === \"tool-call\" && part.toolCallId != null) {\n if (seenToolCallIds.has(part.toolCallId)) return false;\n seenToolCallIds.add(part.toolCallId);\n }\n return true;\n });\n}\n\nexport const AISDKMessageConverter = unstable_createMessageConverter(\n (message: UIMessage, metadata: AISDKMessageConverterMetadata) => {\n const createdAt = new Date();\n const content = convertParts(message, metadata);\n\n switch (message.role) {\n case \"user\":\n return {\n role: \"user\",\n id: message.id,\n createdAt,\n content,\n attachments: message.parts\n ?.filter((p) => p.type === \"file\")\n .map((part, idx) => ({\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 metadata: message.metadata as MessageMetadata,\n };\n\n case \"system\":\n case \"assistant\": {\n const timing = metadata.messageTiming?.[message.id];\n return {\n role: message.role,\n id: message.id,\n createdAt,\n content,\n metadata: {\n ...(message.metadata as MessageMetadata),\n ...(timing && { timing }),\n },\n };\n }\n\n default:\n console.warn(`Unsupported message role: ${message.role}`);\n return [];\n }\n },\n);\n"],"mappings":"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"}
1
+ {"version":3,"file":"convertMessage.js","names":["unstable_createMessageConverter"],"sources":["../../../src/ui/utils/convertMessage.ts"],"sourcesContent":["import { isToolUIPart, getToolName, type UIMessage } from \"ai\";\nimport {\n createMessageConverter as unstable_createMessageConverter,\n type useExternalMessageConverter,\n} from \"@assistant-ui/core/react\";\nimport {\n isMcpAppUri,\n type ReasoningMessagePart,\n type ToolCallMessagePart,\n type TextMessagePart,\n type DataMessagePart,\n type SourceMessagePart,\n type SourceProviderMetadata,\n type FileMessagePart,\n type ThreadMessageLike,\n type McpAppMetadata,\n} from \"@assistant-ui/core\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\nimport { unwrapModelContentEnvelope } from \"../../modelContentEnvelope\";\n\ntype MessageMetadata = ThreadMessageLike[\"metadata\"];\nexport type AISDKMessageConverterMetadata =\n useExternalMessageConverter.Metadata & {\n toolArgsKeyOrderCache?: Map<string, Map<string, string[]>>;\n toolLastInputCache?: Map<string, ReadonlyJSONObject>;\n mcpAppMetadataCache?: Map<string, McpAppMetadata>;\n /** Id of the currently-streaming message, flagged optimistic (#4037). */\n optimisticMessageId?: string | undefined;\n };\n\nfunction stripClosingDelimiters(json: string): string {\n return json.replace(/[}\\]\"]+$/, \"\");\n}\n\nconst MCP_APP_METADATA_CACHE_MAX = 100;\n\nfunction extractMcpAppMetadata(\n part: unknown,\n cache: Map<string, McpAppMetadata> | undefined,\n): McpAppMetadata | undefined {\n if (!part || typeof part !== \"object\") return undefined;\n const meta = (part as { callProviderMetadata?: unknown })\n .callProviderMetadata;\n const mcp =\n meta && typeof meta === \"object\"\n ? (meta as { mcp?: unknown }).mcp\n : undefined;\n const app =\n mcp && typeof mcp === \"object\" ? (mcp as { app?: unknown }).app : undefined;\n let a: Record<string, unknown>;\n if (app && typeof app === \"object\") {\n a = app as Record<string, unknown>;\n } else {\n // MCP-UI tools (e.g. xmcp) surface the UI pointer as\n // result._meta[\"ui/resourceUri\"] rather than in callProviderMetadata.\n const output = (part as { output?: unknown }).output;\n const outMeta =\n output && typeof output === \"object\"\n ? (output as { _meta?: unknown })._meta\n : undefined;\n const uiResourceUri =\n outMeta && typeof outMeta === \"object\"\n ? (outMeta as Record<string, unknown>)[\"ui/resourceUri\"]\n : undefined;\n if (typeof uiResourceUri !== \"string\") return undefined;\n a = { resourceUri: uiResourceUri };\n }\n if (typeof a[\"resourceUri\"] !== \"string\") return undefined;\n if (!isMcpAppUri(a[\"resourceUri\"])) return undefined;\n const cached = cache?.get(a[\"resourceUri\"]);\n if (cached) {\n cache!.delete(a[\"resourceUri\"]);\n cache!.set(a[\"resourceUri\"], cached);\n return cached;\n }\n const out: { -readonly [K in keyof McpAppMetadata]: McpAppMetadata[K] } = {\n resourceUri: a[\"resourceUri\"],\n };\n if (typeof a[\"mimeType\"] === \"string\") out.mimeType = a[\"mimeType\"];\n if (Array.isArray(a[\"visibility\"])) {\n out.visibility = a[\"visibility\"].filter(\n (v): v is \"model\" | \"app\" => v === \"model\" || v === \"app\",\n );\n }\n if (cache) {\n if (cache.size >= MCP_APP_METADATA_CACHE_MAX) {\n const oldest = cache.keys().next().value;\n if (oldest !== undefined) cache.delete(oldest);\n }\n cache.set(a[\"resourceUri\"], out);\n }\n return out;\n}\n\nconst hasOwn = (value: object, key: string) => Object.hasOwn(value, key);\n\nconst stabilizeToolArgsValue = (\n value: unknown,\n path: string,\n keyOrderByPath: Map<string, string[]>,\n): unknown => {\n if (Array.isArray(value)) {\n return value.map((item, idx) =>\n stabilizeToolArgsValue(item, `${path}[${idx}]`, keyOrderByPath),\n );\n }\n\n if (value && typeof value === \"object\") {\n const record = value as Record<string, unknown>;\n const currentKeys = Object.keys(record);\n const previousOrder = keyOrderByPath.get(path) ?? [];\n const previousOrderSet = new Set(previousOrder);\n const nextOrder = [\n ...previousOrder.filter((key) => hasOwn(record, key)),\n ...currentKeys.filter((key) => !previousOrderSet.has(key)),\n ];\n keyOrderByPath.set(path, nextOrder);\n\n return Object.fromEntries(\n nextOrder.map((key) => [\n key,\n stabilizeToolArgsValue(record[key], `${path}.${key}`, keyOrderByPath),\n ]),\n );\n }\n\n return value;\n};\n\nfunction stableStringifyToolArgs(\n keyOrderCache: Map<string, Map<string, string[]>> | undefined,\n cacheKey: string,\n args: ReadonlyJSONObject,\n): string {\n const keyOrderByPath = keyOrderCache?.get(cacheKey) ?? new Map();\n keyOrderCache?.set(cacheKey, keyOrderByPath);\n\n const stableArgs = stabilizeToolArgsValue(\n args,\n \"$\",\n keyOrderByPath,\n ) as ReadonlyJSONObject;\n return JSON.stringify(stableArgs);\n}\n\nfunction getToolApprovalAndInterrupt(\n part: {\n approval?:\n | {\n id: string;\n approved?: boolean;\n reason?: string;\n isAutomatic?: boolean;\n }\n | undefined;\n },\n toolStatus: { type: string; payload?: unknown } | undefined,\n): {\n approval?: NonNullable<ToolCallMessagePart[\"approval\"]>;\n interrupt?: NonNullable<ToolCallMessagePart[\"interrupt\"]>;\n} {\n if (part.approval && typeof part.approval.id === \"string\") {\n const { id, approved, reason, isAutomatic } = part.approval;\n return {\n approval: {\n id,\n ...(typeof approved === \"boolean\" && { approved }),\n ...(typeof reason === \"string\" && { reason }),\n ...(isAutomatic === true && { isAutomatic: true }),\n },\n };\n }\n\n if (toolStatus?.type === \"interrupt\") {\n return {\n interrupt: toolStatus.payload as NonNullable<\n ToolCallMessagePart[\"interrupt\"]\n >,\n };\n }\n\n return {};\n}\n\ntype MessageContent = Exclude<ThreadMessageLike[\"content\"], string>;\n\nfunction convertParts(\n message: UIMessage,\n metadata: AISDKMessageConverterMetadata,\n): MessageContent {\n if (!message.parts || message.parts.length === 0) {\n return [];\n }\n\n const converted = message.parts\n .filter(\n (p) =>\n p.type !== \"step-start\" &&\n (message.role !== \"user\" || p.type !== \"file\"),\n )\n .map((part) => {\n if (part.type === \"text\") {\n return {\n type: \"text\",\n text: part.text,\n } satisfies TextMessagePart;\n }\n\n if (part.type === \"reasoning\") {\n return {\n type: \"reasoning\",\n text: part.text,\n } satisfies ReasoningMessagePart;\n }\n\n if (isToolUIPart(part)) {\n const toolName = getToolName(part);\n const toolCallId = part.toolCallId;\n const argsKeyOrderCacheKey = `${message.id}:${toolCallId}`;\n\n const rawInput = part.input as ReadonlyJSONObject | null | undefined;\n let args: ReadonlyJSONObject;\n if (\n rawInput != null &&\n typeof rawInput === \"object\" &&\n !Array.isArray(rawInput)\n ) {\n args = rawInput;\n metadata.toolLastInputCache?.set(argsKeyOrderCacheKey, args);\n } else {\n args = metadata.toolLastInputCache?.get(argsKeyOrderCacheKey) ?? {};\n }\n\n let result: unknown;\n let modelContent: ToolCallMessagePart[\"modelContent\"];\n let isError = false;\n\n if (part.state === \"output-available\") {\n const unwrapped = unwrapModelContentEnvelope(part.output);\n result = unwrapped.result;\n modelContent = unwrapped.modelContent;\n } else if (part.state === \"output-error\") {\n isError = true;\n result = { error: part.errorText };\n } else if (part.state === \"output-denied\") {\n isError = true;\n result = {\n error:\n (part as { approval?: { reason?: string } }).approval?.reason ||\n \"Tool approval denied\",\n };\n }\n\n let argsText = stableStringifyToolArgs(\n metadata.toolArgsKeyOrderCache,\n argsKeyOrderCacheKey,\n args,\n );\n if (part.state === \"input-streaming\") {\n // strip closing delimiters added by the AI SDK's fix-json\n argsText = stripClosingDelimiters(argsText);\n } else {\n metadata.toolArgsKeyOrderCache?.delete(argsKeyOrderCacheKey);\n if (\n part.state === \"output-available\" ||\n part.state === \"output-error\" ||\n part.state === \"output-denied\"\n ) {\n metadata.toolLastInputCache?.delete(argsKeyOrderCacheKey);\n }\n }\n\n const toolStatus = metadata.toolStatuses?.[toolCallId];\n const mcpApp = extractMcpAppMetadata(\n part,\n metadata.mcpAppMetadataCache,\n );\n return {\n type: \"tool-call\",\n toolName,\n toolCallId,\n argsText,\n args,\n result,\n isError,\n ...(modelContent !== undefined && { modelContent }),\n ...(mcpApp && { mcp: { app: mcpApp } }),\n ...getToolApprovalAndInterrupt(part, toolStatus),\n } satisfies ToolCallMessagePart;\n }\n\n if (part.type === \"source-url\") {\n return {\n type: \"source\",\n sourceType: \"url\",\n id: part.sourceId,\n url: part.url,\n ...(part.title != null ? { title: part.title } : undefined),\n ...(part.providerMetadata != null\n ? {\n providerMetadata:\n part.providerMetadata as SourceProviderMetadata,\n }\n : undefined),\n } satisfies SourceMessagePart;\n }\n\n if (part.type === \"file\") {\n return {\n type: \"file\",\n data: part.url,\n mimeType: part.mediaType,\n ...(part.filename != null && { filename: part.filename }),\n } satisfies FileMessagePart;\n }\n\n if (part.type === \"source-document\") {\n return {\n type: \"source\",\n sourceType: \"document\",\n id: part.sourceId,\n title: part.title,\n mediaType: part.mediaType,\n ...(part.filename != null ? { filename: part.filename } : undefined),\n ...(part.providerMetadata != null\n ? {\n providerMetadata:\n part.providerMetadata as SourceProviderMetadata,\n }\n : undefined),\n } satisfies SourceMessagePart;\n }\n\n if (part.type.startsWith(\"data-\")) {\n return {\n type: \"data\",\n name: part.type.substring(5),\n data: (part as any).data,\n } satisfies DataMessagePart;\n }\n\n console.warn(`Unsupported message part type: ${part.type}`);\n return null;\n })\n .filter(Boolean) as MessageContent[number][];\n\n const seenToolCallIds = new Set<string>();\n return converted.filter((part) => {\n if (part.type === \"tool-call\" && part.toolCallId != null) {\n if (seenToolCallIds.has(part.toolCallId)) return false;\n seenToolCallIds.add(part.toolCallId);\n }\n return true;\n });\n}\n\nexport const AISDKMessageConverter = unstable_createMessageConverter(\n (message: UIMessage, metadata: AISDKMessageConverterMetadata) => {\n const createdAt = new Date();\n const content = convertParts(message, metadata);\n\n switch (message.role) {\n case \"user\":\n return {\n role: \"user\",\n id: message.id,\n createdAt,\n content,\n attachments: message.parts\n ?.filter((p) => p.type === \"file\")\n .map((part, idx) => ({\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 metadata: message.metadata as MessageMetadata,\n };\n\n case \"system\":\n case \"assistant\": {\n const timing = metadata.messageTiming?.[message.id];\n const isOptimistic =\n message.role === \"assistant\" &&\n message.id === metadata.optimisticMessageId;\n return {\n role: message.role,\n id: message.id,\n createdAt,\n content,\n metadata: {\n ...(message.metadata as MessageMetadata),\n ...(timing && { timing }),\n ...(isOptimistic && { isOptimistic: true }),\n },\n };\n }\n\n default:\n console.warn(`Unsupported message role: ${message.role}`);\n return [];\n }\n },\n);\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"usage.d.ts","names":[],"sources":["../src/usage.ts"],"mappings":";KAGY,gBAAA;EACV,WAAA;EACA,WAAA;EACA,YAAA;EACA,eAAA;EACA,iBAAA;AAAA;AAAA,UAGe,4BAAA;EACf,IAAA;EACA,QAAQ;AAAA;AAAA,iBAoGM,0BAAA,CACd,OAAA,EAAS,4BAAA,eACR,gBAAgB;AAAA,iBAeH,yBAAA,CACd,QAAA,WAAmB,4BAAA,iBAClB,gBAAgB;AAAA,iBAmBH,mBAAA,CAAA,GAAuB,gBAAgB"}
1
+ {"version":3,"file":"usage.d.ts","names":[],"sources":["../src/usage.ts"],"mappings":";KAGY,gBAAA;EACV,WAAA;EACA,WAAA;EACA,YAAA;EACA,eAAA;EACA,iBAAA;AAAA;AAAA,UAGe,4BAAA;EACf,IAAA;EACA,QAAQ;AAAA;AAAA,iBAoGM,0BAAA,CACd,OAAA,EAAS,4BAAA,eACR,gBAAgB;AAAA,iBAeH,yBAAA,CACd,QAAA,WAAmB,4BAAA,iBAClB,gBAAgB;AAAA,iBAmBH,mBAAA,IAAuB,gBAAgB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react-ai-sdk",
3
- "version": "1.3.28",
3
+ "version": "1.3.31",
4
4
  "description": "Vercel AI SDK adapter for assistant-ui",
5
5
  "keywords": [
6
6
  "ai-sdk",
@@ -30,10 +30,10 @@
30
30
  ],
31
31
  "sideEffects": false,
32
32
  "dependencies": {
33
- "@ai-sdk/react": "^3.0.190",
34
- "@assistant-ui/core": "^0.2.6",
35
- "@assistant-ui/store": "^0.2.12",
36
- "ai": "^6.0.188",
33
+ "@ai-sdk/react": "^3.0.195",
34
+ "@assistant-ui/core": "^0.2.8",
35
+ "@assistant-ui/store": "^0.2.13",
36
+ "ai": "^6.0.193",
37
37
  "assistant-cloud": "*"
38
38
  },
39
39
  "peerDependencies": {
@@ -53,8 +53,8 @@
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",
57
- "assistant-stream": "0.3.17"
56
+ "@assistant-ui/x-buildutils": "0.0.10",
57
+ "assistant-stream": "0.3.18"
58
58
  },
59
59
  "publishConfig": {
60
60
  "access": "public",
@@ -125,4 +125,28 @@ describe("frontendTools", () => {
125
125
 
126
126
  expect(output).toEqual({ type: "text", value: "hello" });
127
127
  });
128
+
129
+ it("forwards providerOptions verbatim when present", () => {
130
+ const tools = frontendTools({
131
+ getWeather: {
132
+ description: "Get the weather",
133
+ parameters: { type: "object", properties: {} },
134
+ providerOptions: { anthropic: { deferLoading: true } },
135
+ },
136
+ });
137
+
138
+ expect(tools.getWeather?.providerOptions).toEqual({
139
+ anthropic: { deferLoading: true },
140
+ });
141
+ });
142
+
143
+ it("omits providerOptions when absent", () => {
144
+ const tools = frontendTools({
145
+ getWeather: {
146
+ parameters: { type: "object", properties: {} },
147
+ },
148
+ });
149
+
150
+ expect(tools.getWeather).not.toHaveProperty("providerOptions");
151
+ });
128
152
  });
@@ -1,6 +1,5 @@
1
1
  import { jsonSchema, type ToolSet } from "ai";
2
- import type { JSONSchema7 } from "json-schema";
3
- import type { ToolModelContentPart } from "assistant-stream";
2
+ import type { ToolJSONSchema, ToolModelContentPart } from "assistant-stream";
4
3
  import { unwrapModelContentEnvelope } from "./modelContentEnvelope";
5
4
 
6
5
  const toAISDKContent = (parts: readonly ToolModelContentPart[]) => ({
@@ -25,7 +24,7 @@ const toAISDKContent = (parts: readonly ToolModelContentPart[]) => ({
25
24
  }),
26
25
  });
27
26
 
28
- const defaultToModelOutput = ({ output }: { output: unknown }) => {
27
+ export const defaultToModelOutput = ({ output }: { output: unknown }) => {
29
28
  const { modelContent } = unwrapModelContentEnvelope(output);
30
29
  if (modelContent !== undefined) {
31
30
  return toAISDKContent(modelContent);
@@ -35,9 +34,7 @@ const defaultToModelOutput = ({ output }: { output: unknown }) => {
35
34
  : { type: "json" as const, value: (output ?? null) as any };
36
35
  };
37
36
 
38
- export const frontendTools = (
39
- tools: Record<string, { description?: string; parameters: JSONSchema7 }>,
40
- ): ToolSet =>
37
+ export const frontendTools = (tools: Record<string, ToolJSONSchema>): ToolSet =>
41
38
  Object.fromEntries(
42
39
  Object.entries(tools).map(([name, t]) => [
43
40
  name,
@@ -45,6 +42,7 @@ export const frontendTools = (
45
42
  ...(t.description !== undefined && { description: t.description }),
46
43
  inputSchema: jsonSchema(t.parameters),
47
44
  toModelOutput: defaultToModelOutput,
45
+ ...(t.providerOptions && { providerOptions: t.providerOptions }),
48
46
  },
49
47
  ]),
50
48
  ) as ToolSet;
@@ -0,0 +1,90 @@
1
+ import { jsonSchema, type ToolSet } from "ai";
2
+ import { toJSONSchema, type ToolJSONSchema } from "assistant-stream";
3
+ import type { Toolkit, ToolkitDeclaration } from "@assistant-ui/core/react";
4
+ import { defaultToModelOutput, frontendTools } from "./frontendTools";
5
+
6
+ const EMPTY_SCHEMA = { type: "object" as const, properties: {} };
7
+
8
+ const humanNotSupported = (): never => {
9
+ throw new Error(
10
+ "`human()` is not available during server-side tool execution.",
11
+ );
12
+ };
13
+
14
+ // AI SDK leaves `abortSignal` optional; assistant-ui's execute requires one.
15
+ const neverAbort = new AbortController().signal;
16
+
17
+ export interface GenerativeToolsOptions {
18
+ /**
19
+ * The server build of a generative toolkit (schema + server `execute`). Typed
20
+ * as the canonical {@link Toolkit} so callers don't need to cast; the server
21
+ * build carries `execute`, recovered internally as {@link ToolkitDeclaration}.
22
+ */
23
+ toolkit: Toolkit;
24
+ /**
25
+ * Tools uploaded by the frontend (the request body's `tools`). Merged in
26
+ * alongside the `toolkit`; a server `execute` from `toolkit` takes precedence
27
+ * over an uploaded entry of the same name.
28
+ */
29
+ frontendTools?: Record<string, ToolJSONSchema>;
30
+ }
31
+
32
+ /**
33
+ * Builds an AI SDK `ToolSet` for server-side use with `streamText` /
34
+ * `generateText` from a generative `toolkit` and the frontend-uploaded tools.
35
+ *
36
+ * Each toolkit tool's `execute` runs on the server. Pair this with the
37
+ * `"use generative"` compiler: import the toolkit in a server route (where it
38
+ * resolves to the server build — schema + `execute`, with `render` stripped) and
39
+ * pass it here. Tools without an `execute` are still exposed to the model but
40
+ * left for the client to fulfill. `frontendTools` lets the client contribute
41
+ * tools that aren't in the static toolkit.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * const { tools } = await req.json();
46
+ * streamText({
47
+ * model,
48
+ * messages,
49
+ * tools: generativeTools({ toolkit: docsToolkit, frontendTools: tools }),
50
+ * });
51
+ * ```
52
+ */
53
+ export const generativeTools = (options: GenerativeToolsOptions): ToolSet => ({
54
+ ...(options.frontendTools ? frontendTools(options.frontendTools) : {}),
55
+ // `toolkit` last so its server-side `execute` wins over an uploaded entry of
56
+ // the same name. The cast recovers the declaration shape — the server build
57
+ // carries `execute`, which the canonical `Toolkit` type erases.
58
+ ...toServerToolSet(options.toolkit as ToolkitDeclaration),
59
+ });
60
+
61
+ const toServerToolSet = (toolkit: ToolkitDeclaration): ToolSet =>
62
+ Object.fromEntries(
63
+ Object.entries(toolkit)
64
+ .filter(([, t]) => !t.disabled)
65
+ .map(([name, t]) => {
66
+ const execute = t.execute;
67
+ return [
68
+ name,
69
+ {
70
+ ...(t.description !== undefined && { description: t.description }),
71
+ inputSchema: jsonSchema(
72
+ t.parameters ? toJSONSchema(t.parameters) : EMPTY_SCHEMA,
73
+ ),
74
+ toModelOutput: t.toModelOutput ?? defaultToModelOutput,
75
+ ...(t.providerOptions && { providerOptions: t.providerOptions }),
76
+ ...(execute && {
77
+ execute: (
78
+ args: unknown,
79
+ callOptions: { toolCallId: string; abortSignal?: AbortSignal },
80
+ ) =>
81
+ execute(args as never, {
82
+ toolCallId: callOptions.toolCallId,
83
+ abortSignal: callOptions.abortSignal ?? neverAbort,
84
+ human: humanNotSupported,
85
+ }),
86
+ }),
87
+ },
88
+ ];
89
+ }),
90
+ ) as ToolSet;