@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
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
RemoteThreadInitializeResponse,
|
|
3
3
|
RemoteThreadMetadata,
|
|
4
4
|
} from "./types";
|
|
5
|
+
import { generateId } from "../../utils/id";
|
|
5
6
|
|
|
6
7
|
export type RemoteThreadData =
|
|
7
8
|
| {
|
|
@@ -11,6 +12,7 @@ export type RemoteThreadData =
|
|
|
11
12
|
readonly status: "new";
|
|
12
13
|
readonly title: undefined;
|
|
13
14
|
readonly custom: undefined;
|
|
15
|
+
readonly localOrigin?: true;
|
|
14
16
|
}
|
|
15
17
|
| {
|
|
16
18
|
readonly id: string;
|
|
@@ -20,6 +22,7 @@ export type RemoteThreadData =
|
|
|
20
22
|
readonly status: "regular" | "archived";
|
|
21
23
|
readonly title?: string | undefined;
|
|
22
24
|
readonly custom: undefined;
|
|
25
|
+
readonly localOrigin?: true;
|
|
23
26
|
}
|
|
24
27
|
| {
|
|
25
28
|
readonly id: string;
|
|
@@ -30,6 +33,7 @@ export type RemoteThreadData =
|
|
|
30
33
|
readonly title?: string | undefined;
|
|
31
34
|
readonly lastMessageAt?: Date | undefined;
|
|
32
35
|
readonly custom?: Record<string, unknown> | undefined;
|
|
36
|
+
readonly localOrigin?: true;
|
|
33
37
|
};
|
|
34
38
|
|
|
35
39
|
export type THREAD_MAPPING_ID = string & { __brand: "THREAD_MAPPING_ID" };
|
|
@@ -38,6 +42,8 @@ export function createThreadMappingId(id: string): THREAD_MAPPING_ID {
|
|
|
38
42
|
return id as THREAD_MAPPING_ID;
|
|
39
43
|
}
|
|
40
44
|
|
|
45
|
+
export const LOCAL_THREAD_ID_PREFIX = "__LOCALID_";
|
|
46
|
+
|
|
41
47
|
export const normalizeCursor = (c: string | undefined): string | undefined =>
|
|
42
48
|
c || undefined;
|
|
43
49
|
|
|
@@ -98,6 +104,123 @@ export type RemoteThreadState = {
|
|
|
98
104
|
readonly threadData: Readonly<Record<THREAD_MAPPING_ID, RemoteThreadData>>;
|
|
99
105
|
};
|
|
100
106
|
|
|
107
|
+
export const createEmptyRemoteThreadState = (): RemoteThreadState => ({
|
|
108
|
+
isLoading: true,
|
|
109
|
+
isLoadingMore: false,
|
|
110
|
+
cursor: undefined,
|
|
111
|
+
newThreadId: undefined,
|
|
112
|
+
threadIds: [],
|
|
113
|
+
archivedThreadIds: [],
|
|
114
|
+
threadIdMap: {},
|
|
115
|
+
threadData: {},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
export const seedNewThread = (
|
|
119
|
+
state: RemoteThreadState,
|
|
120
|
+
): { id: string; state: RemoteThreadState } => {
|
|
121
|
+
let id: string;
|
|
122
|
+
do {
|
|
123
|
+
id = `${LOCAL_THREAD_ID_PREFIX}${generateId()}`;
|
|
124
|
+
} while (state.threadIdMap[id]);
|
|
125
|
+
const mappingId = createThreadMappingId(id);
|
|
126
|
+
return {
|
|
127
|
+
id,
|
|
128
|
+
state: {
|
|
129
|
+
...state,
|
|
130
|
+
newThreadId: id,
|
|
131
|
+
threadIdMap: {
|
|
132
|
+
...state.threadIdMap,
|
|
133
|
+
[id]: mappingId,
|
|
134
|
+
},
|
|
135
|
+
threadData: {
|
|
136
|
+
...state.threadData,
|
|
137
|
+
[mappingId]: {
|
|
138
|
+
status: "new",
|
|
139
|
+
id,
|
|
140
|
+
remoteId: undefined,
|
|
141
|
+
externalId: undefined,
|
|
142
|
+
title: undefined,
|
|
143
|
+
custom: undefined,
|
|
144
|
+
localOrigin: true,
|
|
145
|
+
} satisfies RemoteThreadData,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// A list() response predating a mid-flight local transition (a thread
|
|
152
|
+
// initialized while the request was running) omits that thread, and the
|
|
153
|
+
// completed-optimistic replay cannot re-add it because the merged threadData
|
|
154
|
+
// already carries the final status. Threads whose status moved from new (or
|
|
155
|
+
// nonexistent) at request time to regular/archived are re-inserted at the
|
|
156
|
+
// top, in the order the pre-response lists carried them: updateStatusReducer
|
|
157
|
+
// prepends as transitions happen, so the pre-response lists hold newest
|
|
158
|
+
// first, which threadData insertion order need not match. Threads the server already knew stay governed by the response, so
|
|
159
|
+
// a server-side deletion is not resurrected — and a thread unknown to the
|
|
160
|
+
// snapshot is only rescued when it carries the localOrigin marker stamped at
|
|
161
|
+
// draft creation, so a deep-linked thread the fetch path deliberately
|
|
162
|
+
// appended at the tail is not hoisted, whatever its remote id looks like.
|
|
163
|
+
export const preserveMidLoadTransitions = (
|
|
164
|
+
state: RemoteThreadState,
|
|
165
|
+
priorOrder: Pick<RemoteThreadState, "threadIds" | "archivedThreadIds">,
|
|
166
|
+
statusAtRequest: ReadonlyMap<string, RemoteThreadData["status"]>,
|
|
167
|
+
): RemoteThreadState => {
|
|
168
|
+
const regular = new Set(state.threadIds);
|
|
169
|
+
const archived = new Set(state.archivedThreadIds);
|
|
170
|
+
const rescuedRegular: RemoteThreadData[] = [];
|
|
171
|
+
const rescuedArchived: RemoteThreadData[] = [];
|
|
172
|
+
|
|
173
|
+
const contains = (ids: ReadonlySet<string>, data: RemoteThreadData) =>
|
|
174
|
+
ids.has(data.id) || (data.remoteId !== undefined && ids.has(data.remoteId));
|
|
175
|
+
|
|
176
|
+
for (const data of Object.values(state.threadData)) {
|
|
177
|
+
const before = statusAtRequest.get(data.id);
|
|
178
|
+
if (before !== undefined && before !== "new") continue;
|
|
179
|
+
if (before === undefined && data.localOrigin !== true) continue;
|
|
180
|
+
|
|
181
|
+
if (data.status === "regular" && !contains(regular, data)) {
|
|
182
|
+
rescuedRegular.push(data);
|
|
183
|
+
regular.add(data.id);
|
|
184
|
+
} else if (data.status === "archived" && !contains(archived, data)) {
|
|
185
|
+
rescuedArchived.push(data);
|
|
186
|
+
archived.add(data.id);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (rescuedRegular.length === 0 && rescuedArchived.length === 0) return state;
|
|
191
|
+
|
|
192
|
+
const position = (ids: readonly string[], data: RemoteThreadData) => {
|
|
193
|
+
const byId = ids.indexOf(data.id);
|
|
194
|
+
if (byId !== -1) return byId;
|
|
195
|
+
const byRemoteId =
|
|
196
|
+
data.remoteId !== undefined ? ids.indexOf(data.remoteId) : -1;
|
|
197
|
+
return byRemoteId !== -1 ? byRemoteId : Number.MAX_SAFE_INTEGER;
|
|
198
|
+
};
|
|
199
|
+
rescuedRegular.sort(
|
|
200
|
+
(a, b) =>
|
|
201
|
+
position(priorOrder.threadIds, a) - position(priorOrder.threadIds, b),
|
|
202
|
+
);
|
|
203
|
+
rescuedArchived.sort(
|
|
204
|
+
(a, b) =>
|
|
205
|
+
position(priorOrder.archivedThreadIds, a) -
|
|
206
|
+
position(priorOrder.archivedThreadIds, b),
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
...state,
|
|
211
|
+
threadIds: [...rescuedRegular.map((d) => d.id), ...state.threadIds],
|
|
212
|
+
archivedThreadIds: [
|
|
213
|
+
...rescuedArchived.map((d) => d.id),
|
|
214
|
+
...state.archivedThreadIds,
|
|
215
|
+
],
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export const statusSnapshot = (
|
|
220
|
+
state: RemoteThreadState,
|
|
221
|
+
): ReadonlyMap<string, RemoteThreadData["status"]> =>
|
|
222
|
+
new Map(Object.values(state.threadData).map((d) => [d.id, d.status]));
|
|
223
|
+
|
|
101
224
|
export const getThreadData = (
|
|
102
225
|
state: RemoteThreadState,
|
|
103
226
|
threadIdOrRemoteId: string,
|
|
@@ -96,6 +96,10 @@ export type RemoteThreadListAdapter = {
|
|
|
96
96
|
|
|
97
97
|
export type RemoteThreadListOptions = {
|
|
98
98
|
runtimeHook: () => AssistantRuntime;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The adapter reference should remain stable across renders. Replacing it reloads the list and drops cached threads that are not in the replacement page. In-flight mutations from the previous adapter are cancelled.
|
|
102
|
+
*/
|
|
99
103
|
adapter: RemoteThreadListAdapter;
|
|
100
104
|
|
|
101
105
|
/**
|
|
@@ -78,6 +78,23 @@ sees only the first result.
|
|
|
78
78
|
### A.7. Previously-resolved tool loses its `result` (back to undefined)
|
|
79
79
|
Silently ignored. The entry stays in the resolved phase internally.
|
|
80
80
|
|
|
81
|
+
### A.8. Tool call carries a provider `approval`
|
|
82
|
+
The entry is marked `skipExecute`, exactly like a call observed with
|
|
83
|
+
a `result`, whether the gate is present when the call is first observed
|
|
84
|
+
or lands on an already-active entry. A gate belongs to the provider:
|
|
85
|
+
`approved === true` means the provider is producing the result, `false`
|
|
86
|
+
means it records the denial, so the frontend `execute` never fires for
|
|
87
|
+
that `toolCallId` once the gate is present. `streamCall` still fires
|
|
88
|
+
once and the backend result flows through A.5 when it lands.
|
|
89
|
+
|
|
90
|
+
If the gate lands after the args stream has closed but before an
|
|
91
|
+
in-flight `execute` resolves, the execute runs to completion (its side
|
|
92
|
+
effects happen) but its result chunk is dropped: `onResult` never fires,
|
|
93
|
+
and the `executing` status, plus any `human()` interrupt the execution
|
|
94
|
+
parked, stays up until the promise settles. Only a gate that lands after
|
|
95
|
+
the result chunk has already been emitted is fully too late; the adapter
|
|
96
|
+
must project the gate before that (#6285).
|
|
97
|
+
|
|
81
98
|
## B. Tool call disappears from snapshot
|
|
82
99
|
|
|
83
100
|
### B.1. Tool call removed entirely (rollback, branch switch)
|
|
@@ -47,6 +47,7 @@ const createAssistantMessage = (
|
|
|
47
47
|
toolCallId?: string;
|
|
48
48
|
toolName?: string;
|
|
49
49
|
nestedMessages?: ThreadAssistantMessage[];
|
|
50
|
+
approval?: { id: string; approved?: boolean };
|
|
50
51
|
},
|
|
51
52
|
): ThreadAssistantMessage => ({
|
|
52
53
|
id: "m-1",
|
|
@@ -70,6 +71,7 @@ const createAssistantMessage = (
|
|
|
70
71
|
...(options?.result !== undefined && { result: options.result }),
|
|
71
72
|
...(options?.isError !== undefined && { isError: options.isError }),
|
|
72
73
|
...(options?.nestedMessages && { messages: options.nestedMessages }),
|
|
74
|
+
...(options?.approval && { approval: options.approval }),
|
|
73
75
|
},
|
|
74
76
|
],
|
|
75
77
|
});
|
|
@@ -370,6 +372,144 @@ describe("ToolInvocationTracker", () => {
|
|
|
370
372
|
});
|
|
371
373
|
});
|
|
372
374
|
|
|
375
|
+
it("never executes a tool call that carries a provider approval", async () => {
|
|
376
|
+
const execute = vi.fn(async () => ({ deleted: true }));
|
|
377
|
+
const getTools = () => ({
|
|
378
|
+
deleteFile: {
|
|
379
|
+
parameters: { type: "object", properties: {} },
|
|
380
|
+
execute,
|
|
381
|
+
} satisfies Tool,
|
|
382
|
+
});
|
|
383
|
+
const onResult = vi.fn();
|
|
384
|
+
const tracker = new ToolInvocationTracker(getTools, {
|
|
385
|
+
onResult,
|
|
386
|
+
onStatusesChange: () => {},
|
|
387
|
+
});
|
|
388
|
+
tracker.setState(createState([]));
|
|
389
|
+
|
|
390
|
+
const gated = (approval: { id: string; approved?: boolean }) =>
|
|
391
|
+
createAssistantMessage(
|
|
392
|
+
'{"path":"/tmp/a"}',
|
|
393
|
+
{ path: "/tmp/a" },
|
|
394
|
+
{ toolName: "deleteFile", approval },
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
tracker.setState(createState([gated({ id: "approval-1" })], false));
|
|
398
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
399
|
+
expect(execute).not.toHaveBeenCalled();
|
|
400
|
+
|
|
401
|
+
tracker.setState(
|
|
402
|
+
createState([gated({ id: "approval-1", approved: true })], true),
|
|
403
|
+
);
|
|
404
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
405
|
+
|
|
406
|
+
expect(execute).not.toHaveBeenCalled();
|
|
407
|
+
expect(onResult).not.toHaveBeenCalled();
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it("stops executing a live tool call once a provider approval lands", async () => {
|
|
411
|
+
const execute = vi.fn(async () => ({ deleted: true }));
|
|
412
|
+
const getTools = () => ({
|
|
413
|
+
deleteFile: {
|
|
414
|
+
parameters: { type: "object", properties: {} },
|
|
415
|
+
execute,
|
|
416
|
+
} satisfies Tool,
|
|
417
|
+
});
|
|
418
|
+
const onResult = vi.fn();
|
|
419
|
+
const tracker = new ToolInvocationTracker(getTools, {
|
|
420
|
+
onResult,
|
|
421
|
+
onStatusesChange: () => {},
|
|
422
|
+
});
|
|
423
|
+
tracker.setState(createState([]));
|
|
424
|
+
|
|
425
|
+
tracker.setState(
|
|
426
|
+
createState(
|
|
427
|
+
[
|
|
428
|
+
createAssistantMessage(
|
|
429
|
+
'{"path":"/tmp',
|
|
430
|
+
{},
|
|
431
|
+
{ toolName: "deleteFile" },
|
|
432
|
+
),
|
|
433
|
+
],
|
|
434
|
+
true,
|
|
435
|
+
),
|
|
436
|
+
);
|
|
437
|
+
tracker.setState(
|
|
438
|
+
createState(
|
|
439
|
+
[
|
|
440
|
+
createAssistantMessage(
|
|
441
|
+
'{"path":"/tmp/a"}',
|
|
442
|
+
{ path: "/tmp/a" },
|
|
443
|
+
{ toolName: "deleteFile", approval: { id: "approval-1" } },
|
|
444
|
+
),
|
|
445
|
+
],
|
|
446
|
+
true,
|
|
447
|
+
),
|
|
448
|
+
);
|
|
449
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
450
|
+
|
|
451
|
+
expect(execute).not.toHaveBeenCalled();
|
|
452
|
+
expect(onResult).not.toHaveBeenCalled();
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it("drops the result of an in-flight execute once a provider approval lands", async () => {
|
|
456
|
+
let resolveExecute!: (value: { deleted: boolean }) => void;
|
|
457
|
+
const execute = vi.fn(
|
|
458
|
+
() =>
|
|
459
|
+
new Promise<{ deleted: boolean }>((r) => {
|
|
460
|
+
resolveExecute = r;
|
|
461
|
+
}),
|
|
462
|
+
);
|
|
463
|
+
const getTools = () => ({
|
|
464
|
+
deleteFile: {
|
|
465
|
+
parameters: { type: "object", properties: {} },
|
|
466
|
+
execute,
|
|
467
|
+
} satisfies Tool,
|
|
468
|
+
});
|
|
469
|
+
const onResult = vi.fn();
|
|
470
|
+
const tracker = new ToolInvocationTracker(getTools, {
|
|
471
|
+
onResult,
|
|
472
|
+
onStatusesChange: () => {},
|
|
473
|
+
});
|
|
474
|
+
tracker.setState(createState([]));
|
|
475
|
+
|
|
476
|
+
tracker.setState(
|
|
477
|
+
createState(
|
|
478
|
+
[
|
|
479
|
+
createAssistantMessage(
|
|
480
|
+
'{"path":"/tmp/a"}',
|
|
481
|
+
{ path: "/tmp/a" },
|
|
482
|
+
{
|
|
483
|
+
toolName: "deleteFile",
|
|
484
|
+
},
|
|
485
|
+
),
|
|
486
|
+
],
|
|
487
|
+
true,
|
|
488
|
+
),
|
|
489
|
+
);
|
|
490
|
+
await waitFor(() => expect(execute).toHaveBeenCalledTimes(1));
|
|
491
|
+
|
|
492
|
+
tracker.setState(
|
|
493
|
+
createState(
|
|
494
|
+
[
|
|
495
|
+
createAssistantMessage(
|
|
496
|
+
'{"path":"/tmp/a"}',
|
|
497
|
+
{ path: "/tmp/a" },
|
|
498
|
+
{
|
|
499
|
+
toolName: "deleteFile",
|
|
500
|
+
approval: { id: "approval-1" },
|
|
501
|
+
},
|
|
502
|
+
),
|
|
503
|
+
],
|
|
504
|
+
true,
|
|
505
|
+
),
|
|
506
|
+
);
|
|
507
|
+
resolveExecute({ deleted: true });
|
|
508
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
509
|
+
|
|
510
|
+
expect(onResult).not.toHaveBeenCalled();
|
|
511
|
+
});
|
|
512
|
+
|
|
373
513
|
it("does not re-execute asynchronously loaded resolved tool calls after reset", async () => {
|
|
374
514
|
const execute = vi.fn(async () => ({ forecast: "ok" }));
|
|
375
515
|
const getTools = () => ({
|
|
@@ -519,6 +519,7 @@ export class ToolInvocationTracker {
|
|
|
519
519
|
// The host already set the result (via the live snapshot's
|
|
520
520
|
// `setResponse` path). Suppress the executor's redundant emit.
|
|
521
521
|
if (entry?.hasResult) return;
|
|
522
|
+
if (entry.skipExecute) return;
|
|
522
523
|
|
|
523
524
|
this._invokeOnResult({
|
|
524
525
|
type: "add-tool-result",
|
|
@@ -794,6 +795,8 @@ export class ToolInvocationTracker {
|
|
|
794
795
|
);
|
|
795
796
|
}
|
|
796
797
|
|
|
798
|
+
if (content.approval !== undefined) entry.skipExecute = true;
|
|
799
|
+
|
|
797
800
|
this._processArgsText(entry, content);
|
|
798
801
|
|
|
799
802
|
if (content.result !== undefined && !entry.hasResult) {
|
|
@@ -44,6 +44,10 @@ import type { ComposerSendOptions } from "../scopes/composer";
|
|
|
44
44
|
import { fileMatchesAccept } from "../../adapters/attachment";
|
|
45
45
|
import { getThreadMessageText } from "../../utils/text";
|
|
46
46
|
import { resolveToolApprovalResponse } from "../../runtime/utils/resolveToolApprovalResponse";
|
|
47
|
+
import {
|
|
48
|
+
AttachmentAddOperations,
|
|
49
|
+
drainAttachmentAdd,
|
|
50
|
+
} from "../../runtime/utils/attachment-add-operations";
|
|
47
51
|
import { toMessagePartStatus } from "../../utils/normalizePartStatus";
|
|
48
52
|
import { ModelContext } from "./model-context-client";
|
|
49
53
|
import { ThreadSuggestions } from "./suggestions";
|
|
@@ -420,53 +424,6 @@ type ComposerClientResourceProps = {
|
|
|
420
424
|
attachmentAdapter?: AttachmentAdapter | undefined;
|
|
421
425
|
};
|
|
422
426
|
|
|
423
|
-
type AttachmentAddOperation = {
|
|
424
|
-
cancelled: boolean;
|
|
425
|
-
attachmentIds: Set<string>;
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
class AttachmentAddOperations {
|
|
429
|
-
private readonly operations = new Set<AttachmentAddOperation>();
|
|
430
|
-
|
|
431
|
-
start() {
|
|
432
|
-
const operation: AttachmentAddOperation = {
|
|
433
|
-
cancelled: false,
|
|
434
|
-
attachmentIds: new Set(),
|
|
435
|
-
};
|
|
436
|
-
this.operations.add(operation);
|
|
437
|
-
return operation;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
accept(operation: AttachmentAddOperation, attachmentId: string) {
|
|
441
|
-
if (operation.cancelled) return false;
|
|
442
|
-
operation.attachmentIds.add(attachmentId);
|
|
443
|
-
return true;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
finish(operation: AttachmentAddOperation) {
|
|
447
|
-
this.operations.delete(operation);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
isCancelled(operation: AttachmentAddOperation) {
|
|
451
|
-
return operation.cancelled;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
cancel(attachmentId: string) {
|
|
455
|
-
for (const operation of [...this.operations]) {
|
|
456
|
-
if (!operation.attachmentIds.has(attachmentId)) continue;
|
|
457
|
-
operation.cancelled = true;
|
|
458
|
-
this.operations.delete(operation);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
cancelAll() {
|
|
463
|
-
for (const operation of this.operations) {
|
|
464
|
-
operation.cancelled = true;
|
|
465
|
-
}
|
|
466
|
-
this.operations.clear();
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
427
|
const useQueueItemClient = ({
|
|
471
428
|
item,
|
|
472
429
|
onMove,
|
|
@@ -486,19 +443,6 @@ const useQueueItemClient = ({
|
|
|
486
443
|
|
|
487
444
|
const QueueItemClient = resource(useQueueItemClient);
|
|
488
445
|
|
|
489
|
-
const drainAdapterAdd = async (
|
|
490
|
-
result: ReturnType<AttachmentAdapter["add"]>,
|
|
491
|
-
upsert: (attachment: Attachment) => boolean,
|
|
492
|
-
) => {
|
|
493
|
-
if (Symbol.asyncIterator in result) {
|
|
494
|
-
for await (const attachment of result) {
|
|
495
|
-
if (!upsert(attachment)) break;
|
|
496
|
-
}
|
|
497
|
-
} else {
|
|
498
|
-
upsert(await result);
|
|
499
|
-
}
|
|
500
|
-
};
|
|
501
|
-
|
|
502
446
|
// State whose setter tracks the latest value in a ref, so imperative
|
|
503
447
|
// call sequences (setText immediately followed by send) observe the write
|
|
504
448
|
// before React re-renders — legacy composer parity.
|
|
@@ -672,7 +616,7 @@ const useComposerClientResource = ({
|
|
|
672
616
|
if (!isCreateAttachment(fileOrAttachment) && attachmentAdapter) {
|
|
673
617
|
const operation = attachmentAddOperations.start();
|
|
674
618
|
try {
|
|
675
|
-
await
|
|
619
|
+
await drainAttachmentAdd(
|
|
676
620
|
attachmentAdapter.add({ file: fileOrAttachment }),
|
|
677
621
|
(attachment) => {
|
|
678
622
|
if (!attachmentAddOperations.accept(operation, attachment.id))
|
|
@@ -795,6 +739,17 @@ const useComposerClientResource = ({
|
|
|
795
739
|
}
|
|
796
740
|
},
|
|
797
741
|
cancel: () => {
|
|
742
|
+
// An edit session ends here, so its in-flight adapter adds must not
|
|
743
|
+
// land in a later session; the thread composer's cancel stops the run
|
|
744
|
+
// and leaves the draft (and its pending adds) alone.
|
|
745
|
+
if (type === "edit") {
|
|
746
|
+
attachmentAddOperations.cancelAll();
|
|
747
|
+
const removed = attachmentsRef.current;
|
|
748
|
+
setAttachments([]);
|
|
749
|
+
removePendingAttachments(removed).catch((error) => {
|
|
750
|
+
console.error("Failed to remove cancelled edit attachments", error);
|
|
751
|
+
});
|
|
752
|
+
}
|
|
798
753
|
onCancel?.();
|
|
799
754
|
if (type === "edit") setIsEditing(false);
|
|
800
755
|
},
|
|
@@ -1085,7 +1040,7 @@ const useExternalThread = ({
|
|
|
1085
1040
|
}));
|
|
1086
1041
|
|
|
1087
1042
|
return {
|
|
1088
|
-
isEmpty: messages.length === 0,
|
|
1043
|
+
isEmpty: messages.length === 0 && !isLoading,
|
|
1089
1044
|
isDisabled: false,
|
|
1090
1045
|
isLoading,
|
|
1091
1046
|
isRunning,
|
|
@@ -93,7 +93,10 @@ const useSingleThreadList = ({
|
|
|
93
93
|
!(
|
|
94
94
|
typeof selector === "object" &&
|
|
95
95
|
"index" in selector &&
|
|
96
|
-
selector.index === 0
|
|
96
|
+
selector.index === 0 &&
|
|
97
|
+
// Index selectors address their archived/regular subset, and the
|
|
98
|
+
// archived subset here is always empty.
|
|
99
|
+
!selector.archived
|
|
97
100
|
)
|
|
98
101
|
) {
|
|
99
102
|
throw new Error(
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { createTapRoot, flushTapSync, useResource } from "@assistant-ui/tap";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { Suggestions, type SuggestionConfig } from "./suggestions";
|
|
5
|
+
|
|
6
|
+
describe("Suggestions", () => {
|
|
7
|
+
it("updates when the configured suggestions change", () => {
|
|
8
|
+
let setSuggestions!: (suggestions: SuggestionConfig[]) => void;
|
|
9
|
+
const root = createTapRoot(function SuggestionsRoot() {
|
|
10
|
+
const [suggestions, setValue] = useState<SuggestionConfig[]>([
|
|
11
|
+
"account-a",
|
|
12
|
+
]);
|
|
13
|
+
setSuggestions = setValue;
|
|
14
|
+
return useResource(Suggestions(suggestions));
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
expect(root.getValue().getState().suggestions[0]?.prompt).toBe(
|
|
19
|
+
"account-a",
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
flushTapSync(() => setSuggestions(["account-b"]));
|
|
23
|
+
|
|
24
|
+
expect(root.getValue().getState().suggestions[0]?.prompt).toBe(
|
|
25
|
+
"account-b",
|
|
26
|
+
);
|
|
27
|
+
} finally {
|
|
28
|
+
root.unmount();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useMemo
|
|
1
|
+
import { useMemo } from "react";
|
|
2
2
|
import { resource, withKey } from "@assistant-ui/tap";
|
|
3
3
|
import type { ClientOutput } from "@assistant-ui/store";
|
|
4
4
|
import { useClientLookup } from "@assistant-ui/store/client";
|
|
@@ -40,26 +40,25 @@ const useSuggestionsClient = (
|
|
|
40
40
|
const useStaticSuggestions = (
|
|
41
41
|
suggestions?: SuggestionConfig[],
|
|
42
42
|
): ClientOutput<"suggestions"> => {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
const state = useMemo<SuggestionsState>(
|
|
44
|
+
() => ({
|
|
45
|
+
suggestions: (suggestions ?? []).map((s) => {
|
|
46
|
+
if (typeof s === "string") {
|
|
47
|
+
return {
|
|
48
|
+
title: s,
|
|
49
|
+
label: "",
|
|
50
|
+
prompt: s,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
46
53
|
return {
|
|
47
|
-
title: s,
|
|
48
|
-
label:
|
|
49
|
-
prompt: s,
|
|
54
|
+
title: s.title,
|
|
55
|
+
label: s.label,
|
|
56
|
+
prompt: s.prompt,
|
|
50
57
|
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
prompt: s.prompt,
|
|
56
|
-
};
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
suggestions: normalizedSuggestions,
|
|
61
|
-
};
|
|
62
|
-
});
|
|
58
|
+
}),
|
|
59
|
+
}),
|
|
60
|
+
[suggestions],
|
|
61
|
+
);
|
|
63
62
|
|
|
64
63
|
return useSuggestionsClient(state);
|
|
65
64
|
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { ThreadListAdapterChangedError } from "../../runtimes/remote-thread-list/adapter-changed";
|
|
3
|
+
import { handleRuntimeAction } from "./handle-runtime-action";
|
|
4
|
+
|
|
5
|
+
describe("handleRuntimeAction", () => {
|
|
6
|
+
it("does not log a silent adapter-change cancellation", async () => {
|
|
7
|
+
const error = new ThreadListAdapterChangedError();
|
|
8
|
+
const consoleError = vi
|
|
9
|
+
.spyOn(console, "error")
|
|
10
|
+
.mockImplementation(() => {});
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
await expect(
|
|
14
|
+
handleRuntimeAction("thread list archive", () => Promise.reject(error)),
|
|
15
|
+
).rejects.toBe(error);
|
|
16
|
+
expect(consoleError).not.toHaveBeenCalled();
|
|
17
|
+
} finally {
|
|
18
|
+
consoleError.mockRestore();
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("logs an ordinary rejection", async () => {
|
|
23
|
+
const error = new Error("archive failed");
|
|
24
|
+
const consoleError = vi
|
|
25
|
+
.spyOn(console, "error")
|
|
26
|
+
.mockImplementation(() => {});
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
await expect(
|
|
30
|
+
handleRuntimeAction("thread list archive", () => Promise.reject(error)),
|
|
31
|
+
).rejects.toBe(error);
|
|
32
|
+
expect(consoleError).toHaveBeenCalledExactlyOnceWith(
|
|
33
|
+
"[assistant-ui] thread list archive failed:",
|
|
34
|
+
error,
|
|
35
|
+
);
|
|
36
|
+
} finally {
|
|
37
|
+
consoleError.mockRestore();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isSilentRuntimeAction } from "../../utils/silent-runtime-action";
|
|
2
|
+
|
|
1
3
|
export const handleRuntimeAction = (
|
|
2
4
|
label: string,
|
|
3
5
|
execute: () => Promise<void>,
|
|
@@ -5,6 +7,7 @@ export const handleRuntimeAction = (
|
|
|
5
7
|
const task = execute();
|
|
6
8
|
|
|
7
9
|
void task.catch((error: unknown) => {
|
|
10
|
+
if (isSilentRuntimeAction(error)) return;
|
|
8
11
|
console.error(`[assistant-ui] ${label} failed:`, error);
|
|
9
12
|
});
|
|
10
13
|
return task;
|
|
@@ -19,7 +19,7 @@ export type MessageState = ThreadMessage & {
|
|
|
19
19
|
* @example
|
|
20
20
|
* ```ts
|
|
21
21
|
* import { WebSpeechSynthesisAdapter } from "@assistant-ui/react";
|
|
22
|
-
* import { useChatRuntime } from "@assistant-ui/
|
|
22
|
+
* import { useChatRuntime } from "@assistant-ui/ai-sdk";
|
|
23
23
|
*
|
|
24
24
|
* const runtime = useChatRuntime({
|
|
25
25
|
* adapters: {
|
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
import type { FileMessagePart, TextMessagePart } from "../../types/message";
|
|
2
1
|
import type { QueuePlacement } from "../../runtime/queue/external-thread-queue-adapter";
|
|
2
|
+
import type { QueueItemState } from "../../runtime/queue/queue-item";
|
|
3
3
|
|
|
4
|
-
export type QueueItemState
|
|
5
|
-
readonly id: string;
|
|
6
|
-
/** @deprecated Derive from the text parts of `parts` instead. Removal after 2026-11-05. */
|
|
7
|
-
readonly prompt: string;
|
|
8
|
-
readonly parts: readonly (FileMessagePart | TextMessagePart)[];
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export const EMPTY_QUEUE_ITEMS: readonly QueueItemState[] = Object.freeze([]);
|
|
4
|
+
export type { QueueItemState };
|
|
12
5
|
|
|
13
6
|
export type QueueItemMethods = {
|
|
14
7
|
getState(): QueueItemState;
|