@assistant-ui/ai-sdk 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +32 -0
- package/dist/adapters/aiSDKFormatAdapter.d.ts +8 -0
- package/dist/adapters/aiSDKFormatAdapter.d.ts.map +1 -0
- package/dist/adapters/aiSDKFormatAdapter.js +26 -0
- package/dist/adapters/aiSDKFormatAdapter.js.map +1 -0
- package/dist/adapters/vercelAttachmentAdapter.d.ts +6 -0
- package/dist/adapters/vercelAttachmentAdapter.d.ts.map +1 -0
- package/dist/adapters/vercelAttachmentAdapter.js +37 -0
- package/dist/adapters/vercelAttachmentAdapter.js.map +1 -0
- package/dist/aiSDKExtras.d.ts +11 -0
- package/dist/aiSDKExtras.d.ts.map +1 -0
- package/dist/aiSDKExtras.js +7 -0
- package/dist/aiSDKExtras.js.map +1 -0
- package/dist/converters/convertMessage.d.ts +28 -0
- package/dist/converters/convertMessage.d.ts.map +1 -0
- package/dist/converters/convertMessage.js +249 -0
- package/dist/converters/convertMessage.js.map +1 -0
- package/dist/converters/modelContentEnvelope.d.ts +16 -0
- package/dist/converters/modelContentEnvelope.d.ts.map +1 -0
- package/dist/converters/modelContentEnvelope.js +22 -0
- package/dist/converters/modelContentEnvelope.js.map +1 -0
- package/dist/converters/toCreateMessage.d.ts +7 -0
- package/dist/converters/toCreateMessage.d.ts.map +1 -0
- package/dist/converters/toCreateMessage.js +58 -0
- package/dist/converters/toCreateMessage.js.map +1 -0
- package/dist/converters/toolOutputConversion.d.ts +29 -0
- package/dist/converters/toolOutputConversion.d.ts.map +1 -0
- package/dist/converters/toolOutputConversion.js +31 -0
- package/dist/converters/toolOutputConversion.js.map +1 -0
- package/dist/hooks.d.ts +19 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +20 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +13 -0
- package/dist/index.native.d.ts +12 -0
- package/dist/index.native.js +11 -0
- package/dist/model-context/injectInteractableContext.d.ts +37 -0
- package/dist/model-context/injectInteractableContext.d.ts.map +1 -0
- package/dist/model-context/injectInteractableContext.js +52 -0
- package/dist/model-context/injectInteractableContext.js.map +1 -0
- package/dist/model-context/injectQuoteContext.d.ts +27 -0
- package/dist/model-context/injectQuoteContext.d.ts.map +1 -0
- package/dist/model-context/injectQuoteContext.js +51 -0
- package/dist/model-context/injectQuoteContext.js.map +1 -0
- package/dist/runtime/AISDKChat.d.ts +16 -0
- package/dist/runtime/AISDKChat.d.ts.map +1 -0
- package/dist/runtime/AISDKChat.js +69 -0
- package/dist/runtime/AISDKChat.js.map +1 -0
- package/dist/runtime/AISDKThreads.d.ts +45 -0
- package/dist/runtime/AISDKThreads.d.ts.map +1 -0
- package/dist/runtime/AISDKThreads.js +203 -0
- package/dist/runtime/AISDKThreads.js.map +1 -0
- package/dist/runtime/useAISDKRuntime.d.ts +49 -0
- package/dist/runtime/useAISDKRuntime.d.ts.map +1 -0
- package/dist/runtime/useAISDKRuntime.js +266 -0
- package/dist/runtime/useAISDKRuntime.js.map +1 -0
- package/dist/runtime/useChatRuntime.d.ts +13 -0
- package/dist/runtime/useChatRuntime.d.ts.map +1 -0
- package/dist/runtime/useChatRuntime.js +52 -0
- package/dist/runtime/useChatRuntime.js.map +1 -0
- package/dist/runtime/useChatThread.d.ts +77 -0
- package/dist/runtime/useChatThread.d.ts.map +1 -0
- package/dist/runtime/useChatThread.js +134 -0
- package/dist/runtime/useChatThread.js.map +1 -0
- package/dist/runtime/useExternalHistory.d.ts +11 -0
- package/dist/runtime/useExternalHistory.d.ts.map +1 -0
- package/dist/runtime/useExternalHistory.js +278 -0
- package/dist/runtime/useExternalHistory.js.map +1 -0
- package/dist/runtime/useResourceCleanup.d.ts +5 -0
- package/dist/runtime/useResourceCleanup.d.ts.map +1 -0
- package/dist/runtime/useResourceCleanup.js +47 -0
- package/dist/runtime/useResourceCleanup.js.map +1 -0
- package/dist/runtime/useStreamingTiming.d.ts +15 -0
- package/dist/runtime/useStreamingTiming.d.ts.map +1 -0
- package/dist/runtime/useStreamingTiming.js +38 -0
- package/dist/runtime/useStreamingTiming.js.map +1 -0
- package/dist/tools/frontendTools.d.ts +33 -0
- package/dist/tools/frontendTools.d.ts.map +1 -0
- package/dist/tools/frontendTools.js +33 -0
- package/dist/tools/frontendTools.js.map +1 -0
- package/dist/tools/generativeTools.d.ts +73 -0
- package/dist/tools/generativeTools.d.ts.map +1 -0
- package/dist/tools/generativeTools.js +245 -0
- package/dist/tools/generativeTools.js.map +1 -0
- package/dist/tools/mcp-stdio.node.d.ts +2 -0
- package/dist/tools/mcp-stdio.node.js +2 -0
- package/dist/tools/mcp-stdio.unsupported.d.ts +6 -0
- package/dist/tools/mcp-stdio.unsupported.d.ts.map +1 -0
- package/dist/tools/mcp-stdio.unsupported.js +11 -0
- package/dist/tools/mcp-stdio.unsupported.js.map +1 -0
- package/dist/transport/AssistantChatTransport.d.ts +23 -0
- package/dist/transport/AssistantChatTransport.d.ts.map +1 -0
- package/dist/transport/AssistantChatTransport.js +134 -0
- package/dist/transport/AssistantChatTransport.js.map +1 -0
- package/dist/transport/resumable.d.ts +38 -0
- package/dist/transport/resumable.d.ts.map +1 -0
- package/dist/transport/resumable.js +113 -0
- package/dist/transport/resumable.js.map +1 -0
- package/dist/usage.d.ts +19 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +127 -0
- package/dist/usage.js.map +1 -0
- package/dist/utils/getVercelAIMessages.d.ts +7 -0
- package/dist/utils/getVercelAIMessages.d.ts.map +1 -0
- package/dist/utils/getVercelAIMessages.js +9 -0
- package/dist/utils/getVercelAIMessages.js.map +1 -0
- package/dist/utils/sliceMessagesUntil.d.ts +6 -0
- package/dist/utils/sliceMessagesUntil.d.ts.map +1 -0
- package/dist/utils/sliceMessagesUntil.js +12 -0
- package/dist/utils/sliceMessagesUntil.js.map +1 -0
- package/package.json +100 -0
- package/src/__tests__/standalone.e2e.ts +59 -0
- package/src/adapters/aiSDKFormatAdapter.ts +41 -0
- package/src/adapters/vercelAttachmentAdapter.test.ts +29 -0
- package/src/adapters/vercelAttachmentAdapter.ts +36 -0
- package/src/aiSDKExtras.ts +11 -0
- package/src/converters/convertMessage.test.ts +1130 -0
- package/src/converters/convertMessage.ts +457 -0
- package/src/converters/modelContentEnvelope.ts +41 -0
- package/src/converters/toCreateMessage.test.ts +596 -0
- package/src/converters/toCreateMessage.ts +99 -0
- package/src/converters/toolOutputConversion.ts +26 -0
- package/src/hooks.ts +23 -0
- package/src/index.native.ts +21 -0
- package/src/index.ts +29 -0
- package/src/model-context/injectInteractableContext.test.ts +167 -0
- package/src/model-context/injectInteractableContext.ts +61 -0
- package/src/model-context/injectQuoteContext.test.ts +33 -0
- package/src/model-context/injectQuoteContext.ts +62 -0
- package/src/runtime/AISDKChat.integration.test.tsx +47 -0
- package/src/runtime/AISDKChat.react-compiler.test.ts +49 -0
- package/src/runtime/AISDKChat.test.ts +193 -0
- package/src/runtime/AISDKChat.ts +47 -0
- package/src/runtime/AISDKThreads.cloud.test.ts +136 -0
- package/src/runtime/AISDKThreads.test.ts +548 -0
- package/src/runtime/AISDKThreads.ts +223 -0
- package/src/runtime/__tests__/controlled-transport.ts +43 -0
- package/src/runtime/useAISDKRuntime.approval.test.tsx +69 -0
- package/src/runtime/useAISDKRuntime.test.ts +1021 -0
- package/src/runtime/useAISDKRuntime.tool-output.test.tsx +113 -0
- package/src/runtime/useAISDKRuntime.ts +524 -0
- package/src/runtime/useChatOnTap.test.ts +58 -0
- package/src/runtime/useChatRuntime.integration.test.tsx +67 -0
- package/src/runtime/useChatRuntime.test.ts +522 -0
- package/src/runtime/useChatRuntime.ts +49 -0
- package/src/runtime/useChatThread.ts +286 -0
- package/src/runtime/useExternalHistory.test.ts +1289 -0
- package/src/runtime/useExternalHistory.ts +435 -0
- package/src/runtime/useResourceCleanup.ts +32 -0
- package/src/runtime/useStreamingTiming.ts +68 -0
- package/src/tools/frontendTools.test.ts +205 -0
- package/src/tools/frontendTools.ts +83 -0
- package/src/tools/generativeTools.test.ts +785 -0
- package/src/tools/generativeTools.ts +470 -0
- package/src/tools/mcp-stdio.node.ts +1 -0
- package/src/tools/mcp-stdio.unsupported.ts +12 -0
- package/src/transport/AssistantChatTransport.test.ts +300 -0
- package/src/transport/AssistantChatTransport.ts +204 -0
- package/src/transport/resumable.test.ts +262 -0
- package/src/transport/resumable.ts +161 -0
- package/src/usage.test.ts +186 -0
- package/src/usage.ts +174 -0
- package/src/utils/getVercelAIMessages.ts +11 -0
- package/src/utils/sliceMessagesUntil.test.ts +55 -0
- package/src/utils/sliceMessagesUntil.ts +20 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useExternalHistory.js","names":["AssistantRuntime","ThreadHistoryAdapter","ThreadMessage","MessageFormatAdapter","MessageFormatRepository","ExportedMessageRepository","getExternalStoreMessages","MessageRepository","useAui","useRef","useEffect","useState","RefObject","useCallback","useMemo","toExportedMessageRepository","toThreadMessages","messages","TMessage","survivingIds","Set","survivors","flatMap","m","message","console","warn","parentId","has","add","id","headId","isAwaitingToolApproval","status","type","reason","snapshotExternalMessages","Map","map","useExternalHistory","runtimeRef","historyAdapter","storageFormatAdapter","onSetMessages","loadedRef","itemEpoch","setItemEpoch","aui","optionalThreadListItem","threadListItem","source","hasLoaded","setHasLoaded","historyIds","persistedInnerIds","deferredTelemetryIds","persistedExternalMessages","onSetMessagesRef","current","formatAdapter","undefined","withFormat","Error","isLoading","loadHistory","repo","load","length","getId","converted","thread","import","tempRepo","getMessages","error","pin","remoteId","getState","subscribe","n","threadState","isRunning","runStartRef","persistTimerRef","ReturnType","setTimeout","persistInFlightRef","Promise","resolve","stepBoundariesRef","wasRunningRef","toolCallCountRef","adapter","unsubscribe","wasRunning","lastMsg","at","role","currentToolCallCount","content","filter","p","push","Date","now","clearTimeout","persistSettled","ignoreRunning","latest","boundaries","durationMs","lastAssistant","findLast","tcCount","totalSteps","stepDur","i","Math","round","stepTimestamps","endMs","start_ms","end_ms","telemetryOptions","then","changedRunMessageIds","externalMessages","previous","get","some","item","index","lastInnerMessageId","failedUpdateIds","getLastInnerId","msgs","toBatchItems","msg","idx","innerMessages","isTerminal","isAwaitingToolCalls","isReady","update","isPersistedMessage","batchItems","innerId","append","delete","reportTelemetry","nextSnapshot","catch","deleteMessage","messageId","deleteMessages","bind","messageIndex","findIndex","previousInnerMessages","slice","itemsToDelete","deletion"],"sources":["../../src/runtime/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\nconst isAwaitingToolApproval = (message: ThreadMessage) =>\n message.status?.type === \"requires-action\" &&\n message.status.reason === \"tool-calls\";\n\nconst snapshotExternalMessages = <TMessage>(\n messages: readonly ThreadMessage[],\n) =>\n new Map<string, TMessage[]>(\n messages.map((message) => [\n message.id,\n [...getExternalStoreMessages<TMessage>(message)],\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 const [itemEpoch, setItemEpoch] = useState(0);\n\n const aui = useAui();\n const optionalThreadListItem = useCallback(\n () => (aui.threadListItem.source ? aui.threadListItem : null),\n [aui],\n );\n\n const [hasLoaded, setHasLoaded] = useState(false);\n\n const historyIds = useRef(new Set<string>());\n const persistedInnerIds = useRef(new Set<string>());\n const deferredTelemetryIds = useRef(new Set<string>());\n const persistedExternalMessages = useRef(new Map<string, TMessage[]>());\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 const isLoading = formatAdapter != null && !hasLoaded;\n\n useEffect(() => {\n if (!formatAdapter || loadedRef.current) return undefined;\n\n const loadHistory = async () => {\n try {\n const repo = await formatAdapter.load();\n if (repo && repo.messages.length > 0) {\n for (const m of repo.messages) {\n persistedInnerIds.current.add(\n storageFormatAdapter.getId(m.message),\n );\n }\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 for (const m of converted.messages) {\n historyIds.current.add(m.message.id);\n if (isAwaitingToolApproval(m.message)) {\n deferredTelemetryIds.current.add(m.message.id);\n }\n }\n persistedExternalMessages.current =\n snapshotExternalMessages<TMessage>(\n converted.messages.map((m) => m.message),\n );\n }\n } catch (error) {\n console.error(\"Failed to load message history:\", error);\n } finally {\n setHasLoaded(true);\n }\n };\n\n formatAdapter.pin?.();\n\n const remoteId = optionalThreadListItem()?.getState().remoteId;\n if (!remoteId) {\n setHasLoaded(true);\n return aui.subscribe(() => {\n if (optionalThreadListItem()?.getState().remoteId) {\n setItemEpoch((n) => n + 1);\n }\n });\n }\n\n const threadState = runtimeRef.current.thread.getState();\n if (threadState.isRunning || threadState.messages.length > 0) {\n loadedRef.current = true;\n setHasLoaded(true);\n return undefined;\n }\n\n loadedRef.current = true;\n void loadHistory();\n return undefined;\n }, [\n formatAdapter,\n toThreadMessages,\n runtimeRef,\n optionalThreadListItem,\n aui,\n itemEpoch,\n storageFormatAdapter,\n ]);\n\n const runStartRef = useRef<number | null>(null);\n const persistTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const persistInFlightRef = useRef<Promise<void>>(Promise.resolve());\n const stepBoundariesRef = useRef<number[]>([]);\n const wasRunningRef = useRef(false);\n const toolCallCountRef = useRef(0);\n\n useEffect(() => {\n if (!formatAdapter) return;\n const adapter = formatAdapter;\n\n const unsubscribe = runtimeRef.current.thread.subscribe(() => {\n const threadState = runtimeRef.current.thread.getState();\n const { isRunning } = threadState;\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 = threadState.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 adapter.pin?.();\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(() => {\n persistSettled(false);\n }, 0);\n });\n\n function persistSettled(ignoreRunning: boolean) {\n persistTimerRef.current = null;\n const latest = runtimeRef.current.thread.getState();\n if (!ignoreRunning && latest.isRunning) return;\n\n const boundaries = stepBoundariesRef.current;\n const durationMs = 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 persistInFlightRef.current = persistInFlightRef.current\n .then(async () => {\n const changedRunMessageIds = new Set<string>();\n for (const message of latest.messages) {\n const externalMessages =\n getExternalStoreMessages<TMessage>(message);\n const previous = persistedExternalMessages.current.get(message.id);\n if (\n previous === undefined ||\n previous.length !== externalMessages.length ||\n externalMessages.some((item, index) => item !== previous[index])\n ) {\n changedRunMessageIds.add(message.id);\n }\n }\n\n const { messages } = latest;\n let lastInnerMessageId: string | null = null;\n const failedUpdateIds = new Set<string>();\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 isTerminal =\n message.status === undefined ||\n message.status.type === \"complete\" ||\n message.status.type === \"incomplete\";\n const isAwaitingToolCalls = isAwaitingToolApproval(message);\n // A paused message's later content can only reach storage via update, so it is persisted early only when the adapter supports update.\n const isReady =\n isTerminal ||\n (isAwaitingToolCalls && adapter.update !== undefined);\n\n if (!isReady) {\n lastInnerMessageId =\n getLastInnerId(innerMessages) ?? lastInnerMessageId;\n continue;\n }\n\n const isPersistedMessage = historyIds.current.has(message.id);\n if (isPersistedMessage && !changedRunMessageIds.has(message.id)) {\n lastInnerMessageId =\n getLastInnerId(innerMessages) ?? lastInnerMessageId;\n continue;\n }\n if (!isPersistedMessage) {\n historyIds.current.add(message.id);\n deferredTelemetryIds.current.add(message.id);\n }\n\n const batchItems = toBatchItems(innerMessages);\n for (const item of batchItems) {\n const innerId = storageFormatAdapter.getId(item.message);\n if (!persistedInnerIds.current.has(innerId)) {\n await adapter.append(item);\n persistedInnerIds.current.add(innerId);\n } else if (durationMs !== undefined) {\n try {\n await adapter.update?.(item, innerId);\n } catch {\n // A failed update drops the message from the refreshed baseline so it retries on the next run stop.\n failedUpdateIds.add(message.id);\n }\n }\n }\n\n lastInnerMessageId =\n getLastInnerId(innerMessages) ?? lastInnerMessageId;\n\n if (deferredTelemetryIds.current.has(message.id) && isTerminal) {\n deferredTelemetryIds.current.delete(message.id);\n adapter.reportTelemetry?.(batchItems, telemetryOptions);\n }\n }\n\n const nextSnapshot = snapshotExternalMessages<TMessage>(\n latest.messages,\n );\n for (const id of failedUpdateIds) {\n nextSnapshot.delete(id);\n }\n persistedExternalMessages.current = nextSnapshot;\n })\n .catch((error) => {\n console.error(\"Failed to persist message history:\", error);\n });\n }\n\n return () => {\n unsubscribe();\n if (persistTimerRef.current) {\n clearTimeout(persistTimerRef.current);\n persistTimerRef.current = null;\n persistSettled(false);\n }\n };\n }, [formatAdapter, storageFormatAdapter, runtimeRef]);\n\n const deleteMessage = useCallback(\n async (messageId: string) => {\n const deleteMessages = formatAdapter?.delete?.bind(formatAdapter);\n if (!deleteMessages) return;\n\n const messages = runtimeRef.current.thread.getState().messages;\n const messageIndex = messages.findIndex((m) => m.id === messageId);\n if (messageIndex === -1) return;\n\n const previousInnerMessages = messages\n .slice(0, messageIndex)\n .flatMap(getExternalStoreMessages<TMessage>);\n let parentId = previousInnerMessages.at(-1)\n ? storageFormatAdapter.getId(previousInnerMessages.at(-1)!)\n : null;\n const itemsToDelete = getExternalStoreMessages<TMessage>(\n messages[messageIndex]!,\n ).map((message) => {\n const item = { parentId, message };\n parentId = storageFormatAdapter.getId(message);\n return item;\n });\n\n const deletion = persistInFlightRef.current.then(async () => {\n await deleteMessages(itemsToDelete);\n\n historyIds.current.delete(messageId);\n deferredTelemetryIds.current.delete(messageId);\n persistedExternalMessages.current.delete(messageId);\n for (const item of itemsToDelete) {\n persistedInnerIds.current.delete(\n storageFormatAdapter.getId(item.message),\n );\n }\n });\n\n persistInFlightRef.current = deletion.catch(() => {});\n await deletion;\n },\n [formatAdapter, runtimeRef, storageFormatAdapter],\n );\n\n return { isLoading, deleteMessage };\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useResourceCleanup.d.ts","names":[],"sources":["../../src/runtime/useResourceCleanup.ts"],"mappings":";cAGa,qBAAsB,kBAAkB"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useEffect, useRef } from "@assistant-ui/tap/react-shim";
|
|
2
|
+
import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
|
|
3
|
+
import { useAssistantClientDestroySignal } from "@assistant-ui/store/internal";
|
|
4
|
+
//#region src/runtime/useResourceCleanup.ts
|
|
5
|
+
const useResourceCleanup = (enabled, cleanup) => {
|
|
6
|
+
const $ = c(7);
|
|
7
|
+
const destroySignal = useAssistantClientDestroySignal();
|
|
8
|
+
const cleanupRef = useRef(cleanup);
|
|
9
|
+
const enabledRef = useRef(enabled);
|
|
10
|
+
const registeredSignalRef = useRef(void 0);
|
|
11
|
+
let t0;
|
|
12
|
+
if ($[0] !== cleanup || $[1] !== enabled) {
|
|
13
|
+
t0 = () => {
|
|
14
|
+
cleanupRef.current = cleanup;
|
|
15
|
+
enabledRef.current = enabled;
|
|
16
|
+
};
|
|
17
|
+
$[0] = cleanup;
|
|
18
|
+
$[1] = enabled;
|
|
19
|
+
$[2] = t0;
|
|
20
|
+
} else t0 = $[2];
|
|
21
|
+
useEffect(t0);
|
|
22
|
+
let t1;
|
|
23
|
+
let t2;
|
|
24
|
+
if ($[3] !== destroySignal || $[4] !== enabled) {
|
|
25
|
+
t1 = () => {
|
|
26
|
+
if (!enabled || !destroySignal) return;
|
|
27
|
+
if (registeredSignalRef.current === destroySignal) return;
|
|
28
|
+
registeredSignalRef.current = destroySignal;
|
|
29
|
+
destroySignal.addEventListener("abort", () => {
|
|
30
|
+
if (enabledRef.current) cleanupRef.current();
|
|
31
|
+
}, { once: true });
|
|
32
|
+
};
|
|
33
|
+
t2 = [destroySignal, enabled];
|
|
34
|
+
$[3] = destroySignal;
|
|
35
|
+
$[4] = enabled;
|
|
36
|
+
$[5] = t1;
|
|
37
|
+
$[6] = t2;
|
|
38
|
+
} else {
|
|
39
|
+
t1 = $[5];
|
|
40
|
+
t2 = $[6];
|
|
41
|
+
}
|
|
42
|
+
useEffect(t1, t2);
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
export { useResourceCleanup };
|
|
46
|
+
|
|
47
|
+
//# sourceMappingURL=useResourceCleanup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useResourceCleanup.js","names":["useEffect","useRef","useAssistantClientDestroySignal","useResourceCleanup","enabled","cleanup","$","_c","destroySignal","cleanupRef","enabledRef","registeredSignalRef","undefined","t0","current","t1","t2","addEventListener","once"],"sources":["../../src/runtime/useResourceCleanup.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport { useAssistantClientDestroySignal } from \"@assistant-ui/store/internal\";\n\nexport const useResourceCleanup = (enabled: boolean, cleanup: () => void) => {\n const destroySignal = useAssistantClientDestroySignal();\n const cleanupRef = useRef(cleanup);\n const enabledRef = useRef(enabled);\n const registeredSignalRef = useRef<AbortSignal | undefined>(undefined);\n\n useEffect(() => {\n cleanupRef.current = cleanup;\n enabledRef.current = enabled;\n });\n\n useEffect(() => {\n if (!enabled || !destroySignal) return undefined;\n if (registeredSignalRef.current === destroySignal) return undefined;\n\n registeredSignalRef.current = destroySignal;\n destroySignal.addEventListener(\n \"abort\",\n () => {\n if (enabledRef.current) cleanupRef.current();\n },\n { once: true },\n );\n\n // The listener must survive standalone soft unmounts so a later permanent\n // client destroy still cleans up the retained resource state.\n return undefined;\n }, [destroySignal, enabled]);\n};\n"],"mappings":";;;;AAGA,MAAaG,sBAAqBC,SAAAC,YAAA;CAAA,MAAAC,IAAAC,EAAA,CAAA;CAChC,MAAAC,gBAAsBN,gCAAgC;CACtD,MAAAO,aAAmBR,OAAOI,OAAO;CACjC,MAAAK,aAAmBT,OAAOG,OAAO;CACjC,MAAAO,sBAA4BV,OAAgCW,KAAAA,CAAS;CAAE,IAAAC;CAAA,IAAAP,EAAA,OAAAD,WAAAC,EAAA,OAAAF,SAAA;EAE7DS,WAAA;GACRJ,WAAUK,UAAWT;GACrBK,WAAUI,UAAWV;EAAH;EACnBE,EAAA,KAAAD;EAAAC,EAAA,KAAAF;EAAAE,EAAA,KAAAO;CAAA,OAAAA,KAAAP,EAAA;CAHDN,UAAUa,EAGT;CAAC,IAAAE;CAAA,IAAAC;CAAA,IAAAV,EAAA,OAAAE,iBAAAF,EAAA,OAAAF,SAAA;EAEQW,WAAA;GACR,IAAI,CAACX,WAAD,CAAaI,eAAa;GAC9B,IAAIG,oBAAmBG,YAAaN,eAAa;GAEjDG,oBAAmBG,UAAWN;GAC9BA,cAAaS,iBACX,eACA;IACE,IAAIP,WAAUI,SAAUL,WAAUK,QAAS;GAAE,GAE/C,EAAAI,MAAQ,KAAK,CACf;EAAC;EAKAF,KAAA,CAACR,eAAeJ,OAAO;EAACE,EAAA,KAAAE;EAAAF,EAAA,KAAAF;EAAAE,EAAA,KAAAS;EAAAT,EAAA,KAAAU;CAAA,OAAA;EAAAD,KAAAT,EAAA;EAAAU,KAAAV,EAAA;CAAA;CAhB3BN,UAAUe,IAgBPC,EAAwB;AAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { StreamingTimingAccessors } from "@assistant-ui/core/react";
|
|
2
|
+
import { MessageTiming } from "@assistant-ui/core";
|
|
3
|
+
import { UIMessage } from "@ai-sdk/react";
|
|
4
|
+
//#region src/runtime/useStreamingTiming.d.ts
|
|
5
|
+
declare const aiSdkStreamingTimingAccessors: StreamingTimingAccessors<UIMessage>;
|
|
6
|
+
/**
|
|
7
|
+
* Tracks streaming timing for AI SDK messages client-side. Delegates to the
|
|
8
|
+
* shared `useStreamingTiming` primitive in `@assistant-ui/core/react`,
|
|
9
|
+
* adapted to the `UIMessage` shape. Timing is finalized when streaming ends
|
|
10
|
+
* and stored per message id.
|
|
11
|
+
*/
|
|
12
|
+
declare const useStreamingTiming: (messages: UIMessage[], isRunning: boolean) => Record<string, MessageTiming>;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { aiSdkStreamingTimingAccessors, useStreamingTiming };
|
|
15
|
+
//# sourceMappingURL=useStreamingTiming.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useStreamingTiming.d.ts","names":[],"sources":["../../src/runtime/useStreamingTiming.ts"],"mappings":";;;;cA8Ca,+BAA+B,yBAAyB;;;;;;;cAaxD,qBACX,UAAU,aACV,uBACC,eAAe"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useStreamingTiming as useStreamingTiming$1 } from "@assistant-ui/core/react";
|
|
3
|
+
import { isToolUIPart } from "ai";
|
|
4
|
+
//#region src/runtime/useStreamingTiming.ts
|
|
5
|
+
const findAssistant = (messages, messageId) => messages.find((m) => m.role === "assistant" && m.id === messageId);
|
|
6
|
+
const getTextLength = (messages, messageId) => {
|
|
7
|
+
const message = findAssistant(messages, messageId);
|
|
8
|
+
if (!message?.parts) return 0;
|
|
9
|
+
let len = 0;
|
|
10
|
+
for (const part of message.parts) if (part.type === "text") len += part.text.length;
|
|
11
|
+
return len;
|
|
12
|
+
};
|
|
13
|
+
const getToolCallCount = (messages, messageId) => {
|
|
14
|
+
const message = findAssistant(messages, messageId);
|
|
15
|
+
if (!message?.parts) return 0;
|
|
16
|
+
let count = 0;
|
|
17
|
+
for (const part of message.parts) if (isToolUIPart(part)) count++;
|
|
18
|
+
return count;
|
|
19
|
+
};
|
|
20
|
+
const getAssistantMessageId = (messages) => messages.findLast((m) => m.role === "assistant")?.id;
|
|
21
|
+
const aiSdkStreamingTimingAccessors = {
|
|
22
|
+
getAssistantMessageId,
|
|
23
|
+
getTextLength,
|
|
24
|
+
getToolCallCount
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Tracks streaming timing for AI SDK messages client-side. Delegates to the
|
|
28
|
+
* shared `useStreamingTiming` primitive in `@assistant-ui/core/react`,
|
|
29
|
+
* adapted to the `UIMessage` shape. Timing is finalized when streaming ends
|
|
30
|
+
* and stored per message id.
|
|
31
|
+
*/
|
|
32
|
+
const useStreamingTiming = (messages, isRunning) => {
|
|
33
|
+
return useStreamingTiming$1(messages, isRunning, aiSdkStreamingTimingAccessors);
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
36
|
+
export { aiSdkStreamingTimingAccessors, useStreamingTiming };
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=useStreamingTiming.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useStreamingTiming.js","names":["UIMessage","isToolUIPart","MessageTiming","useStreamingTiming","useStreamingTimingPrimitive","StreamingTimingAccessors","findAssistant","messages","messageId","find","m","role","id","getTextLength","message","parts","len","part","type","text","length","getToolCallCount","count","getAssistantMessageId","findLast","aiSdkStreamingTimingAccessors","isRunning"],"sources":["../../src/runtime/useStreamingTiming.ts"],"sourcesContent":["\"use client\";\n\nimport type { UIMessage } from \"@ai-sdk/react\";\nimport { isToolUIPart } from \"ai\";\nimport type { MessageTiming } from \"@assistant-ui/core\";\nimport {\n useStreamingTiming as useStreamingTimingPrimitive,\n type StreamingTimingAccessors,\n} from \"@assistant-ui/core/react\";\n\nconst findAssistant = (\n messages: readonly UIMessage[],\n messageId: string,\n): UIMessage | undefined =>\n messages.find((m) => m.role === \"assistant\" && m.id === messageId);\n\nconst getTextLength = (\n messages: readonly UIMessage[],\n messageId: string,\n): number => {\n const message = findAssistant(messages, messageId);\n if (!message?.parts) return 0;\n let len = 0;\n for (const part of message.parts) {\n if (part.type === \"text\") len += part.text.length;\n }\n return len;\n};\n\nconst getToolCallCount = (\n messages: readonly UIMessage[],\n messageId: string,\n): number => {\n const message = findAssistant(messages, messageId);\n if (!message?.parts) return 0;\n let count = 0;\n for (const part of message.parts) {\n if (isToolUIPart(part)) count++;\n }\n return count;\n};\n\nconst getAssistantMessageId = (\n messages: readonly UIMessage[],\n): string | undefined => messages.findLast((m) => m.role === \"assistant\")?.id;\n\nexport const aiSdkStreamingTimingAccessors: StreamingTimingAccessors<UIMessage> =\n {\n getAssistantMessageId,\n getTextLength,\n getToolCallCount,\n };\n\n/**\n * Tracks streaming timing for AI SDK messages client-side. Delegates to the\n * shared `useStreamingTiming` primitive in `@assistant-ui/core/react`,\n * adapted to the `UIMessage` shape. Timing is finalized when streaming ends\n * and stored per message id.\n */\nexport const useStreamingTiming = (\n messages: UIMessage[],\n isRunning: boolean,\n): Record<string, MessageTiming> =>\n useStreamingTimingPrimitive(\n messages,\n isRunning,\n aiSdkStreamingTimingAccessors,\n );\n"],"mappings":";;;;AAUA,MAAMM,iBACJC,UACAC,cAEAD,SAASE,MAAMC,MAAMA,EAAEC,SAAS,eAAeD,EAAEE,OAAOJ,SAAS;AAEnE,MAAMK,iBACJN,UACAC,cACW;CACX,MAAMM,UAAUR,cAAcC,UAAUC,SAAS;CACjD,IAAI,CAACM,SAASC,OAAO,OAAO;CAC5B,IAAIC,MAAM;CACV,KAAK,MAAMC,QAAQH,QAAQC,OACzB,IAAIE,KAAKC,SAAS,QAAQF,OAAOC,KAAKE,KAAKC;CAE7C,OAAOJ;AACT;AAEA,MAAMK,oBACJd,UACAC,cACW;CACX,MAAMM,UAAUR,cAAcC,UAAUC,SAAS;CACjD,IAAI,CAACM,SAASC,OAAO,OAAO;CAC5B,IAAIO,QAAQ;CACZ,KAAK,MAAML,QAAQH,QAAQC,OACzB,IAAId,aAAagB,IAAI,GAAGK;CAE1B,OAAOA;AACT;AAEA,MAAMC,yBACJhB,aACuBA,SAASiB,UAAUd,MAAMA,EAAEC,SAAS,WAAW,CAAC,EAAEC;AAE3E,MAAaa,gCACX;CACEF;CACAV;CACAQ;AACF;;;;;;;AAQF,MAAalB,sBAAqBI,UAAAmB,cAAA;CAAA,OAIhCtB,qBACEG,UACAmB,WACAD,6BACF;AAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ToolSet } from "ai";
|
|
2
|
+
import { ToolJSONSchema } from "assistant-stream";
|
|
3
|
+
//#region src/tools/frontendTools.d.ts
|
|
4
|
+
/** Frontend tool definitions uploaded by AssistantChatTransport. */
|
|
5
|
+
type FrontendTools = Record<string, ToolJSONSchema>;
|
|
6
|
+
declare const defaultToModelOutput: ({ output }: {
|
|
7
|
+
output: unknown;
|
|
8
|
+
}) => {
|
|
9
|
+
type: "content";
|
|
10
|
+
value: ({
|
|
11
|
+
type: "text";
|
|
12
|
+
text: string;
|
|
13
|
+
} | {
|
|
14
|
+
filename?: string;
|
|
15
|
+
type: "file";
|
|
16
|
+
data: {
|
|
17
|
+
type: "data";
|
|
18
|
+
data: string;
|
|
19
|
+
};
|
|
20
|
+
mediaType: string;
|
|
21
|
+
text?: never;
|
|
22
|
+
})[];
|
|
23
|
+
} | {
|
|
24
|
+
type: "text";
|
|
25
|
+
value: string;
|
|
26
|
+
} | {
|
|
27
|
+
type: "json";
|
|
28
|
+
value: import("ai").JSONValue;
|
|
29
|
+
};
|
|
30
|
+
declare const frontendTools: (tools: FrontendTools) => ToolSet;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { FrontendTools, defaultToModelOutput, frontendTools };
|
|
33
|
+
//# sourceMappingURL=frontendTools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontendTools.d.ts","names":[],"sources":["../../src/tools/frontendTools.ts"],"mappings":";;;;KASY,gBAAgB,eAAe;cAE9B,yBAAwB;EAAc;;;;;;;;;;;;;;;;;;;;;;;cAuDtC,gBAAiB,OAAO,kBAAgB"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { unwrapModelContentEnvelope } from "../converters/modelContentEnvelope.js";
|
|
2
|
+
import { toAISDKContent, toAISDKDefaultOutput } from "../converters/toolOutputConversion.js";
|
|
3
|
+
import { jsonSchema } from "ai";
|
|
4
|
+
//#region src/tools/frontendTools.ts
|
|
5
|
+
const defaultToModelOutput = ({ output }) => {
|
|
6
|
+
const { result, modelContent } = unwrapModelContentEnvelope(output);
|
|
7
|
+
if (modelContent !== void 0) return toAISDKContent(modelContent);
|
|
8
|
+
return toAISDKDefaultOutput(result);
|
|
9
|
+
};
|
|
10
|
+
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11
|
+
function validateFrontendTool(name, tool) {
|
|
12
|
+
if (!isPlainObject(tool)) throw new Error(`frontendTools() expected tool "${name}" to be an object with a JSON Schema parameters object.`);
|
|
13
|
+
if (!isPlainObject(tool.parameters)) throw new Error(`frontendTools() expected tool "${name}" to include a JSON Schema parameters object.`);
|
|
14
|
+
if (tool.description !== void 0 && typeof tool.description !== "string") throw new Error(`frontendTools() expected tool "${name}" description to be a string.`);
|
|
15
|
+
if (tool.providerOptions !== void 0 && !isPlainObject(tool.providerOptions)) throw new Error(`frontendTools() expected tool "${name}" providerOptions to be an object.`);
|
|
16
|
+
}
|
|
17
|
+
function validateFrontendTools(tools) {
|
|
18
|
+
if (!isPlainObject(tools)) throw new Error("frontendTools() expected tools to be an object keyed by tool name.");
|
|
19
|
+
for (const [name, tool] of Object.entries(tools)) validateFrontendTool(name, tool);
|
|
20
|
+
}
|
|
21
|
+
const frontendTools = (tools) => {
|
|
22
|
+
validateFrontendTools(tools);
|
|
23
|
+
return Object.fromEntries(Object.entries(tools).map(([name, tool]) => [name, {
|
|
24
|
+
...tool.description !== void 0 && { description: tool.description },
|
|
25
|
+
inputSchema: jsonSchema(tool.parameters),
|
|
26
|
+
toModelOutput: defaultToModelOutput,
|
|
27
|
+
...tool.providerOptions && { providerOptions: tool.providerOptions }
|
|
28
|
+
}]));
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
31
|
+
export { defaultToModelOutput, frontendTools };
|
|
32
|
+
|
|
33
|
+
//# sourceMappingURL=frontendTools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontendTools.js","names":["jsonSchema","ToolSet","ToolJSONSchema","unwrapModelContentEnvelope","toAISDKContent","toAISDKDefaultOutput","FrontendTools","Record","defaultToModelOutput","output","result","modelContent","undefined","isPlainObject","value","Array","isArray","validateFrontendTool","name","tool","Error","parameters","description","providerOptions","validateFrontendTools","tools","Object","entries","frontendTools","fromEntries","map","inputSchema","toModelOutput"],"sources":["../../src/tools/frontendTools.ts"],"sourcesContent":["import { jsonSchema, type ToolSet } from \"ai\";\nimport type { ToolJSONSchema } from \"assistant-stream\";\nimport { unwrapModelContentEnvelope } from \"../converters/modelContentEnvelope\";\nimport {\n toAISDKContent,\n toAISDKDefaultOutput,\n} from \"../converters/toolOutputConversion\";\n\n/** Frontend tool definitions uploaded by AssistantChatTransport. */\nexport type FrontendTools = Record<string, ToolJSONSchema>;\n\nexport const defaultToModelOutput = ({ output }: { output: unknown }) => {\n const { result, modelContent } = unwrapModelContentEnvelope(output);\n if (modelContent !== undefined) {\n return toAISDKContent(modelContent);\n }\n return toAISDKDefaultOutput(result);\n};\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nfunction validateFrontendTool(\n name: string,\n tool: unknown,\n): asserts tool is ToolJSONSchema {\n if (!isPlainObject(tool)) {\n throw new Error(\n `frontendTools() expected tool \"${name}\" to be an object with a JSON Schema parameters object.`,\n );\n }\n\n if (!isPlainObject(tool.parameters)) {\n throw new Error(\n `frontendTools() expected tool \"${name}\" to include a JSON Schema parameters object.`,\n );\n }\n\n if (tool.description !== undefined && typeof tool.description !== \"string\") {\n throw new Error(\n `frontendTools() expected tool \"${name}\" description to be a string.`,\n );\n }\n\n if (\n tool.providerOptions !== undefined &&\n !isPlainObject(tool.providerOptions)\n ) {\n throw new Error(\n `frontendTools() expected tool \"${name}\" providerOptions to be an object.`,\n );\n }\n}\n\nfunction validateFrontendTools(tools: unknown): asserts tools is FrontendTools {\n if (!isPlainObject(tools)) {\n throw new Error(\n \"frontendTools() expected tools to be an object keyed by tool name.\",\n );\n }\n\n for (const [name, tool] of Object.entries(tools)) {\n validateFrontendTool(name, tool);\n }\n}\n\nexport const frontendTools = (tools: FrontendTools): ToolSet => {\n validateFrontendTools(tools);\n\n return Object.fromEntries(\n Object.entries(tools).map(([name, tool]) => [\n name,\n {\n ...(tool.description !== undefined && {\n description: tool.description,\n }),\n inputSchema: jsonSchema(tool.parameters),\n toModelOutput: defaultToModelOutput,\n ...(tool.providerOptions && { providerOptions: tool.providerOptions }),\n },\n ]),\n ) as ToolSet;\n};\n"],"mappings":";;;;AAWA,MAAaQ,wBAAwB,EAAEC,aAAkC;CACvE,MAAM,EAAEC,QAAQC,iBAAiBR,2BAA2BM,MAAM;CAClE,IAAIE,iBAAiBC,KAAAA,GACnB,OAAOR,eAAeO,YAAY;CAEpC,OAAON,qBAAqBK,MAAM;AACpC;AAEA,MAAMG,iBAAiBC,UACrB,OAAOA,UAAU,YAAYA,UAAU,QAAQ,CAACC,MAAMC,QAAQF,KAAK;AAErE,SAASG,qBACPC,MACAC,MACgC;CAChC,IAAI,CAACN,cAAcM,IAAI,GACrB,MAAM,IAAIC,MACR,kCAAkCF,KAAI,wDACxC;CAGF,IAAI,CAACL,cAAcM,KAAKE,UAAU,GAChC,MAAM,IAAID,MACR,kCAAkCF,KAAI,8CACxC;CAGF,IAAIC,KAAKG,gBAAgBV,KAAAA,KAAa,OAAOO,KAAKG,gBAAgB,UAChE,MAAM,IAAIF,MACR,kCAAkCF,KAAI,8BACxC;CAGF,IACEC,KAAKI,oBAAoBX,KAAAA,KACzB,CAACC,cAAcM,KAAKI,eAAe,GAEnC,MAAM,IAAIH,MACR,kCAAkCF,KAAI,mCACxC;AAEJ;AAEA,SAASM,sBAAsBC,OAAgD;CAC7E,IAAI,CAACZ,cAAcY,KAAK,GACtB,MAAM,IAAIL,MACR,oEACF;CAGF,KAAK,MAAM,CAACF,MAAMC,SAASO,OAAOC,QAAQF,KAAK,GAC7CR,qBAAqBC,MAAMC,IAAI;AAEnC;AAEA,MAAaS,iBAAiBH,UAAkC;CAC9DD,sBAAsBC,KAAK;CAE3B,OAAOC,OAAOG,YACZH,OAAOC,QAAQF,KAAK,CAAC,CAACK,KAAK,CAACZ,MAAMC,UAAU,CAC1CD,MACA;EACE,GAAIC,KAAKG,gBAAgBV,KAAAA,KAAa,EACpCU,aAAaH,KAAKG,YACpB;EACAS,aAAa/B,WAAWmB,KAAKE,UAAU;EACvCW,eAAexB;EACf,GAAIW,KAAKI,mBAAmB,EAAEA,iBAAiBJ,KAAKI,gBAAgB;CACtE,CAAC,CACF,CACH;AACF"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { FrontendTools } from "./frontendTools.js";
|
|
2
|
+
import { Toolkit } from "@assistant-ui/core/react";
|
|
3
|
+
import { ToolSet } from "ai";
|
|
4
|
+
//#region src/tools/generativeTools.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* @deprecated Options for the deprecated {@link generativeTools}. Use
|
|
7
|
+
* {@link AISDKToolkit} with {@link AISDKToolkitOptions} /
|
|
8
|
+
* {@link AISDKToolkitToolsOptions} instead.
|
|
9
|
+
*/
|
|
10
|
+
interface GenerativeToolsOptions {
|
|
11
|
+
/**
|
|
12
|
+
* The server build of a generative toolkit (schema + server `execute`). Typed
|
|
13
|
+
* as the canonical {@link Toolkit} so callers don't need to cast; the server
|
|
14
|
+
* build carries `execute`, recovered internally as {@link ToolkitDefinition}.
|
|
15
|
+
*/
|
|
16
|
+
toolkit: Toolkit;
|
|
17
|
+
/**
|
|
18
|
+
* Tools uploaded by the frontend (the request body's `tools`). Merged in
|
|
19
|
+
* alongside the `toolkit`; a server `execute` from `toolkit` takes precedence
|
|
20
|
+
* over an uploaded entry of the same name.
|
|
21
|
+
*/
|
|
22
|
+
frontendTools?: FrontendTools;
|
|
23
|
+
}
|
|
24
|
+
type AISDKToolkitOptions = {
|
|
25
|
+
toolkit: Toolkit;
|
|
26
|
+
};
|
|
27
|
+
type AISDKToolkitToolsOptions = {
|
|
28
|
+
/**
|
|
29
|
+
* Tools uploaded by the frontend request body.
|
|
30
|
+
*/
|
|
31
|
+
frontend?: FrontendTools;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Builds an AI SDK `ToolSet` for server-side use with `streamText` /
|
|
35
|
+
* `generateText` from a generative `toolkit` and the frontend-uploaded tools.
|
|
36
|
+
*
|
|
37
|
+
* Each toolkit tool's `execute` runs on the server. Pair this with the
|
|
38
|
+
* `"use generative"` compiler: import the toolkit in a server route (where it
|
|
39
|
+
* resolves to the server build — schema + `execute`, with `render` stripped) and
|
|
40
|
+
* pass it here. Tools without an `execute` are still exposed to the model but
|
|
41
|
+
* left for the client to fulfill. `frontendTools` lets the client contribute
|
|
42
|
+
* tools that aren't in the static toolkit.
|
|
43
|
+
*
|
|
44
|
+
* @deprecated Use {@link AISDKToolkit} instead:
|
|
45
|
+
* `new AISDKToolkit({ toolkit }).tools({ frontend })`. It is a strict superset
|
|
46
|
+
* (it also opens MCP server connections), so it replaces `generativeTools`
|
|
47
|
+
* everywhere. The `frontendTools` option is named `frontend` on `.tools()`, and
|
|
48
|
+
* `.tools()` is async. `generativeTools` will be removed in a future version.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* // Define once at module scope so any MCP connections pool across requests.
|
|
53
|
+
* const aiToolkit = new AISDKToolkit({ toolkit: docsToolkit });
|
|
54
|
+
*
|
|
55
|
+
* // In your route handler:
|
|
56
|
+
* const { tools } = await req.json();
|
|
57
|
+
* streamText({
|
|
58
|
+
* model,
|
|
59
|
+
* messages,
|
|
60
|
+
* tools: await aiToolkit.tools({ frontend: tools }),
|
|
61
|
+
* });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare const generativeTools: (options: GenerativeToolsOptions) => ToolSet;
|
|
65
|
+
declare class AISDKToolkit {
|
|
66
|
+
#private;
|
|
67
|
+
constructor(options: AISDKToolkitOptions);
|
|
68
|
+
tools(options?: AISDKToolkitToolsOptions): Promise<ToolSet>;
|
|
69
|
+
close(): Promise<void>;
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
export { AISDKToolkit, AISDKToolkitOptions, AISDKToolkitToolsOptions, GenerativeToolsOptions, generativeTools };
|
|
73
|
+
//# sourceMappingURL=generativeTools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generativeTools.d.ts","names":[],"sources":["../../src/tools/generativeTools.ts"],"mappings":";;;;;;;;;UAsFiB;;;;;;EAMf,SAAS;;;;;;EAMT,gBAAgB;;KAGN;EACV,SAAS;;KAGC;;;;EAIV,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAkCA,kBAAmB,SAAS,2BAAyB;cAYrD;;EAIC,YAAA,SAAS;EAIf,MAAM,UAAS,2BAAgC,QAAQ;EAsBvD,SAAS"}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { unwrapModelContentEnvelope } from "../converters/modelContentEnvelope.js";
|
|
2
|
+
import { toAISDKContent, toAISDKDefaultOutput } from "../converters/toolOutputConversion.js";
|
|
3
|
+
import { frontendTools } from "./frontendTools.js";
|
|
4
|
+
import { jsonSchema } from "ai";
|
|
5
|
+
import { toJSONSchema } from "assistant-stream";
|
|
6
|
+
import { createMCPClient } from "@ai-sdk/mcp";
|
|
7
|
+
import { Experimental_StdioMCPTransport } from "#mcp-stdio";
|
|
8
|
+
//#region src/tools/generativeTools.ts
|
|
9
|
+
const EMPTY_SCHEMA = {
|
|
10
|
+
type: "object",
|
|
11
|
+
properties: {}
|
|
12
|
+
};
|
|
13
|
+
const humanNotSupported = () => {
|
|
14
|
+
throw new Error("`human()` is not available during server-side tool execution.");
|
|
15
|
+
};
|
|
16
|
+
const neverAbort = new AbortController().signal;
|
|
17
|
+
var MCPConnectionTimeoutError = class extends Error {};
|
|
18
|
+
const createMcpConnectionTimeoutError = (name, phase, timeoutMs) => new MCPConnectionTimeoutError(`MCP toolkit entry "${name}" timed out while ${phase} after ${timeoutMs}ms.`);
|
|
19
|
+
const withMcpConnectionTimeout = async (promise, options) => {
|
|
20
|
+
const timeoutMs = options.config.connectionTimeout;
|
|
21
|
+
if (timeoutMs === void 0) return await promise;
|
|
22
|
+
const remainingMs = timeoutMs - (Date.now() - options.startedAt);
|
|
23
|
+
const timeoutError = () => createMcpConnectionTimeoutError(options.name, options.phase, timeoutMs);
|
|
24
|
+
if (remainingMs <= 0) throw timeoutError();
|
|
25
|
+
let timeout;
|
|
26
|
+
try {
|
|
27
|
+
return await Promise.race([promise, new Promise((_, reject) => {
|
|
28
|
+
timeout = setTimeout(() => reject(timeoutError()), remainingMs);
|
|
29
|
+
})]);
|
|
30
|
+
} finally {
|
|
31
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const parametersToInputSchema = (parameters) => jsonSchema(parameters ? toJSONSchema(parameters) : EMPTY_SCHEMA);
|
|
35
|
+
/**
|
|
36
|
+
* Builds an AI SDK `ToolSet` for server-side use with `streamText` /
|
|
37
|
+
* `generateText` from a generative `toolkit` and the frontend-uploaded tools.
|
|
38
|
+
*
|
|
39
|
+
* Each toolkit tool's `execute` runs on the server. Pair this with the
|
|
40
|
+
* `"use generative"` compiler: import the toolkit in a server route (where it
|
|
41
|
+
* resolves to the server build — schema + `execute`, with `render` stripped) and
|
|
42
|
+
* pass it here. Tools without an `execute` are still exposed to the model but
|
|
43
|
+
* left for the client to fulfill. `frontendTools` lets the client contribute
|
|
44
|
+
* tools that aren't in the static toolkit.
|
|
45
|
+
*
|
|
46
|
+
* @deprecated Use {@link AISDKToolkit} instead:
|
|
47
|
+
* `new AISDKToolkit({ toolkit }).tools({ frontend })`. It is a strict superset
|
|
48
|
+
* (it also opens MCP server connections), so it replaces `generativeTools`
|
|
49
|
+
* everywhere. The `frontendTools` option is named `frontend` on `.tools()`, and
|
|
50
|
+
* `.tools()` is async. `generativeTools` will be removed in a future version.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* // Define once at module scope so any MCP connections pool across requests.
|
|
55
|
+
* const aiToolkit = new AISDKToolkit({ toolkit: docsToolkit });
|
|
56
|
+
*
|
|
57
|
+
* // In your route handler:
|
|
58
|
+
* const { tools } = await req.json();
|
|
59
|
+
* streamText({
|
|
60
|
+
* model,
|
|
61
|
+
* messages,
|
|
62
|
+
* tools: await aiToolkit.tools({ frontend: tools }),
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
const generativeTools = (options) => {
|
|
67
|
+
assertNoMcpToolkitTools(options.toolkit);
|
|
68
|
+
return {
|
|
69
|
+
...options.frontendTools ? frontendTools(options.frontendTools) : {},
|
|
70
|
+
...toProviderToolSet(options.toolkit),
|
|
71
|
+
...toServerToolSet(options.toolkit)
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
var AISDKToolkit = class {
|
|
75
|
+
#toolkit;
|
|
76
|
+
#mcpClients = /* @__PURE__ */ new Map();
|
|
77
|
+
constructor(options) {
|
|
78
|
+
this.#toolkit = options.toolkit;
|
|
79
|
+
}
|
|
80
|
+
async tools(options = {}) {
|
|
81
|
+
const frontendToolSet = options.frontend ? frontendTools(options.frontend) : {};
|
|
82
|
+
const mcpToolSet = await this.#mcpTools();
|
|
83
|
+
const providerToolSet = toProviderToolSet(this.#toolkit);
|
|
84
|
+
const serverToolSet = toServerToolSet(this.#toolkit);
|
|
85
|
+
assertNoMcpToolNameCollisions(mcpToolSet, [
|
|
86
|
+
{
|
|
87
|
+
source: "frontend",
|
|
88
|
+
tools: frontendToolSet
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
source: "provider",
|
|
92
|
+
tools: providerToolSet
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
source: "toolkit",
|
|
96
|
+
tools: serverToolSet
|
|
97
|
+
}
|
|
98
|
+
]);
|
|
99
|
+
return {
|
|
100
|
+
...frontendToolSet,
|
|
101
|
+
...mcpToolSet.tools,
|
|
102
|
+
...providerToolSet,
|
|
103
|
+
...serverToolSet
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async close() {
|
|
107
|
+
const clientEntries = [...this.#mcpClients.entries()];
|
|
108
|
+
const clientNames = clientEntries.map(([name]) => name);
|
|
109
|
+
this.#mcpClients.clear();
|
|
110
|
+
const clientResults = await Promise.allSettled(clientEntries.map(([, clientPromise]) => clientPromise));
|
|
111
|
+
const clients = clientResults.flatMap((result, index) => result.status === "fulfilled" ? [[clientNames[index], result.value]] : []);
|
|
112
|
+
const closeResults = await Promise.allSettled(clients.map(([, client]) => client.close()));
|
|
113
|
+
const errors = [...clientResults.flatMap((result, index) => result.status === "rejected" ? [toMcpToolkitError(clientNames[index], "connect", result.reason)] : []), ...closeResults.flatMap((result, index) => result.status === "rejected" ? [toMcpToolkitError(clients[index][0], "close", result.reason)] : [])];
|
|
114
|
+
if (errors.length === 1) throw errors[0];
|
|
115
|
+
if (errors.length > 1) throw new AggregateError(errors, "Failed to close one or more MCP clients");
|
|
116
|
+
}
|
|
117
|
+
async #mcpTools() {
|
|
118
|
+
const toolSets = await Promise.all(Object.entries(this.#toolkit).filter((entry) => isMcpToolkitTool(entry[1])).map(async ([name, tool]) => {
|
|
119
|
+
const startedAt = Date.now();
|
|
120
|
+
const client = await this.#mcpClient(name, tool.server, startedAt).catch((error) => {
|
|
121
|
+
if (error instanceof MCPConnectionTimeoutError) throw error;
|
|
122
|
+
throw toMcpToolkitError(name, "connect", error);
|
|
123
|
+
});
|
|
124
|
+
try {
|
|
125
|
+
return [
|
|
126
|
+
name,
|
|
127
|
+
tool,
|
|
128
|
+
await withMcpConnectionTimeout(client.tools(), {
|
|
129
|
+
name,
|
|
130
|
+
config: tool.server,
|
|
131
|
+
phase: "listing tools",
|
|
132
|
+
startedAt
|
|
133
|
+
})
|
|
134
|
+
];
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (error instanceof MCPConnectionTimeoutError) {
|
|
137
|
+
this.#mcpClients.delete(name);
|
|
138
|
+
client.close().catch(() => {});
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
throw toMcpToolkitError(name, "list tools", error);
|
|
142
|
+
}
|
|
143
|
+
}));
|
|
144
|
+
const tools = {};
|
|
145
|
+
const toolSources = /* @__PURE__ */ new Map();
|
|
146
|
+
for (const [serverName, mcpTool, toolSet] of toolSets) for (const [toolName, tool] of Object.entries(toolSet)) {
|
|
147
|
+
if (isDisabledMcpTool(mcpTool.tools?.[toolName])) continue;
|
|
148
|
+
const exposedName = `${mcpTool.prefix ?? ""}${toolName}`;
|
|
149
|
+
const existingServerName = toolSources.get(exposedName);
|
|
150
|
+
if (existingServerName) throw new Error(`MCP tool name collision: "${exposedName}" is exposed by both "${existingServerName}" and "${serverName}". Rename one of the toolkit entries or expose distinct MCP tool names.`);
|
|
151
|
+
toolSources.set(exposedName, serverName);
|
|
152
|
+
tools[exposedName] = tool;
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
tools,
|
|
156
|
+
sources: toolSources
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
#mcpClient(name, config, startedAt) {
|
|
160
|
+
const existing = this.#mcpClients.get(name);
|
|
161
|
+
if (existing) return existing;
|
|
162
|
+
const createPromise = createMCPClient(toMCPClientConfig(config));
|
|
163
|
+
let next;
|
|
164
|
+
next = withMcpConnectionTimeout(createPromise, {
|
|
165
|
+
name,
|
|
166
|
+
config,
|
|
167
|
+
phase: "connecting",
|
|
168
|
+
startedAt
|
|
169
|
+
}).catch((error) => {
|
|
170
|
+
if (this.#mcpClients.get(name) === next) this.#mcpClients.delete(name);
|
|
171
|
+
if (error instanceof MCPConnectionTimeoutError) createPromise.then((client) => client.close()).catch(() => {});
|
|
172
|
+
throw error;
|
|
173
|
+
});
|
|
174
|
+
this.#mcpClients.set(name, next);
|
|
175
|
+
return next;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const toMCPClientConfig = (config) => {
|
|
179
|
+
if (config.type === "stdio") return { transport: new Experimental_StdioMCPTransport({
|
|
180
|
+
command: config.command,
|
|
181
|
+
...config.args && { args: [...config.args] },
|
|
182
|
+
...config.env && { env: config.env },
|
|
183
|
+
...config.cwd && { cwd: config.cwd }
|
|
184
|
+
}) };
|
|
185
|
+
return { transport: {
|
|
186
|
+
type: config.type,
|
|
187
|
+
url: config.url,
|
|
188
|
+
...config.headers && { headers: config.headers },
|
|
189
|
+
...config.redirect && { redirect: config.redirect }
|
|
190
|
+
} };
|
|
191
|
+
};
|
|
192
|
+
const assertNoMcpToolNameCollisions = (mcp, toolSets) => {
|
|
193
|
+
for (const [toolName, serverName] of mcp.sources) for (const { source, tools } of toolSets) {
|
|
194
|
+
if (!Object.prototype.hasOwnProperty.call(tools, toolName)) continue;
|
|
195
|
+
throw new Error(`MCP tool "${toolName}" from "${serverName}" conflicts with ${source} tool "${toolName}". Rename one of the tools so each model-visible tool name is unique.`);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const isMcpToolkitTool = (tool) => tool.type === "mcp" && !tool.disabled;
|
|
199
|
+
const getErrorMessage = (error) => error instanceof Error ? error.message || error.name : String(error);
|
|
200
|
+
const toMcpToolkitError = (entryName, action, error) => {
|
|
201
|
+
return new Error(`MCP toolkit entry "${entryName}" failed to ${action}: ${getErrorMessage(error)}`, { cause: error });
|
|
202
|
+
};
|
|
203
|
+
const isDisabledMcpTool = (config) => config?.disabled === true;
|
|
204
|
+
const assertNoMcpToolkitTools = (toolkit) => {
|
|
205
|
+
const mcpToolName = Object.entries(toolkit).find(([, tool]) => isMcpToolkitTool(tool))?.[0];
|
|
206
|
+
if (!mcpToolName) return;
|
|
207
|
+
throw new Error(`MCP toolkit entry "${mcpToolName}" requires AISDKToolkit. Use new AISDKToolkit({ toolkit }).tools(...) instead of generativeTools(...).`);
|
|
208
|
+
};
|
|
209
|
+
const toAISDKToModelOutput = (toModelOutput) => async (options) => {
|
|
210
|
+
const { result, modelContent } = unwrapModelContentEnvelope(options.output);
|
|
211
|
+
if (modelContent !== void 0) return toAISDKContent(modelContent);
|
|
212
|
+
if (!toModelOutput) return toAISDKDefaultOutput(result);
|
|
213
|
+
const parts = await toModelOutput({
|
|
214
|
+
...options,
|
|
215
|
+
output: result
|
|
216
|
+
});
|
|
217
|
+
return toAISDKContent(parts);
|
|
218
|
+
};
|
|
219
|
+
const toServerToolSet = (toolkit) => Object.fromEntries(Object.entries(toolkit).filter(([, t]) => t.type !== "mcp" && t.type !== "provider" && !t.disabled).map(([name, t]) => {
|
|
220
|
+
const execute = t.execute;
|
|
221
|
+
return [name, {
|
|
222
|
+
...t.description !== void 0 && { description: t.description },
|
|
223
|
+
inputSchema: parametersToInputSchema(t.parameters),
|
|
224
|
+
toModelOutput: toAISDKToModelOutput(t.toModelOutput),
|
|
225
|
+
...t.providerOptions && { providerOptions: t.providerOptions },
|
|
226
|
+
...execute && { execute: (args, callOptions) => execute(args, {
|
|
227
|
+
toolCallId: callOptions.toolCallId,
|
|
228
|
+
abortSignal: callOptions.abortSignal ?? neverAbort,
|
|
229
|
+
human: humanNotSupported
|
|
230
|
+
}) }
|
|
231
|
+
}];
|
|
232
|
+
}));
|
|
233
|
+
const toProviderToolSet = (toolkit) => Object.fromEntries(Object.entries(toolkit).filter((entry) => isProviderToolkitTool(entry[1])).map(([name, t]) => [name, {
|
|
234
|
+
type: "provider",
|
|
235
|
+
id: t.providerId,
|
|
236
|
+
args: t.args,
|
|
237
|
+
...t.parameters && { inputSchema: parametersToInputSchema(t.parameters) },
|
|
238
|
+
...t.providerOptions && { providerOptions: t.providerOptions },
|
|
239
|
+
...t.supportsDeferredResults !== void 0 && { supportsDeferredResults: t.supportsDeferredResults }
|
|
240
|
+
}]));
|
|
241
|
+
const isProviderToolkitTool = (tool) => tool.type === "provider" && !tool.disabled;
|
|
242
|
+
//#endregion
|
|
243
|
+
export { AISDKToolkit, generativeTools };
|
|
244
|
+
|
|
245
|
+
//# sourceMappingURL=generativeTools.js.map
|