@assistant-ui/core 0.3.13 → 0.3.14
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/speech.d.ts.map +1 -1
- package/dist/adapters/speech.js +9 -15
- package/dist/adapters/speech.js.map +1 -1
- package/dist/adapters/thread-history.d.ts +2 -0
- package/dist/adapters/thread-history.d.ts.map +1 -1
- package/dist/adapters/voice.d.ts.map +1 -1
- package/dist/adapters/voice.js +27 -15
- package/dist/adapters/voice.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/internal.js +6 -6
- package/dist/model-context/frame/host.d.ts.map +1 -1
- package/dist/model-context/frame/host.js +2 -1
- package/dist/model-context/frame/host.js.map +1 -1
- package/dist/model-context/registry.d.ts.map +1 -1
- package/dist/model-context/registry.js +16 -4
- package/dist/model-context/registry.js.map +1 -1
- package/dist/react/RuntimeAdapter.d.ts +8 -1
- package/dist/react/RuntimeAdapter.d.ts.map +1 -1
- package/dist/react/RuntimeAdapter.js +23 -18
- package/dist/react/RuntimeAdapter.js.map +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.js +28 -12
- package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
- package/dist/react/client/AdaptedRemoteThread.d.ts +11 -0
- package/dist/react/client/AdaptedRemoteThread.d.ts.map +1 -0
- package/dist/react/client/AdaptedRemoteThread.js +27 -0
- package/dist/react/client/AdaptedRemoteThread.js.map +1 -0
- package/dist/react/client/InMemoryThreadList.d.ts +19 -0
- package/dist/react/client/InMemoryThreadList.d.ts.map +1 -0
- package/dist/react/client/InMemoryThreadList.js +418 -0
- package/dist/react/client/InMemoryThreadList.js.map +1 -0
- package/dist/react/client/Interactables.d.ts.map +1 -1
- package/dist/react/client/Interactables.js +159 -102
- package/dist/react/client/Interactables.js.map +1 -1
- package/dist/react/client/RemoteThreadList.d.ts +34 -0
- package/dist/react/client/RemoteThreadList.d.ts.map +1 -0
- package/dist/react/client/RemoteThreadList.js +826 -0
- package/dist/react/client/RemoteThreadList.js.map +1 -0
- package/dist/react/client/Tools.d.ts.map +1 -1
- package/dist/react/client/Tools.js +38 -27
- package/dist/react/client/Tools.js.map +1 -1
- package/dist/react/index.d.ts +7 -2
- package/dist/react/index.js +5 -2
- package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -1
- package/dist/react/interactables-legacy/Interactables.js +145 -110
- package/dist/react/interactables-legacy/Interactables.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarCopy.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useActionBarCopy.js +13 -18
- package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarEdit.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useActionBarEdit.js +2 -4
- package/dist/react/primitive-hooks/useActionBarEdit.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarReload.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useActionBarReload.js +2 -4
- package/dist/react/primitive-hooks/useActionBarReload.js.map +1 -1
- package/dist/react/primitive-hooks/useBranchPickerNext.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useBranchPickerNext.js +2 -6
- package/dist/react/primitive-hooks/useBranchPickerNext.js.map +1 -1
- package/dist/react/primitive-hooks/useBranchPickerPrevious.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useBranchPickerPrevious.js +2 -6
- package/dist/react/primitive-hooks/useBranchPickerPrevious.js.map +1 -1
- package/dist/react/primitive-hooks/useComposerCancel.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useComposerCancel.js +2 -4
- package/dist/react/primitive-hooks/useComposerCancel.js.map +1 -1
- package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useComposerSend.js +2 -4
- package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
- package/dist/react/primitive-hooks/useSuggestionTrigger.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useSuggestionTrigger.js +6 -2
- package/dist/react/primitive-hooks/useSuggestionTrigger.js.map +1 -1
- package/dist/react/primitives/message/MessageParts.d.ts +13 -0
- package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageParts.js.map +1 -1
- package/dist/react/primitives/thread/ThreadMessages.d.ts.map +1 -1
- package/dist/react/primitives/thread/ThreadMessages.js +1 -1
- package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +22 -10
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +109 -71
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +8 -2
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +40 -13
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadResource.d.ts +30 -0
- package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -0
- package/dist/react/runtimes/RemoteThreadResource.js +134 -0
- package/dist/react/runtimes/RemoteThreadResource.js.map +1 -0
- package/dist/react/runtimes/RuntimeAdapterProvider.d.ts +3 -10
- package/dist/react/runtimes/RuntimeAdapterProvider.d.ts.map +1 -1
- package/dist/react/runtimes/RuntimeAdapterProvider.js +3 -6
- package/dist/react/runtimes/RuntimeAdapterProvider.js.map +1 -1
- package/dist/react/runtimes/assistant-transport/commandQueue.d.ts +17 -0
- package/dist/react/runtimes/assistant-transport/commandQueue.d.ts.map +1 -0
- package/dist/react/runtimes/assistant-transport/commandQueue.js +55 -0
- package/dist/react/runtimes/assistant-transport/commandQueue.js.map +1 -0
- package/dist/react/runtimes/assistant-transport/replayBoundaryStream.d.ts +11 -0
- package/dist/react/runtimes/assistant-transport/replayBoundaryStream.d.ts.map +1 -0
- package/dist/react/runtimes/assistant-transport/replayBoundaryStream.js +165 -0
- package/dist/react/runtimes/assistant-transport/replayBoundaryStream.js.map +1 -0
- package/dist/react/runtimes/assistant-transport/runManager.d.ts +15 -0
- package/dist/react/runtimes/assistant-transport/runManager.d.ts.map +1 -0
- package/dist/react/runtimes/assistant-transport/runManager.js +86 -0
- package/dist/react/runtimes/assistant-transport/runManager.js.map +1 -0
- package/dist/react/runtimes/assistant-transport/types.d.ts +130 -0
- package/dist/react/runtimes/assistant-transport/types.d.ts.map +1 -0
- package/dist/react/runtimes/assistant-transport/types.js +0 -0
- package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts +14 -0
- package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -0
- package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js +296 -0
- package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js.map +1 -0
- package/dist/react/runtimes/assistant-transport/useConvertedState.d.ts +7 -0
- package/dist/react/runtimes/assistant-transport/useConvertedState.d.ts.map +1 -0
- package/dist/react/runtimes/assistant-transport/useConvertedState.js +25 -0
- package/dist/react/runtimes/assistant-transport/useConvertedState.js.map +1 -0
- package/dist/react/runtimes/assistant-transport/useLatestRef.d.ts +7 -0
- package/dist/react/runtimes/assistant-transport/useLatestRef.d.ts.map +1 -0
- package/dist/react/runtimes/assistant-transport/useLatestRef.js +27 -0
- package/dist/react/runtimes/assistant-transport/useLatestRef.js.map +1 -0
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +39 -13
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts +2 -1
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +3 -3
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/auiV0.d.ts +4 -0
- package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/auiV0.js +7 -0
- package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +22 -12
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListRuntime.d.ts +16 -0
- package/dist/react/runtimes/cloud/useCloudThreadListRuntime.d.ts.map +1 -0
- package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js +36 -0
- package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js.map +1 -0
- package/dist/react/runtimes/external-message-converter.js +14 -14
- package/dist/react/runtimes/external-message-converter.js.map +1 -1
- package/dist/react/runtimes/useExternalStoreRuntime.d.ts.map +1 -1
- package/dist/react/runtimes/useExternalStoreRuntime.js +44 -28
- package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
- package/dist/react/runtimes/useExternalStoreSharedOptions.js.map +1 -1
- package/dist/react/runtimes/useLocalRuntime.js +1 -1
- package/dist/react/runtimes/useRuntimeAdapters.d.ts +9 -0
- package/dist/react/runtimes/useRuntimeAdapters.d.ts.map +1 -0
- package/dist/react/runtimes/useRuntimeAdapters.js +20 -0
- package/dist/react/runtimes/useRuntimeAdapters.js.map +1 -0
- package/dist/runtime/api/composer-runtime.d.ts +5 -0
- package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
- package/dist/runtime/api/composer-runtime.js.map +1 -1
- package/dist/runtime/api/message-runtime.js +1 -1
- package/dist/runtime/api/thread-list-runtime.d.ts +1 -0
- package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-list-runtime.js +4 -2
- package/dist/runtime/api/thread-list-runtime.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts +12 -2
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-runtime.js +8 -2
- package/dist/runtime/api/thread-runtime.js.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js +8 -7
- package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts +1 -0
- package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.js +3 -3
- package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +0 -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 +10 -4
- package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts +6 -0
- package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/internal.js +5 -5
- package/dist/runtime/queue/message-queue.d.ts.map +1 -1
- package/dist/runtime/queue/message-queue.js +2 -1
- package/dist/runtime/queue/message-queue.js.map +1 -1
- package/dist/runtime/utils/thread-runtime-lifecycle.d.ts +8 -0
- package/dist/runtime/utils/thread-runtime-lifecycle.d.ts.map +1 -0
- package/dist/runtime/utils/thread-runtime-lifecycle.js +11 -0
- package/dist/runtime/utils/thread-runtime-lifecycle.js.map +1 -0
- package/dist/runtimes/external-store/external-store-shared-options.js.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +4 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +8 -0
- 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 +35 -13
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/internal.d.ts +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts +3 -0
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +53 -13
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +1 -0
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +3 -0
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.js +3 -0
- package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
- package/dist/runtimes/remote-thread-list/types.d.ts +29 -1
- package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +1 -12
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +67 -38
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
- package/dist/store/clients/suggestions.js +2 -2
- package/dist/store/clients/suggestions.js.map +1 -1
- package/dist/store/clients/thread-selection-events.d.ts +10 -0
- package/dist/store/clients/thread-selection-events.d.ts.map +1 -0
- package/dist/store/clients/thread-selection-events.js +40 -0
- package/dist/store/clients/thread-selection-events.js.map +1 -0
- package/dist/store/env.d.ts +5 -0
- package/dist/store/env.d.ts.map +1 -0
- package/dist/store/env.js +12 -0
- package/dist/store/env.js.map +1 -0
- package/dist/store/index.d.ts +5 -3
- package/dist/store/index.js +4 -2
- package/dist/store/internal.d.ts +4 -1
- package/dist/store/internal.js +4 -1
- package/dist/store/primitive-predicates.d.ts +19 -0
- package/dist/store/primitive-predicates.d.ts.map +1 -0
- package/dist/store/primitive-predicates.js +19 -0
- package/dist/store/primitive-predicates.js.map +1 -0
- package/dist/store/runtime-clients/attachment-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/attachment-runtime-client.js +18 -11
- package/dist/store/runtime-clients/attachment-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/handle-runtime-action.d.ts +5 -0
- package/dist/store/runtime-clients/handle-runtime-action.d.ts.map +1 -0
- package/dist/store/runtime-clients/handle-runtime-action.js +12 -0
- package/dist/store/runtime-clients/handle-runtime-action.js.map +1 -0
- package/dist/store/runtime-clients/handle-thread-list-action.d.ts +5 -0
- package/dist/store/runtime-clients/handle-thread-list-action.d.ts.map +1 -0
- package/dist/store/runtime-clients/handle-thread-list-action.js +7 -0
- package/dist/store/runtime-clients/handle-thread-list-action.js.map +1 -0
- package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-list-item-runtime-client.js +62 -29
- package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-list-runtime-client.js +5 -6
- package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
- package/dist/store/scopes/composer.d.ts +9 -4
- package/dist/store/scopes/composer.d.ts.map +1 -1
- package/dist/store/scopes/thread-list-item.d.ts +7 -6
- package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
- package/dist/store/scopes/thread.d.ts +8 -7
- package/dist/store/scopes/thread.d.ts.map +1 -1
- package/dist/store/scopes/threads.d.ts +17 -1
- package/dist/store/scopes/threads.d.ts.map +1 -1
- package/dist/subscribable/subscribable.d.ts +2 -1
- package/dist/subscribable/subscribable.d.ts.map +1 -1
- package/dist/subscribable/subscribable.js +23 -18
- package/dist/subscribable/subscribable.js.map +1 -1
- package/dist/utils/composite-context-provider.d.ts +1 -0
- package/dist/utils/composite-context-provider.d.ts.map +1 -1
- package/dist/utils/composite-context-provider.js +25 -9
- package/dist/utils/composite-context-provider.js.map +1 -1
- package/dist/utils/notify-event-listeners.d.ts +2 -2
- package/dist/utils/notify-event-listeners.d.ts.map +1 -1
- package/dist/utils/notify-event-listeners.js +2 -2
- package/dist/utils/notify-event-listeners.js.map +1 -1
- package/package.json +7 -7
- package/src/adapters/speech.test.ts +118 -1
- package/src/adapters/speech.ts +21 -22
- package/src/adapters/thread-history.ts +2 -0
- package/src/adapters/voice.test.ts +69 -0
- package/src/adapters/voice.ts +27 -27
- package/src/index.ts +1 -0
- package/src/model-context/frame/host.test.ts +23 -0
- package/src/model-context/frame/host.ts +2 -1
- package/src/model-context/registry.test.ts +131 -0
- package/src/model-context/registry.ts +16 -6
- package/src/react/RuntimeAdapter.test.ts +2 -0
- package/src/react/RuntimeAdapter.ts +20 -8
- package/src/react/adapters/LocalStorageThreadListAdapter.tsx +34 -17
- package/src/react/client/AdaptedRemoteThread.ts +41 -0
- package/src/react/client/InMemoryThreadList.test.tsx +148 -0
- package/src/react/client/InMemoryThreadList.ts +290 -0
- package/src/react/client/Interactables.test.ts +326 -2
- package/src/react/client/Interactables.ts +227 -110
- package/src/react/client/RemoteThreadList.test.ts +1026 -0
- package/src/react/client/RemoteThreadList.ts +1067 -0
- package/src/react/client/Tools.ts +36 -29
- package/src/react/index.ts +13 -0
- package/src/react/interactables-legacy/Interactables.test.ts +317 -0
- package/src/react/interactables-legacy/Interactables.ts +204 -128
- package/src/react/primitive-hooks/useActionBarCopy.ts +2 -7
- package/src/react/primitive-hooks/useActionBarEdit.ts +2 -1
- package/src/react/primitive-hooks/useActionBarReload.ts +2 -6
- package/src/react/primitive-hooks/useBranchPickerNext.ts +2 -7
- package/src/react/primitive-hooks/useBranchPickerPrevious.ts +2 -7
- package/src/react/primitive-hooks/useComposerCancel.ts +2 -1
- package/src/react/primitive-hooks/useComposerSend.ts +2 -5
- package/src/react/primitive-hooks/useSuggestionTrigger.test.ts +11 -0
- package/src/react/primitive-hooks/useSuggestionTrigger.ts +4 -5
- package/src/react/primitives/message/MessageParts.tsx +0 -1
- package/src/react/primitives/thread/ThreadMessages.tsx +2 -1
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.retry.test.tsx +116 -0
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +1 -1
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +57 -6
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.title.test.tsx +272 -0
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +159 -124
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +59 -20
- package/src/react/runtimes/RemoteThreadResource.test.ts +141 -0
- package/src/react/runtimes/RemoteThreadResource.ts +231 -0
- package/src/react/runtimes/RuntimeAdapterProvider.tsx +8 -20
- package/src/react/runtimes/assistant-transport/commandQueue.ts +68 -0
- package/src/react/runtimes/assistant-transport/replayBoundaryStream.test.ts +455 -0
- package/src/react/runtimes/assistant-transport/replayBoundaryStream.ts +172 -0
- package/src/react/runtimes/assistant-transport/runManager.ts +124 -0
- package/src/react/runtimes/assistant-transport/transport-delivery.test.tsx +79 -0
- package/src/react/runtimes/assistant-transport/transport-parentid.test.tsx +97 -0
- package/src/react/runtimes/assistant-transport/transport-scheduling.test.ts +414 -0
- package/src/react/runtimes/assistant-transport/types.ts +170 -0
- package/src/react/runtimes/assistant-transport/useAssistantTransport.spec.md +141 -0
- package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.test.tsx +625 -0
- package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.ts +481 -0
- package/src/react/runtimes/assistant-transport/useConvertedState.ts +20 -0
- package/src/react/runtimes/assistant-transport/useLatestRef.ts +9 -0
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +131 -6
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +61 -15
- package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +6 -4
- package/src/react/runtimes/cloud/auiV0.ts +17 -1
- package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +36 -21
- package/src/react/runtimes/cloud/useCloudThreadListRuntime.test.tsx +56 -0
- package/src/react/runtimes/cloud/useCloudThreadListRuntime.ts +31 -0
- package/src/react/runtimes/external-message-converter.test.ts +143 -0
- package/src/react/runtimes/external-message-converter.ts +41 -41
- package/src/react/runtimes/remote-thread-hook-on-tap.test.ts +58 -0
- package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +83 -0
- package/src/react/runtimes/useExternalStoreRuntime.suggestions.test.tsx +13 -3
- package/src/react/runtimes/useExternalStoreRuntime.ts +7 -0
- package/src/react/runtimes/useExternalStoreSharedOptions.ts +3 -1
- package/src/react/runtimes/useLocalRuntime.test.tsx +130 -4
- package/src/react/runtimes/useRuntimeAdapters.test.tsx +62 -0
- package/src/react/runtimes/useRuntimeAdapters.ts +28 -0
- package/src/runtime/api/composer-runtime.ts +5 -0
- package/src/runtime/api/thread-list-runtime.ts +3 -1
- package/src/runtime/api/thread-runtime.ts +20 -2
- package/src/runtime/base/base-composer-runtime-core.ts +6 -5
- package/src/runtime/base/base-thread-runtime-core.test.ts +191 -0
- package/src/runtime/base/base-thread-runtime-core.ts +11 -2
- package/src/runtime/base/default-thread-composer-runtime-core.ts +12 -4
- package/src/runtime/interfaces/thread-runtime-core.ts +6 -0
- package/src/runtime/queue/message-queue.ts +2 -1
- package/src/runtime/utils/thread-runtime-lifecycle.ts +18 -0
- package/src/runtimes/external-store/external-store-shared-options.ts +3 -1
- package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +4 -1
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +99 -34
- package/src/runtimes/local/local-thread-runtime-core.test.ts +373 -4
- package/src/runtimes/local/local-thread-runtime-core.ts +79 -14
- package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +4 -0
- package/src/runtimes/remote-thread-list/empty-thread-core.ts +4 -0
- package/src/runtimes/remote-thread-list/types.ts +30 -0
- package/src/runtimes/tool-invocations/EDGE_CASES.md +17 -6
- package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +243 -0
- package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +127 -61
- package/src/store/clients/model-context-client.test.ts +16 -8
- package/src/store/clients/suggestions.ts +2 -2
- package/src/store/clients/thread-selection-events.ts +21 -0
- package/src/store/env.ts +11 -0
- package/src/store/index.ts +14 -1
- package/src/store/internal.ts +13 -0
- package/src/store/primitive-predicates.test.ts +184 -0
- package/src/store/primitive-predicates.ts +43 -0
- package/src/store/runtime-clients/attachment-runtime-client.test.ts +41 -0
- package/src/store/runtime-clients/attachment-runtime-client.ts +2 -1
- package/src/store/runtime-clients/handle-runtime-action.ts +11 -0
- package/src/store/runtime-clients/handle-thread-list-action.test.ts +69 -0
- package/src/store/runtime-clients/handle-thread-list-action.ts +6 -0
- package/src/store/runtime-clients/thread-list-item-runtime-client.ts +15 -7
- package/src/store/runtime-clients/thread-list-runtime-client.ts +9 -6
- package/src/store/scopes/composer.ts +9 -4
- package/src/store/scopes/thread-list-item.ts +7 -6
- package/src/store/scopes/thread.ts +8 -7
- package/src/store/scopes/threads.ts +14 -0
- package/src/subscribable/subscribable.test.ts +77 -1
- package/src/subscribable/subscribable.ts +38 -23
- package/src/tests/RemoteThreadListThreadListRuntimeCore-switchToThread-unarchive.test.ts +25 -0
- package/src/tests/auiV0Encode.test.ts +66 -0
- package/src/tests/base-composer-runtime-core-send.test.ts +30 -0
- package/src/tests/base-composer-runtime-core.test.ts +28 -0
- package/src/tests/composer-can-cancel.test.ts +101 -0
- package/src/tests/external-store-thread-list-runtime-core.test.ts +101 -0
- package/src/tests/external-store-thread-runtime-core-adapter.test.ts +191 -1
- package/src/tests/external-store-thread-runtime-core.test.ts +124 -0
- package/src/tests/interactables-toolui-migration.test.tsx +66 -0
- package/src/tests/message-queue.test.ts +28 -0
- package/src/tests/session-reset-notification.test.ts +145 -0
- package/src/tests/thread-switch-events.test.tsx +99 -1
- package/src/tests/tools-scope-migration.test.tsx +58 -0
- package/src/utils/composite-context-provider.test.ts +139 -0
- package/src/utils/composite-context-provider.ts +26 -8
- package/src/utils/notify-event-listeners.ts +10 -5
|
@@ -10,6 +10,9 @@ observe via `setState(snapshot)` and what the current behavior is.
|
|
|
10
10
|
> first observation — args regress, args change after first completion,
|
|
11
11
|
> result is replaced, result is cleared, key order shuffles — the tracker
|
|
12
12
|
> never invokes the host's tool callback a second time.
|
|
13
|
+
>
|
|
14
|
+
> `reset()` starts a new execution boundary. A reused `toolCallId` in the
|
|
15
|
+
> next boundary gets a new execution identity and may fire once there.
|
|
13
16
|
|
|
14
17
|
This guarantees host-side side effects (the typical reason `streamCall` /
|
|
15
18
|
`execute` exists at all) can't double-run. The cost: post-completion
|
|
@@ -65,7 +68,7 @@ The tracker calls `setResponse` on the active controller and closes it.
|
|
|
65
68
|
The backend result is emitted before the args stream closes, so a stale
|
|
66
69
|
args parse failure cannot replace it. `reader.response.get()` resolves.
|
|
67
70
|
If the tool also had a frontend `execute`, the executor is short-circuited
|
|
68
|
-
via
|
|
71
|
+
via the entry's per-execution skip marker. Single fire.
|
|
69
72
|
|
|
70
73
|
### A.6. Previously-resolved tool's `result` is replaced
|
|
71
74
|
Silently ignored — `entry.hasResult` short-circuits both the
|
|
@@ -153,15 +156,16 @@ tracker's view of "what we last observed". The next snapshot retries.
|
|
|
153
156
|
|
|
154
157
|
### F.1. `reset()` called while `execute()` invocations are in flight
|
|
155
158
|
`abort()` is invoked, in-flight executions reject with
|
|
156
|
-
`Tool execution aborted`. Once they settle, the cleanup logic
|
|
157
|
-
`_executing`. The settled-resolver promises fire so
|
|
158
|
-
resolves.
|
|
159
|
+
`Tool execution aborted`. Once they settle, the cleanup logic removes their
|
|
160
|
+
execution identities from `_executing`. The settled-resolver promises fire so
|
|
161
|
+
the abort promise resolves.
|
|
159
162
|
|
|
160
163
|
### F.2. `setState` called during `reset()`'s in-flight abort
|
|
161
164
|
The new snapshot is processed against an empty `_entries`. Tool calls
|
|
162
165
|
in it are seeded as restored (because `reset()` re-armed
|
|
163
|
-
`_pendingRestore`). Eventual cancellation `result` chunks for the
|
|
164
|
-
|
|
166
|
+
`_pendingRestore`). Eventual cancellation `result` chunks for the aborted
|
|
167
|
+
executions are dropped when their execution identity no longer matches the
|
|
168
|
+
current entry.
|
|
165
169
|
|
|
166
170
|
### F.3. `resume(toolCallId, payload)` for an unknown id
|
|
167
171
|
Silently no-ops. (The pre-class hook *threw*; the tracker softens this
|
|
@@ -175,6 +179,13 @@ rebuilt pipeline does not re-fire `streamCall` for them) and the
|
|
|
175
179
|
snapshot is processed against the fresh pipeline. Repeated failures
|
|
176
180
|
keep the tracker dead with a visible error to avoid restart loops.
|
|
177
181
|
|
|
182
|
+
### F.5. Reset followed by same-id reuse
|
|
183
|
+
The assistant-stream pipeline assigns each streamed tool part an opaque
|
|
184
|
+
execution identity. Human-input callbacks, lifecycle callbacks, result chunks,
|
|
185
|
+
and pending execution cleanup are accepted only when that identity is still
|
|
186
|
+
current for the `toolCallId`. A late callback from before `reset()` is therefore
|
|
187
|
+
dropped even when the next session reuses the id.
|
|
188
|
+
|
|
178
189
|
## Known limitations
|
|
179
190
|
|
|
180
191
|
### Host callback throws
|
|
@@ -1172,3 +1172,246 @@ describe("ToolInvocationTracker", () => {
|
|
|
1172
1172
|
}
|
|
1173
1173
|
});
|
|
1174
1174
|
});
|
|
1175
|
+
|
|
1176
|
+
describe("ToolInvocationTracker reset", () => {
|
|
1177
|
+
const trackerWith = (tools: Record<string, Tool>) => {
|
|
1178
|
+
let statuses: Record<string, ToolExecutionStatus> = {};
|
|
1179
|
+
const tracker = new ToolInvocationTracker(() => tools, {
|
|
1180
|
+
onResult: vi.fn(),
|
|
1181
|
+
onStatusesChange: (s: ReadonlyMap<string, ToolExecutionStatus>) => {
|
|
1182
|
+
statuses = Object.fromEntries(s);
|
|
1183
|
+
},
|
|
1184
|
+
});
|
|
1185
|
+
return { tracker, statuses: () => statuses };
|
|
1186
|
+
};
|
|
1187
|
+
|
|
1188
|
+
it("clears statuses for discarded executions that never settle", async () => {
|
|
1189
|
+
const { tracker, statuses } = trackerWith({
|
|
1190
|
+
weatherSearch: {
|
|
1191
|
+
parameters: { type: "object", properties: {} },
|
|
1192
|
+
execute: vi.fn(() => new Promise(() => {})),
|
|
1193
|
+
} satisfies Tool,
|
|
1194
|
+
});
|
|
1195
|
+
tracker.setState(createState([]));
|
|
1196
|
+
tracker.setState(
|
|
1197
|
+
createState([
|
|
1198
|
+
createAssistantMessage('{"query":"London"}', { query: "London" }),
|
|
1199
|
+
]),
|
|
1200
|
+
);
|
|
1201
|
+
await waitFor(() => {
|
|
1202
|
+
expect(statuses()["tool-1"]?.type).toBe("executing");
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
tracker.reset();
|
|
1206
|
+
|
|
1207
|
+
expect(statuses()).toEqual({});
|
|
1208
|
+
|
|
1209
|
+
// A new-session snapshot restoring tool activity must not drag the
|
|
1210
|
+
// discarded id back in through the whole-map republication. The first
|
|
1211
|
+
// post-reset snapshot is the fresh session's empty state, consuming the
|
|
1212
|
+
// pending-restore mark.
|
|
1213
|
+
tracker.setState(createState([]));
|
|
1214
|
+
tracker.setState(
|
|
1215
|
+
createState([
|
|
1216
|
+
createAssistantMessage(
|
|
1217
|
+
'{"query":"Paris"}',
|
|
1218
|
+
{ query: "Paris" },
|
|
1219
|
+
{ toolCallId: "tool-2" },
|
|
1220
|
+
),
|
|
1221
|
+
]),
|
|
1222
|
+
);
|
|
1223
|
+
await waitFor(() => {
|
|
1224
|
+
expect(statuses()["tool-2"]?.type).toBe("executing");
|
|
1225
|
+
});
|
|
1226
|
+
expect(statuses()["tool-1"]).toBeUndefined();
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
it("does not republish sibling statuses when a discarded execution settles after reset", async () => {
|
|
1230
|
+
const settled = Promise.withResolvers<void>();
|
|
1231
|
+
const { tracker, statuses } = trackerWith({
|
|
1232
|
+
weatherSearch: {
|
|
1233
|
+
parameters: { type: "object", properties: {} },
|
|
1234
|
+
execute: vi.fn(
|
|
1235
|
+
(_args, { abortSignal }: { abortSignal: AbortSignal }) =>
|
|
1236
|
+
new Promise((resolve) => {
|
|
1237
|
+
abortSignal.addEventListener("abort", () => {
|
|
1238
|
+
resolve({ ok: true });
|
|
1239
|
+
settled.resolve();
|
|
1240
|
+
});
|
|
1241
|
+
}),
|
|
1242
|
+
),
|
|
1243
|
+
} satisfies Tool,
|
|
1244
|
+
pressureSearch: {
|
|
1245
|
+
parameters: { type: "object", properties: {} },
|
|
1246
|
+
execute: vi.fn(() => new Promise(() => {})),
|
|
1247
|
+
} satisfies Tool,
|
|
1248
|
+
});
|
|
1249
|
+
tracker.setState(createState([]));
|
|
1250
|
+
tracker.setState(
|
|
1251
|
+
createState([
|
|
1252
|
+
createAssistantMessage('{"query":"London"}', { query: "London" }),
|
|
1253
|
+
createAssistantMessage(
|
|
1254
|
+
'{"query":"London"}',
|
|
1255
|
+
{ query: "London" },
|
|
1256
|
+
{
|
|
1257
|
+
toolCallId: "tool-2",
|
|
1258
|
+
toolName: "pressureSearch",
|
|
1259
|
+
},
|
|
1260
|
+
),
|
|
1261
|
+
]),
|
|
1262
|
+
);
|
|
1263
|
+
await waitFor(() => {
|
|
1264
|
+
expect(statuses()["tool-1"]?.type).toBe("executing");
|
|
1265
|
+
expect(statuses()["tool-2"]?.type).toBe("executing");
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
tracker.reset();
|
|
1269
|
+
expect(statuses()).toEqual({});
|
|
1270
|
+
|
|
1271
|
+
// The settle-after-abort of tool-1 is what could republish tool-2;
|
|
1272
|
+
// await it deterministically instead of sleeping.
|
|
1273
|
+
await settled.promise;
|
|
1274
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
1275
|
+
expect(statuses()).toEqual({});
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
it("rejects a human-input request from a discarded execution without resurrecting its status", async () => {
|
|
1279
|
+
let humanFn: ((payload: unknown) => Promise<unknown>) | undefined;
|
|
1280
|
+
const { tracker, statuses } = trackerWith({
|
|
1281
|
+
weatherSearch: {
|
|
1282
|
+
parameters: { type: "object", properties: {} },
|
|
1283
|
+
execute: vi.fn(() => new Promise(() => {})),
|
|
1284
|
+
streamCall: vi.fn((_reader, { human }) => {
|
|
1285
|
+
humanFn = human;
|
|
1286
|
+
}),
|
|
1287
|
+
} satisfies Tool,
|
|
1288
|
+
});
|
|
1289
|
+
tracker.setState(createState([]));
|
|
1290
|
+
tracker.setState(
|
|
1291
|
+
createState([
|
|
1292
|
+
createAssistantMessage('{"query":"London"}', { query: "London" }),
|
|
1293
|
+
]),
|
|
1294
|
+
);
|
|
1295
|
+
await waitFor(() => {
|
|
1296
|
+
expect(statuses()["tool-1"]?.type).toBe("executing");
|
|
1297
|
+
expect(humanFn).toBeDefined();
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1300
|
+
tracker.reset();
|
|
1301
|
+
|
|
1302
|
+
await expect(humanFn!({ request: "approve" })).rejects.toThrow(
|
|
1303
|
+
"Tool execution aborted",
|
|
1304
|
+
);
|
|
1305
|
+
expect(statuses()).toEqual({});
|
|
1306
|
+
});
|
|
1307
|
+
|
|
1308
|
+
it("rejects a late human-input request when reset reuses the tool call id", async () => {
|
|
1309
|
+
const humanFns: ((payload: unknown) => Promise<unknown>)[] = [];
|
|
1310
|
+
const { tracker, statuses } = trackerWith({
|
|
1311
|
+
weatherSearch: {
|
|
1312
|
+
parameters: { type: "object", properties: {} },
|
|
1313
|
+
execute: vi.fn(() => new Promise(() => {})),
|
|
1314
|
+
streamCall: vi.fn((_reader, { human }) => {
|
|
1315
|
+
humanFns.push(human);
|
|
1316
|
+
}),
|
|
1317
|
+
} satisfies Tool,
|
|
1318
|
+
});
|
|
1319
|
+
|
|
1320
|
+
tracker.setState(createState([]));
|
|
1321
|
+
tracker.setState(
|
|
1322
|
+
createState([
|
|
1323
|
+
createAssistantMessage('{"query":"London"}', { query: "London" }),
|
|
1324
|
+
]),
|
|
1325
|
+
);
|
|
1326
|
+
await waitFor(() => {
|
|
1327
|
+
expect(humanFns).toHaveLength(1);
|
|
1328
|
+
expect(statuses()["tool-1"]?.type).toBe("executing");
|
|
1329
|
+
});
|
|
1330
|
+
|
|
1331
|
+
tracker.reset();
|
|
1332
|
+
tracker.setState(createState([]));
|
|
1333
|
+
tracker.setState(
|
|
1334
|
+
createState([
|
|
1335
|
+
createAssistantMessage(
|
|
1336
|
+
'{"query":"Paris"}',
|
|
1337
|
+
{ query: "Paris" },
|
|
1338
|
+
{ toolCallId: "tool-1" },
|
|
1339
|
+
),
|
|
1340
|
+
]),
|
|
1341
|
+
);
|
|
1342
|
+
await waitFor(() => expect(humanFns).toHaveLength(2));
|
|
1343
|
+
|
|
1344
|
+
await expect(humanFns[0]!({ stale: true })).rejects.toThrow(
|
|
1345
|
+
"Tool execution aborted",
|
|
1346
|
+
);
|
|
1347
|
+
expect(statuses()["tool-1"]?.type).toBe("executing");
|
|
1348
|
+
|
|
1349
|
+
tracker.reset();
|
|
1350
|
+
});
|
|
1351
|
+
|
|
1352
|
+
it("drops a late result when reset reuses the tool call id", async () => {
|
|
1353
|
+
const executions: Array<{
|
|
1354
|
+
resolve: (value: { session: string }) => void;
|
|
1355
|
+
promise: Promise<{ session: string }>;
|
|
1356
|
+
}> = [];
|
|
1357
|
+
const onResult = vi.fn();
|
|
1358
|
+
const tracker = new ToolInvocationTracker(
|
|
1359
|
+
() => ({
|
|
1360
|
+
weatherSearch: {
|
|
1361
|
+
parameters: { type: "object", properties: {} },
|
|
1362
|
+
execute: vi.fn(
|
|
1363
|
+
(_args: unknown, { abortSignal }: { abortSignal: AbortSignal }) => {
|
|
1364
|
+
let resolveExecution!: (value: { session: string }) => void;
|
|
1365
|
+
const promise = new Promise<{ session: string }>((resolve) => {
|
|
1366
|
+
resolveExecution = resolve;
|
|
1367
|
+
});
|
|
1368
|
+
const execution = { promise, resolve: resolveExecution };
|
|
1369
|
+
abortSignal.addEventListener("abort", () => {
|
|
1370
|
+
execution.resolve({ session: "old" });
|
|
1371
|
+
});
|
|
1372
|
+
executions.push(execution);
|
|
1373
|
+
return promise;
|
|
1374
|
+
},
|
|
1375
|
+
),
|
|
1376
|
+
} satisfies Tool,
|
|
1377
|
+
}),
|
|
1378
|
+
{ onResult, onStatusesChange: vi.fn() },
|
|
1379
|
+
);
|
|
1380
|
+
|
|
1381
|
+
tracker.setState(createState([]));
|
|
1382
|
+
tracker.setState(
|
|
1383
|
+
createState([
|
|
1384
|
+
createAssistantMessage('{"query":"London"}', { query: "London" }),
|
|
1385
|
+
]),
|
|
1386
|
+
);
|
|
1387
|
+
await waitFor(() => expect(executions).toHaveLength(1));
|
|
1388
|
+
|
|
1389
|
+
tracker.reset();
|
|
1390
|
+
tracker.setState(createState([]));
|
|
1391
|
+
tracker.setState(
|
|
1392
|
+
createState([
|
|
1393
|
+
createAssistantMessage(
|
|
1394
|
+
'{"query":"Paris"}',
|
|
1395
|
+
{ query: "Paris" },
|
|
1396
|
+
{ toolCallId: "tool-1" },
|
|
1397
|
+
),
|
|
1398
|
+
]),
|
|
1399
|
+
);
|
|
1400
|
+
await waitFor(() => expect(executions).toHaveLength(2));
|
|
1401
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1402
|
+
expect(onResult).not.toHaveBeenCalled();
|
|
1403
|
+
|
|
1404
|
+
executions[1]!.resolve({ session: "new" });
|
|
1405
|
+
await waitFor(() => {
|
|
1406
|
+
expect(onResult).toHaveBeenCalledOnce();
|
|
1407
|
+
expect(onResult).toHaveBeenCalledWith(
|
|
1408
|
+
expect.objectContaining({
|
|
1409
|
+
toolCallId: "tool-1",
|
|
1410
|
+
result: { session: "new" },
|
|
1411
|
+
}),
|
|
1412
|
+
);
|
|
1413
|
+
});
|
|
1414
|
+
|
|
1415
|
+
tracker.reset();
|
|
1416
|
+
});
|
|
1417
|
+
});
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
unstable_toolResultStream,
|
|
8
8
|
type Tool,
|
|
9
9
|
type ToolModelContentPart,
|
|
10
|
+
type ToolResultStreamOptions,
|
|
10
11
|
} from "assistant-stream";
|
|
11
12
|
import {
|
|
12
13
|
AssistantMetaTransformStream,
|
|
@@ -15,6 +16,8 @@ import {
|
|
|
15
16
|
import { isJSONValueEqual } from "../../utils/json/is-json-equal";
|
|
16
17
|
import type { ThreadMessage } from "../../types/message";
|
|
17
18
|
|
|
19
|
+
const TOOL_EXECUTION_ID = Symbol.for("assistant-stream.tool-execution-id");
|
|
20
|
+
|
|
18
21
|
/**
|
|
19
22
|
* Streaming execution state for a frontend tool.
|
|
20
23
|
*/
|
|
@@ -39,6 +42,8 @@ type ToolCallEntry = {
|
|
|
39
42
|
toolName: string;
|
|
40
43
|
argsText: string;
|
|
41
44
|
hasResult: boolean;
|
|
45
|
+
executionId?: symbol;
|
|
46
|
+
skipExecute?: boolean;
|
|
42
47
|
} & (
|
|
43
48
|
| {
|
|
44
49
|
/** Restored phase — observed during a history-load snapshot. */
|
|
@@ -52,6 +57,11 @@ type ToolCallEntry = {
|
|
|
52
57
|
}
|
|
53
58
|
);
|
|
54
59
|
|
|
60
|
+
type SettledResolver = {
|
|
61
|
+
executionIds: ReadonlySet<symbol>;
|
|
62
|
+
resolve: () => void;
|
|
63
|
+
};
|
|
64
|
+
|
|
55
65
|
const isArgsTextComplete = (argsText: string) => {
|
|
56
66
|
try {
|
|
57
67
|
JSON.parse(argsText);
|
|
@@ -76,6 +86,11 @@ const isEquivalentCompleteArgsText = (previous: string, next: string) => {
|
|
|
76
86
|
return isJSONValueEqual(previousValue, nextValue);
|
|
77
87
|
};
|
|
78
88
|
|
|
89
|
+
const getToolExecutionId = (value: object): symbol | undefined =>
|
|
90
|
+
(value as Record<PropertyKey, unknown>)[TOOL_EXECUTION_ID] as
|
|
91
|
+
| symbol
|
|
92
|
+
| undefined;
|
|
93
|
+
|
|
79
94
|
/**
|
|
80
95
|
* Plain-class port of the former `useToolInvocations` React hook. Owns the
|
|
81
96
|
* assistant-stream pipeline that drives client-side `streamCall` / `execute`
|
|
@@ -103,27 +118,16 @@ export class ToolInvocationTracker {
|
|
|
103
118
|
private readonly _callbacks: ToolInvocationTracker.Callbacks;
|
|
104
119
|
|
|
105
120
|
private readonly _entries = new Map<string, ToolCallEntry>();
|
|
106
|
-
/**
|
|
107
|
-
* Tool call ids whose `execute` should be short-circuited in the wrapper.
|
|
108
|
-
* Populated when an entry is created with a result already attached
|
|
109
|
-
* (history reload, mid-run resume, etc.) — `execute` is suppressed so
|
|
110
|
-
* client-side side effects don't double-run. Membership outlives the
|
|
111
|
-
* entry: `reset()` deliberately does *not* clear this so post-abort
|
|
112
|
-
* cancellation `result` chunks for pre-resolved entries can still be
|
|
113
|
-
* recognized and dropped. Growth is bounded by the number of pre-resolved
|
|
114
|
-
* tool calls observed in the session.
|
|
115
|
-
*/
|
|
116
|
-
private readonly _skipExecuteStreamIds = new Set<string>();
|
|
117
121
|
private readonly _humanInput = new Map<
|
|
118
122
|
string,
|
|
119
123
|
{
|
|
124
|
+
executionId: symbol;
|
|
120
125
|
resolve: (payload: unknown) => void;
|
|
121
126
|
reject: (reason: unknown) => void;
|
|
122
127
|
}
|
|
123
128
|
>();
|
|
124
|
-
|
|
125
|
-
private readonly
|
|
126
|
-
private readonly _settledResolvers: Array<() => void> = [];
|
|
129
|
+
private readonly _executing = new Set<symbol>();
|
|
130
|
+
private readonly _settledResolvers: SettledResolver[] = [];
|
|
127
131
|
|
|
128
132
|
private _statuses = new Map<string, ToolExecutionStatus>();
|
|
129
133
|
|
|
@@ -166,14 +170,21 @@ export class ToolInvocationTracker {
|
|
|
166
170
|
const [stream, controller] = createAssistantStreamController();
|
|
167
171
|
this._controller = controller;
|
|
168
172
|
|
|
173
|
+
const human = (
|
|
174
|
+
toolCallId: string,
|
|
175
|
+
payload: unknown,
|
|
176
|
+
executionId?: symbol,
|
|
177
|
+
) => this._onHumanInput(toolCallId, payload, executionId);
|
|
169
178
|
const transform = unstable_toolResultStream(
|
|
170
179
|
() => this._getWrappedTools(),
|
|
171
180
|
() => this._ac.signal,
|
|
172
|
-
|
|
181
|
+
human,
|
|
173
182
|
{
|
|
174
|
-
onExecutionStart: (id) =>
|
|
175
|
-
|
|
176
|
-
|
|
183
|
+
onExecutionStart: (id: string, _name: string, executionId?: symbol) =>
|
|
184
|
+
this._onExecutionStart(id, executionId),
|
|
185
|
+
onExecutionEnd: (id: string, _name: string, executionId?: symbol) =>
|
|
186
|
+
this._onExecutionEnd(id, executionId),
|
|
187
|
+
} as ToolResultStreamOptions,
|
|
177
188
|
);
|
|
178
189
|
|
|
179
190
|
stream
|
|
@@ -281,10 +292,16 @@ export class ToolInvocationTracker {
|
|
|
281
292
|
this._pendingRestore = true;
|
|
282
293
|
this._entries.clear();
|
|
283
294
|
this._lastSnapshot = null;
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
295
|
+
void this.abort();
|
|
296
|
+
// Statuses are cleared synchronously: discarded executions may never
|
|
297
|
+
// settle (aborting hands the signal to the tool, it does not force
|
|
298
|
+
// settlement), and a late settlement of one sibling must not republish
|
|
299
|
+
// the others. `_deleteStatus` no-ops for cleared ids, so post-reset
|
|
300
|
+
// settlements stay silent.
|
|
301
|
+
if (this._statuses.size > 0) {
|
|
302
|
+
this._statuses = new Map();
|
|
303
|
+
this._invokeOnStatusesChange();
|
|
304
|
+
}
|
|
288
305
|
} catch (err) {
|
|
289
306
|
console.error("[ToolInvocationTracker] reset failed", err);
|
|
290
307
|
}
|
|
@@ -312,8 +329,9 @@ export class ToolInvocationTracker {
|
|
|
312
329
|
if (this._executing.size === 0) {
|
|
313
330
|
return Promise.resolve();
|
|
314
331
|
}
|
|
332
|
+
const executionIds = new Set(this._executing);
|
|
315
333
|
return new Promise<void>((resolve) => {
|
|
316
|
-
this._settledResolvers.push(resolve);
|
|
334
|
+
this._settledResolvers.push({ executionIds, resolve });
|
|
317
335
|
});
|
|
318
336
|
} catch (err) {
|
|
319
337
|
console.error("[ToolInvocationTracker] abort failed", err);
|
|
@@ -360,34 +378,70 @@ export class ToolInvocationTracker {
|
|
|
360
378
|
return Object.fromEntries(
|
|
361
379
|
Object.entries(tools).map(([name, tool]) => {
|
|
362
380
|
const execute = tool.execute;
|
|
363
|
-
|
|
381
|
+
const streamCall = tool.streamCall;
|
|
382
|
+
if (execute === undefined && streamCall === undefined)
|
|
383
|
+
return [name, tool];
|
|
364
384
|
|
|
365
385
|
const wrappedTool = {
|
|
366
386
|
...tool,
|
|
367
|
-
execute
|
|
368
|
-
...[args, context]: Parameters<
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
387
|
+
...(execute !== undefined && {
|
|
388
|
+
execute: (...[args, context]: Parameters<typeof execute>) => {
|
|
389
|
+
const executionId = getToolExecutionId(context);
|
|
390
|
+
const entry = this._captureExecution(
|
|
391
|
+
context.toolCallId,
|
|
392
|
+
executionId,
|
|
393
|
+
);
|
|
394
|
+
if (!entry || entry.skipExecute) {
|
|
395
|
+
return new Promise(() => {}) as never;
|
|
396
|
+
}
|
|
397
|
+
return execute(args, context);
|
|
398
|
+
},
|
|
399
|
+
}),
|
|
400
|
+
...(streamCall !== undefined && {
|
|
401
|
+
streamCall: (
|
|
402
|
+
...[reader, context]: Parameters<typeof streamCall>
|
|
403
|
+
) => {
|
|
404
|
+
const executionId = getToolExecutionId(context);
|
|
405
|
+
const entry = this._captureExecution(
|
|
406
|
+
context.toolCallId,
|
|
407
|
+
executionId,
|
|
408
|
+
);
|
|
409
|
+
if (!entry) return;
|
|
410
|
+
return streamCall(reader, context);
|
|
411
|
+
},
|
|
412
|
+
}),
|
|
378
413
|
} as Tool;
|
|
379
414
|
return [name, wrappedTool];
|
|
380
415
|
}),
|
|
381
416
|
) as Record<string, Tool>;
|
|
382
417
|
}
|
|
383
418
|
|
|
419
|
+
private _captureExecution(
|
|
420
|
+
toolCallId: string,
|
|
421
|
+
executionId: symbol | undefined,
|
|
422
|
+
): ToolCallEntry | undefined {
|
|
423
|
+
if (executionId === undefined) return undefined;
|
|
424
|
+
const entry = this._entries.get(toolCallId);
|
|
425
|
+
if (!entry?.controller) return undefined;
|
|
426
|
+
if (entry.executionId === undefined) entry.executionId = executionId;
|
|
427
|
+
return entry.executionId === executionId ? entry : undefined;
|
|
428
|
+
}
|
|
429
|
+
|
|
384
430
|
// ──────────────── internal: execution lifecycle callbacks ────────────────
|
|
385
431
|
|
|
386
432
|
private _onHumanInput(
|
|
387
433
|
toolCallId: string,
|
|
388
434
|
payload: unknown,
|
|
435
|
+
executionId?: symbol,
|
|
389
436
|
): Promise<unknown> {
|
|
390
437
|
return new Promise<unknown>((resolve, reject) => {
|
|
438
|
+
// A discarded execution must not resurrect a status entry or park an
|
|
439
|
+
// unanswerable request after its id has been reused.
|
|
440
|
+
const entry = this._entries.get(toolCallId);
|
|
441
|
+
if (!entry?.controller || entry.executionId !== executionId) {
|
|
442
|
+
reject(new Error("Tool execution aborted"));
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
391
445
|
const previous = this._humanInput.get(toolCallId);
|
|
392
446
|
if (previous) {
|
|
393
447
|
try {
|
|
@@ -398,7 +452,11 @@ export class ToolInvocationTracker {
|
|
|
398
452
|
// host rejection handler threw; ignore and proceed
|
|
399
453
|
}
|
|
400
454
|
}
|
|
401
|
-
this._humanInput.set(toolCallId, {
|
|
455
|
+
this._humanInput.set(toolCallId, {
|
|
456
|
+
executionId: executionId!,
|
|
457
|
+
resolve,
|
|
458
|
+
reject,
|
|
459
|
+
});
|
|
402
460
|
this._setStatus(toolCallId, {
|
|
403
461
|
type: "interrupt",
|
|
404
462
|
payload: { type: "human", payload },
|
|
@@ -406,28 +464,42 @@ export class ToolInvocationTracker {
|
|
|
406
464
|
});
|
|
407
465
|
}
|
|
408
466
|
|
|
409
|
-
private _onExecutionStart(
|
|
410
|
-
|
|
467
|
+
private _onExecutionStart(
|
|
468
|
+
toolCallId: string,
|
|
469
|
+
executionId: symbol | undefined,
|
|
470
|
+
): void {
|
|
471
|
+
if (!this._captureExecution(toolCallId, executionId)) return;
|
|
472
|
+
const entry = this._entries.get(toolCallId)!;
|
|
473
|
+
if (entry.skipExecute) return;
|
|
411
474
|
|
|
412
|
-
this._executing.add(
|
|
475
|
+
this._executing.add(executionId!);
|
|
413
476
|
this._setStatus(toolCallId, { type: "executing" });
|
|
414
477
|
}
|
|
415
478
|
|
|
416
|
-
private _onExecutionEnd(
|
|
417
|
-
|
|
479
|
+
private _onExecutionEnd(
|
|
480
|
+
toolCallId: string,
|
|
481
|
+
executionId: symbol | undefined,
|
|
482
|
+
): void {
|
|
483
|
+
if (executionId === undefined || !this._executing.delete(executionId))
|
|
484
|
+
return;
|
|
418
485
|
|
|
419
|
-
this.
|
|
486
|
+
const entry = this._entries.get(toolCallId);
|
|
487
|
+
if (entry?.executionId === executionId) this._deleteStatus(toolCallId);
|
|
420
488
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
430
|
-
|
|
489
|
+
const pending: SettledResolver[] = [];
|
|
490
|
+
this._settledResolvers.forEach(({ executionIds, resolve }) => {
|
|
491
|
+
if ([...executionIds].some((id) => this._executing.has(id))) {
|
|
492
|
+
pending.push({ executionIds, resolve });
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
try {
|
|
496
|
+
resolve();
|
|
497
|
+
} catch {
|
|
498
|
+
// ignore — settled-resolver consumer threw
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
this._settledResolvers.length = 0;
|
|
502
|
+
this._settledResolvers.push(...pending);
|
|
431
503
|
}
|
|
432
504
|
|
|
433
505
|
private _handleResultChunk(chunk: {
|
|
@@ -439,14 +511,10 @@ export class ToolInvocationTracker {
|
|
|
439
511
|
meta: { toolCallId: string; toolName: string };
|
|
440
512
|
}): void {
|
|
441
513
|
const toolCallId = chunk.meta.toolCallId;
|
|
514
|
+
const executionId = getToolExecutionId(chunk);
|
|
442
515
|
const entry = this._entries.get(toolCallId);
|
|
443
516
|
|
|
444
|
-
|
|
445
|
-
// The post-abort cancellation chunk lands here after the entry is
|
|
446
|
-
// gone; suppress via the long-lived skip-execute marker.
|
|
447
|
-
if (!entry && this._skipExecuteStreamIds.has(toolCallId)) {
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
517
|
+
if (!entry || entry.executionId !== executionId) return;
|
|
450
518
|
|
|
451
519
|
// The host already set the result (via the live snapshot's
|
|
452
520
|
// `setResponse` path). Suppress the executor's redundant emit.
|
|
@@ -538,14 +606,12 @@ export class ToolInvocationTracker {
|
|
|
538
606
|
toolName,
|
|
539
607
|
toolCallId,
|
|
540
608
|
});
|
|
541
|
-
if (skipExecute) {
|
|
542
|
-
this._skipExecuteStreamIds.add(toolCallId);
|
|
543
|
-
}
|
|
544
609
|
const entry: ToolCallEntry = {
|
|
545
610
|
toolName,
|
|
546
611
|
controller: toolCallController,
|
|
547
612
|
argsText: "",
|
|
548
613
|
hasResult: false,
|
|
614
|
+
skipExecute,
|
|
549
615
|
argsComplete: false,
|
|
550
616
|
};
|
|
551
617
|
this._entries.set(toolCallId, entry);
|
|
@@ -39,10 +39,12 @@ describe("ModelContext", () => {
|
|
|
39
39
|
it("reflects modelName from a registered provider", async () => {
|
|
40
40
|
const { sub, unmount } = render();
|
|
41
41
|
try {
|
|
42
|
-
sub
|
|
42
|
+
sub
|
|
43
|
+
.getValue()
|
|
44
|
+
.register(provider({ config: { modelName: "gpt-5.6-luna" } }));
|
|
43
45
|
await tick();
|
|
44
46
|
|
|
45
|
-
expect(sub.getValue().getState().modelName).toBe("gpt-
|
|
47
|
+
expect(sub.getValue().getState().modelName).toBe("gpt-5.6-luna");
|
|
46
48
|
} finally {
|
|
47
49
|
unmount();
|
|
48
50
|
}
|
|
@@ -67,11 +69,15 @@ describe("ModelContext", () => {
|
|
|
67
69
|
it("keeps the same state reference when an extra provider does not change the merged values", async () => {
|
|
68
70
|
const { sub, unmount } = render();
|
|
69
71
|
try {
|
|
70
|
-
sub
|
|
72
|
+
sub
|
|
73
|
+
.getValue()
|
|
74
|
+
.register(provider({ config: { modelName: "gpt-5.6-luna" } }));
|
|
71
75
|
await tick();
|
|
72
76
|
const before = sub.getValue().getState();
|
|
73
77
|
|
|
74
|
-
sub
|
|
78
|
+
sub
|
|
79
|
+
.getValue()
|
|
80
|
+
.register(provider({ config: { modelName: "gpt-5.6-luna" } }));
|
|
75
81
|
await tick();
|
|
76
82
|
const after = sub.getValue().getState();
|
|
77
83
|
|
|
@@ -86,9 +92,9 @@ describe("ModelContext", () => {
|
|
|
86
92
|
try {
|
|
87
93
|
const unsubscribe = sub
|
|
88
94
|
.getValue()
|
|
89
|
-
.register(provider({ config: { modelName: "gpt-
|
|
95
|
+
.register(provider({ config: { modelName: "gpt-5.6-luna" } }));
|
|
90
96
|
await tick();
|
|
91
|
-
expect(sub.getValue().getState().modelName).toBe("gpt-
|
|
97
|
+
expect(sub.getValue().getState().modelName).toBe("gpt-5.6-luna");
|
|
92
98
|
|
|
93
99
|
unsubscribe();
|
|
94
100
|
await tick();
|
|
@@ -102,9 +108,11 @@ describe("ModelContext", () => {
|
|
|
102
108
|
it("reflects modelName synchronously after register without awaiting", () => {
|
|
103
109
|
const { sub, unmount } = render();
|
|
104
110
|
try {
|
|
105
|
-
sub
|
|
111
|
+
sub
|
|
112
|
+
.getValue()
|
|
113
|
+
.register(provider({ config: { modelName: "gpt-5.6-luna" } }));
|
|
106
114
|
|
|
107
|
-
expect(sub.getValue().getState().modelName).toBe("gpt-
|
|
115
|
+
expect(sub.getValue().getState().modelName).toBe("gpt-5.6-luna");
|
|
108
116
|
} finally {
|
|
109
117
|
unmount();
|
|
110
118
|
}
|