@assistant-ui/react 0.15.14 → 0.15.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assistant-transport.d.ts +32 -0
- package/dist/assistant-transport.d.ts.map +1 -0
- package/dist/assistant-transport.js +27 -0
- package/dist/assistant-transport.js.map +1 -0
- package/dist/context/providers/MessageProvider.js +1 -1
- package/dist/hooks/useMessageQuote.js +1 -1
- package/dist/hooks/useToolCallElapsed.js +1 -1
- package/dist/index.d.ts +4 -7
- package/dist/index.js +6 -8
- package/dist/legacy-runtime/AssistantRuntimeProvider.js +1 -1
- package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppsRemoteHost.js +18 -2
- package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
- package/dist/mcp-apps/bridge.js +39 -24
- package/dist/mcp-apps/bridge.js.map +1 -1
- package/dist/model-context/makeAssistantVisible.js +1 -1
- package/dist/primitives/actionBar/ActionBarCopy.js +1 -1
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +1 -1
- package/dist/primitives/actionBar/ActionBarStopSpeaking.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarStopSpeaking.js +24 -37
- package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -1
- package/dist/primitives/actionBar/useActionBarFloatStatus.js +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.js +1 -1
- package/dist/primitives/attachment/AttachmentName.js +1 -1
- package/dist/primitives/attachment/AttachmentRemove.js +1 -1
- package/dist/primitives/attachment/AttachmentThumb.js +1 -1
- package/dist/primitives/branchPicker/BranchPickerCount.js +1 -1
- package/dist/primitives/branchPicker/BranchPickerNumber.js +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.js +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
- package/dist/primitives/composer/ComposerDictationTranscript.js +1 -1
- package/dist/primitives/composer/ComposerInput.js +2 -2
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerQuote.js +1 -1
- package/dist/primitives/composer/ComposerRoot.js +1 -1
- package/dist/primitives/composer/ComposerStopDictation.js +1 -1
- package/dist/primitives/composer/trigger/TriggerPopover.js +1 -1
- package/dist/primitives/composer/useComposerInputState.js +1 -1
- package/dist/primitives/message/MessageIf.js +1 -1
- package/dist/primitives/message/MessagePartsGrouped.js +1 -1
- package/dist/primitives/message/MessageRoot.js +1 -1
- package/dist/primitives/queueItem/QueueItemRemove.js +1 -1
- package/dist/primitives/queueItem/QueueItemSteer.js +1 -1
- package/dist/primitives/queueItem/QueueItemText.js +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +1 -1
- package/dist/primitives/suggestion/SuggestionDescription.js +1 -1
- package/dist/primitives/suggestion/SuggestionTitle.js +1 -1
- package/dist/primitives/suggestion/SuggestionTrigger.js +1 -1
- package/dist/primitives/thread/ThreadIf.js +1 -1
- package/dist/primitives/thread/ThreadRoot.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadRoot.js +41 -9
- package/dist/primitives/thread/ThreadRoot.js.map +1 -1
- package/dist/primitives/thread/ThreadViewport.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadViewport.js +2 -13
- package/dist/primitives/thread/ThreadViewport.js.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +23 -8
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/primitives/threadList/ThreadListNew.js +1 -1
- package/dist/sandbox-host/SandboxHost.js +2 -0
- package/dist/sandbox-host/SandboxHost.js.map +1 -1
- package/dist/unstable/useComposerInput.d.ts +2 -2
- package/dist/unstable/useComposerInput.js +1 -1
- package/dist/unstable/useComposerInput.js.map +1 -1
- package/dist/unstable/useComposerInputHistory.js +1 -1
- package/dist/unstable/useComposerInputHistory.js.map +1 -1
- package/dist/unstable/useLiveCompletionAdapter.d.ts +5 -0
- package/dist/unstable/useLiveCompletionAdapter.d.ts.map +1 -1
- package/dist/unstable/useLiveCompletionAdapter.js +16 -3
- package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
- package/dist/unstable/useMentionAdapter.d.ts +1 -1
- package/dist/utils/smooth/SmoothContext.js +1 -1
- package/dist/utils/useToolArgsFieldStatus.js +1 -1
- package/package.json +9 -9
- package/src/assistant-transport.ts +94 -0
- package/src/index.ts +8 -4
- package/src/mcp-apps/McpAppsRemoteHost.test.ts +29 -3
- package/src/mcp-apps/McpAppsRemoteHost.ts +42 -7
- package/src/mcp-apps/bridge.test.ts +132 -0
- package/src/mcp-apps/bridge.ts +54 -30
- package/src/primitives/actionBar/ActionBarStopSpeaking.tsx +0 -9
- package/src/primitives/composer/ComposerInput.tsx +1 -1
- package/src/primitives/thread/ThreadRoot.test.tsx +251 -0
- package/src/primitives/thread/ThreadRoot.tsx +30 -0
- package/src/primitives/thread/ThreadViewport.tsx +1 -8
- package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +152 -0
- package/src/primitives/thread/useThreadViewportAutoScroll.ts +26 -19
- package/src/sandbox-host/SandboxHost.test.tsx +29 -0
- package/src/sandbox-host/SandboxHost.tsx +2 -0
- package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +24 -3
- package/src/tests/RemoteThreadListRuntime.reloadMainThread.test.tsx +1 -2
- package/src/tests/augmentations.test.ts +49 -0
- package/src/tests/in-memory-thread-list.test.tsx +1 -1
- package/src/tests/threadMessageById.test.tsx +61 -1
- package/src/unstable/useComposerInput.ts +2 -2
- package/src/unstable/useComposerInputHistory.test.tsx +4 -4
- package/src/unstable/useComposerInputHistory.ts +1 -1
- package/src/unstable/useLiveCompletionAdapter.test.tsx +100 -0
- package/src/unstable/useLiveCompletionAdapter.ts +41 -17
- package/dist/client/InMemoryThreadList.d.ts +0 -12
- package/dist/client/InMemoryThreadList.d.ts.map +0 -1
- package/dist/client/InMemoryThreadList.js +0 -374
- package/dist/client/InMemoryThreadList.js.map +0 -1
- package/dist/legacy-runtime/cloud/auiV0.d.ts +0 -119
- package/dist/legacy-runtime/cloud/auiV0.d.ts.map +0 -1
- package/dist/legacy-runtime/cloud/auiV0.js +0 -137
- package/dist/legacy-runtime/cloud/auiV0.js.map +0 -1
- package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.d.ts +0 -16
- package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.d.ts.map +0 -1
- package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.js +0 -36
- package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts +0 -17
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js +0 -55
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.d.ts +0 -11
- package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js +0 -165
- package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts +0 -15
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js +0 -81
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts +0 -126
- package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/types.js +0 -0
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts +0 -14
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +0 -290
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts +0 -7
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js +0 -25
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.d.ts +0 -7
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js +0 -27
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.d.ts +0 -2
- package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.js +0 -2
- package/src/client/InMemoryThreadList.ts +0 -247
- package/src/legacy-runtime/cloud/auiV0.ts +0 -331
- package/src/legacy-runtime/cloud/useCloudThreadListRuntime.ts +0 -31
- package/src/legacy-runtime/runtime-cores/assistant-transport/commandQueue.ts +0 -68
- package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.test.ts +0 -455
- package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.ts +0 -172
- package/src/legacy-runtime/runtime-cores/assistant-transport/runManager.ts +0 -113
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-delivery.test.tsx +0 -79
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-parentid.test.tsx +0 -97
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-scheduling.test.ts +0 -307
- package/src/legacy-runtime/runtime-cores/assistant-transport/types.ts +0 -170
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransport.spec.md +0 -141
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.test.tsx +0 -585
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +0 -476
- package/src/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.ts +0 -20
- package/src/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.ts +0 -9
- package/src/legacy-runtime/runtime-cores/assistant-transport/utils.ts +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLiveCompletionAdapter.js","names":["useCallback","useEffect","useMemo","useRef","useState","Unstable_TriggerAdapter","Unstable_TriggerItem","Unstable_UseLiveCompletionAdapterOptions","fetcher","query","Promise","debounceMs","enabled","NO_QUERY","unstable_useLiveCompletionAdapter","options","adapter","isLoading","state","setState","items","failed","setIsLoading","fetcherRef","current","timerRef","ReturnType","setTimeout","tokenRef","pendingQueryRef","retryableQueryRef","pendingRetryQueryRef","cancelTimer","clearTimeout","rearmPendingRetry","scheduleFetch","token","then","invalidatePending","s","categories","categoryItems","search","queueMicrotask"],"sources":["../../src/unstable/useLiveCompletionAdapter.ts"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type {\n Unstable_TriggerAdapter,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\n\nexport type Unstable_UseLiveCompletionAdapterOptions = {\n /**\n * Fetches the items for a query from an async source. Called debounced; the\n * resolved items are cached and returned synchronously to the popover on the\n * next render.\n */\n readonly fetcher: (query: string) => Promise<readonly Unstable_TriggerItem[]>;\n /** Debounce applied before a fetch fires, in milliseconds. @default 60 */\n readonly debounceMs?: number | undefined;\n /** When `false`, no fetch is scheduled and the adapter stays empty. @default true */\n readonly enabled?: boolean | undefined;\n};\n\n/** Sentinel that no real query (including the empty string) equals, so the first query always fetches. */\nconst NO_QUERY = \"\\u0000\";\n\n/**\n * @deprecated Under active development and may change without notice.\n *\n * Bridges an async completion source (a server search, a gateway RPC) into the\n * synchronous `Unstable_TriggerAdapter` that `ComposerTriggerPopover` consumes.\n * `search(query)` returns the last fetched items synchronously and schedules a\n * debounced fetch when the query changes; when results arrive the returned\n * `adapter` identity changes, which re-runs the popover's lookup so the fresh\n * items render. This is a search-only adapter (`categories` are empty).\n *\n * `isLoading` is `true` while a fetch is in flight. Pass it to the popover's\n * `isLoading` prop to render a loading state.\n *\n * @example\n * ```tsx\n * const mentions = unstable_useLiveCompletionAdapter({\n * fetcher: (query) => searchUsers(query),\n * });\n *\n * <ComposerTriggerPopover\n * char=\"@\"\n * adapter={mentions.adapter}\n * isLoading={mentions.isLoading}\n * directive={{ onInserted }}\n * />\n * ```\n */\nexport function unstable_useLiveCompletionAdapter(\n options: Unstable_UseLiveCompletionAdapterOptions,\n): { adapter: Unstable_TriggerAdapter; isLoading: boolean } {\n const { fetcher, debounceMs = 60, enabled = true } = options;\n\n const [state, setState] = useState<{\n query: string;\n items: readonly Unstable_TriggerItem[];\n failed: boolean;\n }>({ query: NO_QUERY, items: [], failed: false });\n const [isLoading, setIsLoading] = useState(false);\n\n const fetcherRef = useRef(fetcher);\n fetcherRef.current = fetcher;\n\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const tokenRef = useRef(0);\n const pendingQueryRef = useRef<string | null>(null);\n const retryableQueryRef = useRef<string | null>(null);\n const pendingRetryQueryRef = useRef<string | null>(null);\n\n const cancelTimer = useCallback(() => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }, []);\n\n const rearmPendingRetry = useCallback(() => {\n const query = pendingRetryQueryRef.current;\n if (query === null) return;\n retryableQueryRef.current = query;\n pendingRetryQueryRef.current = null;\n }, []);\n\n const scheduleFetch = useCallback(\n (query: string) => {\n if (!enabled) return;\n if (pendingQueryRef.current === query) return;\n rearmPendingRetry();\n if (retryableQueryRef.current === query) {\n retryableQueryRef.current = null;\n pendingRetryQueryRef.current = query;\n }\n pendingQueryRef.current = query;\n cancelTimer();\n const token = ++tokenRef.current;\n setIsLoading(true);\n timerRef.current = setTimeout(() => {\n timerRef.current = null;\n fetcherRef.current(query).then(\n (items) => {\n if (token !== tokenRef.current) return;\n pendingRetryQueryRef.current = null;\n setState({ query, items, failed: false });\n setIsLoading(false);\n },\n () => {\n if (token !== tokenRef.current) return;\n pendingQueryRef.current = null;\n pendingRetryQueryRef.current = null;\n setState({ query, items: [], failed: true });\n setIsLoading(false);\n },\n );\n }, debounceMs);\n },\n [enabled, debounceMs, cancelTimer, rearmPendingRetry],\n );\n\n const invalidatePending = useCallback(() => {\n rearmPendingRetry();\n cancelTimer();\n pendingQueryRef.current = null;\n tokenRef.current += 1;\n setIsLoading(false);\n }, [cancelTimer, rearmPendingRetry]);\n\n useEffect(() => {\n if (enabled) return;\n invalidatePending();\n setState((s) =>\n s.query === NO_QUERY ? s : { query: NO_QUERY, items: [], failed: false },\n );\n }, [enabled, invalidatePending]);\n\n useEffect(() => cancelTimer, [cancelTimer]);\n\n // Arm retries only after the failed state commits. Arming during rejection\n // would let the failure render immediately schedule another request.\n useEffect(() => {\n retryableQueryRef.current = state.failed ? state.query : null;\n }, [state]);\n\n const adapter = useMemo<Unstable_TriggerAdapter>(\n () => ({\n categories: () => [],\n categoryItems: () => [],\n search: (query: string) => {\n // search() runs inside the popover's render; defer state updates with\n // queueMicrotask so they are not dispatched while another component renders.\n if (query !== state.query || retryableQueryRef.current === query) {\n queueMicrotask(() => scheduleFetch(query));\n } else if (\n pendingQueryRef.current !== null &&\n pendingQueryRef.current !== query\n ) {\n // the query returned to a cached value while a fetch for a different\n // query is in flight; drop it so its result cannot overwrite the cache\n queueMicrotask(invalidatePending);\n }\n return state.items;\n },\n }),\n [state, scheduleFetch, invalidatePending],\n );\n\n return { adapter, isLoading };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useLiveCompletionAdapter.js","names":["useCallback","useEffect","useLayoutEffect","useMemo","useRef","useState","Unstable_TriggerAdapter","Unstable_TriggerItem","Unstable_UseLiveCompletionAdapterOptions","fetcher","query","Promise","cacheKey","debounceMs","enabled","NO_QUERY","unstable_useLiveCompletionAdapter","options","adapter","isLoading","state","setState","items","failed","setIsLoading","fetcherRef","current","timerRef","ReturnType","setTimeout","tokenRef","pendingQueryRef","retryableQueryRef","pendingRetryQueryRef","cancelTimer","clearTimeout","rearmPendingRetry","scheduleFetch","token","resolve","then","invalidatePending","cacheKeyRef","s","categories","categoryItems","search","queueMicrotask"],"sources":["../../src/unstable/useLiveCompletionAdapter.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type {\n Unstable_TriggerAdapter,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\n\nexport type Unstable_UseLiveCompletionAdapterOptions = {\n /**\n * Fetches the items for a query from an async source. Called debounced; the\n * resolved items are cached and returned synchronously to the popover on the\n * next render.\n */\n readonly fetcher: (query: string) => Promise<readonly Unstable_TriggerItem[]>;\n /**\n * Identifies the fetcher's data source. Change this when switching accounts,\n * workspaces, or another boundary that should invalidate cached results.\n */\n readonly cacheKey?: string | number | undefined;\n /** Debounce applied before a fetch fires, in milliseconds. @default 60 */\n readonly debounceMs?: number | undefined;\n /** When `false`, no fetch is scheduled and the adapter stays empty. @default true */\n readonly enabled?: boolean | undefined;\n};\n\n/** Sentinel that no real query (including the empty string) equals, so the first query always fetches. */\nconst NO_QUERY = \"\\u0000\";\n\n/**\n * @deprecated Under active development and may change without notice.\n *\n * Bridges an async completion source (a server search, a gateway RPC) into the\n * synchronous `Unstable_TriggerAdapter` that `ComposerTriggerPopover` consumes.\n * `search(query)` returns the last fetched items synchronously and schedules a\n * debounced fetch when the query changes; when results arrive the returned\n * `adapter` identity changes, which re-runs the popover's lookup so the fresh\n * items render. This is a search-only adapter (`categories` are empty).\n *\n * `isLoading` is `true` while a fetch is in flight. Pass it to the popover's\n * `isLoading` prop to render a loading state.\n *\n * @example\n * ```tsx\n * const mentions = unstable_useLiveCompletionAdapter({\n * fetcher: (query) => searchUsers(query),\n * });\n *\n * <ComposerTriggerPopover\n * char=\"@\"\n * adapter={mentions.adapter}\n * isLoading={mentions.isLoading}\n * directive={{ onInserted }}\n * />\n * ```\n */\nexport function unstable_useLiveCompletionAdapter(\n options: Unstable_UseLiveCompletionAdapterOptions,\n): { adapter: Unstable_TriggerAdapter; isLoading: boolean } {\n const { fetcher, cacheKey, debounceMs = 60, enabled = true } = options;\n\n const [state, setState] = useState<{\n query: string;\n items: readonly Unstable_TriggerItem[];\n failed: boolean;\n }>({ query: NO_QUERY, items: [], failed: false });\n const [isLoading, setIsLoading] = useState(false);\n\n const fetcherRef = useRef(fetcher);\n fetcherRef.current = fetcher;\n\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const tokenRef = useRef(0);\n const pendingQueryRef = useRef<string | null>(null);\n const retryableQueryRef = useRef<string | null>(null);\n const pendingRetryQueryRef = useRef<string | null>(null);\n\n const cancelTimer = useCallback(() => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }, []);\n\n const rearmPendingRetry = useCallback(() => {\n const query = pendingRetryQueryRef.current;\n if (query === null) return;\n retryableQueryRef.current = query;\n pendingRetryQueryRef.current = null;\n }, []);\n\n const scheduleFetch = useCallback(\n (query: string) => {\n if (!enabled) return;\n if (pendingQueryRef.current === query) return;\n rearmPendingRetry();\n if (retryableQueryRef.current === query) {\n retryableQueryRef.current = null;\n pendingRetryQueryRef.current = query;\n }\n pendingQueryRef.current = query;\n cancelTimer();\n const token = ++tokenRef.current;\n setIsLoading(true);\n timerRef.current = setTimeout(() => {\n timerRef.current = null;\n Promise.resolve()\n .then(() => fetcherRef.current(query))\n .then(\n (items) => {\n if (token !== tokenRef.current) return;\n pendingRetryQueryRef.current = null;\n setState({ query, items, failed: false });\n setIsLoading(false);\n },\n () => {\n if (token !== tokenRef.current) return;\n pendingQueryRef.current = null;\n pendingRetryQueryRef.current = null;\n setState({ query, items: [], failed: true });\n setIsLoading(false);\n },\n );\n }, debounceMs);\n },\n [enabled, debounceMs, cancelTimer, rearmPendingRetry],\n );\n\n const invalidatePending = useCallback(() => {\n rearmPendingRetry();\n cancelTimer();\n pendingQueryRef.current = null;\n tokenRef.current += 1;\n setIsLoading(false);\n }, [cancelTimer, rearmPendingRetry]);\n\n const cacheKeyRef = useRef(cacheKey);\n useLayoutEffect(() => {\n if (cacheKeyRef.current === cacheKey) return;\n cacheKeyRef.current = cacheKey;\n invalidatePending();\n retryableQueryRef.current = null;\n pendingRetryQueryRef.current = null;\n setState({ query: NO_QUERY, items: [], failed: false });\n }, [cacheKey, invalidatePending]);\n\n useEffect(() => {\n if (enabled) return;\n invalidatePending();\n setState((s) =>\n s.query === NO_QUERY ? s : { query: NO_QUERY, items: [], failed: false },\n );\n }, [enabled, invalidatePending]);\n\n useEffect(() => cancelTimer, [cancelTimer]);\n\n // Arm retries only after the failed state commits. Arming during rejection\n // would let the failure render immediately schedule another request.\n useEffect(() => {\n retryableQueryRef.current = state.failed ? state.query : null;\n }, [state]);\n\n const adapter = useMemo<Unstable_TriggerAdapter>(\n () => ({\n categories: () => [],\n categoryItems: () => [],\n search: (query: string) => {\n // search() runs inside the popover's render; defer state updates with\n // queueMicrotask so they are not dispatched while another component renders.\n if (query !== state.query || retryableQueryRef.current === query) {\n queueMicrotask(() => scheduleFetch(query));\n } else if (\n pendingQueryRef.current !== null &&\n pendingQueryRef.current !== query\n ) {\n // the query returned to a cached value while a fetch for a different\n // query is in flight; drop it so its result cannot overwrite the cache\n queueMicrotask(invalidatePending);\n }\n return state.items;\n },\n }),\n [state, scheduleFetch, invalidatePending],\n );\n\n return { adapter, isLoading };\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Unstable_DirectiveFormatter, Unstable_TriggerAdapter, Unstable_TriggerItem } from "@assistant-ui/core";
|
|
2
|
-
import { ReadonlyJSONObject } from "assistant-stream/utils";
|
|
3
2
|
import { FC } from "react";
|
|
3
|
+
import { ReadonlyJSONObject } from "assistant-stream/utils";
|
|
4
4
|
//#region src/unstable/useMentionAdapter.d.ts
|
|
5
5
|
/** Icon component shape consumed by `ComposerTriggerPopover`'s `iconMap`. */
|
|
6
6
|
type Unstable_IconComponent = FC<{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { createContextStoreHook } from "../../context/react/utils/createContextStoreHook.js";
|
|
3
|
-
import { useAui } from "@assistant-ui/store";
|
|
4
3
|
import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
|
|
4
|
+
import { useAui } from "@assistant-ui/store";
|
|
5
5
|
import { createContext, forwardRef, useContext, useState } from "@assistant-ui/tap/react-shim";
|
|
6
6
|
import { create } from "zustand";
|
|
7
7
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useAuiState } from "@assistant-ui/store";
|
|
2
1
|
import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
|
|
2
|
+
import { useAuiState } from "@assistant-ui/store";
|
|
3
3
|
import { getPartialJsonObjectFieldState } from "assistant-stream/utils";
|
|
4
4
|
//#region src/utils/useToolArgsFieldStatus.ts
|
|
5
5
|
const COMPLETE_STATUS = { type: "complete" };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.15",
|
|
4
4
|
"description": "Open-source TypeScript/React library for building production-grade AI chat experiences",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
],
|
|
56
56
|
"sideEffects": false,
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@assistant-ui/core": "^0.3.
|
|
59
|
-
"@assistant-ui/store": "^0.3.
|
|
60
|
-
"@assistant-ui/tap": "^0.9.
|
|
58
|
+
"@assistant-ui/core": "^0.3.14",
|
|
59
|
+
"@assistant-ui/store": "^0.3.10",
|
|
60
|
+
"@assistant-ui/tap": "^0.9.13",
|
|
61
61
|
"@radix-ui/primitive": "^1.1.7",
|
|
62
62
|
"@radix-ui/react-collection": "^1.1.15",
|
|
63
63
|
"@radix-ui/react-compose-refs": "^1.1.5",
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
"@radix-ui/react-use-callback-ref": "^1.1.4",
|
|
67
67
|
"@radix-ui/react-use-controllable-state": "^1.2.6",
|
|
68
68
|
"@radix-ui/react-use-escape-keydown": "^1.1.5",
|
|
69
|
-
"assistant-cloud": "^0.1.
|
|
70
|
-
"assistant-stream": "^0.3.
|
|
69
|
+
"assistant-cloud": "^0.1.41",
|
|
70
|
+
"assistant-stream": "^0.3.38",
|
|
71
71
|
"radix-ui": "^1.6.7",
|
|
72
72
|
"react-textarea-autosize": "^8.5.9",
|
|
73
73
|
"safe-content-frame": "^0.0.27",
|
|
@@ -91,15 +91,15 @@
|
|
|
91
91
|
"devDependencies": {
|
|
92
92
|
"@testing-library/react": "^16.3.2",
|
|
93
93
|
"@types/json-schema": "^7.0.15",
|
|
94
|
-
"@types/node": "^26.
|
|
94
|
+
"@types/node": "^26.2.0",
|
|
95
95
|
"@types/react": "^19.2.18",
|
|
96
96
|
"@types/react-dom": "^19.2.4",
|
|
97
97
|
"jsdom": "^30.0.1",
|
|
98
98
|
"react": "^19.2.8",
|
|
99
99
|
"react-dom": "^19.2.8",
|
|
100
100
|
"vitest": "^4.1.10",
|
|
101
|
-
"@assistant-ui/vite": "0.0.
|
|
102
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
101
|
+
"@assistant-ui/vite": "0.0.14",
|
|
102
|
+
"@assistant-ui/x-buildutils": "0.0.23"
|
|
103
103
|
},
|
|
104
104
|
"publishConfig": {
|
|
105
105
|
"access": "public",
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
AssistantRuntime,
|
|
5
|
+
UserCommands as CoreUserCommands,
|
|
6
|
+
UserExternalState as CoreUserExternalState,
|
|
7
|
+
} from "@assistant-ui/core";
|
|
8
|
+
import {
|
|
9
|
+
useAssistantTransportRuntime as useCoreAssistantTransportRuntime,
|
|
10
|
+
useAssistantTransportSendCommand as useCoreAssistantTransportSendCommand,
|
|
11
|
+
useAssistantTransportState as useCoreAssistantTransportState,
|
|
12
|
+
} from "@assistant-ui/core/react";
|
|
13
|
+
import type {
|
|
14
|
+
AssistantTransportCommand as CoreAssistantTransportCommand,
|
|
15
|
+
AssistantTransportConnectionMetadata as CoreAssistantTransportConnectionMetadata,
|
|
16
|
+
AssistantTransportOptions as CoreAssistantTransportOptions,
|
|
17
|
+
AssistantTransportProtocol,
|
|
18
|
+
SendCommandsRequestBody as CoreSendCommandsRequestBody,
|
|
19
|
+
} from "@assistant-ui/core/react";
|
|
20
|
+
import type { UserCommands, UserExternalState } from "./augmentations";
|
|
21
|
+
|
|
22
|
+
export type { AssistantTransportProtocol };
|
|
23
|
+
|
|
24
|
+
export type SendCommandsRequestBody = Omit<
|
|
25
|
+
CoreSendCommandsRequestBody,
|
|
26
|
+
"commands"
|
|
27
|
+
> & {
|
|
28
|
+
commands: AssistantTransportCommand[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type AssistantTransportCommand =
|
|
32
|
+
| Exclude<CoreAssistantTransportCommand, CoreUserCommands>
|
|
33
|
+
| UserCommands;
|
|
34
|
+
|
|
35
|
+
export type AssistantTransportConnectionMetadata = Omit<
|
|
36
|
+
CoreAssistantTransportConnectionMetadata,
|
|
37
|
+
"pendingCommands"
|
|
38
|
+
> & {
|
|
39
|
+
pendingCommands: AssistantTransportCommand[];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type AssistantTransportStateConverter<T> = (
|
|
43
|
+
state: T,
|
|
44
|
+
connectionMetadata: AssistantTransportConnectionMetadata,
|
|
45
|
+
) => ReturnType<CoreAssistantTransportOptions<T>["converter"]>;
|
|
46
|
+
|
|
47
|
+
export type AssistantTransportOptions<T> = Omit<
|
|
48
|
+
CoreAssistantTransportOptions<T>,
|
|
49
|
+
"converter" | "prepareSendCommandsRequest" | "onError" | "onCancel"
|
|
50
|
+
> & {
|
|
51
|
+
converter: AssistantTransportStateConverter<T>;
|
|
52
|
+
prepareSendCommandsRequest?: (
|
|
53
|
+
body: SendCommandsRequestBody,
|
|
54
|
+
) => Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
55
|
+
onError?: (
|
|
56
|
+
error: Error,
|
|
57
|
+
params: {
|
|
58
|
+
commands: AssistantTransportCommand[];
|
|
59
|
+
updateState: (updater: (state: T) => T) => void;
|
|
60
|
+
},
|
|
61
|
+
) => void | Promise<void>;
|
|
62
|
+
onCancel?: (params: {
|
|
63
|
+
commands: AssistantTransportCommand[];
|
|
64
|
+
updateState: (updater: (state: T) => T) => void;
|
|
65
|
+
error?: Error;
|
|
66
|
+
}) => void;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const useAssistantTransportRuntime = <T>(
|
|
70
|
+
options: AssistantTransportOptions<T>,
|
|
71
|
+
): AssistantRuntime => {
|
|
72
|
+
return useCoreAssistantTransportRuntime(
|
|
73
|
+
options as CoreAssistantTransportOptions<T>,
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const useAssistantTransportSendCommand = () => {
|
|
78
|
+
const send = useCoreAssistantTransportSendCommand();
|
|
79
|
+
return (command: AssistantTransportCommand) => {
|
|
80
|
+
send(command as CoreAssistantTransportCommand);
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export function useAssistantTransportState(): UserExternalState;
|
|
85
|
+
export function useAssistantTransportState<T>(
|
|
86
|
+
selector: (state: UserExternalState) => T,
|
|
87
|
+
): T;
|
|
88
|
+
export function useAssistantTransportState<T>(
|
|
89
|
+
selector: (state: UserExternalState) => T = (state) => state as T,
|
|
90
|
+
): T | UserExternalState {
|
|
91
|
+
return useCoreAssistantTransportState(
|
|
92
|
+
selector as (state: CoreUserExternalState) => T,
|
|
93
|
+
);
|
|
94
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -43,7 +43,7 @@ export type {
|
|
|
43
43
|
ThreadListItemState,
|
|
44
44
|
} from "@assistant-ui/core";
|
|
45
45
|
|
|
46
|
-
export { useCloudThreadListRuntime } from "
|
|
46
|
+
export { useCloudThreadListRuntime } from "@assistant-ui/core/react";
|
|
47
47
|
export { AssistantCloud } from "assistant-cloud";
|
|
48
48
|
|
|
49
49
|
// --- adapters/attachment ---
|
|
@@ -107,13 +107,13 @@ export {
|
|
|
107
107
|
useAssistantTransportRuntime,
|
|
108
108
|
useAssistantTransportSendCommand,
|
|
109
109
|
useAssistantTransportState,
|
|
110
|
-
} from "./
|
|
110
|
+
} from "./assistant-transport";
|
|
111
111
|
export type {
|
|
112
112
|
AssistantTransportConnectionMetadata,
|
|
113
113
|
AssistantTransportCommand,
|
|
114
114
|
AssistantTransportProtocol,
|
|
115
115
|
SendCommandsRequestBody,
|
|
116
|
-
} from "./
|
|
116
|
+
} from "./assistant-transport";
|
|
117
117
|
|
|
118
118
|
// --- core ---
|
|
119
119
|
export type {
|
|
@@ -430,7 +430,11 @@ export {
|
|
|
430
430
|
export {
|
|
431
431
|
InMemoryThreadList,
|
|
432
432
|
type InMemoryThreadListProps,
|
|
433
|
-
} from "
|
|
433
|
+
} from "@assistant-ui/core/store";
|
|
434
|
+
export {
|
|
435
|
+
RemoteThreadList,
|
|
436
|
+
type RemoteThreadListProps,
|
|
437
|
+
} from "@assistant-ui/core/store";
|
|
434
438
|
export { SingleThreadList } from "@assistant-ui/core/store";
|
|
435
439
|
|
|
436
440
|
export * as INTERNAL from "./internal";
|
|
@@ -41,9 +41,15 @@ describe("McpAppsRemoteHost", () => {
|
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
it("posts serverId params verbatim for tool calls and resource loads", async () => {
|
|
44
|
-
const fetch = vi.fn(async () =>
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
const fetch = vi.fn(async (_input, init) => {
|
|
45
|
+
const request = JSON.parse(String(init?.body)) as { method: string };
|
|
46
|
+
return request.method === "mcp-apps/read-resource"
|
|
47
|
+
? Response.json({
|
|
48
|
+
uri: "ui://example/search",
|
|
49
|
+
html: "<main>Search</main>",
|
|
50
|
+
})
|
|
51
|
+
: Response.json({ content: [{ type: "text", text: "ok" }] });
|
|
52
|
+
}) as unknown as typeof globalThis.fetch;
|
|
47
53
|
const root = mount(fetch);
|
|
48
54
|
|
|
49
55
|
try {
|
|
@@ -123,4 +129,24 @@ describe("McpAppsRemoteHost", () => {
|
|
|
123
129
|
root.unmount();
|
|
124
130
|
}
|
|
125
131
|
});
|
|
132
|
+
|
|
133
|
+
it.each([{}, { uri: " ", html: "" }])(
|
|
134
|
+
"rejects malformed successful resource responses",
|
|
135
|
+
async (response) => {
|
|
136
|
+
const fetch = vi.fn(async () =>
|
|
137
|
+
Response.json(response),
|
|
138
|
+
) as unknown as typeof globalThis.fetch;
|
|
139
|
+
const root = mount(fetch);
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
await expect(
|
|
143
|
+
root.getValue().loadResource({ uri: "ui://example/search" }),
|
|
144
|
+
).rejects.toThrow(
|
|
145
|
+
/Invalid MCP App host response "mcp-apps\/read-resource" from "\/api\/mcp-apps"/,
|
|
146
|
+
);
|
|
147
|
+
} finally {
|
|
148
|
+
root.unmount();
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
);
|
|
126
152
|
});
|
|
@@ -39,6 +39,33 @@ const readErrorBody = async (res: Response): Promise<string | undefined> => {
|
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
+
const invalidMcpAppResource = (options: McpAppsRemoteHostOptions): never => {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Invalid MCP App host response "mcp-apps/read-resource" from "${options.url}": expected a resource with non-empty string "uri" and "html" fields`,
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const parseMcpAppResource = (
|
|
49
|
+
value: unknown,
|
|
50
|
+
options: McpAppsRemoteHostOptions,
|
|
51
|
+
): McpAppResource => {
|
|
52
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
53
|
+
return invalidMcpAppResource(options);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const resource = value as Record<string, unknown>;
|
|
57
|
+
if (
|
|
58
|
+
typeof resource.uri !== "string" ||
|
|
59
|
+
resource.uri.trim() === "" ||
|
|
60
|
+
typeof resource.html !== "string" ||
|
|
61
|
+
resource.html.trim() === ""
|
|
62
|
+
) {
|
|
63
|
+
return invalidMcpAppResource(options);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return value as McpAppResource;
|
|
67
|
+
};
|
|
68
|
+
|
|
42
69
|
async function postToHost(
|
|
43
70
|
options: McpAppsRemoteHostOptions,
|
|
44
71
|
method: string,
|
|
@@ -63,7 +90,14 @@ async function postToHost(
|
|
|
63
90
|
`MCP App host request "${method}" to "${options.url}" failed with ${status}${body ? `: ${body}` : ""}`,
|
|
64
91
|
);
|
|
65
92
|
}
|
|
66
|
-
|
|
93
|
+
try {
|
|
94
|
+
return await res.json();
|
|
95
|
+
} catch (cause) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Invalid MCP App host response "${method}" from "${options.url}": expected valid JSON`,
|
|
98
|
+
{ cause },
|
|
99
|
+
);
|
|
100
|
+
}
|
|
67
101
|
}
|
|
68
102
|
|
|
69
103
|
/**
|
|
@@ -91,12 +125,13 @@ const useMcpAppsRemoteHost = (
|
|
|
91
125
|
};
|
|
92
126
|
};
|
|
93
127
|
return {
|
|
94
|
-
loadResource: (params) =>
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
"mcp-apps/read-resource",
|
|
98
|
-
|
|
99
|
-
)
|
|
128
|
+
loadResource: async (params) => {
|
|
129
|
+
const options = getCurrentOptions();
|
|
130
|
+
return parseMcpAppResource(
|
|
131
|
+
await postToHost(options, "mcp-apps/read-resource", params),
|
|
132
|
+
options,
|
|
133
|
+
);
|
|
134
|
+
},
|
|
100
135
|
callTool: (params) =>
|
|
101
136
|
postToHost(getCurrentOptions(), "tools/call", params),
|
|
102
137
|
readResource: (params) =>
|
|
@@ -124,6 +124,53 @@ describe("createMcpAppBridge", () => {
|
|
|
124
124
|
bridge.dispose();
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
+
it.each([
|
|
128
|
+
[
|
|
129
|
+
"throws",
|
|
130
|
+
() => {
|
|
131
|
+
throw new Error("error callback failed");
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
["rejects", () => Promise.reject(new Error("error callback failed"))],
|
|
135
|
+
])(
|
|
136
|
+
"returns an error response when onError %s",
|
|
137
|
+
async (_behavior, onErrorCallback) => {
|
|
138
|
+
const { frame, captured } = makeFrame();
|
|
139
|
+
const callTool = vi.fn().mockRejectedValue(new Error("tool failed"));
|
|
140
|
+
const consoleError = vi
|
|
141
|
+
.spyOn(console, "error")
|
|
142
|
+
.mockImplementation(() => {});
|
|
143
|
+
const onError = vi.fn(onErrorCallback);
|
|
144
|
+
const bridge = createMcpAppBridge({
|
|
145
|
+
frame,
|
|
146
|
+
handlers: { callTool, onError },
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
deliver(bridge, {
|
|
150
|
+
jsonrpc: "2.0",
|
|
151
|
+
id: 8,
|
|
152
|
+
method: "tools/call",
|
|
153
|
+
params: { name: "search" },
|
|
154
|
+
});
|
|
155
|
+
await flush();
|
|
156
|
+
|
|
157
|
+
expect(onError).toHaveBeenCalledWith(new Error("tool failed"));
|
|
158
|
+
expect(captured).toEqual([
|
|
159
|
+
{
|
|
160
|
+
jsonrpc: "2.0",
|
|
161
|
+
id: 8,
|
|
162
|
+
error: { code: -32603, message: "tool failed" },
|
|
163
|
+
},
|
|
164
|
+
]);
|
|
165
|
+
expect(consoleError).toHaveBeenCalledWith(
|
|
166
|
+
"[assistant-ui] MCP App onError callback threw an error",
|
|
167
|
+
new Error("error callback failed"),
|
|
168
|
+
);
|
|
169
|
+
consoleError.mockRestore();
|
|
170
|
+
bridge.dispose();
|
|
171
|
+
},
|
|
172
|
+
);
|
|
173
|
+
|
|
127
174
|
it("rejects tools/call for disallowed tool with -32602", async () => {
|
|
128
175
|
const { frame, captured } = makeFrame();
|
|
129
176
|
const callTool = vi.fn();
|
|
@@ -244,6 +291,60 @@ describe("createMcpAppBridge", () => {
|
|
|
244
291
|
bridge.dispose();
|
|
245
292
|
});
|
|
246
293
|
|
|
294
|
+
it("isolates throwing notification handlers and reports their errors", () => {
|
|
295
|
+
const { frame, captured } = makeFrame();
|
|
296
|
+
const initializedError = new Error("initialized callback failed");
|
|
297
|
+
const sizeChangeError = new Error("size change callback failed");
|
|
298
|
+
const teardownError = new Error("teardown callback failed");
|
|
299
|
+
const onError = vi.fn();
|
|
300
|
+
const bridge = createMcpAppBridge({
|
|
301
|
+
frame,
|
|
302
|
+
handlers: {
|
|
303
|
+
onInitialized: () => {
|
|
304
|
+
throw initializedError;
|
|
305
|
+
},
|
|
306
|
+
onSizeChange: () => {
|
|
307
|
+
throw sizeChangeError;
|
|
308
|
+
},
|
|
309
|
+
onLog: () => {
|
|
310
|
+
throw "log callback failed";
|
|
311
|
+
},
|
|
312
|
+
onRequestTeardown: () => {
|
|
313
|
+
throw teardownError;
|
|
314
|
+
},
|
|
315
|
+
onError,
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
expect(() => {
|
|
320
|
+
deliver(bridge, {
|
|
321
|
+
jsonrpc: "2.0",
|
|
322
|
+
method: "notifications/initialized",
|
|
323
|
+
});
|
|
324
|
+
deliver(bridge, {
|
|
325
|
+
jsonrpc: "2.0",
|
|
326
|
+
method: "notifications/size_changed",
|
|
327
|
+
});
|
|
328
|
+
deliver(bridge, {
|
|
329
|
+
jsonrpc: "2.0",
|
|
330
|
+
method: "notifications/log",
|
|
331
|
+
});
|
|
332
|
+
deliver(bridge, {
|
|
333
|
+
jsonrpc: "2.0",
|
|
334
|
+
method: "notifications/request_teardown",
|
|
335
|
+
});
|
|
336
|
+
}).not.toThrow();
|
|
337
|
+
|
|
338
|
+
expect(onError.mock.calls.map(([error]) => error)).toEqual([
|
|
339
|
+
initializedError,
|
|
340
|
+
sizeChangeError,
|
|
341
|
+
new Error("log callback failed"),
|
|
342
|
+
teardownError,
|
|
343
|
+
]);
|
|
344
|
+
expect(captured).toEqual([]);
|
|
345
|
+
bridge.dispose();
|
|
346
|
+
});
|
|
347
|
+
|
|
247
348
|
it("notifyToolInput / notifyToolResult / notifyHostContextChanged post both legacy and 2026-01-26 notifications", () => {
|
|
248
349
|
const { frame, captured } = makeFrame();
|
|
249
350
|
const bridge = createMcpAppBridge({ frame });
|
|
@@ -428,4 +529,35 @@ describe("createMcpAppBridge", () => {
|
|
|
428
529
|
expect(onRequestTeardown).toHaveBeenCalledWith({ reason: "done" });
|
|
429
530
|
bridge.dispose();
|
|
430
531
|
});
|
|
532
|
+
|
|
533
|
+
it("isolates a throwing onError from widget error notifications", () => {
|
|
534
|
+
const { frame } = makeFrame();
|
|
535
|
+
const callbackError = new Error("error callback failed");
|
|
536
|
+
const consoleError = vi
|
|
537
|
+
.spyOn(console, "error")
|
|
538
|
+
.mockImplementation(() => {});
|
|
539
|
+
const onError = vi.fn(() => {
|
|
540
|
+
throw callbackError;
|
|
541
|
+
});
|
|
542
|
+
const bridge = createMcpAppBridge({
|
|
543
|
+
frame,
|
|
544
|
+
handlers: { callTool: vi.fn(), onError },
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
expect(() =>
|
|
548
|
+
deliver(bridge, {
|
|
549
|
+
jsonrpc: "2.0",
|
|
550
|
+
method: "notifications/error",
|
|
551
|
+
params: { message: "kaboom" },
|
|
552
|
+
}),
|
|
553
|
+
).not.toThrow();
|
|
554
|
+
|
|
555
|
+
expect(onError).toHaveBeenCalledWith(new Error("kaboom"));
|
|
556
|
+
expect(consoleError).toHaveBeenCalledWith(
|
|
557
|
+
"[assistant-ui] MCP App onError callback threw an error",
|
|
558
|
+
callbackError,
|
|
559
|
+
);
|
|
560
|
+
consoleError.mockRestore();
|
|
561
|
+
bridge.dispose();
|
|
562
|
+
});
|
|
431
563
|
});
|
package/src/mcp-apps/bridge.ts
CHANGED
|
@@ -123,6 +123,23 @@ export function createMcpAppBridge(
|
|
|
123
123
|
});
|
|
124
124
|
};
|
|
125
125
|
|
|
126
|
+
const reportErrorCallbackFailure = (error: unknown) => {
|
|
127
|
+
console.error(
|
|
128
|
+
"[assistant-ui] MCP App onError callback threw an error",
|
|
129
|
+
error,
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const reportError = (error: Error) => {
|
|
134
|
+
try {
|
|
135
|
+
void Promise.resolve(handlers.onError?.(error)).catch(
|
|
136
|
+
reportErrorCallbackFailure,
|
|
137
|
+
);
|
|
138
|
+
} catch (callbackError) {
|
|
139
|
+
reportErrorCallbackFailure(callbackError);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
126
143
|
const handleRequest = async (req: McpAppJsonRpcRequest) => {
|
|
127
144
|
try {
|
|
128
145
|
const params = req.params;
|
|
@@ -376,42 +393,49 @@ export function createMcpAppBridge(
|
|
|
376
393
|
}
|
|
377
394
|
} catch (err) {
|
|
378
395
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
379
|
-
|
|
396
|
+
reportError(error);
|
|
380
397
|
errorResponse(req.id, JSONRPC_ERROR.internalError, error.message);
|
|
381
398
|
}
|
|
382
399
|
};
|
|
383
400
|
|
|
384
401
|
const handleNotification = (note: McpAppJsonRpcNotification) => {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
402
|
+
try {
|
|
403
|
+
switch (normalizeMethod(note.method)) {
|
|
404
|
+
case "notifications/initialized": {
|
|
405
|
+
handlers.onInitialized?.();
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
case "notifications/size_changed": {
|
|
409
|
+
const p = (note.params ?? {}) as { width?: number; height?: number };
|
|
410
|
+
handlers.onSizeChange?.({
|
|
411
|
+
...(typeof p.width === "number" ? { width: p.width } : {}),
|
|
412
|
+
...(typeof p.height === "number" ? { height: p.height } : {}),
|
|
413
|
+
});
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
case "notifications/log": {
|
|
417
|
+
handlers.onLog?.(note.params);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
case "notifications/request_teardown": {
|
|
421
|
+
handlers.onRequestTeardown?.(note.params);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
case "notifications/error": {
|
|
425
|
+
const p = (note.params ?? {}) as { message?: string };
|
|
426
|
+
reportError(
|
|
427
|
+
new Error(
|
|
428
|
+
typeof p.message === "string" ? p.message : "Widget error",
|
|
429
|
+
),
|
|
430
|
+
);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
default:
|
|
434
|
+
return;
|
|
412
435
|
}
|
|
413
|
-
|
|
414
|
-
|
|
436
|
+
} catch (err) {
|
|
437
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
438
|
+
reportError(error);
|
|
415
439
|
}
|
|
416
440
|
};
|
|
417
441
|
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import { forwardRef } from "react";
|
|
4
4
|
import type { ActionButtonProps } from "../../utils/createActionButton";
|
|
5
|
-
import { useEscapeKeydown } from "@radix-ui/react-use-escape-keydown";
|
|
6
5
|
import { Primitive } from "../../utils/Primitive";
|
|
7
6
|
import { composeEventHandlers } from "@radix-ui/primitive";
|
|
8
7
|
import { useActionBarStopSpeaking as useActionBarStopSpeakingBehavior } from "@assistant-ui/core/react";
|
|
@@ -24,14 +23,6 @@ export const ActionBarPrimitiveStopSpeaking = forwardRef<
|
|
|
24
23
|
>((props, ref) => {
|
|
25
24
|
const callback = useActionBarStopSpeaking();
|
|
26
25
|
|
|
27
|
-
// TODO this stops working if the user is not hovering over an older message
|
|
28
|
-
useEscapeKeydown((e) => {
|
|
29
|
-
if (callback) {
|
|
30
|
-
e.preventDefault();
|
|
31
|
-
callback();
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
|
|
35
26
|
return (
|
|
36
27
|
<Primitive.button
|
|
37
28
|
type="button"
|
|
@@ -346,7 +346,7 @@ export const ComposerPrimitiveInput = forwardRef<
|
|
|
346
346
|
)
|
|
347
347
|
return undefined;
|
|
348
348
|
|
|
349
|
-
return aui.on("
|
|
349
|
+
return aui.on("threads.selectionChanged", focus);
|
|
350
350
|
}, [unstable_focusOnThreadSwitched, focus, aui]);
|
|
351
351
|
|
|
352
352
|
useEffect(() => {
|