@assistant-ui/core 0.3.14 → 0.3.16
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/voice.js +11 -0
- package/dist/adapters/voice.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/internal.d.ts +8 -2
- package/dist/internal.js +9 -3
- package/dist/model-context/frame/host.d.ts +1 -1
- package/dist/model-context/frame/host.d.ts.map +1 -1
- package/dist/model-context/frame/host.js +20 -5
- package/dist/model-context/frame/host.js.map +1 -1
- package/dist/model-context/frame/provider.d.ts +6 -0
- package/dist/model-context/frame/provider.d.ts.map +1 -1
- package/dist/model-context/frame/provider.js +126 -16
- package/dist/model-context/frame/provider.js.map +1 -1
- package/dist/model-context/frame/types.d.ts +4 -1
- package/dist/model-context/frame/types.d.ts.map +1 -1
- package/dist/model-context/frame/types.js.map +1 -1
- package/dist/model-context/interactable-composer-metadata.d.ts +1 -1
- package/dist/model-context/interactable-composer-metadata.js +1 -1
- package/dist/model-context/interactable-composer-metadata.js.map +1 -1
- package/dist/model-context/registry.d.ts +2 -2
- package/dist/model-context/registry.d.ts.map +1 -1
- package/dist/model-context/registry.js +9 -28
- package/dist/model-context/registry.js.map +1 -1
- package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
- package/dist/react/client/InMemoryThreadList.js +24 -20
- package/dist/react/client/InMemoryThreadList.js.map +1 -1
- package/dist/react/client/Interactables.d.ts.map +1 -1
- package/dist/react/client/Interactables.js +59 -177
- package/dist/react/client/Interactables.js.map +1 -1
- package/dist/react/client/RemoteThreadList.d.ts +1 -1
- package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
- package/dist/react/client/RemoteThreadList.js +117 -78
- package/dist/react/client/RemoteThreadList.js.map +1 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -1
- package/dist/react/interactables-legacy/Interactables.js +56 -174
- package/dist/react/interactables-legacy/Interactables.js.map +1 -1
- package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts +24 -0
- package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -0
- package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +138 -0
- package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -0
- package/dist/react/primitive-hooks/useActionBarCopy.js +7 -1
- package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
- package/dist/react/primitive-hooks/useThreadIsEmpty.d.ts +3 -0
- package/dist/react/primitive-hooks/useThreadIsEmpty.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useThreadIsEmpty.js +3 -0
- package/dist/react/primitive-hooks/useThreadIsEmpty.js.map +1 -1
- package/dist/react/primitive-hooks/useThreadIsRunning.d.ts +3 -0
- package/dist/react/primitive-hooks/useThreadIsRunning.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useThreadIsRunning.js +3 -0
- package/dist/react/primitive-hooks/useThreadIsRunning.js.map +1 -1
- package/dist/react/primitives/composer/ComposerIf.js.map +1 -1
- package/dist/react/primitives/composer/ComposerQueue.d.ts +2 -1
- package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageParts.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +3 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +6 -0
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +7 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +174 -64
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadResource.js +2 -0
- package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
- package/dist/react/runtimes/assistant-transport/runManager.d.ts.map +1 -1
- package/dist/react/runtimes/assistant-transport/runManager.js +2 -12
- package/dist/react/runtimes/assistant-transport/runManager.js.map +1 -1
- package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
- package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js +1 -1
- package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +3 -10
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +20 -67
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts +12 -0
- package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts.map +1 -0
- package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js +10 -0
- package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js.map +1 -0
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +38 -5
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js.map +1 -1
- package/dist/react/runtimes/createMessageConverter.d.ts +2 -1
- package/dist/react/runtimes/createMessageConverter.d.ts.map +1 -1
- package/dist/react/runtimes/external-message-converter.d.ts +6 -25
- package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
- package/dist/react/runtimes/external-message-converter.js +33 -233
- package/dist/react/runtimes/external-message-converter.js.map +1 -1
- package/dist/runtime/api/composer-runtime.d.ts +1 -1
- package/dist/runtime/api/composer-runtime.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts +1 -2
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js +14 -29
- package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -4
- package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.js +3 -9
- package/dist/runtime/base/base-thread-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 +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
- package/dist/runtime/interfaces/composer-runtime-core.d.ts +1 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts +1 -1
- package/dist/runtime/internal.d.ts +2 -1
- package/dist/runtime/internal.js +2 -1
- package/dist/runtime/queue/external-thread-queue-adapter.d.ts +1 -1
- package/dist/runtime/queue/message-queue.d.ts.map +1 -1
- package/dist/runtime/queue/message-queue.js +1 -1
- package/dist/runtime/queue/message-queue.js.map +1 -1
- package/dist/runtime/queue/queue-item.d.ts +12 -0
- package/dist/runtime/queue/queue-item.d.ts.map +1 -0
- package/dist/runtime/queue/queue-item.js +6 -0
- package/dist/runtime/queue/queue-item.js.map +1 -0
- package/dist/runtime/utils/abortable-thread-load.d.ts +15 -0
- package/dist/runtime/utils/abortable-thread-load.d.ts.map +1 -0
- package/dist/runtime/utils/abortable-thread-load.js +35 -0
- package/dist/runtime/utils/abortable-thread-load.js.map +1 -0
- package/dist/runtime/utils/attachment-add-operations.d.ts +18 -0
- package/dist/runtime/utils/attachment-add-operations.d.ts.map +1 -0
- package/dist/runtime/utils/attachment-add-operations.js +43 -0
- package/dist/runtime/utils/attachment-add-operations.js.map +1 -0
- package/dist/runtime/utils/auto-status.d.ts +6 -1
- package/dist/runtime/utils/auto-status.d.ts.map +1 -1
- package/dist/runtime/utils/auto-status.js +7 -1
- package/dist/runtime/utils/auto-status.js.map +1 -1
- package/dist/runtime/utils/external-message-conversion.d.ts +47 -0
- package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -0
- package/dist/runtime/utils/external-message-conversion.js +221 -0
- package/dist/runtime/utils/external-message-conversion.js.map +1 -0
- package/dist/runtime/utils/external-store-message.d.ts +2 -1
- package/dist/runtime/utils/external-store-message.d.ts.map +1 -1
- package/dist/runtime/utils/external-store-message.js +2 -1
- package/dist/runtime/utils/external-store-message.js.map +1 -1
- package/dist/runtime/utils/message-repository-session.d.ts +30 -0
- package/dist/runtime/utils/message-repository-session.d.ts.map +1 -0
- package/dist/runtime/utils/message-repository-session.js +130 -0
- package/dist/runtime/utils/message-repository-session.js.map +1 -0
- package/dist/runtime/utils/message-repository.d.ts.map +1 -1
- package/dist/runtime/utils/message-repository.js +30 -22
- package/dist/runtime/utils/message-repository.js.map +1 -1
- package/dist/runtime/utils/pending-tool-calls.d.ts +20 -0
- package/dist/runtime/utils/pending-tool-calls.d.ts.map +1 -0
- package/dist/runtime/utils/pending-tool-calls.js +22 -0
- package/dist/runtime/utils/pending-tool-calls.js.map +1 -0
- package/dist/runtime/utils/thread-message-like.d.ts +2 -1
- package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
- package/dist/runtime/utils/thread-message-like.js.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +2 -5
- 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 +3 -10
- 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 +19 -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 +124 -20
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts +102 -0
- package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts.map +1 -0
- package/dist/runtimes/inert/InertThreadRuntimeCore.js +156 -0
- package/dist/runtimes/inert/InertThreadRuntimeCore.js.map +1 -0
- package/dist/runtimes/local/local-thread-runtime-core.d.ts +3 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +45 -8
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +12 -60
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +10 -145
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
- package/dist/runtimes/remote-thread-list/adapter-changed.d.ts +9 -0
- package/dist/runtimes/remote-thread-list/adapter-changed.d.ts.map +1 -0
- package/dist/runtimes/remote-thread-list/adapter-changed.js +13 -0
- package/dist/runtimes/remote-thread-list/adapter-changed.js.map +1 -0
- package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.js +16 -155
- package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
- package/dist/runtimes/remote-thread-list/optimistic-state.d.ts +2 -0
- package/dist/runtimes/remote-thread-list/optimistic-state.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/optimistic-state.js +11 -0
- package/dist/runtimes/remote-thread-list/optimistic-state.js.map +1 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +12 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.js +77 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
- package/dist/runtimes/remote-thread-list/types.d.ts +3 -0
- package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +2 -0
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
- package/dist/store/clients/external-thread.d.ts.map +1 -1
- package/dist/store/clients/external-thread.js +75 -100
- package/dist/store/clients/external-thread.js.map +1 -1
- package/dist/store/clients/single-thread-list.d.ts.map +1 -1
- package/dist/store/clients/single-thread-list.js +1 -1
- package/dist/store/clients/single-thread-list.js.map +1 -1
- package/dist/store/clients/suggestions.d.ts.map +1 -1
- package/dist/store/clients/suggestions.js +16 -7
- package/dist/store/clients/suggestions.js.map +1 -1
- package/dist/store/index.d.ts +4 -2
- package/dist/store/runtime-clients/handle-runtime-action.d.ts.map +1 -1
- package/dist/store/runtime-clients/handle-runtime-action.js +2 -0
- package/dist/store/runtime-clients/handle-runtime-action.js.map +1 -1
- package/dist/store/scope-registration.d.ts +1 -1
- package/dist/store/scopes/composer.d.ts +2 -1
- package/dist/store/scopes/composer.d.ts.map +1 -1
- package/dist/store/scopes/message.d.ts +1 -1
- package/dist/store/scopes/queue-item.d.ts +2 -9
- package/dist/store/scopes/queue-item.d.ts.map +1 -1
- package/dist/store/scopes/queue-item.js +0 -6
- package/dist/types/message.d.ts +8 -4
- package/dist/types/message.d.ts.map +1 -1
- package/dist/types/message.js.map +1 -1
- package/dist/utils/data-url.d.ts +14 -1
- package/dist/utils/data-url.d.ts.map +1 -1
- package/dist/utils/data-url.js +13 -1
- package/dist/utils/data-url.js.map +1 -1
- package/dist/utils/invoke-user-callback.d.ts +5 -0
- package/dist/utils/invoke-user-callback.d.ts.map +1 -0
- package/dist/utils/invoke-user-callback.js +19 -0
- package/dist/utils/invoke-user-callback.js.map +1 -0
- package/dist/utils/normalizePartStatus.js.map +1 -1
- package/dist/utils/silent-runtime-action.d.ts +6 -0
- package/dist/utils/silent-runtime-action.d.ts.map +1 -0
- package/dist/utils/silent-runtime-action.js +7 -0
- package/dist/utils/silent-runtime-action.js.map +1 -0
- package/package.json +10 -10
- package/src/adapters/voice.test.ts +66 -1
- package/src/adapters/voice.ts +8 -0
- package/src/internal.ts +13 -0
- package/src/model-context/frame/host.test.ts +152 -9
- package/src/model-context/frame/host.ts +23 -5
- package/src/model-context/frame/provider.test.ts +601 -19
- package/src/model-context/frame/provider.ts +158 -20
- package/src/model-context/frame/types.ts +5 -0
- package/src/model-context/interactable-composer-metadata.ts +1 -1
- package/src/model-context/registry.test.ts +44 -0
- package/src/model-context/registry.ts +13 -35
- package/src/react/client/InMemoryThreadList.test.tsx +49 -0
- package/src/react/client/InMemoryThreadList.ts +7 -1
- package/src/react/client/Interactables.ts +19 -178
- package/src/react/client/RemoteThreadList.test.ts +292 -0
- package/src/react/client/RemoteThreadList.ts +119 -82
- package/src/react/interactables-legacy/Interactables.ts +20 -177
- package/src/react/interactables-shared/useInteractablePersistenceQueue.test.tsx +161 -0
- package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +206 -0
- package/src/react/primitive-hooks/useActionBarCopy.test.ts +15 -0
- package/src/react/primitive-hooks/useActionBarCopy.ts +13 -3
- package/src/react/primitive-hooks/useThreadIsEmpty.ts +3 -0
- package/src/react/primitive-hooks/useThreadIsRunning.ts +3 -0
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +12 -0
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.republish.test.ts +87 -0
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +251 -76
- package/src/react/runtimes/RemoteThreadResource.ts +2 -0
- package/src/react/runtimes/assistant-transport/runManager.ts +4 -17
- package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.test.tsx +58 -0
- package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.ts +3 -1
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +92 -0
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +40 -141
- package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.test.ts +37 -0
- package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.ts +18 -0
- package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +98 -3
- package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +68 -6
- package/src/react/runtimes/external-message-converter.test.ts +0 -14
- package/src/react/runtimes/external-message-converter.test.tsx +77 -0
- package/src/react/runtimes/external-message-converter.ts +58 -481
- package/src/react/runtimes/useLocalRuntime.test.tsx +5 -5
- package/src/runtime/api/composer-runtime.ts +1 -1
- package/src/runtime/api/message-runtime.test.ts +28 -1
- package/src/runtime/base/base-composer-runtime-core.ts +20 -40
- package/src/runtime/base/base-thread-runtime-core.test.ts +16 -0
- package/src/runtime/base/base-thread-runtime-core.ts +6 -11
- package/src/runtime/base/default-thread-composer-runtime-core.ts +1 -4
- package/src/runtime/interfaces/composer-runtime-core.ts +1 -1
- package/src/runtime/interfaces/thread-runtime-core.ts +1 -1
- package/src/runtime/internal.ts +5 -0
- package/src/runtime/queue/external-thread-queue-adapter.ts +1 -1
- package/src/runtime/queue/message-queue.ts +1 -4
- package/src/runtime/queue/queue-item.ts +10 -0
- package/src/runtime/utils/abortable-thread-load.test.ts +87 -0
- package/src/runtime/utils/abortable-thread-load.ts +53 -0
- package/src/runtime/utils/attachment-add-operations.ts +59 -0
- package/src/runtime/utils/auto-status.ts +33 -0
- package/src/runtime/utils/external-message-conversion.test.ts +113 -0
- package/src/runtime/utils/external-message-conversion.ts +494 -0
- package/src/runtime/utils/external-store-message.ts +2 -0
- package/src/runtime/utils/message-repository-session.test.ts +195 -0
- package/src/runtime/utils/message-repository-session.ts +176 -0
- package/src/runtime/utils/message-repository.ts +33 -22
- package/src/runtime/utils/pending-tool-calls.test.ts +57 -0
- package/src/runtime/utils/pending-tool-calls.ts +35 -0
- package/src/runtime/utils/thread-message-like.ts +2 -0
- package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +6 -14
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +179 -28
- package/src/runtimes/inert/InertThreadRuntimeCore.test.ts +253 -0
- package/src/runtimes/inert/InertThreadRuntimeCore.ts +232 -0
- package/src/runtimes/local/local-thread-runtime-core.test.ts +491 -1
- package/src/runtimes/local/local-thread-runtime-core.ts +86 -11
- package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +17 -209
- package/src/runtimes/remote-thread-list/adapter-changed.ts +10 -0
- package/src/runtimes/remote-thread-list/empty-thread-core.ts +28 -221
- package/src/runtimes/remote-thread-list/optimistic-state.test.ts +18 -0
- package/src/runtimes/remote-thread-list/optimistic-state.ts +13 -0
- package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +37 -0
- package/src/runtimes/remote-thread-list/remote-thread-state.ts +123 -0
- package/src/runtimes/remote-thread-list/types.ts +4 -0
- package/src/runtimes/tool-invocations/EDGE_CASES.md +17 -0
- package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +140 -0
- package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +3 -0
- package/src/store/clients/external-thread.ts +17 -62
- package/src/store/clients/single-thread-list.ts +4 -1
- package/src/store/clients/suggestions.test.ts +31 -0
- package/src/store/clients/suggestions.ts +18 -19
- package/src/store/runtime-clients/handle-runtime-action.test.ts +40 -0
- package/src/store/runtime-clients/handle-runtime-action.ts +3 -0
- package/src/store/scopes/message.ts +1 -1
- package/src/store/scopes/queue-item.ts +2 -9
- package/src/tests/MessageRepository.test.ts +125 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-errors.test.ts +2 -2
- package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +194 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +8 -6
- package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +158 -0
- package/src/tests/empty-thread-core.test.ts +6 -0
- package/src/tests/external-store-thread-runtime-core-adapter.test.ts +184 -0
- package/src/tests/external-store-thread-runtime-core.test.ts +642 -0
- package/src/tests/external-thread-attachments.test.tsx +173 -0
- package/src/tests/external-thread-isEmpty.test.tsx +100 -0
- package/src/tests/remote-thread-list-adapter-switch.test.ts +383 -0
- package/src/tests/single-thread-list-item.test.tsx +47 -0
- package/src/tests/thread-message-like.test.ts +39 -1
- package/src/tests/useRemoteThreadListRuntime-controlled.test.tsx +67 -0
- package/src/types/message.ts +9 -4
- package/src/utils/data-url.test.ts +52 -1
- package/src/utils/data-url.ts +21 -0
- package/src/utils/invoke-user-callback.test.ts +82 -0
- package/src/utils/invoke-user-callback.ts +31 -0
- package/src/utils/normalizePartStatus.ts +1 -1
- package/src/utils/silent-runtime-action.ts +6 -0
- package/dist/store/scopes/queue-item.js.map +0 -1
|
@@ -13,21 +13,25 @@ import {
|
|
|
13
13
|
useClientResource,
|
|
14
14
|
} from "@assistant-ui/store/client";
|
|
15
15
|
import { isDevelopment, useThreadSelectionEvents } from "../../store/internal";
|
|
16
|
-
import { generateId } from "../../utils/id";
|
|
17
16
|
import { OptimisticState } from "../../runtimes/remote-thread-list/optimistic-state";
|
|
18
17
|
import {
|
|
19
18
|
classifyThreads,
|
|
19
|
+
createEmptyRemoteThreadState,
|
|
20
20
|
createThreadMappingId,
|
|
21
21
|
getThreadData,
|
|
22
22
|
normalizeCursor,
|
|
23
23
|
updateStatusReducer,
|
|
24
24
|
type RemoteThreadData,
|
|
25
25
|
type RemoteThreadState,
|
|
26
|
+
preserveMidLoadTransitions,
|
|
27
|
+
seedNewThread,
|
|
28
|
+
statusSnapshot,
|
|
26
29
|
} from "../../runtimes/remote-thread-list/remote-thread-state";
|
|
27
30
|
import type {
|
|
28
31
|
RemoteThreadInitializeResponse,
|
|
29
32
|
RemoteThreadListAdapter,
|
|
30
33
|
} from "../../runtimes/remote-thread-list/types";
|
|
34
|
+
import { ThreadListAdapterChangedError } from "../../runtimes/remote-thread-list/adapter-changed";
|
|
31
35
|
import type { ThreadMessage } from "../../types/message";
|
|
32
36
|
import { AssistantMessageStream } from "assistant-stream";
|
|
33
37
|
import { handleThreadListAction } from "../../store/runtime-clients/handle-thread-list-action";
|
|
@@ -39,20 +43,11 @@ import { AdaptedRemoteThread } from "./AdaptedRemoteThread";
|
|
|
39
43
|
|
|
40
44
|
const RESOLVED_PROMISE = Promise.resolve();
|
|
41
45
|
|
|
42
|
-
const EMPTY_LIST
|
|
43
|
-
isLoading: true,
|
|
44
|
-
isLoadingMore: false,
|
|
45
|
-
cursor: undefined,
|
|
46
|
-
newThreadId: undefined,
|
|
47
|
-
threadIds: [],
|
|
48
|
-
archivedThreadIds: [],
|
|
49
|
-
threadIdMap: {},
|
|
50
|
-
threadData: {},
|
|
51
|
-
};
|
|
46
|
+
const EMPTY_LIST = createEmptyRemoteThreadState();
|
|
52
47
|
|
|
53
48
|
export type RemoteThreadListProps = {
|
|
54
49
|
/**
|
|
55
|
-
* Swapping this to a different backing store does not reload the list. Call `reload()` after a genuine swap. A recreated object for the same store is a no-op.
|
|
50
|
+
* Swapping this to a different backing store does not reload the list. Call `reload()` after a genuine swap. A recreated object for the same store is a no-op. `reload()` after a different adapter instance resets selection and cached records before loading.
|
|
56
51
|
*/
|
|
57
52
|
adapter: RemoteThreadListAdapter;
|
|
58
53
|
/**
|
|
@@ -90,46 +85,14 @@ const toInitializeResult = (
|
|
|
90
85
|
|
|
91
86
|
const applyTitleStream = async (
|
|
92
87
|
stream: Parameters<typeof AssistantMessageStream.fromAssistantStream>[0],
|
|
93
|
-
onTitle: (title: string | undefined) => void
|
|
88
|
+
onTitle: (title: string | undefined) => Promise<void>,
|
|
94
89
|
) => {
|
|
95
90
|
const messageStream = AssistantMessageStream.fromAssistantStream(stream);
|
|
96
91
|
for await (const result of messageStream) {
|
|
97
|
-
onTitle(result.parts.filter((part) => part.type === "text")[0]?.text);
|
|
92
|
+
await onTitle(result.parts.filter((part) => part.type === "text")[0]?.text);
|
|
98
93
|
}
|
|
99
94
|
};
|
|
100
95
|
|
|
101
|
-
const seedNewThread = (
|
|
102
|
-
state: RemoteThreadState,
|
|
103
|
-
): { id: string; state: RemoteThreadState } => {
|
|
104
|
-
let id: string;
|
|
105
|
-
do {
|
|
106
|
-
id = `__LOCALID_${generateId()}`;
|
|
107
|
-
} while (state.threadIdMap[id]);
|
|
108
|
-
const mappingId = createThreadMappingId(id);
|
|
109
|
-
return {
|
|
110
|
-
id,
|
|
111
|
-
state: {
|
|
112
|
-
...state,
|
|
113
|
-
newThreadId: id,
|
|
114
|
-
threadIdMap: {
|
|
115
|
-
...state.threadIdMap,
|
|
116
|
-
[id]: mappingId,
|
|
117
|
-
},
|
|
118
|
-
threadData: {
|
|
119
|
-
...state.threadData,
|
|
120
|
-
[mappingId]: {
|
|
121
|
-
status: "new",
|
|
122
|
-
id,
|
|
123
|
-
remoteId: undefined,
|
|
124
|
-
externalId: undefined,
|
|
125
|
-
title: undefined,
|
|
126
|
-
custom: undefined,
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
};
|
|
131
|
-
};
|
|
132
|
-
|
|
133
96
|
const useThreadListItemClient = (props: {
|
|
134
97
|
data: RemoteThreadData;
|
|
135
98
|
isRunning: boolean;
|
|
@@ -359,6 +322,8 @@ const useRemoteThreadList = (
|
|
|
359
322
|
initialMainId: seeded.id,
|
|
360
323
|
session: {
|
|
361
324
|
adapter,
|
|
325
|
+
adapterAtLoad: adapter,
|
|
326
|
+
adapterGeneration: 0,
|
|
362
327
|
loadGeneration: 0,
|
|
363
328
|
switchGeneration: 0,
|
|
364
329
|
loadPromise: undefined as Promise<void> | undefined,
|
|
@@ -375,6 +340,9 @@ const useRemoteThreadList = (
|
|
|
375
340
|
};
|
|
376
341
|
});
|
|
377
342
|
|
|
343
|
+
// Config updates can invoke `reload()` in the same turn as the re-render, before the effect below runs.
|
|
344
|
+
session.adapter = adapter;
|
|
345
|
+
|
|
378
346
|
const listState = useSyncExternalStore(
|
|
379
347
|
(onStoreChange) => store.subscribe(onStoreChange),
|
|
380
348
|
() => store.value,
|
|
@@ -417,19 +385,22 @@ const useRemoteThreadList = (
|
|
|
417
385
|
const getLoadThreadsPromise = useCallback(() => {
|
|
418
386
|
if (session.loadPromise) return session.loadPromise;
|
|
419
387
|
const generation = session.loadGeneration;
|
|
388
|
+
const adapter = session.adapter;
|
|
389
|
+
const statusAtRequest = statusSnapshot(store.baseValue);
|
|
420
390
|
session.loadPromise = store
|
|
421
391
|
.optimisticUpdate({
|
|
422
|
-
execute: () =>
|
|
392
|
+
execute: () => adapter.list(),
|
|
423
393
|
loading: (state) => ({ ...state, isLoading: true }),
|
|
424
394
|
then: (state, page) => {
|
|
425
395
|
if (generation !== session.loadGeneration) return state;
|
|
396
|
+
session.adapterAtLoad = adapter;
|
|
426
397
|
const fresh = classifyThreads(page.threads, {
|
|
427
398
|
threadIds: [],
|
|
428
399
|
archivedThreadIds: [],
|
|
429
400
|
threadIdMap: {},
|
|
430
401
|
threadData: {},
|
|
431
402
|
});
|
|
432
|
-
|
|
403
|
+
const merged = {
|
|
433
404
|
...state,
|
|
434
405
|
isLoading: false,
|
|
435
406
|
cursor: normalizeCursor(page.nextCursor),
|
|
@@ -444,6 +415,7 @@ const useRemoteThreadList = (
|
|
|
444
415
|
...fresh.threadData,
|
|
445
416
|
},
|
|
446
417
|
};
|
|
418
|
+
return preserveMidLoadTransitions(merged, state, statusAtRequest);
|
|
447
419
|
},
|
|
448
420
|
})
|
|
449
421
|
.catch((error: unknown) => {
|
|
@@ -460,15 +432,34 @@ const useRemoteThreadList = (
|
|
|
460
432
|
}, [session, store]);
|
|
461
433
|
|
|
462
434
|
const reload = useCallback(() => {
|
|
435
|
+
const adapterChanged = adapter !== session.adapterAtLoad;
|
|
463
436
|
session.loadGeneration++;
|
|
464
437
|
session.loadPromise = undefined;
|
|
465
438
|
session.loadMorePromise = undefined;
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
439
|
+
if (adapterChanged) {
|
|
440
|
+
session.adapterGeneration++;
|
|
441
|
+
session.switchGeneration++;
|
|
442
|
+
session.switchTask = undefined;
|
|
443
|
+
session.adapterAtLoad = adapter;
|
|
444
|
+
const seeded = seedNewThread(EMPTY_LIST);
|
|
445
|
+
store.reset({ ...seeded.state, isLoading: true });
|
|
446
|
+
assignMainThreadId(seeded.id);
|
|
447
|
+
notifyRemoteId(undefined, true);
|
|
448
|
+
} else {
|
|
449
|
+
store.update({
|
|
450
|
+
...store.baseValue,
|
|
451
|
+
cursor: undefined,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
470
454
|
return getLoadThreadsPromise();
|
|
471
|
-
}, [
|
|
455
|
+
}, [
|
|
456
|
+
adapter,
|
|
457
|
+
assignMainThreadId,
|
|
458
|
+
getLoadThreadsPromise,
|
|
459
|
+
notifyRemoteId,
|
|
460
|
+
session,
|
|
461
|
+
store,
|
|
462
|
+
]);
|
|
472
463
|
|
|
473
464
|
useEffect(() => {
|
|
474
465
|
void getLoadThreadsPromise();
|
|
@@ -516,6 +507,7 @@ const useRemoteThreadList = (
|
|
|
516
507
|
},
|
|
517
508
|
})
|
|
518
509
|
.catch((error: unknown) => {
|
|
510
|
+
if (generation !== session.loadGeneration) return;
|
|
519
511
|
console.error("[assistant-ui] thread list loadMore failed:", error);
|
|
520
512
|
})
|
|
521
513
|
.then(() => {
|
|
@@ -689,18 +681,34 @@ const useRemoteThreadList = (
|
|
|
689
681
|
[session, store, switchToNewThread],
|
|
690
682
|
);
|
|
691
683
|
|
|
684
|
+
const requireAdapterGeneration = useCallback(
|
|
685
|
+
(generation: number) => {
|
|
686
|
+
if (generation !== session.adapterGeneration) {
|
|
687
|
+
throw new ThreadListAdapterChangedError();
|
|
688
|
+
}
|
|
689
|
+
},
|
|
690
|
+
[session],
|
|
691
|
+
);
|
|
692
|
+
|
|
692
693
|
const initialize = useCallback(
|
|
693
694
|
async (threadId: string) => {
|
|
695
|
+
const currentAdapter = session.adapter;
|
|
696
|
+
const adapterGeneration = session.adapterGeneration;
|
|
694
697
|
if (store.value.newThreadId !== threadId) {
|
|
695
698
|
const data = getThreadData(store.value, threadId);
|
|
696
699
|
if (!data) throw threadNotFoundError(threadId, "initializing it");
|
|
697
700
|
if (data.status === "new") {
|
|
698
701
|
throw threadStatusError(threadId, data.status, "be initialized here");
|
|
699
702
|
}
|
|
700
|
-
|
|
703
|
+
const result = toInitializeResult(await data.initializeTask);
|
|
704
|
+
requireAdapterGeneration(adapterGeneration);
|
|
705
|
+
return result;
|
|
701
706
|
}
|
|
702
707
|
const result = await store.optimisticUpdate({
|
|
703
|
-
execute: () =>
|
|
708
|
+
execute: () => {
|
|
709
|
+
requireAdapterGeneration(adapterGeneration);
|
|
710
|
+
return currentAdapter.initialize(threadId);
|
|
711
|
+
},
|
|
704
712
|
optimistic: (state) => updateStatusReducer(state, threadId, "regular"),
|
|
705
713
|
loading: (state, task) => {
|
|
706
714
|
const mappingId = createThreadMappingId(threadId);
|
|
@@ -716,6 +724,7 @@ const useRemoteThreadList = (
|
|
|
716
724
|
};
|
|
717
725
|
},
|
|
718
726
|
then: (state, { remoteId, externalId }) => {
|
|
727
|
+
if (adapterGeneration !== session.adapterGeneration) return state;
|
|
719
728
|
const data = getThreadData(state, threadId);
|
|
720
729
|
if (!data) return state;
|
|
721
730
|
const mappingId = createThreadMappingId(threadId);
|
|
@@ -737,16 +746,19 @@ const useRemoteThreadList = (
|
|
|
737
746
|
};
|
|
738
747
|
},
|
|
739
748
|
});
|
|
749
|
+
requireAdapterGeneration(adapterGeneration);
|
|
740
750
|
if (threadId === session.mainThreadId) {
|
|
741
751
|
notifyRemoteId(result.remoteId, true);
|
|
742
752
|
}
|
|
743
753
|
return toInitializeResult(result);
|
|
744
754
|
},
|
|
745
|
-
[notifyRemoteId, session, store],
|
|
755
|
+
[notifyRemoteId, requireAdapterGeneration, session, store],
|
|
746
756
|
);
|
|
747
757
|
|
|
748
758
|
const rename = useCallback(
|
|
749
759
|
(threadIdOrRemoteId: string, newTitle: string) => {
|
|
760
|
+
const currentAdapter = session.adapter;
|
|
761
|
+
const adapterGeneration = session.adapterGeneration;
|
|
750
762
|
const data = getThreadData(store.value, threadIdOrRemoteId);
|
|
751
763
|
if (!data) throw threadNotFoundError(threadIdOrRemoteId, "renaming it");
|
|
752
764
|
if (data.status === "new") {
|
|
@@ -755,7 +767,8 @@ const useRemoteThreadList = (
|
|
|
755
767
|
return store.optimisticUpdate({
|
|
756
768
|
execute: async () => {
|
|
757
769
|
const { remoteId } = await data.initializeTask;
|
|
758
|
-
|
|
770
|
+
requireAdapterGeneration(adapterGeneration);
|
|
771
|
+
return currentAdapter.rename(remoteId, newTitle);
|
|
759
772
|
},
|
|
760
773
|
optimistic: (state) => {
|
|
761
774
|
const current = getThreadData(state, threadIdOrRemoteId);
|
|
@@ -773,7 +786,7 @@ const useRemoteThreadList = (
|
|
|
773
786
|
},
|
|
774
787
|
});
|
|
775
788
|
},
|
|
776
|
-
[session, store],
|
|
789
|
+
[requireAdapterGeneration, session, store],
|
|
777
790
|
);
|
|
778
791
|
|
|
779
792
|
const updateCustom = useCallback(
|
|
@@ -781,6 +794,8 @@ const useRemoteThreadList = (
|
|
|
781
794
|
threadIdOrRemoteId: string,
|
|
782
795
|
custom: Record<string, unknown> | undefined,
|
|
783
796
|
) => {
|
|
797
|
+
const currentAdapter = session.adapter;
|
|
798
|
+
const adapterGeneration = session.adapterGeneration;
|
|
784
799
|
const data = getThreadData(store.value, threadIdOrRemoteId);
|
|
785
800
|
if (!data) {
|
|
786
801
|
throw threadNotFoundError(
|
|
@@ -795,7 +810,7 @@ const useRemoteThreadList = (
|
|
|
795
810
|
"update custom metadata",
|
|
796
811
|
);
|
|
797
812
|
}
|
|
798
|
-
if (!
|
|
813
|
+
if (!currentAdapter.updateCustom) {
|
|
799
814
|
throw new Error(
|
|
800
815
|
"Remote thread list adapter does not support updating custom metadata",
|
|
801
816
|
);
|
|
@@ -803,7 +818,7 @@ const useRemoteThreadList = (
|
|
|
803
818
|
return store.optimisticUpdate({
|
|
804
819
|
execute: async () => {
|
|
805
820
|
const { remoteId } = await data.initializeTask;
|
|
806
|
-
|
|
821
|
+
requireAdapterGeneration(adapterGeneration);
|
|
807
822
|
if (!currentAdapter.updateCustom) {
|
|
808
823
|
throw new Error(
|
|
809
824
|
"Remote thread list adapter does not support updating custom metadata",
|
|
@@ -827,30 +842,36 @@ const useRemoteThreadList = (
|
|
|
827
842
|
},
|
|
828
843
|
});
|
|
829
844
|
},
|
|
830
|
-
[session, store],
|
|
845
|
+
[requireAdapterGeneration, session, store],
|
|
831
846
|
);
|
|
832
847
|
|
|
833
848
|
const archive = useCallback(
|
|
834
849
|
async (threadIdOrRemoteId: string) => {
|
|
850
|
+
const currentAdapter = session.adapter;
|
|
851
|
+
const adapterGeneration = session.adapterGeneration;
|
|
835
852
|
const data = getThreadData(store.value, threadIdOrRemoteId);
|
|
836
853
|
if (!data) throw threadNotFoundError(threadIdOrRemoteId, "archiving it");
|
|
837
854
|
if (data.status !== "regular") {
|
|
838
855
|
throw threadStatusError(threadIdOrRemoteId, data.status, "be archived");
|
|
839
856
|
}
|
|
840
857
|
await ensureNotMain(data.id);
|
|
858
|
+
requireAdapterGeneration(adapterGeneration);
|
|
841
859
|
return store.optimisticUpdate({
|
|
842
860
|
execute: async () => {
|
|
843
861
|
const { remoteId } = await data.initializeTask;
|
|
844
|
-
|
|
862
|
+
requireAdapterGeneration(adapterGeneration);
|
|
863
|
+
return currentAdapter.archive(remoteId);
|
|
845
864
|
},
|
|
846
865
|
optimistic: (state) => updateStatusReducer(state, data.id, "archived"),
|
|
847
866
|
});
|
|
848
867
|
},
|
|
849
|
-
[ensureNotMain, session, store],
|
|
868
|
+
[ensureNotMain, requireAdapterGeneration, session, store],
|
|
850
869
|
);
|
|
851
870
|
|
|
852
871
|
const unarchive = useCallback(
|
|
853
872
|
(threadIdOrRemoteId: string) => {
|
|
873
|
+
const currentAdapter = session.adapter;
|
|
874
|
+
const adapterGeneration = session.adapterGeneration;
|
|
854
875
|
const data = getThreadData(store.value, threadIdOrRemoteId);
|
|
855
876
|
if (!data)
|
|
856
877
|
throw threadNotFoundError(threadIdOrRemoteId, "unarchiving it");
|
|
@@ -865,36 +886,43 @@ const useRemoteThreadList = (
|
|
|
865
886
|
execute: async () => {
|
|
866
887
|
try {
|
|
867
888
|
const { remoteId } = await data.initializeTask;
|
|
868
|
-
|
|
889
|
+
requireAdapterGeneration(adapterGeneration);
|
|
890
|
+
return await currentAdapter.unarchive(remoteId);
|
|
869
891
|
} catch (error) {
|
|
870
|
-
|
|
892
|
+
if (adapterGeneration === session.adapterGeneration) {
|
|
893
|
+
await ensureNotMain(data.id);
|
|
894
|
+
}
|
|
871
895
|
throw error;
|
|
872
896
|
}
|
|
873
897
|
},
|
|
874
898
|
optimistic: (state) => updateStatusReducer(state, data.id, "regular"),
|
|
875
899
|
});
|
|
876
900
|
},
|
|
877
|
-
[ensureNotMain, session, store],
|
|
901
|
+
[ensureNotMain, requireAdapterGeneration, session, store],
|
|
878
902
|
);
|
|
879
903
|
|
|
880
904
|
const deleteThread = useCallback(
|
|
881
905
|
async (threadIdOrRemoteId: string) => {
|
|
906
|
+
const currentAdapter = session.adapter;
|
|
907
|
+
const adapterGeneration = session.adapterGeneration;
|
|
882
908
|
const data = getThreadData(store.value, threadIdOrRemoteId);
|
|
883
909
|
if (!data) throw threadNotFoundError(threadIdOrRemoteId, "deleting it");
|
|
884
910
|
if (data.status !== "regular" && data.status !== "archived") {
|
|
885
911
|
throw threadStatusError(threadIdOrRemoteId, data.status, "be deleted");
|
|
886
912
|
}
|
|
887
913
|
await ensureNotMain(data.id);
|
|
914
|
+
requireAdapterGeneration(adapterGeneration);
|
|
888
915
|
onDelete?.(data.id);
|
|
889
916
|
return store.optimisticUpdate({
|
|
890
917
|
execute: async () => {
|
|
891
918
|
const { remoteId } = await data.initializeTask;
|
|
892
|
-
|
|
919
|
+
requireAdapterGeneration(adapterGeneration);
|
|
920
|
+
return currentAdapter.delete(remoteId);
|
|
893
921
|
},
|
|
894
922
|
optimistic: (state) => updateStatusReducer(state, data.id, "deleted"),
|
|
895
923
|
});
|
|
896
924
|
},
|
|
897
|
-
[ensureNotMain, onDelete, session, store],
|
|
925
|
+
[ensureNotMain, onDelete, requireAdapterGeneration, session, store],
|
|
898
926
|
);
|
|
899
927
|
|
|
900
928
|
const generateTitle = useCallback(
|
|
@@ -902,6 +930,8 @@ const useRemoteThreadList = (
|
|
|
902
930
|
threadIdOrRemoteId: string,
|
|
903
931
|
messages: readonly ThreadMessage[] | undefined,
|
|
904
932
|
) => {
|
|
933
|
+
const currentAdapter = session.adapter;
|
|
934
|
+
const adapterGeneration = session.adapterGeneration;
|
|
905
935
|
const data = getThreadData(store.value, threadIdOrRemoteId);
|
|
906
936
|
if (!data) {
|
|
907
937
|
throw threadNotFoundError(threadIdOrRemoteId, "generating its title");
|
|
@@ -914,26 +944,33 @@ const useRemoteThreadList = (
|
|
|
914
944
|
);
|
|
915
945
|
}
|
|
916
946
|
const { remoteId } = await data.initializeTask;
|
|
947
|
+
requireAdapterGeneration(adapterGeneration);
|
|
917
948
|
if (!isSameThread(store.value, data.id, session.mainThreadId)) return;
|
|
918
949
|
if (!messages) return;
|
|
919
|
-
const stream = await
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
...
|
|
930
|
-
|
|
931
|
-
|
|
950
|
+
const stream = await currentAdapter.generateTitle(remoteId, messages);
|
|
951
|
+
requireAdapterGeneration(adapterGeneration);
|
|
952
|
+
await applyTitleStream(stream, async (newTitle) => {
|
|
953
|
+
await store.optimisticUpdate({
|
|
954
|
+
execute: async () => {},
|
|
955
|
+
optimistic: (state) => {
|
|
956
|
+
if (adapterGeneration !== session.adapterGeneration) return state;
|
|
957
|
+
const current = getThreadData(state, data.id);
|
|
958
|
+
if (!current) return state;
|
|
959
|
+
return {
|
|
960
|
+
...state,
|
|
961
|
+
threadData: {
|
|
962
|
+
...state.threadData,
|
|
963
|
+
[current.id]: {
|
|
964
|
+
...current,
|
|
965
|
+
title: newTitle,
|
|
966
|
+
},
|
|
967
|
+
},
|
|
968
|
+
};
|
|
932
969
|
},
|
|
933
970
|
});
|
|
934
971
|
});
|
|
935
972
|
},
|
|
936
|
-
[session, store],
|
|
973
|
+
[requireAdapterGeneration, session, store],
|
|
937
974
|
);
|
|
938
975
|
|
|
939
976
|
const { mainThreadClient, itemOrder, threadListItems } =
|
|
@@ -17,8 +17,7 @@ import { toJSONSchema, toPartialJSONSchema } from "assistant-stream";
|
|
|
17
17
|
import { ModelContext } from "../../store";
|
|
18
18
|
import { buildInteractableModelContext } from "./interactable-model-context";
|
|
19
19
|
import { notifySubscribers as notifyStateSubscribers } from "../../subscribable/subscribable";
|
|
20
|
-
|
|
21
|
-
const PERSISTENCE_DEBOUNCE_MS = 500;
|
|
20
|
+
import { useInteractablePersistenceQueue } from "../interactables-shared/useInteractablePersistenceQueue";
|
|
22
21
|
|
|
23
22
|
const useInteractables = (): ClientOutput<"interactables"> => {
|
|
24
23
|
const [state, setState] = useState<InteractablesState>(() => ({
|
|
@@ -48,26 +47,6 @@ const useInteractables = (): ClientOutput<"interactables"> => {
|
|
|
48
47
|
const adapterRef = useRef<InteractablePersistenceAdapter | undefined>(
|
|
49
48
|
undefined,
|
|
50
49
|
);
|
|
51
|
-
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(
|
|
52
|
-
undefined,
|
|
53
|
-
);
|
|
54
|
-
const syncSeqRef = useRef(0);
|
|
55
|
-
const latestSyncSeqByIdRef = useRef(new Map<string, number>());
|
|
56
|
-
const inFlightPersistenceRef = useRef(0);
|
|
57
|
-
const flushResolversRef = useRef<Array<() => void>>([]);
|
|
58
|
-
const dirtyIdsRef = useRef(new Set<string>());
|
|
59
|
-
|
|
60
|
-
type PersistenceBatch = {
|
|
61
|
-
adapter: InteractablePersistenceAdapter;
|
|
62
|
-
payload: InteractablePersistedState;
|
|
63
|
-
dirtyIds: Set<string>;
|
|
64
|
-
seq: number;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const outgoingQueueRef = useRef<PersistenceBatch[]>([]);
|
|
68
|
-
const runPersistenceRef = useRef<(batch?: PersistenceBatch) => void>(
|
|
69
|
-
() => {},
|
|
70
|
-
);
|
|
71
50
|
|
|
72
51
|
const exportState = useCallback((): InteractablePersistedState => {
|
|
73
52
|
const result: InteractablePersistedState = {};
|
|
@@ -77,139 +56,28 @@ const useInteractables = (): ClientOutput<"interactables"> => {
|
|
|
77
56
|
return result;
|
|
78
57
|
}, []);
|
|
79
58
|
|
|
80
|
-
const
|
|
81
|
-
(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const enqueuePersistence = useCallback(
|
|
93
|
-
(adapter: InteractablePersistenceAdapter) => {
|
|
94
|
-
const batch = takeDirtyBatch(adapter);
|
|
95
|
-
if (!batch) return;
|
|
96
|
-
if (inFlightPersistenceRef.current === 0) {
|
|
97
|
-
runPersistenceRef.current(batch);
|
|
98
|
-
} else {
|
|
99
|
-
outgoingQueueRef.current.push(batch);
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
[takeDirtyBatch],
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
const runPersistence = useCallback(
|
|
106
|
-
async (batch?: PersistenceBatch) => {
|
|
107
|
-
const resolved =
|
|
108
|
-
batch ??
|
|
109
|
-
(adapterRef.current ? takeDirtyBatch(adapterRef.current) : undefined);
|
|
110
|
-
if (!resolved) {
|
|
111
|
-
if (inFlightPersistenceRef.current === 0) {
|
|
112
|
-
for (const resolve of flushResolversRef.current) resolve();
|
|
113
|
-
flushResolversRef.current = [];
|
|
114
|
-
}
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const { adapter, payload, dirtyIds, seq } = resolved;
|
|
119
|
-
inFlightPersistenceRef.current += 1;
|
|
120
|
-
|
|
121
|
-
setStateAndRef((prev) => ({
|
|
122
|
-
...prev,
|
|
123
|
-
persistence: {
|
|
124
|
-
...prev.persistence,
|
|
125
|
-
...Object.fromEntries(
|
|
126
|
-
[...dirtyIds].map((id) => [
|
|
127
|
-
id,
|
|
128
|
-
{ isPending: true, error: undefined },
|
|
129
|
-
]),
|
|
130
|
-
),
|
|
131
|
-
},
|
|
132
|
-
}));
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
await adapter.save(payload);
|
|
136
|
-
setStateAndRef((prev) => {
|
|
137
|
-
let changed = false;
|
|
138
|
-
const persistence = { ...prev.persistence };
|
|
139
|
-
for (const id of dirtyIds) {
|
|
140
|
-
if (
|
|
141
|
-
latestSyncSeqByIdRef.current.get(id) !== seq ||
|
|
142
|
-
dirtyIdsRef.current.has(id)
|
|
143
|
-
)
|
|
144
|
-
continue;
|
|
145
|
-
latestSyncSeqByIdRef.current.delete(id);
|
|
146
|
-
delete persistence[id];
|
|
147
|
-
changed = true;
|
|
148
|
-
}
|
|
149
|
-
return changed ? { ...prev, persistence } : prev;
|
|
150
|
-
});
|
|
151
|
-
} catch (e) {
|
|
152
|
-
setStateAndRef((prev) => {
|
|
153
|
-
let changed = false;
|
|
154
|
-
const persistence = { ...prev.persistence };
|
|
155
|
-
for (const id of dirtyIds) {
|
|
156
|
-
if (
|
|
157
|
-
latestSyncSeqByIdRef.current.get(id) !== seq ||
|
|
158
|
-
dirtyIdsRef.current.has(id)
|
|
159
|
-
)
|
|
160
|
-
continue;
|
|
161
|
-
latestSyncSeqByIdRef.current.delete(id);
|
|
162
|
-
persistence[id] = { isPending: false, error: e };
|
|
163
|
-
changed = true;
|
|
164
|
-
}
|
|
165
|
-
return changed ? { ...prev, persistence } : prev;
|
|
166
|
-
});
|
|
167
|
-
} finally {
|
|
168
|
-
inFlightPersistenceRef.current -= 1;
|
|
169
|
-
const next =
|
|
170
|
-
outgoingQueueRef.current.shift() ??
|
|
171
|
-
(adapterRef.current && dirtyIdsRef.current.size > 0
|
|
172
|
-
? takeDirtyBatch(adapterRef.current)
|
|
173
|
-
: undefined);
|
|
174
|
-
if (next) {
|
|
175
|
-
if (debounceTimerRef.current !== undefined) {
|
|
176
|
-
clearTimeout(debounceTimerRef.current);
|
|
177
|
-
debounceTimerRef.current = undefined;
|
|
178
|
-
}
|
|
179
|
-
runPersistenceRef.current(next);
|
|
180
|
-
} else if (inFlightPersistenceRef.current === 0) {
|
|
181
|
-
for (const resolve of flushResolversRef.current) resolve();
|
|
182
|
-
flushResolversRef.current = [];
|
|
183
|
-
}
|
|
184
|
-
}
|
|
59
|
+
const updatePersistenceStatus = useCallback(
|
|
60
|
+
(
|
|
61
|
+
updater: (
|
|
62
|
+
prev: InteractablesState["persistence"],
|
|
63
|
+
) => InteractablesState["persistence"],
|
|
64
|
+
) => {
|
|
65
|
+
setStateAndRef((prev) => {
|
|
66
|
+
const persistence = updater(prev.persistence);
|
|
67
|
+
return persistence === prev.persistence
|
|
68
|
+
? prev
|
|
69
|
+
: { ...prev, persistence };
|
|
70
|
+
});
|
|
185
71
|
},
|
|
186
|
-
[setStateAndRef
|
|
72
|
+
[setStateAndRef],
|
|
187
73
|
);
|
|
188
|
-
runPersistenceRef.current = (nextBatch) => {
|
|
189
|
-
void runPersistence(nextBatch);
|
|
190
|
-
};
|
|
191
74
|
|
|
192
|
-
const schedulePersistence =
|
|
193
|
-
(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
debounceTimerRef.current = setTimeout(() => {
|
|
200
|
-
debounceTimerRef.current = undefined;
|
|
201
|
-
if (inFlightPersistenceRef.current === 0 && adapterRef.current) {
|
|
202
|
-
enqueuePersistence(adapterRef.current);
|
|
203
|
-
} else {
|
|
204
|
-
debounceTimerRef.current = setTimeout(() => {
|
|
205
|
-
debounceTimerRef.current = undefined;
|
|
206
|
-
if (adapterRef.current) enqueuePersistence(adapterRef.current);
|
|
207
|
-
}, PERSISTENCE_DEBOUNCE_MS);
|
|
208
|
-
}
|
|
209
|
-
}, PERSISTENCE_DEBOUNCE_MS);
|
|
210
|
-
},
|
|
211
|
-
[enqueuePersistence],
|
|
212
|
-
);
|
|
75
|
+
const { flushIfPending, schedulePersistence, flush } =
|
|
76
|
+
useInteractablePersistenceQueue({
|
|
77
|
+
adapterRef,
|
|
78
|
+
snapshot: exportState,
|
|
79
|
+
updatePersistenceStatus,
|
|
80
|
+
});
|
|
213
81
|
|
|
214
82
|
const importState = useCallback(
|
|
215
83
|
(saved: InteractablePersistedState) => {
|
|
@@ -231,31 +99,6 @@ const useInteractables = (): ClientOutput<"interactables"> => {
|
|
|
231
99
|
[setStateAndRef],
|
|
232
100
|
);
|
|
233
101
|
|
|
234
|
-
const flushIfPending = useCallback(() => {
|
|
235
|
-
if (debounceTimerRef.current !== undefined) {
|
|
236
|
-
clearTimeout(debounceTimerRef.current);
|
|
237
|
-
debounceTimerRef.current = undefined;
|
|
238
|
-
}
|
|
239
|
-
if (adapterRef.current) enqueuePersistence(adapterRef.current);
|
|
240
|
-
}, [enqueuePersistence]);
|
|
241
|
-
|
|
242
|
-
const flush = useCallback(async () => {
|
|
243
|
-
if (debounceTimerRef.current !== undefined) {
|
|
244
|
-
clearTimeout(debounceTimerRef.current);
|
|
245
|
-
debounceTimerRef.current = undefined;
|
|
246
|
-
}
|
|
247
|
-
const hasWork =
|
|
248
|
-
inFlightPersistenceRef.current > 0 ||
|
|
249
|
-
dirtyIdsRef.current.size > 0 ||
|
|
250
|
-
outgoingQueueRef.current.length > 0;
|
|
251
|
-
if (!hasWork) return;
|
|
252
|
-
const p = new Promise<void>((resolve) => {
|
|
253
|
-
flushResolversRef.current.push(resolve);
|
|
254
|
-
});
|
|
255
|
-
if (adapterRef.current) enqueuePersistence(adapterRef.current);
|
|
256
|
-
return p;
|
|
257
|
-
}, [enqueuePersistence]);
|
|
258
|
-
|
|
259
102
|
const setPersistenceAdapter = useCallback(
|
|
260
103
|
(adapter: InteractablePersistenceAdapter | undefined) => {
|
|
261
104
|
if (adapterRef.current !== adapter) flushIfPending();
|