@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
|
@@ -16,9 +16,13 @@ import type {
|
|
|
16
16
|
import {
|
|
17
17
|
getExternalStoreMessages,
|
|
18
18
|
bindExternalStoreMessage,
|
|
19
|
+
FALLBACK_ID_PREFIX,
|
|
19
20
|
} from "../../runtime/utils/external-store-message";
|
|
20
21
|
import { ThreadMessageConverter } from "./thread-message-converter";
|
|
21
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
getContentAutoStatus,
|
|
24
|
+
isAutoStatus,
|
|
25
|
+
} from "../../runtime/utils/auto-status";
|
|
22
26
|
import {
|
|
23
27
|
fromThreadMessageLike,
|
|
24
28
|
type ThreadMessageLike,
|
|
@@ -39,8 +43,11 @@ import {
|
|
|
39
43
|
MessageRepository,
|
|
40
44
|
} from "../../runtime/utils/message-repository";
|
|
41
45
|
import { generateId } from "../../utils/id";
|
|
42
|
-
import {
|
|
43
|
-
|
|
46
|
+
import {
|
|
47
|
+
ToolInvocationTracker,
|
|
48
|
+
type ToolExecutionStatus,
|
|
49
|
+
} from "../tool-invocations/ToolInvocationTracker";
|
|
50
|
+
import { EMPTY_QUEUE_ITEMS } from "../../runtime/queue/queue-item";
|
|
44
51
|
import type { QuoteInfo } from "../../types/quote";
|
|
45
52
|
import {
|
|
46
53
|
captureThreadRuntimeGeneration,
|
|
@@ -110,6 +117,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
110
117
|
}
|
|
111
118
|
// Unlike `isLoading`: pass `undefined` through to preserve the `getThreadState` fallback.
|
|
112
119
|
public get isRunning(): boolean | undefined {
|
|
120
|
+
if (this._hasExecutingTools(this._store)) return true;
|
|
113
121
|
return this._store.isRunning;
|
|
114
122
|
}
|
|
115
123
|
|
|
@@ -136,6 +144,16 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
136
144
|
|
|
137
145
|
private _converter = new ThreadMessageConverter();
|
|
138
146
|
|
|
147
|
+
// Ids the host was asked to delete via onDelete. The snapshot pass evicts
|
|
148
|
+
// them from the repository once the host's array no longer carries them;
|
|
149
|
+
// an id the host kept is dropped from the set without eviction.
|
|
150
|
+
// Branch-changing mutations (edit, branch switch, reload) invalidate the
|
|
151
|
+
// set, because after them the incoming array omits off-branch ids for
|
|
152
|
+
// reasons unrelated to deletion. Plain tail sends do not clear: a tail
|
|
153
|
+
// append cannot make a visible id absent, so id-absence stays unambiguous
|
|
154
|
+
// and a delete whose confirmation races a send keeps its eviction.
|
|
155
|
+
private _pendingDeleteEvictions = new Set<string>();
|
|
156
|
+
|
|
139
157
|
private _store!: ExternalStoreAdapter<any>;
|
|
140
158
|
|
|
141
159
|
private _getInitializePromise?: () => Promise<unknown> | undefined;
|
|
@@ -153,6 +171,51 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
153
171
|
* snapshot — only when `adapter.unstable_enableToolInvocations === true`.
|
|
154
172
|
*/
|
|
155
173
|
private _toolInvocations: ToolInvocationTracker | null = null;
|
|
174
|
+
private _toolStatuses: ReadonlyMap<string, ToolExecutionStatus> = new Map();
|
|
175
|
+
private _effectiveIsRunning = false;
|
|
176
|
+
private _inTrackerUpdate = false;
|
|
177
|
+
private _pendingRunningRefresh = false;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Tracker mutations initiated by this class (setState, reset) can publish
|
|
181
|
+
* status changes synchronously. Re-entering the snapshot pipeline from
|
|
182
|
+
* inside them would feed the tracker a stale snapshot and consume the
|
|
183
|
+
* restore arming a reset just installed, so the running refresh is
|
|
184
|
+
* deferred until the mutation returns and stays off the tracker.
|
|
185
|
+
*/
|
|
186
|
+
private _runTrackerUpdate(fn: () => void): void {
|
|
187
|
+
this._inTrackerUpdate = true;
|
|
188
|
+
try {
|
|
189
|
+
fn();
|
|
190
|
+
} finally {
|
|
191
|
+
this._inTrackerUpdate = false;
|
|
192
|
+
}
|
|
193
|
+
if (this._pendingRunningRefresh) {
|
|
194
|
+
this._pendingRunningRefresh = false;
|
|
195
|
+
this._refreshEffectiveIsRunning();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private _refreshEffectiveIsRunning(): void {
|
|
200
|
+
const isRunning = this._getEffectiveIsRunning(this._store);
|
|
201
|
+
if (this._effectiveIsRunning === isRunning) return;
|
|
202
|
+
this._effectiveIsRunning = isRunning;
|
|
203
|
+
this._notifyEventSubscribers(isRunning ? "runStart" : "runEnd", {});
|
|
204
|
+
this._notifySubscribers();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private _hasExecutingTools(store: ExternalStoreAdapter<any>): boolean {
|
|
208
|
+
if (store.unstable_enableToolInvocations !== true) return false;
|
|
209
|
+
if (this._toolInvocations === null) return false;
|
|
210
|
+
for (const status of this._toolStatuses.values()) {
|
|
211
|
+
if (status.type === "executing") return true;
|
|
212
|
+
}
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private _getEffectiveIsRunning(store: ExternalStoreAdapter<any>): boolean {
|
|
217
|
+
return (store.isRunning ?? false) || this._hasExecutingTools(store);
|
|
218
|
+
}
|
|
156
219
|
|
|
157
220
|
public override beginEdit(messageId: string) {
|
|
158
221
|
if (!this._store.onEdit)
|
|
@@ -172,12 +235,17 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
172
235
|
public __internal_setAdapter(store: ExternalStoreAdapter<any>) {
|
|
173
236
|
if (this._store === store) return;
|
|
174
237
|
|
|
175
|
-
|
|
238
|
+
this._updateStoreSnapshot(store);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private _updateStoreSnapshot(store: ExternalStoreAdapter<any>) {
|
|
242
|
+
const previousIsRunning = this._effectiveIsRunning;
|
|
176
243
|
this.isDisabled = store.isDisabled ?? false;
|
|
177
244
|
this.isSendDisabled = store.isSendDisabled ?? false;
|
|
178
245
|
|
|
179
246
|
const oldStore = this._store as ExternalStoreAdapter<any> | undefined;
|
|
180
247
|
this._store = store;
|
|
248
|
+
const isRunning = this._getEffectiveIsRunning(store);
|
|
181
249
|
if (oldStore?.queue !== store.queue) {
|
|
182
250
|
this._transformedQueue = undefined;
|
|
183
251
|
store.queue?.__internal_setDispatchTransform?.((message) => {
|
|
@@ -229,7 +297,8 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
229
297
|
if (
|
|
230
298
|
oldStore &&
|
|
231
299
|
oldStore.isRunning === store.isRunning &&
|
|
232
|
-
oldStore.messageRepository === store.messageRepository
|
|
300
|
+
oldStore.messageRepository === store.messageRepository &&
|
|
301
|
+
previousIsRunning === isRunning
|
|
233
302
|
) {
|
|
234
303
|
this._notifySubscribers();
|
|
235
304
|
return;
|
|
@@ -252,6 +321,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
252
321
|
this.repository.deleteMessage(message.id);
|
|
253
322
|
}
|
|
254
323
|
}
|
|
324
|
+
this._pendingDeleteEvictions.clear();
|
|
255
325
|
this.repository.resetHead(headId);
|
|
256
326
|
messages = this.repository.getMessages();
|
|
257
327
|
}
|
|
@@ -264,7 +334,8 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
264
334
|
this._converter = new ThreadMessageConverter();
|
|
265
335
|
} else if (
|
|
266
336
|
oldStore.isRunning === store.isRunning &&
|
|
267
|
-
oldStore.messages === store.messages
|
|
337
|
+
oldStore.messages === store.messages &&
|
|
338
|
+
previousIsRunning === isRunning
|
|
268
339
|
) {
|
|
269
340
|
this._notifySubscribers();
|
|
270
341
|
// no conversion update
|
|
@@ -278,27 +349,31 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
278
349
|
if (!store.convertMessage) return m;
|
|
279
350
|
|
|
280
351
|
const isLast = idx === (store.messages?.length ?? 0) - 1;
|
|
281
|
-
const
|
|
282
|
-
isLast,
|
|
283
|
-
isRunning,
|
|
284
|
-
false,
|
|
285
|
-
false,
|
|
286
|
-
undefined,
|
|
287
|
-
);
|
|
352
|
+
const fallbackId = `${FALLBACK_ID_PREFIX}${idx}`;
|
|
288
353
|
|
|
289
354
|
if (
|
|
290
355
|
cache &&
|
|
291
356
|
(cache.role !== "assistant" ||
|
|
292
357
|
!isAutoStatus(cache.status) ||
|
|
293
|
-
cache.status ===
|
|
294
|
-
|
|
358
|
+
cache.status ===
|
|
359
|
+
getContentAutoStatus(cache.content, isLast, isRunning))
|
|
360
|
+
) {
|
|
361
|
+
if (
|
|
362
|
+
cache.id.startsWith(FALLBACK_ID_PREFIX) &&
|
|
363
|
+
cache.id !== fallbackId
|
|
364
|
+
) {
|
|
365
|
+
const updated = { ...cache, id: fallbackId };
|
|
366
|
+
bindExternalStoreMessage(updated, m);
|
|
367
|
+
return updated;
|
|
368
|
+
}
|
|
295
369
|
return cache;
|
|
370
|
+
}
|
|
296
371
|
|
|
297
372
|
const messageLike = store.convertMessage(m, idx);
|
|
298
373
|
const newMessage = fromThreadMessageLike(
|
|
299
374
|
messageLike,
|
|
300
|
-
|
|
301
|
-
|
|
375
|
+
fallbackId,
|
|
376
|
+
getContentAutoStatus(messageLike.content, isLast, isRunning),
|
|
302
377
|
);
|
|
303
378
|
bindExternalStoreMessage(newMessage, m);
|
|
304
379
|
return newMessage;
|
|
@@ -324,6 +399,20 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
324
399
|
const parent = messages[i - 1];
|
|
325
400
|
this.repository.addOrUpdateMessage(parent?.id ?? null, message);
|
|
326
401
|
}
|
|
402
|
+
|
|
403
|
+
if (this._pendingDeleteEvictions.size > 0) {
|
|
404
|
+
const incomingIds = new Set(messages.map((m) => m.id));
|
|
405
|
+
for (const id of this._pendingDeleteEvictions) {
|
|
406
|
+
this._pendingDeleteEvictions.delete(id);
|
|
407
|
+
if (incomingIds.has(id)) continue;
|
|
408
|
+
try {
|
|
409
|
+
this.repository.getMessage(id);
|
|
410
|
+
} catch {
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
this.repository.deleteMessage(id);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
327
416
|
} else {
|
|
328
417
|
throw new Error(
|
|
329
418
|
"ExternalStoreAdapter must provide either 'messages' or 'messageRepository'",
|
|
@@ -333,8 +422,9 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
333
422
|
// Common logic for both paths
|
|
334
423
|
if (messages.length > 0) this.ensureInitialized();
|
|
335
424
|
|
|
336
|
-
|
|
337
|
-
|
|
425
|
+
this._effectiveIsRunning = isRunning;
|
|
426
|
+
if (previousIsRunning !== isRunning) {
|
|
427
|
+
if (isRunning) {
|
|
338
428
|
this._notifyEventSubscribers("runStart", {});
|
|
339
429
|
} else {
|
|
340
430
|
this._notifyEventSubscribers("runEnd", {});
|
|
@@ -361,7 +451,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
361
451
|
|
|
362
452
|
this._messages = this.repository.getMessages();
|
|
363
453
|
|
|
364
|
-
this._driveToolInvocations();
|
|
454
|
+
this._runTrackerUpdate(() => this._driveToolInvocations());
|
|
365
455
|
|
|
366
456
|
this._notifySubscribers();
|
|
367
457
|
}
|
|
@@ -380,6 +470,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
380
470
|
if (this._toolInvocations) {
|
|
381
471
|
this._toolInvocations.reset();
|
|
382
472
|
this._toolInvocations = null;
|
|
473
|
+
this._toolStatuses = new Map();
|
|
383
474
|
this._store.setToolStatuses?.({});
|
|
384
475
|
}
|
|
385
476
|
return;
|
|
@@ -423,7 +514,19 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
423
514
|
}
|
|
424
515
|
},
|
|
425
516
|
onStatusesChange: (statuses) => {
|
|
426
|
-
this.
|
|
517
|
+
const hadExecutingTools = this._hasExecutingTools(this._store);
|
|
518
|
+
this._toolStatuses = statuses;
|
|
519
|
+
try {
|
|
520
|
+
this._store.setToolStatuses?.(Object.fromEntries(statuses));
|
|
521
|
+
} finally {
|
|
522
|
+
if (hadExecutingTools !== this._hasExecutingTools(this._store)) {
|
|
523
|
+
if (this._inTrackerUpdate) {
|
|
524
|
+
this._pendingRunningRefresh = true;
|
|
525
|
+
} else {
|
|
526
|
+
this._updateStoreSnapshot(this._store);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
427
530
|
},
|
|
428
531
|
},
|
|
429
532
|
);
|
|
@@ -431,7 +534,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
431
534
|
|
|
432
535
|
this._toolInvocations.setState({
|
|
433
536
|
messages: this._messages,
|
|
434
|
-
isRunning: this._store
|
|
537
|
+
isRunning: this._getEffectiveIsRunning(this._store),
|
|
435
538
|
...(this._store.isLoading !== undefined && {
|
|
436
539
|
isLoading: this._store.isLoading,
|
|
437
540
|
}),
|
|
@@ -475,7 +578,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
475
578
|
throw new Error("Runtime does not support switching branches.");
|
|
476
579
|
|
|
477
580
|
// Silently ignore branch switches while running
|
|
478
|
-
if (this._store
|
|
581
|
+
if (this._getEffectiveIsRunning(this._store)) {
|
|
479
582
|
return;
|
|
480
583
|
}
|
|
481
584
|
|
|
@@ -485,6 +588,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
485
588
|
: null;
|
|
486
589
|
|
|
487
590
|
this.repository.switchToBranch(branchId);
|
|
591
|
+
this._pendingDeleteEvictions.clear();
|
|
488
592
|
this.updateMessages(this.repository.getMessages());
|
|
489
593
|
if (onBranchChange) {
|
|
490
594
|
this._notifyBranchChange(previousHeadId, onBranchChange);
|
|
@@ -547,7 +651,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
547
651
|
// Buffering does not start a run, so the tool-abort below must wait
|
|
548
652
|
// until the queue flushes. By then the prior run (and its tools) has
|
|
549
653
|
// settled.
|
|
550
|
-
if (message.steer ?? this._store
|
|
654
|
+
if (message.steer ?? this._getEffectiveIsRunning(this._store))
|
|
551
655
|
this._store.queue.steer(message);
|
|
552
656
|
else this._store.queue.enqueue(message);
|
|
553
657
|
return;
|
|
@@ -574,6 +678,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
574
678
|
if (isEdit) {
|
|
575
679
|
if (!this._store.onEdit)
|
|
576
680
|
throw new Error("Runtime does not support editing messages.");
|
|
681
|
+
this._pendingDeleteEvictions.clear();
|
|
577
682
|
await this._store.onEdit(message);
|
|
578
683
|
} else {
|
|
579
684
|
await this._store.onNew(message);
|
|
@@ -582,14 +687,29 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
582
687
|
|
|
583
688
|
public async deleteMessage(messageId: string): Promise<void> {
|
|
584
689
|
if (this._store.onDelete) {
|
|
585
|
-
|
|
690
|
+
// The host owns deletion here, and it may decline (fail a server call,
|
|
691
|
+
// cancel a confirm dialog, ignore an off-branch id). The eviction is
|
|
692
|
+
// therefore deferred to the snapshot pass, which evicts only once the
|
|
693
|
+
// host's own array no longer carries the id. Registered before the
|
|
694
|
+
// callback because an optimistic host publishes that snapshot while
|
|
695
|
+
// the callback is still awaited.
|
|
696
|
+
const wasVisible = this.repository
|
|
697
|
+
.getMessages()
|
|
698
|
+
.some((m) => m.id === messageId);
|
|
699
|
+
if (wasVisible) this._pendingDeleteEvictions.add(messageId);
|
|
700
|
+
try {
|
|
701
|
+
await this._store.onDelete(messageId);
|
|
702
|
+
} catch (error) {
|
|
703
|
+
this._pendingDeleteEvictions.delete(messageId);
|
|
704
|
+
throw error;
|
|
705
|
+
}
|
|
586
706
|
return;
|
|
587
707
|
}
|
|
588
708
|
|
|
589
709
|
if (!this._store.setMessages)
|
|
590
710
|
throw new Error("Runtime does not support deleting messages.");
|
|
591
711
|
|
|
592
|
-
if (this._store
|
|
712
|
+
if (this._getEffectiveIsRunning(this._store)) {
|
|
593
713
|
await this._toolInvocations?.abort();
|
|
594
714
|
}
|
|
595
715
|
|
|
@@ -597,7 +717,32 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
597
717
|
const messageIndex = messages.findIndex((m) => m.id === messageId);
|
|
598
718
|
if (messageIndex === -1) throw new Error("Message not found.");
|
|
599
719
|
|
|
720
|
+
this._pendingDeleteEvictions.clear();
|
|
600
721
|
this.updateMessages(messages.filter((message) => message.id !== messageId));
|
|
722
|
+
this._evictDeletedMessage(messageId);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// The snapshot pass only relinks incoming messages; it never evicts, so
|
|
726
|
+
// without this the deleted message survives as a sibling branch that the
|
|
727
|
+
// branch picker can resurrect into the host store. `_messages` is refreshed
|
|
728
|
+
// before notifying so `messages` and the branch graph agree at notify time,
|
|
729
|
+
// mirroring the end of the snapshot pass.
|
|
730
|
+
private _evictDeletedMessage(messageId: string) {
|
|
731
|
+
// Positional fallback ids are remapped in the snapshot pass; evicting
|
|
732
|
+
// them first leaves the pre-renumber node as a sibling of the new head.
|
|
733
|
+
if (messageId.startsWith(FALLBACK_ID_PREFIX)) return;
|
|
734
|
+
// A synchronous host update (e.g. a setMessages that re-entered the
|
|
735
|
+
// snapshot pass) may have evicted the message already; only that case is
|
|
736
|
+
// skipped, so genuine repository errors still propagate.
|
|
737
|
+
try {
|
|
738
|
+
this.repository.getMessage(messageId);
|
|
739
|
+
} catch {
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
this.repository.deleteMessage(messageId);
|
|
744
|
+
this._messages = this.repository.getMessages();
|
|
745
|
+
this._notifySubscribers();
|
|
601
746
|
}
|
|
602
747
|
|
|
603
748
|
public getQueueItems() {
|
|
@@ -622,6 +767,8 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
622
767
|
if (!this._store.onReload)
|
|
623
768
|
throw new Error("Runtime does not support reloading messages.");
|
|
624
769
|
|
|
770
|
+
this._pendingDeleteEvictions.clear();
|
|
771
|
+
|
|
625
772
|
// Auto-abort in-flight client-side tool executions when a run reloads;
|
|
626
773
|
// any results that land afterward would target a turn that no longer
|
|
627
774
|
// exists. See `append` above for full rationale.
|
|
@@ -655,7 +802,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
655
802
|
// back here via __internal_setAdapter. The tracker publishes the
|
|
656
803
|
// cleared status map itself, so adapter-side statuses reset only when
|
|
657
804
|
// the tracker is the source of truth.
|
|
658
|
-
this._toolInvocations?.reset();
|
|
805
|
+
this._runTrackerUpdate(() => this._toolInvocations?.reset());
|
|
659
806
|
|
|
660
807
|
this._store.onLoadExternalState(state);
|
|
661
808
|
}
|
|
@@ -666,7 +813,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
666
813
|
* without run-cancel semantics (`onCancel`, composer draft restoration).
|
|
667
814
|
*/
|
|
668
815
|
public unstable_notifySessionReset(): void {
|
|
669
|
-
this._toolInvocations?.reset();
|
|
816
|
+
this._runTrackerUpdate(() => this._toolInvocations?.reset());
|
|
670
817
|
this._store.queue?.__internal_notifyCancelled?.();
|
|
671
818
|
}
|
|
672
819
|
|
|
@@ -674,6 +821,8 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
674
821
|
if (!this._store.onCancel)
|
|
675
822
|
throw new Error("Runtime does not support cancelling runs.");
|
|
676
823
|
|
|
824
|
+
const generation = captureThreadRuntimeGeneration(this);
|
|
825
|
+
|
|
677
826
|
// Abort any in-flight client-side tool executions. Fire-and-forget —
|
|
678
827
|
// the abort resolves once executions settle, but we don't gate the
|
|
679
828
|
// cancel on it.
|
|
@@ -731,6 +880,8 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
731
880
|
// tick. Read the repository at flush time and re-apply the rollbacks to
|
|
732
881
|
// it, instead of stamping a snapshot captured above over the newer state.
|
|
733
882
|
setTimeout(() => {
|
|
883
|
+
if (!isThreadRuntimeGenerationCurrent(this, generation)) return;
|
|
884
|
+
|
|
734
885
|
this.dropEmptyOptimisticHead();
|
|
735
886
|
if (movedLeaf) {
|
|
736
887
|
const current = this.repository.getMessages();
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { BaseSubscribable } from "../../subscribable/subscribable";
|
|
3
|
+
import { ReadonlyThreadRuntimeCore } from "../readonly/ReadonlyThreadRuntimeCore";
|
|
4
|
+
import { EMPTY_THREAD_CORE } from "../remote-thread-list/empty-thread-core";
|
|
5
|
+
|
|
6
|
+
const READONLY_ERROR = /readonly thread/;
|
|
7
|
+
const EMPTY_ERROR = /empty thread/;
|
|
8
|
+
|
|
9
|
+
const SHARED_THROWING_METHODS = [
|
|
10
|
+
"switchToBranch",
|
|
11
|
+
"append",
|
|
12
|
+
"deleteMessage",
|
|
13
|
+
"startRun",
|
|
14
|
+
"resumeRun",
|
|
15
|
+
"unstable_notifySessionReset",
|
|
16
|
+
"addToolResult",
|
|
17
|
+
"resumeToolCall",
|
|
18
|
+
"respondToToolApproval",
|
|
19
|
+
"speak",
|
|
20
|
+
"connectVoice",
|
|
21
|
+
"muteVoice",
|
|
22
|
+
"unmuteVoice",
|
|
23
|
+
"submitFeedback",
|
|
24
|
+
"exportExternalState",
|
|
25
|
+
"importExternalState",
|
|
26
|
+
"beginEdit",
|
|
27
|
+
"import",
|
|
28
|
+
"reset",
|
|
29
|
+
] as const;
|
|
30
|
+
|
|
31
|
+
const SHARED_THROWING_COMPOSER_METHODS = [
|
|
32
|
+
"setText",
|
|
33
|
+
"setRole",
|
|
34
|
+
"setRunConfig",
|
|
35
|
+
"send",
|
|
36
|
+
"startDictation",
|
|
37
|
+
"setQuote",
|
|
38
|
+
] as const;
|
|
39
|
+
|
|
40
|
+
const SHARED_NOOP_COMPOSER_METHODS = [
|
|
41
|
+
"cancel",
|
|
42
|
+
"stopDictation",
|
|
43
|
+
"moveQueueItem",
|
|
44
|
+
"removeQueueItem",
|
|
45
|
+
] as const;
|
|
46
|
+
|
|
47
|
+
type AnyCore = Record<string, any>;
|
|
48
|
+
|
|
49
|
+
const cores = [
|
|
50
|
+
[
|
|
51
|
+
"ReadonlyThreadRuntimeCore",
|
|
52
|
+
() => new ReadonlyThreadRuntimeCore() as unknown as AnyCore,
|
|
53
|
+
READONLY_ERROR,
|
|
54
|
+
],
|
|
55
|
+
[
|
|
56
|
+
"EMPTY_THREAD_CORE",
|
|
57
|
+
() => EMPTY_THREAD_CORE as unknown as AnyCore,
|
|
58
|
+
EMPTY_ERROR,
|
|
59
|
+
],
|
|
60
|
+
] as const;
|
|
61
|
+
|
|
62
|
+
describe.each(cores)("%s shared inert surface", (_name, makeCore, error) => {
|
|
63
|
+
it.each(SHARED_THROWING_METHODS)("%s throws the core's error", (method) => {
|
|
64
|
+
expect(() => makeCore()[method]!()).toThrow(error);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it.each(SHARED_THROWING_COMPOSER_METHODS)(
|
|
68
|
+
"composer.%s throws the core's error",
|
|
69
|
+
(method) => {
|
|
70
|
+
expect(() => makeCore().composer[method]!()).toThrow(error);
|
|
71
|
+
},
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
it.each(["addAttachment", "removeAttachment"] as const)(
|
|
75
|
+
"composer.%s rejects with the core's error",
|
|
76
|
+
async (method) => {
|
|
77
|
+
await expect(makeCore().composer[method]!()).rejects.toThrow(error);
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
it.each(SHARED_NOOP_COMPOSER_METHODS)("composer.%s is a no-op", (method) => {
|
|
82
|
+
expect(() => makeCore().composer[method]!()).not.toThrow();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("composer.reset and composer.clearAttachments resolve without throwing", async () => {
|
|
86
|
+
const core = makeCore();
|
|
87
|
+
await expect(core.composer.reset()).resolves.toBeUndefined();
|
|
88
|
+
await expect(core.composer.clearAttachments()).resolves.toBeUndefined();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("reports every capability as disabled", () => {
|
|
92
|
+
expect(makeCore().capabilities).toEqual({
|
|
93
|
+
switchToBranch: false,
|
|
94
|
+
switchBranchDuringRun: false,
|
|
95
|
+
edit: false,
|
|
96
|
+
delete: false,
|
|
97
|
+
reload: false,
|
|
98
|
+
refetchThread: false,
|
|
99
|
+
cancel: false,
|
|
100
|
+
unstable_copy: false,
|
|
101
|
+
speech: false,
|
|
102
|
+
dictation: false,
|
|
103
|
+
voice: false,
|
|
104
|
+
attachments: false,
|
|
105
|
+
feedback: false,
|
|
106
|
+
queue: false,
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("exposes inert read-only state", () => {
|
|
111
|
+
const core = makeCore();
|
|
112
|
+
expect(core.getModelContext()).toEqual({});
|
|
113
|
+
expect(core.getEditComposer()).toBeUndefined();
|
|
114
|
+
expect(core.getVoiceVolume()).toBe(0);
|
|
115
|
+
expect(core.speech).toBeUndefined();
|
|
116
|
+
expect(core.voice).toBeUndefined();
|
|
117
|
+
expect(core.state).toBeNull();
|
|
118
|
+
expect(core.suggestions).toEqual([]);
|
|
119
|
+
expect(core.extras).toBeUndefined();
|
|
120
|
+
expect(core.isDisabled).toBe(false);
|
|
121
|
+
expect(core.isSendDisabled).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("exposes an empty composer that accepts everything and holds nothing", () => {
|
|
125
|
+
const composer = makeCore().composer;
|
|
126
|
+
expect(composer.attachmentAccept).toBe("*");
|
|
127
|
+
expect(composer.attachments).toEqual([]);
|
|
128
|
+
expect(composer.text).toBe("");
|
|
129
|
+
expect(composer.role).toBe("user");
|
|
130
|
+
expect(composer.runConfig).toEqual({});
|
|
131
|
+
expect(composer.queue).toEqual([]);
|
|
132
|
+
expect(composer.dictation).toBeUndefined();
|
|
133
|
+
expect(composer.quote).toBeUndefined();
|
|
134
|
+
expect(composer.isEmpty).toBe(true);
|
|
135
|
+
expect(composer.canSend).toBe(false);
|
|
136
|
+
expect(composer.canCancel).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("throws one stable error object from every mutator", () => {
|
|
140
|
+
const core = makeCore();
|
|
141
|
+
const thrown = SHARED_THROWING_METHODS.map((method) => {
|
|
142
|
+
try {
|
|
143
|
+
core[method]!();
|
|
144
|
+
return undefined;
|
|
145
|
+
} catch (e) {
|
|
146
|
+
return e;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
expect(thrown[0]).toBeInstanceOf(Error);
|
|
151
|
+
expect(thrown.every((e) => e === thrown[0])).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("throws a receiver TypeError when a mutator is detached", () => {
|
|
155
|
+
const detached = makeCore().append!;
|
|
156
|
+
expect(() => detached()).toThrow(TypeError);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("subscribeVoiceVolume and unstable_on hand back no-op unsubscribers", () => {
|
|
160
|
+
const core = makeCore();
|
|
161
|
+
expect(() => core.subscribeVoiceVolume(() => {})()).not.toThrow();
|
|
162
|
+
expect(() => core.unstable_on("runStart", () => {})()).not.toThrow();
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe("no-op versus throw divergences", () => {
|
|
167
|
+
it.each(["cancelRun", "stopSpeaking", "disconnectVoice"] as const)(
|
|
168
|
+
"%s is a no-op on the readonly core but throws on the empty core",
|
|
169
|
+
(method) => {
|
|
170
|
+
const readonly = new ReadonlyThreadRuntimeCore() as unknown as AnyCore;
|
|
171
|
+
expect(() => readonly[method]!()).not.toThrow();
|
|
172
|
+
expect(() =>
|
|
173
|
+
(EMPTY_THREAD_CORE as unknown as AnyCore)[method]!(),
|
|
174
|
+
).toThrow(EMPTY_ERROR);
|
|
175
|
+
},
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
it("readonly is not loading; empty is loading so it is not read as an empty conversation", () => {
|
|
179
|
+
expect(new ReadonlyThreadRuntimeCore().isLoading).toBe(false);
|
|
180
|
+
expect(EMPTY_THREAD_CORE.isLoading).toBe(true);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("readonly composer is not editing; empty composer is", () => {
|
|
184
|
+
expect(new ReadonlyThreadRuntimeCore().composer.isEditing).toBe(false);
|
|
185
|
+
expect(EMPTY_THREAD_CORE.composer.isEditing).toBe(true);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("the two cores carry distinct error objects and messages", () => {
|
|
189
|
+
const grab = (fn: () => void) => {
|
|
190
|
+
try {
|
|
191
|
+
fn();
|
|
192
|
+
return undefined;
|
|
193
|
+
} catch (e) {
|
|
194
|
+
return e as Error;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const readonlyError = grab(() => new ReadonlyThreadRuntimeCore().append());
|
|
199
|
+
const emptyError = grab(() =>
|
|
200
|
+
(EMPTY_THREAD_CORE as unknown as AnyCore).append!(),
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
expect(readonlyError).not.toBe(emptyError);
|
|
204
|
+
expect(readonlyError?.message).toBe(
|
|
205
|
+
"This is a readonly thread. You cannot perform mutations on readonly threads.",
|
|
206
|
+
);
|
|
207
|
+
expect(emptyError?.message).toMatch(/placeholder for the main thread/);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe("readonly notifications", () => {
|
|
212
|
+
it("notifies subscribers and settles waitForUpdate when messages change", async () => {
|
|
213
|
+
const core = new ReadonlyThreadRuntimeCore();
|
|
214
|
+
const seen: number[] = [];
|
|
215
|
+
const unsubscribe = core.subscribe(() => {
|
|
216
|
+
seen.push(core.messages.length);
|
|
217
|
+
});
|
|
218
|
+
const update = core.waitForUpdate();
|
|
219
|
+
|
|
220
|
+
core.setMessages([
|
|
221
|
+
{
|
|
222
|
+
id: "m1",
|
|
223
|
+
role: "user",
|
|
224
|
+
content: [{ type: "text", text: "hi" }],
|
|
225
|
+
createdAt: new Date(0),
|
|
226
|
+
status: { type: "complete", reason: "stop" },
|
|
227
|
+
metadata: { custom: {} },
|
|
228
|
+
} as any,
|
|
229
|
+
]);
|
|
230
|
+
|
|
231
|
+
await expect(update).resolves.toBeUndefined();
|
|
232
|
+
expect(seen).toEqual([1]);
|
|
233
|
+
unsubscribe();
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe("empty singleton", () => {
|
|
238
|
+
it("does not retain subscribe callbacks", () => {
|
|
239
|
+
const subscribers = (
|
|
240
|
+
EMPTY_THREAD_CORE as unknown as Record<string, unknown>
|
|
241
|
+
)._subscribers as Set<unknown>;
|
|
242
|
+
const before = subscribers.size;
|
|
243
|
+
const unsubscribe = EMPTY_THREAD_CORE.subscribe(() => undefined);
|
|
244
|
+
expect(subscribers.size).toBe(before);
|
|
245
|
+
expect(() => unsubscribe()).not.toThrow();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("rejects waitForUpdate instead of hanging", async () => {
|
|
249
|
+
await expect(
|
|
250
|
+
(EMPTY_THREAD_CORE as unknown as BaseSubscribable).waitForUpdate(),
|
|
251
|
+
).rejects.toThrow(EMPTY_ERROR);
|
|
252
|
+
});
|
|
253
|
+
});
|