@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
|
@@ -3,7 +3,6 @@ import { describe, expect, it, vi } from "vitest";
|
|
|
3
3
|
import {
|
|
4
4
|
ToolInvocationTracker,
|
|
5
5
|
type ToolExecutionStatus,
|
|
6
|
-
type ToolInvocationTrackerSnapshot,
|
|
7
6
|
} from "./ToolInvocationTracker";
|
|
8
7
|
import type {
|
|
9
8
|
ThreadAssistantMessage,
|
|
@@ -34,7 +33,7 @@ async function waitFor(
|
|
|
34
33
|
const createState = (
|
|
35
34
|
messages: ThreadAssistantMessage[],
|
|
36
35
|
isRunning: boolean = true,
|
|
37
|
-
):
|
|
36
|
+
): ToolInvocationTracker.Snapshot => ({
|
|
38
37
|
messages: messages as readonly ThreadMessage[],
|
|
39
38
|
isRunning,
|
|
40
39
|
});
|
|
@@ -152,6 +151,131 @@ describe("ToolInvocationTracker", () => {
|
|
|
152
151
|
}
|
|
153
152
|
});
|
|
154
153
|
|
|
154
|
+
it("does not auto-submit a parse-error result when divergent argsText closes without a backend result", async () => {
|
|
155
|
+
// A human-in-the-loop tool whose argsText diverges mid-stream and never
|
|
156
|
+
// re-converges, with no backend result at close time. The args stream must
|
|
157
|
+
// not close on the divergent snapshot (which holds a stale prefix the
|
|
158
|
+
// execution path would parse), so no bogus parse-error result is
|
|
159
|
+
// auto-submitted to resume the host graph and abandon the pending
|
|
160
|
+
// interrupt.
|
|
161
|
+
const execute = vi.fn(async () => ({ forecast: "ok" }));
|
|
162
|
+
const streamCall = vi.fn((_reader, { human }) => {
|
|
163
|
+
// Request human input immediately — sets up the pending interrupt.
|
|
164
|
+
void human({ request: "approve" });
|
|
165
|
+
});
|
|
166
|
+
const getTools = () => ({
|
|
167
|
+
weatherSearch: {
|
|
168
|
+
parameters: { type: "object", properties: {} },
|
|
169
|
+
execute,
|
|
170
|
+
streamCall,
|
|
171
|
+
} satisfies Tool,
|
|
172
|
+
});
|
|
173
|
+
const onResult = vi.fn();
|
|
174
|
+
let statuses: Record<string, ToolExecutionStatus> = {};
|
|
175
|
+
const onStatusesChange = (s: ReadonlyMap<string, ToolExecutionStatus>) => {
|
|
176
|
+
statuses = Object.fromEntries(s);
|
|
177
|
+
};
|
|
178
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const tracker = new ToolInvocationTracker(getTools, {
|
|
182
|
+
onResult,
|
|
183
|
+
onStatusesChange,
|
|
184
|
+
});
|
|
185
|
+
tracker.setState(createState([]));
|
|
186
|
+
|
|
187
|
+
tracker.setState(
|
|
188
|
+
createState([
|
|
189
|
+
createAssistantMessage('{"query":"London","longitude":0', {
|
|
190
|
+
query: "London",
|
|
191
|
+
longitude: 0,
|
|
192
|
+
}),
|
|
193
|
+
]),
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
// The pending interrupt is set up via streamCall → human().
|
|
197
|
+
await waitFor(() => {
|
|
198
|
+
expect(statuses["tool-1"]?.type).toBe("interrupt");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Divergent regression (not a prefix of the streamed text).
|
|
202
|
+
tracker.setState(
|
|
203
|
+
createState([
|
|
204
|
+
createAssistantMessage('{"query":"London","longitude":-0.125', {
|
|
205
|
+
query: "London",
|
|
206
|
+
longitude: -0.125,
|
|
207
|
+
}),
|
|
208
|
+
]),
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// Complete valid JSON, divergent from the streamed prefix, no backend
|
|
212
|
+
// result. Previously this closed the args stream on the snapshot's
|
|
213
|
+
// completeness, parsed the stale prefix, and auto-submitted an error.
|
|
214
|
+
tracker.setState(
|
|
215
|
+
createState([
|
|
216
|
+
createAssistantMessage(
|
|
217
|
+
'{"query":"London","longitude":-0.125,"latitude":51.5072}',
|
|
218
|
+
{ query: "London", longitude: -0.125, latitude: 51.5072 },
|
|
219
|
+
),
|
|
220
|
+
]),
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
for (let i = 0; i < 5; i++) {
|
|
224
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// No bogus parse-error result is auto-submitted, so the host graph is
|
|
228
|
+
// not resumed with a fake tool failure and the pending interrupt is
|
|
229
|
+
// preserved.
|
|
230
|
+
expect(onResult).not.toHaveBeenCalled();
|
|
231
|
+
// The frontend execute never ran: the stale prefix was never parsed.
|
|
232
|
+
expect(execute).not.toHaveBeenCalled();
|
|
233
|
+
} finally {
|
|
234
|
+
warnSpy.mockRestore();
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("does not auto-submit a parse-error result for a non-executable tool whose divergent argsText closes", async () => {
|
|
239
|
+
// Same close-gating mismatch as the executable case, but for a tool with
|
|
240
|
+
// no frontend execute. Closing on the divergent complete snapshot would
|
|
241
|
+
// still parse the incomplete stale prefix and fabricate a parse-error
|
|
242
|
+
// result, so the close must gate on the controller's streamed content.
|
|
243
|
+
const getTools = () => ({
|
|
244
|
+
weatherSearch: {
|
|
245
|
+
parameters: { type: "object", properties: {} },
|
|
246
|
+
} satisfies Tool,
|
|
247
|
+
});
|
|
248
|
+
const onResult = vi.fn();
|
|
249
|
+
const onStatusesChange = () => {};
|
|
250
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
const tracker = new ToolInvocationTracker(getTools, {
|
|
254
|
+
onResult,
|
|
255
|
+
onStatusesChange,
|
|
256
|
+
});
|
|
257
|
+
tracker.setState(createState([]));
|
|
258
|
+
|
|
259
|
+
// Incomplete prefix while the run is still streaming.
|
|
260
|
+
tracker.setState(
|
|
261
|
+
createState([createAssistantMessage('{"a":1', { a: 1 })], true),
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
// Run settles and the snapshot regresses to a complete divergent text.
|
|
265
|
+
tracker.setState(
|
|
266
|
+
createState([createAssistantMessage('{"a":2}', { a: 2 })], false),
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
for (let i = 0; i < 5; i++) {
|
|
270
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
expect(onResult).not.toHaveBeenCalled();
|
|
274
|
+
} finally {
|
|
275
|
+
warnSpy.mockRestore();
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
155
279
|
it("clears executing status under the logical toolCallId when reset() lands while execute is pending", async () => {
|
|
156
280
|
// Tests the F.1 lifecycle: reset() aborts in-flight execute() invocations
|
|
157
281
|
// and clears their executing status. The status key is the logical
|
|
@@ -948,12 +1072,8 @@ describe("ToolInvocationTracker", () => {
|
|
|
948
1072
|
// result replacement) and verifying streamCall fires exactly once.
|
|
949
1073
|
//
|
|
950
1074
|
// The pathological mid-stream regression case (A.2) is covered by
|
|
951
|
-
// the
|
|
952
|
-
//
|
|
953
|
-
// assistant-stream's `ToolCallStreamController.setResponse` ordering
|
|
954
|
-
// (parse-failure result reaches the reader before the backend
|
|
955
|
-
// result); that's tracked separately and out of scope for the
|
|
956
|
-
// tracker-level contract.
|
|
1075
|
+
// the assistant-stream ordering regression test in
|
|
1076
|
+
// packages/assistant-stream/src/core/modules/tool-call.test.ts.
|
|
957
1077
|
const streamCall = vi.fn();
|
|
958
1078
|
const execute = vi.fn(async () => ({ forecast: "ok" }));
|
|
959
1079
|
const getTools = () => ({
|
|
@@ -35,34 +35,6 @@ export type AddToolResultCommand = {
|
|
|
35
35
|
readonly modelContent?: readonly ToolModelContentPart[];
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
export type ToolInvocationTrackerSnapshot = {
|
|
39
|
-
readonly messages: readonly ThreadMessage[];
|
|
40
|
-
/** Whether the producing runtime is currently streaming new output. */
|
|
41
|
-
readonly isRunning: boolean;
|
|
42
|
-
/**
|
|
43
|
-
* Whether the producing runtime is still loading historical state.
|
|
44
|
-
* When `true`, every snapshot is treated as historical (no `streamCall` /
|
|
45
|
-
* `execute` fires). When `false`, processing resumes as live.
|
|
46
|
-
*/
|
|
47
|
-
readonly isLoading?: boolean;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export type ToolInvocationTrackerCallbacks = {
|
|
51
|
-
/**
|
|
52
|
-
* Invoked when a client-side `execute()` returns a result and the runtime
|
|
53
|
-
* needs to feed it back into the conversation.
|
|
54
|
-
*/
|
|
55
|
-
onResult: (command: AddToolResultCommand) => void;
|
|
56
|
-
/**
|
|
57
|
-
* Invoked whenever the per-tool-call status map changes (executing /
|
|
58
|
-
* interrupt / cleared). The callback receives a fresh map; mutating the
|
|
59
|
-
* argument is not supported.
|
|
60
|
-
*/
|
|
61
|
-
onStatusesChange: (
|
|
62
|
-
statuses: ReadonlyMap<string, ToolExecutionStatus>,
|
|
63
|
-
) => void;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
38
|
type ToolCallEntry = {
|
|
67
39
|
toolName: string;
|
|
68
40
|
argsText: string;
|
|
@@ -123,10 +95,12 @@ const isEquivalentCompleteArgsText = (previous: string, next: string) => {
|
|
|
123
95
|
* the hot message-processing path, so a malformed snapshot must never crash
|
|
124
96
|
* the host runtime. See ./EDGE_CASES.md for the known non-trivial state
|
|
125
97
|
* transitions and what each does today.
|
|
98
|
+
*
|
|
99
|
+
* @deprecated Internal — for framework bindings; not a public API. May change without notice.
|
|
126
100
|
*/
|
|
127
101
|
export class ToolInvocationTracker {
|
|
128
102
|
private readonly _getTools: () => Record<string, Tool> | undefined;
|
|
129
|
-
private readonly _callbacks:
|
|
103
|
+
private readonly _callbacks: ToolInvocationTracker.Callbacks;
|
|
130
104
|
|
|
131
105
|
private readonly _entries = new Map<string, ToolCallEntry>();
|
|
132
106
|
/**
|
|
@@ -157,7 +131,7 @@ export class ToolInvocationTracker {
|
|
|
157
131
|
private _pendingRestore = true;
|
|
158
132
|
|
|
159
133
|
/** Cached last snapshot, used to skip processing on identical re-renders. */
|
|
160
|
-
private _lastSnapshot:
|
|
134
|
+
private _lastSnapshot: ToolInvocationTracker.Snapshot | null = null;
|
|
161
135
|
private _isRunning = false;
|
|
162
136
|
|
|
163
137
|
private _controller!: ReturnType<typeof createAssistantStreamController>[1];
|
|
@@ -175,7 +149,7 @@ export class ToolInvocationTracker {
|
|
|
175
149
|
|
|
176
150
|
constructor(
|
|
177
151
|
getTools: () => Record<string, Tool> | undefined,
|
|
178
|
-
callbacks:
|
|
152
|
+
callbacks: ToolInvocationTracker.Callbacks,
|
|
179
153
|
) {
|
|
180
154
|
this._getTools = getTools;
|
|
181
155
|
this._callbacks = callbacks;
|
|
@@ -235,7 +209,7 @@ export class ToolInvocationTracker {
|
|
|
235
209
|
* Feed the next observed snapshot into the tracker. Called from the host
|
|
236
210
|
* runtime whenever its message list / running state changes.
|
|
237
211
|
*/
|
|
238
|
-
public setState(snapshot:
|
|
212
|
+
public setState(snapshot: ToolInvocationTracker.Snapshot): void {
|
|
239
213
|
try {
|
|
240
214
|
// Recover from a dead pipeline before processing anything. We demote
|
|
241
215
|
// all active entries to "restored" so the rebuilt pipeline does not
|
|
@@ -687,14 +661,16 @@ export class ToolInvocationTracker {
|
|
|
687
661
|
}
|
|
688
662
|
|
|
689
663
|
if (!entry.argsComplete && entry.controller) {
|
|
664
|
+
// ToolExecutionStream parses the streamed prefix on close, so the close
|
|
665
|
+
// gates on the streamed content; a divergent snapshot (A.2) can be
|
|
666
|
+
// complete while the controller still holds an incomplete stale prefix.
|
|
690
667
|
const shouldClose = this._shouldCloseArgsStream({
|
|
691
668
|
toolName: content.toolName,
|
|
692
|
-
argsText:
|
|
669
|
+
argsText: entry.argsText,
|
|
693
670
|
hasResult,
|
|
694
671
|
});
|
|
695
672
|
if (shouldClose) {
|
|
696
673
|
entry.controller.argsText.close();
|
|
697
|
-
entry.argsText = content.argsText;
|
|
698
674
|
entry.argsComplete = true;
|
|
699
675
|
}
|
|
700
676
|
}
|
|
@@ -780,3 +756,35 @@ export class ToolInvocationTracker {
|
|
|
780
756
|
}
|
|
781
757
|
}
|
|
782
758
|
}
|
|
759
|
+
|
|
760
|
+
export namespace ToolInvocationTracker {
|
|
761
|
+
export type ExecutionStatus = ToolExecutionStatus;
|
|
762
|
+
|
|
763
|
+
export type Snapshot = {
|
|
764
|
+
readonly messages: readonly ThreadMessage[];
|
|
765
|
+
/** Whether the producing runtime is currently streaming new output. */
|
|
766
|
+
readonly isRunning: boolean;
|
|
767
|
+
/**
|
|
768
|
+
* Whether the producing runtime is still loading historical state.
|
|
769
|
+
* When `true`, every snapshot is treated as historical (no `streamCall` /
|
|
770
|
+
* `execute` fires). When `false`, processing resumes as live.
|
|
771
|
+
*/
|
|
772
|
+
readonly isLoading?: boolean;
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
export type Callbacks = {
|
|
776
|
+
/**
|
|
777
|
+
* Invoked when a client-side `execute()` returns a result and the runtime
|
|
778
|
+
* needs to feed it back into the conversation.
|
|
779
|
+
*/
|
|
780
|
+
onResult: (command: AddToolResultCommand) => void;
|
|
781
|
+
/**
|
|
782
|
+
* Invoked whenever the per-tool-call status map changes (executing /
|
|
783
|
+
* interrupt / cleared). The callback receives a fresh map; mutating the
|
|
784
|
+
* argument is not supported.
|
|
785
|
+
*/
|
|
786
|
+
onStatusesChange: (
|
|
787
|
+
statuses: ReadonlyMap<string, ToolExecutionStatus>,
|
|
788
|
+
) => void;
|
|
789
|
+
};
|
|
790
|
+
}
|
|
@@ -149,7 +149,7 @@ describe("mergeModelContexts", () => {
|
|
|
149
149
|
});
|
|
150
150
|
});
|
|
151
151
|
|
|
152
|
-
it("
|
|
152
|
+
it("rejects duplicate tools at the same priority", () => {
|
|
153
153
|
expect(() =>
|
|
154
154
|
mergeModelContexts(
|
|
155
155
|
new Set([
|
|
@@ -160,6 +160,26 @@ describe("mergeModelContexts", () => {
|
|
|
160
160
|
).toThrow(/already exists/);
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
+
it("silently replaces a same-priority tool when the later registration sets overwrite", () => {
|
|
164
|
+
const merged = mergeModelContexts(
|
|
165
|
+
new Set([
|
|
166
|
+
provider({ tools: { duplicate: toolFixture() } }),
|
|
167
|
+
provider({
|
|
168
|
+
tools: {
|
|
169
|
+
duplicate: {
|
|
170
|
+
...toolFixture(),
|
|
171
|
+
description: "latest",
|
|
172
|
+
overwrite: true,
|
|
173
|
+
} as Tool<any, any>,
|
|
174
|
+
},
|
|
175
|
+
}),
|
|
176
|
+
]),
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
expect(merged.tools?.duplicate?.description).toBe("latest");
|
|
180
|
+
expect(merged.tools?.duplicate?.overwrite).toBeUndefined();
|
|
181
|
+
});
|
|
182
|
+
|
|
163
183
|
it("preserves the highest priority when a lower-priority provider reuses the same tool object", () => {
|
|
164
184
|
const shared = {
|
|
165
185
|
...toolFixture(),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Unsubscribe } from "../../types/unsubscribe";
|
|
2
2
|
import type { ThreadRuntimeEventType } from "../../runtime/interfaces/thread-runtime-core";
|
|
3
3
|
import type { ThreadRuntime } from "../../runtime/api/thread-runtime";
|
|
4
|
-
import { useMemo, useEffect, RefObject } from "react";
|
|
4
|
+
import { useMemo, useEffect, type RefObject } from "react";
|
|
5
5
|
import { useResource, resource, withKey } from "@assistant-ui/tap";
|
|
6
6
|
import { liveRef } from "./liveRef";
|
|
7
7
|
import {
|
|
@@ -113,6 +113,7 @@ const useThreadClient = ({
|
|
|
113
113
|
deleteMessage: runtime.deleteMessage,
|
|
114
114
|
startRun: runtime.startRun,
|
|
115
115
|
resumeRun: runtime.resumeRun,
|
|
116
|
+
importExternalState: runtime.importExternalState,
|
|
116
117
|
cancelRun: runtime.cancelRun,
|
|
117
118
|
getModelContext: runtime.getModelContext,
|
|
118
119
|
export: runtime.export,
|
|
@@ -108,6 +108,7 @@ export type ThreadMethods = {
|
|
|
108
108
|
* @param initialMessages - Optional array of initial messages to populate the thread
|
|
109
109
|
*/
|
|
110
110
|
reset(initialMessages?: readonly ThreadMessageLike[]): void;
|
|
111
|
+
importExternalState?(state: unknown): void;
|
|
111
112
|
message(selector: { id: string } | { index: number }): MessageMethods;
|
|
112
113
|
/** @deprecated This API is still under active development and might change without notice. */
|
|
113
114
|
stopSpeaking(): void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { SubscribableWithState } from "./subscribable";
|
|
3
|
+
import { ShallowMemoizeSubject } from "./subscribable";
|
|
4
|
+
|
|
5
|
+
type TestState = {
|
|
6
|
+
status: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const createBinding = (initialState: TestState) => {
|
|
11
|
+
let state = initialState;
|
|
12
|
+
const subscribers = new Set<() => void>();
|
|
13
|
+
|
|
14
|
+
const binding: SubscribableWithState<TestState, null> = {
|
|
15
|
+
path: null,
|
|
16
|
+
getState: () => state,
|
|
17
|
+
subscribe: (callback) => {
|
|
18
|
+
subscribers.add(callback);
|
|
19
|
+
return () => subscribers.delete(callback);
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
binding,
|
|
25
|
+
update(nextState: TestState) {
|
|
26
|
+
state = nextState;
|
|
27
|
+
for (const callback of subscribers) callback();
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
describe("ShallowMemoizeSubject", () => {
|
|
33
|
+
it("notifies subscribers when a state key is removed", () => {
|
|
34
|
+
const source = createBinding({
|
|
35
|
+
status: "running",
|
|
36
|
+
error: "Connection failed",
|
|
37
|
+
});
|
|
38
|
+
const subject = new ShallowMemoizeSubject(source.binding);
|
|
39
|
+
const subscriber = vi.fn();
|
|
40
|
+
subject.subscribe(subscriber);
|
|
41
|
+
|
|
42
|
+
source.update({ status: "running" });
|
|
43
|
+
|
|
44
|
+
expect(subscriber).toHaveBeenCalledOnce();
|
|
45
|
+
expect(subject.getState()).toEqual({ status: "running" });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("does not notify subscribers for a shallow-equal state", () => {
|
|
49
|
+
const source = createBinding({ status: "running" });
|
|
50
|
+
const subject = new ShallowMemoizeSubject(source.binding);
|
|
51
|
+
const subscriber = vi.fn();
|
|
52
|
+
subject.subscribe(subscriber);
|
|
53
|
+
|
|
54
|
+
source.update({ status: "running" });
|
|
55
|
+
|
|
56
|
+
expect(subscriber).not.toHaveBeenCalled();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Unsubscribe } from "../types/unsubscribe";
|
|
2
|
+
import { notifyEventListeners } from "../utils/notify-event-listeners";
|
|
2
3
|
|
|
3
4
|
export const SKIP_UPDATE = Symbol("skip-update");
|
|
4
5
|
export type SKIP_UPDATE = typeof SKIP_UPDATE;
|
|
@@ -39,7 +40,10 @@ function shallowEqual<T extends object>(
|
|
|
39
40
|
if (objA === undefined) return false;
|
|
40
41
|
if (objB === undefined) return false;
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
const keysA = Object.keys(objA);
|
|
44
|
+
if (keysA.length !== Object.keys(objB).length) return false;
|
|
45
|
+
|
|
46
|
+
for (const key of keysA) {
|
|
43
47
|
const valueA = objA[key as keyof T];
|
|
44
48
|
const valueB = objB[key as keyof T];
|
|
45
49
|
if (!Object.is(valueA, valueB)) return false;
|
|
@@ -99,7 +103,12 @@ export abstract class BaseSubject {
|
|
|
99
103
|
|
|
100
104
|
protected abstract _connect(): Unsubscribe;
|
|
101
105
|
|
|
102
|
-
protected notifySubscribers(payload?: unknown) {
|
|
106
|
+
protected notifySubscribers(payload?: unknown, errorContext?: string) {
|
|
107
|
+
if (errorContext) {
|
|
108
|
+
notifyEventListeners(this._subscriptions, payload, errorContext);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
103
112
|
for (const callback of this._subscriptions) callback(payload);
|
|
104
113
|
}
|
|
105
114
|
|
|
@@ -132,10 +141,11 @@ export class ShallowMemoizeSubject<TState extends object, TPath>
|
|
|
132
141
|
return this.binding.path;
|
|
133
142
|
}
|
|
134
143
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
) {
|
|
144
|
+
private binding: SubscribableWithState<TState | SKIP_UPDATE, TPath>;
|
|
145
|
+
|
|
146
|
+
constructor(binding: SubscribableWithState<TState | SKIP_UPDATE, TPath>) {
|
|
138
147
|
super();
|
|
148
|
+
this.binding = binding;
|
|
139
149
|
const state = binding.getState();
|
|
140
150
|
if (state === SKIP_UPDATE)
|
|
141
151
|
throw new Error("Entry not available in the store");
|
|
@@ -175,10 +185,11 @@ export class LazyMemoizeSubject<TState extends object, TPath>
|
|
|
175
185
|
return this.binding.path;
|
|
176
186
|
}
|
|
177
187
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
) {
|
|
188
|
+
private binding: SubscribableWithState<TState | SKIP_UPDATE, TPath>;
|
|
189
|
+
|
|
190
|
+
constructor(binding: SubscribableWithState<TState | SKIP_UPDATE, TPath>) {
|
|
181
191
|
super();
|
|
192
|
+
this.binding = binding;
|
|
182
193
|
}
|
|
183
194
|
|
|
184
195
|
private _previousStateDirty = true;
|
|
@@ -219,8 +230,11 @@ export class NestedSubscriptionSubject<
|
|
|
219
230
|
return this.binding.path;
|
|
220
231
|
}
|
|
221
232
|
|
|
222
|
-
|
|
233
|
+
private binding: NestedSubscribable<TState, TPath>;
|
|
234
|
+
|
|
235
|
+
constructor(binding: NestedSubscribable<TState, TPath>) {
|
|
223
236
|
super();
|
|
237
|
+
this.binding = binding;
|
|
224
238
|
}
|
|
225
239
|
|
|
226
240
|
public getState() {
|
|
@@ -260,8 +274,11 @@ export class NestedSubscriptionSubject<
|
|
|
260
274
|
export class EventSubscriptionSubject<
|
|
261
275
|
TEvent extends string,
|
|
262
276
|
> extends BaseSubject {
|
|
263
|
-
|
|
277
|
+
private config: EventSubscribable<TEvent>;
|
|
278
|
+
|
|
279
|
+
constructor(config: EventSubscribable<TEvent>) {
|
|
264
280
|
super();
|
|
281
|
+
this.config = config;
|
|
265
282
|
}
|
|
266
283
|
|
|
267
284
|
public getState() {
|
|
@@ -273,8 +290,9 @@ export class EventSubscriptionSubject<
|
|
|
273
290
|
}
|
|
274
291
|
|
|
275
292
|
protected _connect(): Unsubscribe {
|
|
293
|
+
const errorContext = `Runtime event "${this.config.event}"`;
|
|
276
294
|
const callback = (payload?: unknown) => {
|
|
277
|
-
this.notifySubscribers(payload);
|
|
295
|
+
this.notifySubscribers(payload, errorContext);
|
|
278
296
|
};
|
|
279
297
|
|
|
280
298
|
let lastState = this.config.binding.getState();
|
|
@@ -110,6 +110,18 @@ describe("MessageRepository", () => {
|
|
|
110
110
|
}).toThrow(/Parent message not found/);
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
+
it("should throw when a message would be linked under an ancestor with the same id", () => {
|
|
114
|
+
const message = createTestMessage({ id: "message-id" });
|
|
115
|
+
repository.addOrUpdateMessage(null, message);
|
|
116
|
+
|
|
117
|
+
expect(() => {
|
|
118
|
+
repository.addOrUpdateMessage(
|
|
119
|
+
"message-id",
|
|
120
|
+
createTestMessage({ id: "message-id" }),
|
|
121
|
+
);
|
|
122
|
+
}).toThrow(/same id already exists in the parent tree/);
|
|
123
|
+
});
|
|
124
|
+
|
|
113
125
|
it("should retrieve all messages in the current branch", () => {
|
|
114
126
|
const parent = createTestMessage({ id: "parent-id" });
|
|
115
127
|
const child = createTestMessage({ id: "child-id" });
|
|
@@ -79,6 +79,39 @@ const applyListResult = (
|
|
|
79
79
|
};
|
|
80
80
|
|
|
81
81
|
describe("list + delete race condition", () => {
|
|
82
|
+
it("stale list() does not re-add a thread deleted before list started", async () => {
|
|
83
|
+
const state = new OptimisticState<RemoteThreadState>(
|
|
84
|
+
applyListResult(EMPTY_STATE, {
|
|
85
|
+
threads: [{ remoteId: "thread-A", status: "regular", title: "A" }],
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
const deleteDeferred = deferred<void>();
|
|
89
|
+
const listDeferred = deferred<ListResult>();
|
|
90
|
+
|
|
91
|
+
const deletePromise = state.optimisticUpdate({
|
|
92
|
+
execute: () => deleteDeferred.promise,
|
|
93
|
+
optimistic: (s) => updateStatusReducer(s, "thread-A", "deleted"),
|
|
94
|
+
});
|
|
95
|
+
const listPromise = state.optimisticUpdate({
|
|
96
|
+
execute: () => listDeferred.promise,
|
|
97
|
+
loading: (s) => ({ ...s, isLoading: true }),
|
|
98
|
+
then: applyListResult,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
deleteDeferred.resolve();
|
|
102
|
+
await deletePromise;
|
|
103
|
+
|
|
104
|
+
listDeferred.resolve({
|
|
105
|
+
threads: [{ remoteId: "thread-A", status: "regular", title: "A" }],
|
|
106
|
+
});
|
|
107
|
+
await listPromise;
|
|
108
|
+
|
|
109
|
+
expect(state.value.threadIds).not.toContain("thread-A");
|
|
110
|
+
expect(
|
|
111
|
+
state.value.threadData[createThreadMappingId("thread-A")],
|
|
112
|
+
).toBeUndefined();
|
|
113
|
+
});
|
|
114
|
+
|
|
82
115
|
it("stale list() does not re-add a thread deleted while list was in flight", async () => {
|
|
83
116
|
const state = new OptimisticState<RemoteThreadState>(EMPTY_STATE);
|
|
84
117
|
|