@assistant-ui/core 0.2.21 → 0.2.23
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/adapters/attachment.d.ts.map +1 -1
- package/dist/adapters/speech.d.ts.map +1 -1
- package/dist/adapters/speech.js +9 -2
- package/dist/adapters/speech.js.map +1 -1
- package/dist/adapters/thread-history.d.ts +8 -0
- package/dist/adapters/thread-history.d.ts.map +1 -1
- package/dist/adapters/voice.d.ts.map +1 -1
- package/dist/adapters/voice.js +13 -5
- package/dist/adapters/voice.js.map +1 -1
- package/dist/internal.d.ts +5 -2
- package/dist/internal.js +5 -2
- package/dist/model-context/frame/host.d.ts +1 -0
- package/dist/model-context/frame/host.d.ts.map +1 -1
- package/dist/model-context/frame/host.js +41 -21
- package/dist/model-context/frame/host.js.map +1 -1
- package/dist/model-context/frame/provider.d.ts.map +1 -1
- package/dist/model-context/frame/provider.js +1 -0
- package/dist/model-context/frame/provider.js.map +1 -1
- package/dist/model-context/interactable-composer-metadata.js.map +1 -1
- package/dist/model-context/types.d.ts.map +1 -1
- package/dist/model-context/types.js +13 -4
- package/dist/model-context/types.js.map +1 -1
- package/dist/react/AssistantProvider.js.map +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.js +78 -50
- package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
- package/dist/react/client/Interactables.js.map +1 -1
- package/dist/react/client/Tools.d.ts.map +1 -1
- package/dist/react/client/interactable-model-context.d.ts.map +1 -1
- package/dist/react/client/interactable-model-context.js +1 -0
- package/dist/react/client/interactable-model-context.js.map +1 -1
- package/dist/react/interactables-legacy/useInteractableState.js +1 -0
- package/dist/react/interactables-legacy/useInteractableState.js.map +1 -1
- package/dist/react/model-context/useInteractable.js.map +1 -1
- package/dist/react/model-context/useInteractableState.js +1 -0
- package/dist/react/model-context/useInteractableState.js.map +1 -1
- package/dist/react/model-context/useInteractableVersions.js +1 -0
- package/dist/react/model-context/useInteractableVersions.js.map +1 -1
- package/dist/react/model-context/useToolArgsStatus.js +1 -0
- package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarCopy.js +14 -13
- package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarEdit.js +1 -0
- package/dist/react/primitive-hooks/useActionBarEdit.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarFeedback.js +1 -0
- package/dist/react/primitive-hooks/useActionBarFeedback.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarReload.js +1 -0
- package/dist/react/primitive-hooks/useActionBarReload.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarSpeak.js +1 -0
- package/dist/react/primitive-hooks/useActionBarSpeak.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarStopSpeaking.js +1 -0
- package/dist/react/primitive-hooks/useActionBarStopSpeaking.js.map +1 -1
- package/dist/react/primitive-hooks/useBranchPickerNext.js +1 -0
- package/dist/react/primitive-hooks/useBranchPickerNext.js.map +1 -1
- package/dist/react/primitive-hooks/useBranchPickerPrevious.js +1 -0
- package/dist/react/primitive-hooks/useBranchPickerPrevious.js.map +1 -1
- package/dist/react/primitive-hooks/useComposerAddAttachment.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useComposerAddAttachment.js +1 -0
- package/dist/react/primitive-hooks/useComposerAddAttachment.js.map +1 -1
- package/dist/react/primitive-hooks/useComposerCancel.js +1 -0
- package/dist/react/primitive-hooks/useComposerCancel.js.map +1 -1
- package/dist/react/primitive-hooks/useComposerDictate.js +1 -0
- package/dist/react/primitive-hooks/useComposerDictate.js.map +1 -1
- package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useComposerSend.js +1 -0
- package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
- package/dist/react/primitive-hooks/useEditComposerCancel.js +1 -0
- package/dist/react/primitive-hooks/useEditComposerCancel.js.map +1 -1
- package/dist/react/primitive-hooks/useEditComposerSend.js +1 -0
- package/dist/react/primitive-hooks/useEditComposerSend.js.map +1 -1
- package/dist/react/primitive-hooks/useMessageBranching.js +1 -0
- package/dist/react/primitive-hooks/useMessageBranching.js.map +1 -1
- package/dist/react/primitive-hooks/useMessageReload.js +1 -0
- package/dist/react/primitive-hooks/useMessageReload.js.map +1 -1
- package/dist/react/primitive-hooks/useSuggestionTrigger.js +1 -0
- package/dist/react/primitive-hooks/useSuggestionTrigger.js.map +1 -1
- package/dist/react/primitive-hooks/useThreadListItemArchive.js +1 -0
- package/dist/react/primitive-hooks/useThreadListItemArchive.js.map +1 -1
- package/dist/react/primitive-hooks/useThreadListItemDelete.js +1 -0
- package/dist/react/primitive-hooks/useThreadListItemDelete.js.map +1 -1
- package/dist/react/primitive-hooks/useThreadListItemTrigger.js +1 -0
- package/dist/react/primitive-hooks/useThreadListItemTrigger.js.map +1 -1
- package/dist/react/primitive-hooks/useThreadListItemUnarchive.js +1 -0
- package/dist/react/primitive-hooks/useThreadListItemUnarchive.js.map +1 -1
- package/dist/react/primitive-hooks/useThreadListLoadMore.js +1 -0
- package/dist/react/primitive-hooks/useThreadListLoadMore.js.map +1 -1
- package/dist/react/primitive-hooks/useThreadListNew.js +1 -0
- package/dist/react/primitive-hooks/useThreadListNew.js.map +1 -1
- package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.d.ts.map +1 -1
- package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js +1 -1
- package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js.map +1 -1
- package/dist/react/primitives/composer/ComposerAttachments.js.map +1 -1
- package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
- package/dist/react/primitives/composer/ComposerQueue.js.map +1 -1
- package/dist/react/primitives/generativeUI/GenerativeUI.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
- package/dist/react/primitives/message/MessageParts.d.ts +2 -1
- package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageParts.js +46 -36
- package/dist/react/primitives/message/MessageParts.js.map +1 -1
- package/dist/react/primitives/part/PartMessages.js.map +1 -1
- package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
- package/dist/react/primitives/thread/ThreadSuggestions.js.map +1 -1
- package/dist/react/primitives/threadList/ThreadListItems.js.map +1 -1
- package/dist/react/providers/ChainOfThoughtPartByIndexProvider.d.ts.map +1 -1
- package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js +18 -30
- package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js.map +1 -1
- package/dist/react/providers/PartByIndexProvider.d.ts.map +1 -1
- package/dist/react/providers/PartByIndexProvider.js +18 -30
- package/dist/react/providers/PartByIndexProvider.js.map +1 -1
- package/dist/react/providers/TextMessagePartProvider.js +1 -0
- package/dist/react/providers/TextMessagePartProvider.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +1 -0
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +9 -2
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +55 -12
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +31 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +12 -2
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts +1 -1
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +3 -2
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/auiV0.d.ts +12 -1
- package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/auiV0.js +2 -1
- package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +75 -73
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
- package/dist/react/runtimes/createMessageConverter.d.ts.map +1 -1
- package/dist/react/runtimes/external-message-converter.js +2 -2
- package/dist/react/runtimes/external-message-converter.js.map +1 -1
- package/dist/react/runtimes/useExternalStoreSharedOptions.js +1 -0
- package/dist/react/runtimes/useExternalStoreSharedOptions.js.map +1 -1
- package/dist/react/runtimes/useLocalRuntime.d.ts.map +1 -1
- package/dist/react/runtimes/useLocalRuntime.js +9 -1
- package/dist/react/runtimes/useLocalRuntime.js.map +1 -1
- package/dist/runtime/api/assistant-runtime.d.ts +1 -1
- package/dist/runtime/api/assistant-runtime.d.ts.map +1 -1
- package/dist/runtime/api/assistant-runtime.js +1 -1
- package/dist/runtime/api/assistant-runtime.js.map +1 -1
- package/dist/runtime/api/attachment-runtime.d.ts +1 -1
- package/dist/runtime/api/attachment-runtime.d.ts.map +1 -1
- package/dist/runtime/api/attachment-runtime.js +1 -1
- package/dist/runtime/api/attachment-runtime.js.map +1 -1
- package/dist/runtime/api/composer-runtime.d.ts +2 -2
- package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
- package/dist/runtime/api/composer-runtime.js +2 -2
- package/dist/runtime/api/composer-runtime.js.map +1 -1
- package/dist/runtime/api/message-part-runtime.d.ts +4 -4
- package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
- package/dist/runtime/api/message-part-runtime.js +3 -3
- package/dist/runtime/api/message-part-runtime.js.map +1 -1
- package/dist/runtime/api/message-runtime.d.ts +1 -1
- package/dist/runtime/api/message-runtime.d.ts.map +1 -1
- package/dist/runtime/api/message-runtime.js +6 -6
- package/dist/runtime/api/message-runtime.js.map +1 -1
- package/dist/runtime/api/thread-list-item-runtime.d.ts +1 -1
- package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-list-item-runtime.js +4 -3
- package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
- package/dist/runtime/api/thread-list-runtime.d.ts +1 -1
- package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-list-runtime.js +1 -1
- package/dist/runtime/api/thread-list-runtime.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts +4 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js +41 -14
- package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.js +3 -2
- package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.d.ts +2 -2
- package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.js +6 -4
- package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.js +3 -3
- package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
- package/dist/runtime/utils/message-repository.d.ts.map +1 -1
- package/dist/runtime/utils/message-repository.js +4 -2
- package/dist/runtime/utils/message-repository.js.map +1 -1
- package/dist/runtime/utils/thread-message-like.js +4 -1
- package/dist/runtime/utils/thread-message-like.js.map +1 -1
- package/dist/runtimes/external-store/external-store-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js +12 -0
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/internal.d.ts +2 -1
- package/dist/runtimes/internal.js +2 -1
- package/dist/runtimes/local/local-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts +3 -0
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +49 -15
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/optimistic-state.d.ts +4 -4
- package/dist/runtimes/remote-thread-list/optimistic-state.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/optimistic-state.js +22 -9
- package/dist/runtimes/remote-thread-list/optimistic-state.js.map +1 -1
- package/dist/runtimes/remote-thread-list/types.d.ts +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +32 -27
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +3 -2
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
- package/dist/store/clients/chain-of-thought-client.d.ts.map +1 -1
- package/dist/store/clients/no-op-composer-client.d.ts.map +1 -1
- package/dist/store/clients/no-op-composer-client.js +1 -0
- package/dist/store/clients/no-op-composer-client.js.map +1 -1
- package/dist/store/runtime-clients/attachment-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/message-part-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-list-runtime-client.js +1 -0
- package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.js +17 -15
- package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
- package/dist/store/scopes/thread.d.ts +1 -0
- package/dist/store/scopes/thread.d.ts.map +1 -1
- package/dist/subscribable/subscribable.d.ts +4 -4
- package/dist/subscribable/subscribable.d.ts.map +1 -1
- package/dist/subscribable/subscribable.js +14 -6
- package/dist/subscribable/subscribable.js.map +1 -1
- package/dist/tests/remote-thread-list-test-helpers.d.ts +2 -1
- package/dist/tests/remote-thread-list-test-helpers.d.ts.map +1 -1
- package/dist/tests/remote-thread-list-test-helpers.js +5 -2
- package/dist/tests/remote-thread-list-test-helpers.js.map +1 -1
- package/dist/types/attachment.d.ts +2 -1
- package/dist/types/attachment.d.ts.map +1 -1
- package/dist/types/attachment.js +6 -0
- package/dist/types/attachment.js.map +1 -0
- package/dist/types/message.d.ts +1 -0
- package/dist/types/message.d.ts.map +1 -1
- package/dist/types/message.js.map +1 -1
- package/dist/utils/data-url.d.ts +9 -0
- package/dist/utils/data-url.d.ts.map +1 -0
- package/dist/utils/data-url.js +14 -0
- package/dist/utils/data-url.js.map +1 -0
- package/dist/utils/notify-event-listeners.d.ts +6 -0
- package/dist/utils/notify-event-listeners.d.ts.map +1 -0
- package/dist/utils/notify-event-listeners.js +16 -0
- package/dist/utils/notify-event-listeners.js.map +1 -0
- package/package.json +10 -9
- package/src/adapters/speech.test.ts +88 -0
- package/src/adapters/speech.ts +17 -2
- package/src/adapters/thread-history.ts +8 -0
- package/src/adapters/voice.test.ts +130 -0
- package/src/adapters/voice.ts +26 -6
- package/src/internal.ts +6 -1
- package/src/model-context/frame/host.test.ts +147 -0
- package/src/model-context/frame/host.ts +56 -19
- package/src/model-context/frame/provider.test.ts +83 -0
- package/src/model-context/frame/provider.ts +1 -0
- package/src/model-context/types.ts +16 -6
- package/src/react/adapters/LocalStorageThreadListAdapter.test.ts +82 -0
- package/src/react/adapters/LocalStorageThreadListAdapter.tsx +108 -58
- package/src/react/primitive-hooks/useActionBarCopy.test.ts +79 -0
- package/src/react/primitive-hooks/useActionBarCopy.ts +4 -3
- package/src/react/primitives/chainOfThought/ChainOfThoughtParts.tsx +1 -3
- package/src/react/primitives/message/MessageParts.tsx +48 -35
- package/src/react/providers/ChainOfThoughtPartByIndexProvider.tsx +19 -2
- package/src/react/providers/PartByIndexProvider.tsx +16 -2
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +18 -0
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +1 -0
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +68 -13
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +34 -0
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +69 -0
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +27 -6
- package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.test.ts +16 -0
- package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +7 -2
- package/src/react/runtimes/cloud/auiV0.ts +14 -0
- package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +90 -88
- package/src/react/runtimes/external-message-converter.test.ts +346 -0
- package/src/react/runtimes/external-message-converter.test.tsx +21 -0
- package/src/react/runtimes/external-message-converter.ts +2 -2
- package/src/react/runtimes/useLocalRuntime.test.tsx +99 -0
- package/src/react/runtimes/useLocalRuntime.ts +13 -1
- package/src/runtime/api/assistant-runtime.ts +4 -1
- package/src/runtime/api/attachment-runtime.ts +8 -2
- package/src/runtime/api/composer-runtime.ts +9 -5
- package/src/runtime/api/message-part-runtime.ts +10 -3
- package/src/runtime/api/message-runtime.test.ts +81 -0
- package/src/runtime/api/message-runtime.ts +11 -8
- package/src/runtime/api/thread-list-item-runtime.ts +13 -3
- package/src/runtime/api/thread-list-runtime.ts +10 -2
- package/src/runtime/base/base-composer-runtime-core.ts +77 -31
- package/src/runtime/base/base-thread-runtime-core.ts +7 -2
- package/src/runtime/base/default-edit-composer-runtime-core.ts +18 -4
- package/src/runtime/base/default-thread-composer-runtime-core.ts +13 -3
- package/src/runtime/utils/message-repository.ts +19 -13
- package/src/runtime/utils/thread-message-like.ts +1 -0
- package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +4 -1
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +15 -0
- package/src/runtimes/internal.ts +3 -0
- package/src/runtimes/local/local-thread-runtime-core.test.ts +746 -1
- package/src/runtimes/local/local-thread-runtime-core.ts +89 -23
- package/src/runtimes/local/should-continue.test.ts +122 -0
- package/src/runtimes/remote-thread-list/optimistic-state.test.ts +63 -0
- package/src/runtimes/remote-thread-list/optimistic-state.ts +49 -17
- package/src/runtimes/remote-thread-list/types.ts +1 -1
- package/src/runtimes/tool-invocations/EDGE_CASES.md +13 -19
- package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +128 -8
- package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +42 -34
- package/src/store/clients/model-context-client.test.ts +21 -1
- package/src/store/runtime-clients/thread-runtime-client.ts +2 -1
- package/src/store/scopes/thread.ts +1 -0
- package/src/subscribable/subscribable.test.ts +58 -0
- package/src/subscribable/subscribable.ts +29 -11
- package/src/tests/MessageRepository.test.ts +12 -0
- package/src/tests/OptimisticState-list-race.test.ts +33 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-switchToThread-order.test.ts +223 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-switchToThread-position.test.ts +121 -0
- package/src/tests/auiV0Encode.test.ts +74 -0
- package/src/tests/base-composer-runtime-core-addAttachment.test.ts +44 -0
- package/src/tests/base-composer-runtime-core-send.test.ts +433 -5
- package/src/tests/base-composer-runtime-core.test.ts +422 -0
- package/src/tests/default-edit-composer-runtime-core.test.ts +43 -5
- package/src/tests/event-subscription-listener-errors.test.ts +205 -0
- package/src/tests/external-store-thread-list-runtime-core.test.ts +6 -6
- package/src/tests/external-store-thread-runtime-core-adapter.test.ts +575 -0
- package/src/tests/external-store-thread-runtime-core.test.ts +55 -1
- package/src/tests/remote-thread-list-test-helpers.ts +14 -7
- package/src/tests/thread-message-like.test.ts +57 -0
- package/src/types/attachment.ts +5 -0
- package/src/types/message.ts +1 -0
- package/src/utils/data-url.test.ts +50 -0
- package/src/utils/data-url.ts +9 -0
- package/src/utils/notify-event-listeners.ts +30 -0
- package/src/tests/OptimisticState-delete-crash.test.ts +0 -208
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.23",
|
|
4
4
|
"description": "Framework-agnostic core runtime for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"assistant",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
],
|
|
60
60
|
"sideEffects": false,
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"assistant-stream": "^0.3.
|
|
62
|
+
"assistant-stream": "^0.3.28",
|
|
63
63
|
"nanoid": "^6.0.0"
|
|
64
64
|
},
|
|
65
65
|
"optionalDevDependencies": {
|
|
@@ -91,15 +91,15 @@
|
|
|
91
91
|
"@testing-library/react": "^16.3.2",
|
|
92
92
|
"@types/react": "^19.2.17",
|
|
93
93
|
"jsdom": "^29.1.1",
|
|
94
|
-
"react": "^19.2.
|
|
95
|
-
"react-dom": "^19.2.
|
|
94
|
+
"react": "^19.2.8",
|
|
95
|
+
"react-dom": "^19.2.8",
|
|
96
96
|
"vitest": "^4.1.10",
|
|
97
97
|
"zustand": "^5.0.14",
|
|
98
|
-
"@assistant-ui/store": "0.2.
|
|
99
|
-
"@assistant-ui/tap": "0.9.
|
|
100
|
-
"@assistant-ui/vite": "0.0.
|
|
101
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
102
|
-
"assistant-cloud": "0.1.
|
|
98
|
+
"@assistant-ui/store": "0.2.22",
|
|
99
|
+
"@assistant-ui/tap": "0.9.6",
|
|
100
|
+
"@assistant-ui/vite": "0.0.11",
|
|
101
|
+
"@assistant-ui/x-buildutils": "0.0.20",
|
|
102
|
+
"assistant-cloud": "0.1.37"
|
|
103
103
|
},
|
|
104
104
|
"publishConfig": {
|
|
105
105
|
"access": "public",
|
|
@@ -117,6 +117,7 @@
|
|
|
117
117
|
"scripts": {
|
|
118
118
|
"build": "aui-build",
|
|
119
119
|
"test": "vitest run",
|
|
120
|
+
"test:react-compiler": "vitest run --config vitest.react-compiler.config.ts",
|
|
120
121
|
"test:watch": "vitest"
|
|
121
122
|
}
|
|
122
123
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { WebSpeechSynthesisAdapter } from "./speech";
|
|
3
|
+
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
vi.restoreAllMocks();
|
|
6
|
+
vi.unstubAllGlobals();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
describe("WebSpeechSynthesisAdapter", () => {
|
|
10
|
+
it("isolates a late subscriber that throws after the utterance ended", async () => {
|
|
11
|
+
const listeners = new Map<string, EventListener>();
|
|
12
|
+
class MockSpeechSynthesisUtterance {
|
|
13
|
+
addEventListener(type: string, listener: EventListener) {
|
|
14
|
+
listeners.set(type, listener);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
vi.stubGlobal("SpeechSynthesisUtterance", MockSpeechSynthesisUtterance);
|
|
18
|
+
vi.stubGlobal("window", {
|
|
19
|
+
speechSynthesis: {
|
|
20
|
+
speak: vi.fn(),
|
|
21
|
+
cancel: vi.fn(),
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
const listenerError = new Error("late listener failed");
|
|
25
|
+
const consoleError = vi
|
|
26
|
+
.spyOn(console, "error")
|
|
27
|
+
.mockImplementation(() => {});
|
|
28
|
+
|
|
29
|
+
const result = new WebSpeechSynthesisAdapter().speak("Hello");
|
|
30
|
+
listeners.get("end")!(new Event("end"));
|
|
31
|
+
|
|
32
|
+
const lateListener = vi.fn();
|
|
33
|
+
result.subscribe(() => {
|
|
34
|
+
throw listenerError;
|
|
35
|
+
});
|
|
36
|
+
result.subscribe(lateListener);
|
|
37
|
+
await Promise.resolve();
|
|
38
|
+
|
|
39
|
+
expect(consoleError).toHaveBeenCalledWith(
|
|
40
|
+
"[assistant-ui] Speech synthesis listener threw an error",
|
|
41
|
+
listenerError,
|
|
42
|
+
);
|
|
43
|
+
expect(lateListener).toHaveBeenCalledOnce();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("continues notifying listeners when one throws", () => {
|
|
47
|
+
const listeners = new Map<string, EventListener>();
|
|
48
|
+
class MockSpeechSynthesisUtterance {
|
|
49
|
+
addEventListener(type: string, listener: EventListener) {
|
|
50
|
+
listeners.set(type, listener);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const speak = vi.fn();
|
|
54
|
+
vi.stubGlobal("SpeechSynthesisUtterance", MockSpeechSynthesisUtterance);
|
|
55
|
+
vi.stubGlobal("window", {
|
|
56
|
+
speechSynthesis: {
|
|
57
|
+
speak,
|
|
58
|
+
cancel: vi.fn(),
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
const listenerError = new Error("listener failed");
|
|
62
|
+
const consoleError = vi
|
|
63
|
+
.spyOn(console, "error")
|
|
64
|
+
.mockImplementation(() => {});
|
|
65
|
+
const result = new WebSpeechSynthesisAdapter().speak("Hello");
|
|
66
|
+
const laterListener = vi.fn();
|
|
67
|
+
|
|
68
|
+
result.subscribe(() => {
|
|
69
|
+
throw listenerError;
|
|
70
|
+
});
|
|
71
|
+
result.subscribe(laterListener);
|
|
72
|
+
listeners.get("end")?.({} as Event);
|
|
73
|
+
|
|
74
|
+
expect(speak).toHaveBeenCalledWith(
|
|
75
|
+
expect.any(MockSpeechSynthesisUtterance),
|
|
76
|
+
);
|
|
77
|
+
expect(laterListener).toHaveBeenCalledOnce();
|
|
78
|
+
expect(result.status).toEqual({
|
|
79
|
+
type: "ended",
|
|
80
|
+
reason: "finished",
|
|
81
|
+
error: undefined,
|
|
82
|
+
});
|
|
83
|
+
expect(consoleError).toHaveBeenCalledWith(
|
|
84
|
+
"[assistant-ui] Speech synthesis listener threw an error",
|
|
85
|
+
listenerError,
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
});
|
package/src/adapters/speech.ts
CHANGED
|
@@ -22,6 +22,21 @@ export type SpeechSynthesisAdapter = {
|
|
|
22
22
|
speak: (text: string) => SpeechSynthesisAdapter.Utterance;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
const notifySpeechSynthesisListeners = (
|
|
26
|
+
listeners: Iterable<() => void>,
|
|
27
|
+
): void => {
|
|
28
|
+
for (const listener of listeners) {
|
|
29
|
+
try {
|
|
30
|
+
listener();
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error(
|
|
33
|
+
"[assistant-ui] Speech synthesis listener threw an error",
|
|
34
|
+
error,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
25
40
|
export namespace DictationAdapter {
|
|
26
41
|
export type Status =
|
|
27
42
|
| {
|
|
@@ -64,7 +79,7 @@ export class WebSpeechSynthesisAdapter implements SpeechSynthesisAdapter {
|
|
|
64
79
|
if (res.status.type === "ended") return;
|
|
65
80
|
|
|
66
81
|
res.status = { type: "ended", reason, error };
|
|
67
|
-
subscribers
|
|
82
|
+
notifySpeechSynthesisListeners(subscribers);
|
|
68
83
|
};
|
|
69
84
|
|
|
70
85
|
utterance.addEventListener("end", () => handleEnd("finished"));
|
|
@@ -82,7 +97,7 @@ export class WebSpeechSynthesisAdapter implements SpeechSynthesisAdapter {
|
|
|
82
97
|
if (res.status.type === "ended") {
|
|
83
98
|
let cancelled = false;
|
|
84
99
|
queueMicrotask(() => {
|
|
85
|
-
if (!cancelled) callback
|
|
100
|
+
if (!cancelled) notifySpeechSynthesisListeners([callback]);
|
|
86
101
|
});
|
|
87
102
|
return () => {
|
|
88
103
|
cancelled = true;
|
|
@@ -65,6 +65,14 @@ export type ThreadHistoryAdapter = {
|
|
|
65
65
|
options: ChatModelRunOptions,
|
|
66
66
|
): AsyncGenerator<ChatModelRunResult, void, unknown>;
|
|
67
67
|
append(item: ExportedMessageRepositoryItem): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Rewrites a previously appended message in place, keyed by its message id.
|
|
70
|
+
* Adapters that implement this let a runtime persist a run paused for tool
|
|
71
|
+
* approval and finalize the same message once the run resumes. An update may
|
|
72
|
+
* arrive for an id whose earlier write failed; treat it as an upsert keyed
|
|
73
|
+
* on the message id rather than assuming the entry exists.
|
|
74
|
+
*/
|
|
75
|
+
update?(item: ExportedMessageRepositoryItem): Promise<void>;
|
|
68
76
|
delete?(items: ExportedMessageRepositoryItem[]): Promise<void>;
|
|
69
77
|
/** Required when used with `useAISDKRuntime` / `useChatRuntime`. */
|
|
70
78
|
withFormat?<TMessage, TStorageFormat extends Record<string, unknown>>(
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
createVoiceSession,
|
|
4
|
+
type VoiceSessionControls,
|
|
5
|
+
type VoiceSessionHelpers,
|
|
6
|
+
} from "./voice";
|
|
7
|
+
|
|
8
|
+
const createTestSession = () => {
|
|
9
|
+
let helpers: VoiceSessionHelpers | undefined;
|
|
10
|
+
const session = createVoiceSession({}, async (sessionHelpers) => {
|
|
11
|
+
helpers = sessionHelpers;
|
|
12
|
+
return {
|
|
13
|
+
disconnect: vi.fn(),
|
|
14
|
+
mute: vi.fn(),
|
|
15
|
+
unmute: vi.fn(),
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (!helpers) throw new Error("Voice session setup did not start");
|
|
20
|
+
return { helpers, session };
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const createPendingTestSession = () => {
|
|
24
|
+
let resolveControls!: (controls: VoiceSessionControls) => void;
|
|
25
|
+
const controlsPromise = new Promise<VoiceSessionControls>((resolve) => {
|
|
26
|
+
resolveControls = resolve;
|
|
27
|
+
});
|
|
28
|
+
const controls = {
|
|
29
|
+
disconnect: vi.fn(),
|
|
30
|
+
mute: vi.fn(),
|
|
31
|
+
unmute: vi.fn(),
|
|
32
|
+
};
|
|
33
|
+
const session = createVoiceSession({}, () => controlsPromise);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
controls,
|
|
37
|
+
controlsPromise,
|
|
38
|
+
resolveControls: () => resolveControls(controls),
|
|
39
|
+
session,
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
vi.restoreAllMocks();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("createVoiceSession", () => {
|
|
48
|
+
it("applies mute requested while setup is pending", async () => {
|
|
49
|
+
const { controls, controlsPromise, resolveControls, session } =
|
|
50
|
+
createPendingTestSession();
|
|
51
|
+
|
|
52
|
+
session.mute();
|
|
53
|
+
|
|
54
|
+
expect(session.isMuted).toBe(true);
|
|
55
|
+
expect(controls.mute).not.toHaveBeenCalled();
|
|
56
|
+
|
|
57
|
+
resolveControls();
|
|
58
|
+
await controlsPromise;
|
|
59
|
+
|
|
60
|
+
expect(controls.mute).toHaveBeenCalledOnce();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("does not apply stale mute after unmuting during setup", async () => {
|
|
64
|
+
const { controls, controlsPromise, resolveControls, session } =
|
|
65
|
+
createPendingTestSession();
|
|
66
|
+
|
|
67
|
+
session.mute();
|
|
68
|
+
session.unmute();
|
|
69
|
+
resolveControls();
|
|
70
|
+
await controlsPromise;
|
|
71
|
+
|
|
72
|
+
expect(session.isMuted).toBe(false);
|
|
73
|
+
expect(controls.mute).not.toHaveBeenCalled();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("continues notifying listeners when one throws", () => {
|
|
77
|
+
const listenerError = new Error("listener failed");
|
|
78
|
+
const consoleError = vi
|
|
79
|
+
.spyOn(console, "error")
|
|
80
|
+
.mockImplementation(() => {});
|
|
81
|
+
const { helpers, session } = createTestSession();
|
|
82
|
+
|
|
83
|
+
const transcriptListener = vi.fn();
|
|
84
|
+
session.onTranscript(() => {
|
|
85
|
+
throw listenerError;
|
|
86
|
+
});
|
|
87
|
+
session.onTranscript(transcriptListener);
|
|
88
|
+
|
|
89
|
+
const modeListener = vi.fn();
|
|
90
|
+
session.onModeChange(() => {
|
|
91
|
+
throw listenerError;
|
|
92
|
+
});
|
|
93
|
+
session.onModeChange(modeListener);
|
|
94
|
+
|
|
95
|
+
const volumeListener = vi.fn();
|
|
96
|
+
session.onVolumeChange(() => {
|
|
97
|
+
throw listenerError;
|
|
98
|
+
});
|
|
99
|
+
session.onVolumeChange(volumeListener);
|
|
100
|
+
|
|
101
|
+
const statusListener = vi.fn();
|
|
102
|
+
session.onStatusChange(() => {
|
|
103
|
+
throw listenerError;
|
|
104
|
+
});
|
|
105
|
+
session.onStatusChange(statusListener);
|
|
106
|
+
|
|
107
|
+
helpers.emitTranscript({ role: "assistant", text: "hello" });
|
|
108
|
+
helpers.emitMode("speaking");
|
|
109
|
+
helpers.emitVolume(0.5);
|
|
110
|
+
helpers.end("finished");
|
|
111
|
+
|
|
112
|
+
expect(transcriptListener).toHaveBeenCalledWith({
|
|
113
|
+
role: "assistant",
|
|
114
|
+
text: "hello",
|
|
115
|
+
});
|
|
116
|
+
expect(modeListener).toHaveBeenCalledWith("speaking");
|
|
117
|
+
expect(volumeListener).toHaveBeenCalledWith(0.5);
|
|
118
|
+
expect(statusListener).toHaveBeenCalledWith({
|
|
119
|
+
type: "ended",
|
|
120
|
+
reason: "finished",
|
|
121
|
+
error: undefined,
|
|
122
|
+
});
|
|
123
|
+
expect(helpers.isDisposed()).toBe(true);
|
|
124
|
+
expect(consoleError).toHaveBeenCalledTimes(4);
|
|
125
|
+
expect(consoleError).toHaveBeenCalledWith(
|
|
126
|
+
"[assistant-ui] Voice session listener threw an error",
|
|
127
|
+
listenerError,
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
});
|
package/src/adapters/voice.ts
CHANGED
|
@@ -57,6 +57,22 @@ export type VoiceSessionHelpers = {
|
|
|
57
57
|
isDisposed: () => boolean;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
const notifyListeners = <T>(
|
|
61
|
+
listeners: ReadonlySet<(value: T) => void>,
|
|
62
|
+
value: T,
|
|
63
|
+
) => {
|
|
64
|
+
for (const listener of listeners) {
|
|
65
|
+
try {
|
|
66
|
+
listener(value);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error(
|
|
69
|
+
"[assistant-ui] Voice session listener threw an error",
|
|
70
|
+
error,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
60
76
|
export function createVoiceSession(
|
|
61
77
|
options: { abortSignal?: AbortSignal },
|
|
62
78
|
setup: (helpers: VoiceSessionHelpers) => Promise<VoiceSessionControls>,
|
|
@@ -85,25 +101,25 @@ export function createVoiceSession(
|
|
|
85
101
|
setStatus: (status) => {
|
|
86
102
|
if (disposed) return;
|
|
87
103
|
currentStatus = status;
|
|
88
|
-
|
|
104
|
+
notifyListeners(statusCbs, status);
|
|
89
105
|
},
|
|
90
106
|
end: (reason, error?) => {
|
|
91
107
|
if (disposed) return;
|
|
92
108
|
currentStatus = { type: "ended", reason, error };
|
|
93
|
-
|
|
109
|
+
notifyListeners(statusCbs, currentStatus);
|
|
94
110
|
cleanup();
|
|
95
111
|
},
|
|
96
112
|
emitTranscript: (item) => {
|
|
97
113
|
if (disposed) return;
|
|
98
|
-
|
|
114
|
+
notifyListeners(transcriptCbs, item);
|
|
99
115
|
},
|
|
100
116
|
emitMode: (mode) => {
|
|
101
117
|
if (disposed) return;
|
|
102
|
-
|
|
118
|
+
notifyListeners(modeCbs, mode);
|
|
103
119
|
},
|
|
104
120
|
emitVolume: (volume) => {
|
|
105
121
|
if (disposed) return;
|
|
106
|
-
|
|
122
|
+
notifyListeners(volumeCbs, volume);
|
|
107
123
|
},
|
|
108
124
|
isDisposed: () => disposed,
|
|
109
125
|
};
|
|
@@ -155,7 +171,11 @@ export function createVoiceSession(
|
|
|
155
171
|
try {
|
|
156
172
|
if (disposed) return;
|
|
157
173
|
controls = await setup(helpers);
|
|
158
|
-
if (disposed)
|
|
174
|
+
if (disposed) {
|
|
175
|
+
controls.disconnect();
|
|
176
|
+
} else if (isMuted) {
|
|
177
|
+
controls.mute();
|
|
178
|
+
}
|
|
159
179
|
} catch (error) {
|
|
160
180
|
helpers.end("error", error);
|
|
161
181
|
}
|
package/src/internal.ts
CHANGED
|
@@ -40,7 +40,8 @@ export { CompositeContextProvider } from "./utils/composite-context-provider";
|
|
|
40
40
|
|
|
41
41
|
// Shared attachment data-URL encoder, reused by framework adapters so the
|
|
42
42
|
// FileReader fallback lives in one place.
|
|
43
|
-
export { getFileDataURL } from "./adapters/attachment";
|
|
43
|
+
export { getFileDataURL, fileMatchesAccept } from "./adapters/attachment";
|
|
44
|
+
export { isCreateAttachment } from "./types/attachment";
|
|
44
45
|
|
|
45
46
|
// Streaming-stable tool-args stringifier, reused by framework adapters so the
|
|
46
47
|
// key-order stabilization lives in one place.
|
|
@@ -53,6 +54,10 @@ export {
|
|
|
53
54
|
// validation lives in one place.
|
|
54
55
|
export { isJSONValue, isRecord } from "./utils/json/is-json";
|
|
55
56
|
|
|
57
|
+
// Data-URL decoder and http(s) matcher, reused by framework adapters so the
|
|
58
|
+
// outbound part conversion lives in one place.
|
|
59
|
+
export { httpUrlPattern, parseDataUrl } from "./utils/data-url";
|
|
60
|
+
|
|
56
61
|
// Runtime extras helper for external-store adapters. Internal because the
|
|
57
62
|
// tap-native runtime path replaces the `thread.extras` side-channel it wraps.
|
|
58
63
|
export {
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { AssistantFrameHost } from "./host";
|
|
3
|
+
import { type FrameMessage, FRAME_MESSAGE_CHANNEL } from "./types";
|
|
4
|
+
|
|
5
|
+
const executionContext = {
|
|
6
|
+
toolCallId: "tool-call",
|
|
7
|
+
abortSignal: new AbortController().signal,
|
|
8
|
+
human: async () => undefined,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const createHost = () => {
|
|
12
|
+
let handleMessage: ((event: MessageEvent) => void) | undefined;
|
|
13
|
+
const addEventListener = vi.fn(
|
|
14
|
+
(_type: string, listener: EventListenerOrEventListenerObject) => {
|
|
15
|
+
handleMessage = listener as (event: MessageEvent) => void;
|
|
16
|
+
},
|
|
17
|
+
);
|
|
18
|
+
const removeEventListener = vi.fn();
|
|
19
|
+
vi.stubGlobal("window", { addEventListener, removeEventListener });
|
|
20
|
+
|
|
21
|
+
const postMessage = vi.fn();
|
|
22
|
+
const iframeWindow = { postMessage } as unknown as Window;
|
|
23
|
+
const host = new AssistantFrameHost(iframeWindow);
|
|
24
|
+
|
|
25
|
+
const dispatchMessage = (message: FrameMessage) =>
|
|
26
|
+
handleMessage?.({
|
|
27
|
+
source: iframeWindow,
|
|
28
|
+
data: {
|
|
29
|
+
channel: FRAME_MESSAGE_CHANNEL,
|
|
30
|
+
message,
|
|
31
|
+
},
|
|
32
|
+
} as unknown as MessageEvent);
|
|
33
|
+
|
|
34
|
+
dispatchMessage({
|
|
35
|
+
type: "model-context-update",
|
|
36
|
+
context: {
|
|
37
|
+
tools: {
|
|
38
|
+
search: {
|
|
39
|
+
type: "frontend",
|
|
40
|
+
parameters: { type: "object", properties: {} },
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const execute = host.getModelContext().tools?.search?.execute;
|
|
47
|
+
if (!execute) throw new Error("Expected the search tool to be available");
|
|
48
|
+
|
|
49
|
+
return { dispatchMessage, execute, host, postMessage };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
vi.useFakeTimers();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
vi.useRealTimers();
|
|
58
|
+
vi.unstubAllGlobals();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("AssistantFrameHost", () => {
|
|
62
|
+
it("resolves tool calls from frame results", async () => {
|
|
63
|
+
const { dispatchMessage, execute, host } = createHost();
|
|
64
|
+
const result = Promise.resolve(
|
|
65
|
+
execute({ query: "weather" }, executionContext),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
dispatchMessage({
|
|
69
|
+
type: "tool-result",
|
|
70
|
+
id: "tool-0",
|
|
71
|
+
result: "sunny",
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await expect(result).resolves.toBe("sunny");
|
|
75
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
76
|
+
host.dispose();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("rejects pending tool calls when disposed", async () => {
|
|
80
|
+
const { execute, host } = createHost();
|
|
81
|
+
const result = Promise.resolve(execute({}, executionContext));
|
|
82
|
+
|
|
83
|
+
host.dispose();
|
|
84
|
+
|
|
85
|
+
await expect(result).rejects.toThrow(
|
|
86
|
+
"AssistantFrameHost has been disposed",
|
|
87
|
+
);
|
|
88
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("rejects pending tool calls when execution is aborted", async () => {
|
|
92
|
+
const { execute, host } = createHost();
|
|
93
|
+
const abortController = new AbortController();
|
|
94
|
+
const abortError = new Error("Run cancelled");
|
|
95
|
+
abortError.name = "AbortError";
|
|
96
|
+
const result = Promise.resolve(
|
|
97
|
+
execute(
|
|
98
|
+
{},
|
|
99
|
+
{
|
|
100
|
+
...executionContext,
|
|
101
|
+
abortSignal: abortController.signal,
|
|
102
|
+
},
|
|
103
|
+
),
|
|
104
|
+
);
|
|
105
|
+
const onRejected = vi.fn();
|
|
106
|
+
void result.catch(onRejected);
|
|
107
|
+
|
|
108
|
+
abortController.abort(abortError);
|
|
109
|
+
await Promise.resolve();
|
|
110
|
+
|
|
111
|
+
expect(onRejected).toHaveBeenCalledWith(abortError);
|
|
112
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
113
|
+
host.dispose();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("does not post tool calls when execution is already aborted", async () => {
|
|
117
|
+
const { execute, host, postMessage } = createHost();
|
|
118
|
+
const abortController = new AbortController();
|
|
119
|
+
const abortError = new Error("Run cancelled");
|
|
120
|
+
abortError.name = "AbortError";
|
|
121
|
+
abortController.abort(abortError);
|
|
122
|
+
|
|
123
|
+
await expect(
|
|
124
|
+
execute(
|
|
125
|
+
{},
|
|
126
|
+
{
|
|
127
|
+
...executionContext,
|
|
128
|
+
abortSignal: abortController.signal,
|
|
129
|
+
},
|
|
130
|
+
),
|
|
131
|
+
).rejects.toBe(abortError);
|
|
132
|
+
|
|
133
|
+
expect(postMessage).toHaveBeenCalledOnce();
|
|
134
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
135
|
+
host.dispose();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("rejects tool calls made after disposal without posting a request", async () => {
|
|
139
|
+
const { execute, host, postMessage } = createHost();
|
|
140
|
+
host.dispose();
|
|
141
|
+
|
|
142
|
+
await expect(execute({}, executionContext)).rejects.toThrow(
|
|
143
|
+
"AssistantFrameHost has been disposed",
|
|
144
|
+
);
|
|
145
|
+
expect(postMessage).toHaveBeenCalledOnce();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -40,6 +40,13 @@ const deserializeModelContext = (
|
|
|
40
40
|
}),
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
+
const getAbortReason = (signal: AbortSignal): unknown => {
|
|
44
|
+
if (signal.reason !== undefined) return signal.reason;
|
|
45
|
+
const error = new Error("Tool call was aborted");
|
|
46
|
+
error.name = "AbortError";
|
|
47
|
+
return error;
|
|
48
|
+
};
|
|
49
|
+
|
|
43
50
|
export class AssistantFrameHost implements ModelContextProvider {
|
|
44
51
|
private _context: ModelContext = {};
|
|
45
52
|
private _subscribers = new Set<() => void>();
|
|
@@ -53,6 +60,7 @@ export class AssistantFrameHost implements ModelContextProvider {
|
|
|
53
60
|
private _requestCounter = 0;
|
|
54
61
|
private _iframeWindow: Window;
|
|
55
62
|
private _targetOrigin: string;
|
|
63
|
+
private _disposed = false;
|
|
56
64
|
|
|
57
65
|
constructor(iframeWindow: Window, targetOrigin: string = "*") {
|
|
58
66
|
this._iframeWindow = iframeWindow;
|
|
@@ -104,7 +112,8 @@ export class AssistantFrameHost implements ModelContextProvider {
|
|
|
104
112
|
name,
|
|
105
113
|
{
|
|
106
114
|
...tool,
|
|
107
|
-
execute: (args: any
|
|
115
|
+
execute: (args: any, context: { abortSignal: AbortSignal }) =>
|
|
116
|
+
this.callTool(name, args, context.abortSignal),
|
|
108
117
|
} as Tool<any, any>,
|
|
109
118
|
]),
|
|
110
119
|
),
|
|
@@ -112,7 +121,11 @@ export class AssistantFrameHost implements ModelContextProvider {
|
|
|
112
121
|
this.notifySubscribers();
|
|
113
122
|
}
|
|
114
123
|
|
|
115
|
-
private callTool(
|
|
124
|
+
private callTool(
|
|
125
|
+
toolName: string,
|
|
126
|
+
args: any,
|
|
127
|
+
abortSignal: AbortSignal,
|
|
128
|
+
): Promise<any> {
|
|
116
129
|
return this.sendRequest(
|
|
117
130
|
{
|
|
118
131
|
type: "tool-call",
|
|
@@ -122,6 +135,7 @@ export class AssistantFrameHost implements ModelContextProvider {
|
|
|
122
135
|
},
|
|
123
136
|
30000,
|
|
124
137
|
`Tool call "${toolName}" timed out`,
|
|
138
|
+
abortSignal,
|
|
125
139
|
);
|
|
126
140
|
}
|
|
127
141
|
|
|
@@ -129,36 +143,54 @@ export class AssistantFrameHost implements ModelContextProvider {
|
|
|
129
143
|
message: T,
|
|
130
144
|
timeout = 30000,
|
|
131
145
|
timeoutMessage = "Request timed out",
|
|
146
|
+
abortSignal?: AbortSignal,
|
|
132
147
|
): Promise<any> {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
);
|
|
148
|
+
if (this._disposed) {
|
|
149
|
+
return Promise.reject(new Error("AssistantFrameHost has been disposed"));
|
|
150
|
+
}
|
|
151
|
+
if (abortSignal?.aborted) {
|
|
152
|
+
return Promise.reject(getAbortReason(abortSignal));
|
|
153
|
+
}
|
|
140
154
|
|
|
141
|
-
|
|
155
|
+
return new Promise((resolve, reject) => {
|
|
156
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
157
|
+
const onAbort = () => {
|
|
158
|
+
if (!abortSignal) return;
|
|
142
159
|
const pending = this._pendingRequests.get(message.id);
|
|
143
160
|
if (pending) {
|
|
144
|
-
pending.reject(
|
|
161
|
+
pending.reject(getAbortReason(abortSignal));
|
|
145
162
|
this._pendingRequests.delete(message.id);
|
|
146
163
|
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
164
|
+
};
|
|
165
|
+
const cleanup = () => {
|
|
166
|
+
if (timeoutId !== undefined) clearTimeout(timeoutId);
|
|
167
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
168
|
+
};
|
|
151
169
|
|
|
152
170
|
this._pendingRequests.set(message.id, {
|
|
153
171
|
resolve: (value: any) => {
|
|
154
|
-
|
|
155
|
-
|
|
172
|
+
cleanup();
|
|
173
|
+
resolve(value);
|
|
156
174
|
},
|
|
157
175
|
reject: (error: any) => {
|
|
158
|
-
|
|
159
|
-
|
|
176
|
+
cleanup();
|
|
177
|
+
reject(error);
|
|
160
178
|
},
|
|
161
179
|
});
|
|
180
|
+
|
|
181
|
+
timeoutId = setTimeout(() => {
|
|
182
|
+
const pending = this._pendingRequests.get(message.id);
|
|
183
|
+
if (pending) {
|
|
184
|
+
pending.reject(new Error(timeoutMessage));
|
|
185
|
+
this._pendingRequests.delete(message.id);
|
|
186
|
+
}
|
|
187
|
+
}, timeout);
|
|
188
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
189
|
+
|
|
190
|
+
this._iframeWindow.postMessage(
|
|
191
|
+
{ channel: FRAME_MESSAGE_CHANNEL, message },
|
|
192
|
+
this._targetOrigin,
|
|
193
|
+
);
|
|
162
194
|
});
|
|
163
195
|
}
|
|
164
196
|
|
|
@@ -188,8 +220,13 @@ export class AssistantFrameHost implements ModelContextProvider {
|
|
|
188
220
|
}
|
|
189
221
|
|
|
190
222
|
dispose() {
|
|
223
|
+
this._disposed = true;
|
|
191
224
|
window.removeEventListener("message", this.handleMessage);
|
|
192
225
|
this._subscribers.clear();
|
|
226
|
+
const error = new Error("AssistantFrameHost has been disposed");
|
|
227
|
+
for (const pending of this._pendingRequests.values()) {
|
|
228
|
+
pending.reject(error);
|
|
229
|
+
}
|
|
193
230
|
this._pendingRequests.clear();
|
|
194
231
|
}
|
|
195
232
|
}
|