@assistant-ui/core 0.3.16 → 0.3.17
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/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +2 -1
- package/dist/internal.js +2 -1
- package/dist/model-context/frame/provider.d.ts +3 -0
- package/dist/model-context/frame/provider.d.ts.map +1 -1
- package/dist/model-context/frame/provider.js +71 -21
- package/dist/model-context/frame/provider.js.map +1 -1
- package/dist/model-context/tool-call-text.d.ts +26 -0
- package/dist/model-context/tool-call-text.d.ts.map +1 -0
- package/dist/model-context/tool-call-text.js +18 -0
- package/dist/model-context/tool-call-text.js.map +1 -0
- package/dist/react/RuntimeAdapter.js +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.js +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
- package/dist/react/client/AdaptedRemoteThread.js +1 -1
- package/dist/react/client/InMemoryThreadList.js +1 -1
- package/dist/react/client/Interactables.js +1 -1
- package/dist/react/client/RemoteThreadList.d.ts +15 -0
- package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
- package/dist/react/client/RemoteThreadList.js +240 -138
- package/dist/react/client/RemoteThreadList.js.map +1 -1
- package/dist/react/client/Tools.d.ts.map +1 -1
- package/dist/react/client/Tools.js +6 -2
- package/dist/react/client/Tools.js.map +1 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.js +2 -1
- package/dist/react/interactables-legacy/Interactables.js +4 -4
- package/dist/react/interactables-legacy/Interactables.js.map +1 -1
- package/dist/react/model-context/toolbox.d.ts +2 -14
- package/dist/react/model-context/toolbox.d.ts.map +1 -1
- package/dist/react/model-context/toolbox.js +1 -13
- package/dist/react/model-context/toolbox.js.map +1 -1
- package/dist/react/model-context/useAssistantContext.js +37 -7
- package/dist/react/model-context/useAssistantContext.js.map +1 -1
- package/dist/react/model-context/useAuiToolOverrides.js +31 -5
- package/dist/react/model-context/useAuiToolOverrides.js.map +1 -1
- package/dist/react/model-context/useInlineRender.d.ts.map +1 -1
- package/dist/react/model-context/useInlineRender.js +10 -36
- package/dist/react/model-context/useInlineRender.js.map +1 -1
- package/dist/react/primitive-hooks/useMessageError.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useMessageError.js +2 -8
- package/dist/react/primitive-hooks/useMessageError.js.map +1 -1
- package/dist/react/primitive-hooks/useThreadListLoadMore.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useThreadListLoadMore.js +2 -4
- package/dist/react/primitive-hooks/useThreadListLoadMore.js.map +1 -1
- package/dist/react/primitives/composer/ComposerAttachments.d.ts.map +1 -1
- package/dist/react/primitives/composer/ComposerAttachments.js +4 -3
- package/dist/react/primitives/composer/ComposerAttachments.js.map +1 -1
- package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
- package/dist/react/primitives/composer/ComposerQueue.js +4 -3
- package/dist/react/primitives/composer/ComposerQueue.js.map +1 -1
- package/dist/react/primitives/message/MessageAttachments.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageAttachments.js +7 -6
- package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.js +2 -2
- package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
- package/dist/react/primitives/message/MessageParts.js +3 -3
- package/dist/react/primitives/message/MessageParts.js.map +1 -1
- package/dist/react/primitives/threadList/ThreadListItems.d.ts.map +1 -1
- package/dist/react/primitives/threadList/ThreadListItems.js +5 -4
- package/dist/react/primitives/threadList/ThreadListItems.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +6 -4
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +44 -22
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +6 -5
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +79 -62
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadResource.d.ts +1 -6
- package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadResource.js +3 -3
- package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
- package/dist/react/runtimes/assistant-transport/useLatestRef.js +2 -2
- package/dist/react/runtimes/assistant-transport/useLatestRef.js.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +23 -18
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/auiV0.d.ts +5 -1
- package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
- package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts +29 -0
- package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts.map +1 -0
- package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js +159 -0
- package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js.map +1 -0
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts +1 -9
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +16 -153
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
- package/dist/react/runtimes/external-message-converter.js +1 -1
- package/dist/react/runtimes/external-message-converter.js.map +1 -1
- package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
- package/dist/react/runtimes/useRemoteThreadListRuntime.js +14 -6
- package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
- package/dist/react/types/MessagePartComponentTypes.d.ts +7 -3
- package/dist/react/types/MessagePartComponentTypes.d.ts.map +1 -1
- package/dist/react/types/scopes/tools.d.ts +2 -0
- package/dist/react/types/scopes/tools.d.ts.map +1 -1
- package/dist/runtime/api/message-part-runtime.d.ts +2 -2
- package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
- package/dist/runtime/api/message-part-runtime.js +1 -1
- package/dist/runtime/api/message-part-runtime.js.map +1 -1
- package/dist/runtime/api/thread-list-runtime.d.ts +9 -1
- package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-list-runtime.js +5 -0
- package/dist/runtime/api/thread-list-runtime.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -2
- package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.js +2 -2
- package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
- package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +14 -2
- package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts +15 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/utils/auto-status.d.ts +1 -1
- package/dist/runtime/utils/auto-status.d.ts.map +1 -1
- package/dist/runtime/utils/auto-status.js +6 -2
- package/dist/runtime/utils/auto-status.js.map +1 -1
- package/dist/runtime/utils/external-message-conversion.d.ts +3 -2
- package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -1
- package/dist/runtime/utils/external-message-conversion.js +4 -3
- package/dist/runtime/utils/external-message-conversion.js.map +1 -1
- package/dist/runtime/utils/resolveToolApprovalResponse.d.ts +6 -3
- package/dist/runtime/utils/resolveToolApprovalResponse.d.ts.map +1 -1
- package/dist/runtime/utils/resolveToolApprovalResponse.js +12 -3
- package/dist/runtime/utils/resolveToolApprovalResponse.js.map +1 -1
- package/dist/runtime/utils/thread-message-like.d.ts +2 -10
- 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/runtime/utils/tool-call-tree.d.ts +39 -0
- package/dist/runtime/utils/tool-call-tree.d.ts.map +1 -0
- package/dist/runtime/utils/tool-call-tree.js +85 -0
- package/dist/runtime/utils/tool-call-tree.js.map +1 -0
- package/dist/runtimes/external-store/external-store-adapter.d.ts +29 -2
- package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +2 -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 +34 -28
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts +1 -1
- package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtimes/inert/InertThreadRuntimeCore.js.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +3 -1
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +1 -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 +30 -13
- package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
- package/dist/runtimes/remote-thread-list/title.d.ts +9 -0
- package/dist/runtimes/remote-thread-list/title.d.ts.map +1 -0
- package/dist/runtimes/remote-thread-list/title.js +6 -0
- package/dist/runtimes/remote-thread-list/title.js.map +1 -0
- package/dist/runtimes/remote-thread-list/types.d.ts +6 -5
- package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +31 -8
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +97 -60
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
- package/dist/store/clients/external-thread.d.ts +1 -1
- package/dist/store/clients/external-thread.d.ts.map +1 -1
- package/dist/store/clients/external-thread.js +11 -4
- package/dist/store/clients/external-thread.js.map +1 -1
- package/dist/store/clients/single-thread-list.js +1 -1
- package/dist/store/clients/suggestions.d.ts.map +1 -1
- package/dist/store/clients/suggestions.js +18 -6
- package/dist/store/clients/suggestions.js.map +1 -1
- package/dist/store/clients/thread-message-client.js +1 -1
- package/dist/store/clients/thread-selection-events.js +1 -1
- package/dist/store/index.d.ts +2 -1
- package/dist/store/index.js +2 -1
- package/dist/store/internal.d.ts +2 -2
- package/dist/store/internal.js +2 -2
- package/dist/store/primitive-predicates.d.ts +4 -1
- package/dist/store/primitive-predicates.d.ts.map +1 -1
- package/dist/store/primitive-predicates.js +9 -1
- package/dist/store/primitive-predicates.js.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.js +1 -1
- package/dist/store/runtime-clients/message-runtime-client.js +1 -1
- package/dist/store/runtime-clients/thread-list-item-runtime-client.js +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 +124 -105
- package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.js +1 -1
- package/dist/store/runtime-clients/useSubscribable.d.ts +3 -1
- package/dist/store/runtime-clients/useSubscribable.d.ts.map +1 -1
- package/dist/store/runtime-clients/useSubscribable.js +1 -1
- package/dist/store/runtime-clients/useSubscribable.js.map +1 -1
- package/dist/store/scopes/part.d.ts +6 -3
- package/dist/store/scopes/part.d.ts.map +1 -1
- package/dist/subscribable/subscribable.d.ts +8 -1
- package/dist/subscribable/subscribable.d.ts.map +1 -1
- package/dist/subscribable/subscribable.js +24 -16
- package/dist/subscribable/subscribable.js.map +1 -1
- package/dist/types/message.d.ts +40 -1
- package/dist/types/message.d.ts.map +1 -1
- package/dist/types/message.js +7 -1
- package/dist/types/message.js.map +1 -1
- package/package.json +12 -17
- package/src/index.ts +6 -1
- package/src/internal.ts +8 -0
- package/src/model-context/frame/provider.test.ts +217 -1
- package/src/model-context/frame/provider.ts +111 -23
- package/src/model-context/tool-call-text.test.ts +67 -0
- package/src/model-context/tool-call-text.ts +67 -0
- package/src/react/AssistantRuntimeProvider.test.tsx +66 -2
- package/src/react/adapters/LocalStorageThreadListAdapter.tsx +1 -3
- package/src/react/client/RemoteThreadList.background.test.ts +384 -0
- package/src/react/client/RemoteThreadList.concurrent.test.tsx +274 -0
- package/src/react/client/RemoteThreadList.test.ts +273 -1
- package/src/react/client/RemoteThreadList.ts +293 -83
- package/src/react/client/Tools.ts +7 -1
- package/src/react/index.ts +4 -0
- package/src/react/model-context/modelContextHooks.test.tsx +131 -0
- package/src/react/model-context/toolbox.test.ts +9 -9
- package/src/react/model-context/toolbox.ts +9 -36
- package/src/react/model-context/useAssistantContext.ts +4 -2
- package/src/react/model-context/useAuiToolOverrides.ts +4 -2
- package/src/react/model-context/useInlineRender.test.tsx +48 -0
- package/src/react/model-context/useInlineRender.ts +14 -10
- package/src/react/primitive-hooks/useMessageError.ts +2 -19
- package/src/react/primitive-hooks/useThreadListLoadMore.ts +2 -3
- package/src/react/primitives/composer/ComposerAttachments.test.tsx +76 -0
- package/src/react/primitives/composer/ComposerAttachments.tsx +9 -4
- package/src/react/primitives/composer/ComposerQueue.test.tsx +72 -0
- package/src/react/primitives/composer/ComposerQueue.tsx +7 -4
- package/src/react/primitives/message/MessageAttachments.test.tsx +107 -37
- package/src/react/primitives/message/MessageAttachments.tsx +10 -7
- package/src/react/primitives/message/MessageGroupedParts.tsx +2 -2
- package/src/react/primitives/message/MessageParts.tsx +3 -3
- package/src/react/primitives/threadList/ThreadListItems.test.tsx +99 -0
- package/src/react/primitives/threadList/ThreadListItems.tsx +8 -5
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +94 -1
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +72 -31
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.switch-delete.test.ts +169 -0
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +114 -68
- package/src/react/runtimes/RemoteThreadResource.ts +1 -4
- package/src/react/runtimes/assistant-transport/transport-scheduling.test.ts +51 -2
- package/src/react/runtimes/assistant-transport/useLatestRef.ts +2 -2
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +45 -0
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +65 -1
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +42 -35
- package/src/react/runtimes/cloud/auiV0.ts +5 -0
- package/src/react/runtimes/cloud/createCloudThreadListAdapter.test.ts +88 -0
- package/src/react/runtimes/cloud/createCloudThreadListAdapter.ts +236 -0
- package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +90 -1
- package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +33 -238
- package/src/react/runtimes/external-message-converter.ts +1 -0
- package/src/react/runtimes/useRemoteThreadListRuntime.concurrent.test.tsx +216 -0
- package/src/react/runtimes/useRemoteThreadListRuntime.ts +24 -6
- package/src/react/types/MessagePartComponentTypes.ts +7 -3
- package/src/react/types/scopes/tools.ts +2 -0
- package/src/runtime/api/message-part-runtime.ts +3 -3
- package/src/runtime/api/thread-list-runtime.ts +25 -1
- package/src/runtime/base/base-thread-runtime-core.test.ts +94 -3
- package/src/runtime/base/base-thread-runtime-core.ts +10 -4
- package/src/runtime/interfaces/thread-list-runtime-core.ts +20 -1
- package/src/runtime/interfaces/thread-runtime-core.ts +17 -1
- package/src/runtime/utils/auto-status.test.ts +57 -0
- package/src/runtime/utils/auto-status.ts +13 -1
- package/src/runtime/utils/external-message-conversion.test.ts +97 -0
- package/src/runtime/utils/external-message-conversion.ts +17 -1
- package/src/runtime/utils/resolveToolApprovalResponse.test.ts +73 -0
- package/src/runtime/utils/resolveToolApprovalResponse.ts +27 -6
- package/src/runtime/utils/thread-message-like.ts +2 -10
- package/src/runtime/utils/tool-call-tree.test.ts +191 -0
- package/src/runtime/utils/tool-call-tree.ts +97 -0
- package/src/runtimes/external-store/external-store-adapter.ts +38 -2
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +56 -33
- package/src/runtimes/inert/InertThreadRuntimeCore.ts +1 -1
- package/src/runtimes/local/local-thread-runtime-core.test.ts +29 -3
- package/src/runtimes/local/local-thread-runtime-core.ts +5 -1
- package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +112 -0
- package/src/runtimes/remote-thread-list/remote-thread-state.ts +55 -16
- package/src/runtimes/remote-thread-list/title.ts +3 -0
- package/src/runtimes/remote-thread-list/types.ts +6 -5
- package/src/runtimes/tool-invocations/EDGE_CASES.md +110 -5
- package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +750 -136
- package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +144 -91
- package/src/store/clients/external-thread.ts +14 -8
- package/src/store/clients/suggestions.test.ts +155 -1
- package/src/store/clients/suggestions.ts +42 -34
- package/src/store/index.ts +4 -0
- package/src/store/internal.ts +2 -0
- package/src/store/primitive-predicates.ts +35 -0
- package/src/store/runtime-clients/thread-list-runtime-client.ts +16 -2
- package/src/store/runtime-clients/useSubscribable.ts +8 -2
- package/src/store/scopes/part.ts +6 -3
- package/src/subscribable/subscribable.test.ts +50 -0
- package/src/subscribable/subscribable.ts +33 -21
- package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +111 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +78 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-reload.test.ts +52 -0
- package/src/tests/external-store-optimistic-id.test.ts +79 -0
- package/src/tests/external-store-repository-instance.test.ts +166 -0
- package/src/tests/external-store-thread-runtime-core-adapter.test.ts +22 -16
- package/src/tests/external-thread-attachments.test.tsx +85 -1
- package/src/tests/remote-thread-list-adapter-switch.test.ts +66 -1
- package/src/tests/thread-switch-events.test.tsx +397 -2
- package/src/types/message.ts +50 -2
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AppendMessage,
|
|
3
|
+
ThreadMessage,
|
|
4
|
+
ToolCallMessagePart,
|
|
5
|
+
} from "../../types/message";
|
|
2
6
|
import type { ThreadMessageLike } from "../../runtime/utils/thread-message-like";
|
|
3
7
|
import type { AttachmentAdapter } from "../../adapters/attachment";
|
|
4
8
|
import type {
|
|
@@ -14,7 +18,10 @@ import type {
|
|
|
14
18
|
ResumeRunConfig,
|
|
15
19
|
ThreadSuggestion,
|
|
16
20
|
} from "../../runtime/interfaces/thread-runtime-core";
|
|
17
|
-
import type {
|
|
21
|
+
import type {
|
|
22
|
+
ExportedMessageRepository,
|
|
23
|
+
MessageRepository,
|
|
24
|
+
} from "../../runtime/utils/message-repository";
|
|
18
25
|
import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
19
26
|
import type { ToolExecutionStatus } from "../tool-invocations/ToolInvocationTracker";
|
|
20
27
|
import type { ExternalThreadQueueAdapter } from "../../runtime/queue/external-thread-queue-adapter";
|
|
@@ -105,6 +112,15 @@ type ExternalStoreAdapterBase<T> = {
|
|
|
105
112
|
isLoading?: boolean | undefined;
|
|
106
113
|
messages?: readonly T[];
|
|
107
114
|
messageRepository?: ExportedMessageRepository;
|
|
115
|
+
/**
|
|
116
|
+
* An externally owned message repository instance. When provided, the
|
|
117
|
+
* thread runtime adopts it as its branch store and swaps to it atomically
|
|
118
|
+
* whenever a different instance is passed, so hosts that route multiple
|
|
119
|
+
* conversations through one runtime keep each conversation's history and
|
|
120
|
+
* branches isolated in its own instance. Omit it to keep the runtime's own
|
|
121
|
+
* repository.
|
|
122
|
+
*/
|
|
123
|
+
unstable_messageRepositoryInstance?: MessageRepository | undefined;
|
|
108
124
|
suggestions?: readonly ThreadSuggestion[] | undefined;
|
|
109
125
|
state?: ReadonlyJSONValue | undefined;
|
|
110
126
|
extras?: unknown;
|
|
@@ -207,6 +223,26 @@ type ExternalStoreAdapterBase<T> = {
|
|
|
207
223
|
* `modelContent` populated when present.
|
|
208
224
|
*/
|
|
209
225
|
unstable_enableToolInvocations?: boolean | undefined;
|
|
226
|
+
/**
|
|
227
|
+
* Decides whether a tool call's result is produced on the client. Only
|
|
228
|
+
* consulted when `unstable_enableToolInvocations` is `true`.
|
|
229
|
+
*
|
|
230
|
+
* A provider that runs tools itself answers its own calls, and its result
|
|
231
|
+
* arrives one or more snapshots after the call's arguments complete. In
|
|
232
|
+
* that window the call is complete and result-less, so a registered tool
|
|
233
|
+
* of the same name would otherwise execute locally and produce a result
|
|
234
|
+
* the provider never asked for. An adapter that can tell the two apart
|
|
235
|
+
* supplies this predicate; it is read once per tool call, when the call is
|
|
236
|
+
* first observed live.
|
|
237
|
+
*
|
|
238
|
+
* The predicate is also what licenses running a frontend tool while the
|
|
239
|
+
* provider's run is still open. Without it, ownership is unknown until the
|
|
240
|
+
* run ends, so a registered tool executes only once the run's outcome is
|
|
241
|
+
* known and cannot fire on a call the provider was about to answer or gate.
|
|
242
|
+
*/
|
|
243
|
+
unstable_isClientToolCall?:
|
|
244
|
+
| ((toolCall: ToolCallMessagePart) => boolean)
|
|
245
|
+
| undefined;
|
|
210
246
|
/**
|
|
211
247
|
* Receives the current per-tool-call execution status map whenever it
|
|
212
248
|
* changes. Only invoked when `unstable_enableToolInvocations` is `true`
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { shallowEqual } from "@assistant-ui/store/client";
|
|
1
2
|
import type { AppendMessage, ThreadMessage } from "../../types/message";
|
|
2
3
|
import type { Attachment } from "../../types/attachment";
|
|
3
4
|
import type {
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
type ThreadMessageLike,
|
|
29
30
|
} from "../../runtime/utils/thread-message-like";
|
|
30
31
|
import { getThreadMessageText } from "../../utils/text";
|
|
32
|
+
import { shallowArrayEqual } from "../../runtime/utils/external-message-conversion";
|
|
31
33
|
import type {
|
|
32
34
|
RuntimeCapabilities,
|
|
33
35
|
ThreadRuntimeCore,
|
|
@@ -43,6 +45,7 @@ import {
|
|
|
43
45
|
MessageRepository,
|
|
44
46
|
} from "../../runtime/utils/message-repository";
|
|
45
47
|
import { generateId } from "../../utils/id";
|
|
48
|
+
import { walkToolCallTree } from "../../runtime/utils/tool-call-tree";
|
|
46
49
|
import {
|
|
47
50
|
ToolInvocationTracker,
|
|
48
51
|
type ToolExecutionStatus,
|
|
@@ -57,7 +60,7 @@ import {
|
|
|
57
60
|
const EMPTY_ARRAY: readonly ThreadSuggestion[] = Object.freeze([]);
|
|
58
61
|
|
|
59
62
|
const observeAdapterCallback = (
|
|
60
|
-
name: "onAddToolResult" | "
|
|
63
|
+
name: "onAddToolResult" | "onCancel",
|
|
61
64
|
result: Promise<void> | void,
|
|
62
65
|
) => {
|
|
63
66
|
void Promise.resolve(result).catch((error) => {
|
|
@@ -68,15 +71,6 @@ const observeAdapterCallback = (
|
|
|
68
71
|
});
|
|
69
72
|
};
|
|
70
73
|
|
|
71
|
-
const shallowEqual = (a: object, b: object): boolean => {
|
|
72
|
-
const aKeys = Object.keys(a);
|
|
73
|
-
if (aKeys.length !== Object.keys(b).length) return false;
|
|
74
|
-
for (const key of aKeys) {
|
|
75
|
-
if ((a as any)[key] !== (b as any)[key]) return false;
|
|
76
|
-
}
|
|
77
|
-
return true;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
74
|
export const hasUpcomingMessage = (
|
|
81
75
|
isRunning: boolean,
|
|
82
76
|
messages: readonly ThreadMessage[],
|
|
@@ -154,6 +148,11 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
154
148
|
// and a delete whose confirmation races a send keeps its eviction.
|
|
155
149
|
private _pendingDeleteEvictions = new Set<string>();
|
|
156
150
|
|
|
151
|
+
// Placeholder id for the upcoming assistant message, reused across snapshot
|
|
152
|
+
// passes while the same tail message awaits its response so the placeholder
|
|
153
|
+
// keeps one identity per response.
|
|
154
|
+
private _optimistic: { id: string; parentId: string | null } | null = null;
|
|
155
|
+
|
|
157
156
|
private _store!: ExternalStoreAdapter<any>;
|
|
158
157
|
|
|
159
158
|
private _getInitializePromise?: () => Promise<unknown> | undefined;
|
|
@@ -246,6 +245,14 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
246
245
|
const oldStore = this._store as ExternalStoreAdapter<any> | undefined;
|
|
247
246
|
this._store = store;
|
|
248
247
|
const isRunning = this._getEffectiveIsRunning(store);
|
|
248
|
+
const repositoryInstance = store.unstable_messageRepositoryInstance;
|
|
249
|
+
const repositoryChanged =
|
|
250
|
+
repositoryInstance !== undefined &&
|
|
251
|
+
repositoryInstance !== this.repository;
|
|
252
|
+
if (repositoryChanged) {
|
|
253
|
+
this.repository = repositoryInstance;
|
|
254
|
+
this._pendingDeleteEvictions.clear();
|
|
255
|
+
}
|
|
249
256
|
if (oldStore?.queue !== store.queue) {
|
|
250
257
|
this._transformedQueue = undefined;
|
|
251
258
|
store.queue?.__internal_setDispatchTransform?.((message) => {
|
|
@@ -296,6 +303,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
296
303
|
// Handle messageRepository
|
|
297
304
|
if (
|
|
298
305
|
oldStore &&
|
|
306
|
+
!repositoryChanged &&
|
|
299
307
|
oldStore.isRunning === store.isRunning &&
|
|
300
308
|
oldStore.messageRepository === store.messageRepository &&
|
|
301
309
|
previousIsRunning === isRunning
|
|
@@ -308,7 +316,11 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
308
316
|
const headId =
|
|
309
317
|
store.messageRepository.headId ?? incoming.at(-1)?.message.id ?? null;
|
|
310
318
|
|
|
311
|
-
if (
|
|
319
|
+
if (
|
|
320
|
+
oldStore &&
|
|
321
|
+
!repositoryChanged &&
|
|
322
|
+
oldStore.messageRepository === store.messageRepository
|
|
323
|
+
) {
|
|
312
324
|
this.repository.resetHead(headId);
|
|
313
325
|
messages = this.repository.getMessages();
|
|
314
326
|
} else {
|
|
@@ -333,6 +345,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
333
345
|
if (oldStore.convertMessage !== store.convertMessage) {
|
|
334
346
|
this._converter = new ThreadMessageConverter();
|
|
335
347
|
} else if (
|
|
348
|
+
!repositoryChanged &&
|
|
336
349
|
oldStore.isRunning === store.isRunning &&
|
|
337
350
|
oldStore.messages === store.messages &&
|
|
338
351
|
previousIsRunning === isRunning
|
|
@@ -436,9 +449,13 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
436
449
|
// (prior placeholders, mid-run id-swap siblings); export() never persists them.
|
|
437
450
|
let optimisticId: string | null = null;
|
|
438
451
|
if (hasUpcomingMessage(isRunning, messages)) {
|
|
439
|
-
|
|
452
|
+
const parentId = messages.at(-1)?.id ?? null;
|
|
453
|
+
if (this._optimistic?.parentId !== parentId) {
|
|
454
|
+
this._optimistic = { id: generateId(), parentId };
|
|
455
|
+
}
|
|
456
|
+
optimisticId = this._optimistic.id;
|
|
440
457
|
this.repository.addOrUpdateMessage(
|
|
441
|
-
|
|
458
|
+
parentId,
|
|
442
459
|
fromThreadMessageLike(
|
|
443
460
|
{ role: "assistant", content: [], metadata: { isOptimistic: true } },
|
|
444
461
|
optimisticId,
|
|
@@ -447,10 +464,20 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
447
464
|
);
|
|
448
465
|
}
|
|
449
466
|
|
|
467
|
+
if (optimisticId === null) this._optimistic = null;
|
|
450
468
|
this.repository.resetHead(optimisticId ?? messages.at(-1)?.id ?? null);
|
|
451
469
|
|
|
452
|
-
|
|
470
|
+
const messagesSnapshot = this.repository.getMessages();
|
|
471
|
+
if (
|
|
472
|
+
!this._messages ||
|
|
473
|
+
!shallowArrayEqual(this._messages, messagesSnapshot)
|
|
474
|
+
) {
|
|
475
|
+
this._messages = messagesSnapshot;
|
|
476
|
+
}
|
|
453
477
|
|
|
478
|
+
if (repositoryChanged) {
|
|
479
|
+
this._runTrackerUpdate(() => this._toolInvocations?.reset());
|
|
480
|
+
}
|
|
454
481
|
this._runTrackerUpdate(() => this._driveToolInvocations());
|
|
455
482
|
|
|
456
483
|
this._notifySubscribers();
|
|
@@ -529,6 +556,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
529
556
|
}
|
|
530
557
|
},
|
|
531
558
|
},
|
|
559
|
+
(toolCall) => this._store.unstable_isClientToolCall?.(toolCall),
|
|
532
560
|
);
|
|
533
561
|
}
|
|
534
562
|
|
|
@@ -557,17 +585,9 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
557
585
|
private _findMessageIdForToolCall(toolCallId: string): string | undefined {
|
|
558
586
|
if (this._messagesForToolCallIndex !== this._messages) {
|
|
559
587
|
this._toolCallToMessageId.clear();
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
for (const part of message.content) {
|
|
564
|
-
if (!part || part.type !== "tool-call") continue;
|
|
565
|
-
this._toolCallToMessageId.set(part.toolCallId, message.id);
|
|
566
|
-
if (part.messages) visit(part.messages);
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
};
|
|
570
|
-
visit(this._messages);
|
|
588
|
+
for (const { part, messageId } of walkToolCallTree(this._messages)) {
|
|
589
|
+
this._toolCallToMessageId.set(part.toolCallId, messageId);
|
|
590
|
+
}
|
|
571
591
|
this._messagesForToolCallIndex = this._messages;
|
|
572
592
|
}
|
|
573
593
|
return this._toolCallToMessageId.get(toolCallId);
|
|
@@ -671,7 +691,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
671
691
|
// user messages — matches the satellites' historical opt-in cancel
|
|
672
692
|
// behavior, which is now built in.
|
|
673
693
|
if (message.startRun ?? message.role === "user") {
|
|
674
|
-
await this._toolInvocations?.abort();
|
|
694
|
+
await this._toolInvocations?.abort({ discardPending: true });
|
|
675
695
|
}
|
|
676
696
|
if (!isThreadRuntimeGenerationCurrent(this, generation)) return;
|
|
677
697
|
|
|
@@ -772,7 +792,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
772
792
|
// Auto-abort in-flight client-side tool executions when a run reloads;
|
|
773
793
|
// any results that land afterward would target a turn that no longer
|
|
774
794
|
// exists. See `append` above for full rationale.
|
|
775
|
-
await this._toolInvocations?.abort();
|
|
795
|
+
await this._toolInvocations?.abort({ discardPending: true });
|
|
776
796
|
|
|
777
797
|
await this._store.onReload(config.parentId, config);
|
|
778
798
|
}
|
|
@@ -826,7 +846,7 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
826
846
|
// Abort any in-flight client-side tool executions. Fire-and-forget —
|
|
827
847
|
// the abort resolves once executions settle, but we don't gate the
|
|
828
848
|
// cancel on it.
|
|
829
|
-
void this._toolInvocations?.abort();
|
|
849
|
+
void this._toolInvocations?.abort({ discardPending: true });
|
|
830
850
|
|
|
831
851
|
// Before the run is aborted, so the settle it produces keeps the pending
|
|
832
852
|
// items instead of dispatching the next one at the moment the user
|
|
@@ -936,13 +956,16 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
936
956
|
);
|
|
937
957
|
}
|
|
938
958
|
|
|
939
|
-
public respondToToolApproval(
|
|
959
|
+
public respondToToolApproval(
|
|
960
|
+
options: RespondToToolApprovalOptions,
|
|
961
|
+
): Promise<void> {
|
|
940
962
|
if (!this._store.onRespondToToolApproval)
|
|
941
963
|
throw new Error("Runtime does not support tool approvals.");
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
964
|
+
try {
|
|
965
|
+
return Promise.resolve(this._store.onRespondToToolApproval(options));
|
|
966
|
+
} catch (error) {
|
|
967
|
+
return Promise.reject(error);
|
|
968
|
+
}
|
|
946
969
|
}
|
|
947
970
|
|
|
948
971
|
public override reset(initialMessages?: readonly ThreadMessageLike[]) {
|
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
ChatModelRunOptions,
|
|
6
6
|
ChatModelRunResult,
|
|
7
7
|
} from "../../runtime/utils/chat-model-adapter";
|
|
8
|
-
import type { AppendMessage } from "../../types/message";
|
|
8
|
+
import type { AppendMessage, ToolCallMessagePart } from "../../types/message";
|
|
9
9
|
import type { LocalRuntimeOptionsBase } from "./local-runtime-options";
|
|
10
10
|
import {
|
|
11
11
|
ExportedMessageRepository,
|
|
@@ -61,7 +61,7 @@ const userMessage = (text: string): AppendMessage => ({
|
|
|
61
61
|
|
|
62
62
|
const toolCallPart = (
|
|
63
63
|
toolName: string,
|
|
64
|
-
approval?:
|
|
64
|
+
approval?: ToolCallMessagePart["approval"],
|
|
65
65
|
) => ({
|
|
66
66
|
type: "tool-call" as const,
|
|
67
67
|
toolCallId: `call-${toolName}`,
|
|
@@ -73,7 +73,7 @@ const toolCallPart = (
|
|
|
73
73
|
|
|
74
74
|
const toolCallResult = (
|
|
75
75
|
toolName: string,
|
|
76
|
-
approval?:
|
|
76
|
+
approval?: ToolCallMessagePart["approval"],
|
|
77
77
|
): ChatModelRunResult => ({
|
|
78
78
|
content: [toolCallPart(toolName, approval)],
|
|
79
79
|
status: { type: "requires-action", reason: "tool-calls" },
|
|
@@ -485,6 +485,32 @@ describe("LocalThreadRuntimeCore tool approvals", () => {
|
|
|
485
485
|
});
|
|
486
486
|
});
|
|
487
487
|
|
|
488
|
+
it("records a free-form answer alongside the decision", async () => {
|
|
489
|
+
const { thread, runs } = createApprovalThread(
|
|
490
|
+
toolCallResult("send_email", { id: "a1", display: "text" }),
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
await thread.append(userMessage("send an email"));
|
|
494
|
+
await flush();
|
|
495
|
+
|
|
496
|
+
await thread.respondToToolApproval({
|
|
497
|
+
approvalId: "a1",
|
|
498
|
+
approved: true,
|
|
499
|
+
text: "Quarterly update",
|
|
500
|
+
});
|
|
501
|
+
await flush();
|
|
502
|
+
|
|
503
|
+
const toolCall = runs[1]!
|
|
504
|
+
.unstable_getMessage()
|
|
505
|
+
.content.find((part) => part.type === "tool-call");
|
|
506
|
+
expect(toolCall?.approval).toEqual({
|
|
507
|
+
id: "a1",
|
|
508
|
+
display: "text",
|
|
509
|
+
approved: true,
|
|
510
|
+
text: "Quarterly update",
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
|
|
488
514
|
it("treats a terminal resolution as non-pending and continues the run", async () => {
|
|
489
515
|
const { thread, runs } = createApprovalThread(
|
|
490
516
|
toolCallResult("deploy", { id: "a1", resolution: "expired" }),
|
|
@@ -900,8 +900,9 @@ export class LocalThreadRuntimeCore
|
|
|
900
900
|
approvalId,
|
|
901
901
|
approved,
|
|
902
902
|
optionId,
|
|
903
|
+
text,
|
|
903
904
|
reason,
|
|
904
|
-
}: RespondToToolApprovalOptions) {
|
|
905
|
+
}: RespondToToolApprovalOptions): Promise<void> {
|
|
905
906
|
let message = this.repository
|
|
906
907
|
.getMessages()
|
|
907
908
|
.findLast(
|
|
@@ -944,6 +945,7 @@ export class LocalThreadRuntimeCore
|
|
|
944
945
|
...targetApproval,
|
|
945
946
|
approved,
|
|
946
947
|
...(optionId != null && { optionId }),
|
|
948
|
+
...(text != null && { text }),
|
|
947
949
|
...(reason != null && { reason }),
|
|
948
950
|
};
|
|
949
951
|
if (approved) return { ...c, approval };
|
|
@@ -968,5 +970,7 @@ export class LocalThreadRuntimeCore
|
|
|
968
970
|
} else {
|
|
969
971
|
this._persistPausedMessage(parentId, message);
|
|
970
972
|
}
|
|
973
|
+
|
|
974
|
+
return Promise.resolve();
|
|
971
975
|
}
|
|
972
976
|
}
|
|
@@ -1,9 +1,54 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import {
|
|
3
|
+
classifyThreads,
|
|
3
4
|
createEmptyRemoteThreadState,
|
|
4
5
|
createThreadMappingId,
|
|
5
6
|
seedNewThread,
|
|
7
|
+
updateStatusReducer,
|
|
6
8
|
} from "./remote-thread-state";
|
|
9
|
+
import type {
|
|
10
|
+
RemoteThreadData,
|
|
11
|
+
RemoteThreadState,
|
|
12
|
+
} from "./remote-thread-state";
|
|
13
|
+
|
|
14
|
+
const initializedDraft = () => {
|
|
15
|
+
const seeded = seedNewThread(createEmptyRemoteThreadState());
|
|
16
|
+
const regular = updateStatusReducer(seeded.state, seeded.id, "regular");
|
|
17
|
+
const mappingId = regular.threadIdMap[seeded.id]!;
|
|
18
|
+
const initializeTask = Promise.resolve({
|
|
19
|
+
remoteId: "remote-1",
|
|
20
|
+
externalId: "remote-1",
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
id: seeded.id,
|
|
24
|
+
mappingId,
|
|
25
|
+
initializeTask,
|
|
26
|
+
state: {
|
|
27
|
+
...regular,
|
|
28
|
+
threadIdMap: { ...regular.threadIdMap, "remote-1": mappingId },
|
|
29
|
+
threadData: {
|
|
30
|
+
...regular.threadData,
|
|
31
|
+
[mappingId]: {
|
|
32
|
+
...regular.threadData[mappingId]!,
|
|
33
|
+
remoteId: "remote-1",
|
|
34
|
+
externalId: "remote-1",
|
|
35
|
+
initializeTask,
|
|
36
|
+
} as RemoteThreadData,
|
|
37
|
+
},
|
|
38
|
+
} as RemoteThreadState,
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const expectOneSlotPerIdentity = (state: RemoteThreadState) => {
|
|
43
|
+
const remoteIds = Object.values(state.threadData)
|
|
44
|
+
.map((data) => data.remoteId)
|
|
45
|
+
.filter((remoteId) => remoteId !== undefined);
|
|
46
|
+
expect(new Set(remoteIds).size).toBe(remoteIds.length);
|
|
47
|
+
|
|
48
|
+
for (const id of [...state.threadIds, ...state.archivedThreadIds]) {
|
|
49
|
+
expect(state.threadData[state.threadIdMap[id]!]?.id).toBe(id);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
7
52
|
|
|
8
53
|
describe("remote thread state", () => {
|
|
9
54
|
it("creates an empty state", () => {
|
|
@@ -34,4 +79,71 @@ describe("remote thread state", () => {
|
|
|
34
79
|
);
|
|
35
80
|
expect(Object.keys(second.state.threadData)).toEqual([first.id, second.id]);
|
|
36
81
|
});
|
|
82
|
+
|
|
83
|
+
it("refreshes the local slot when a listed thread already has a mapping", () => {
|
|
84
|
+
const draft = initializedDraft();
|
|
85
|
+
|
|
86
|
+
const merged = classifyThreads(
|
|
87
|
+
[
|
|
88
|
+
{
|
|
89
|
+
status: "regular",
|
|
90
|
+
remoteId: "remote-1",
|
|
91
|
+
externalId: "remote-1",
|
|
92
|
+
title: "from the server",
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
{
|
|
96
|
+
threadIds: [],
|
|
97
|
+
archivedThreadIds: [],
|
|
98
|
+
threadIdMap: { ...draft.state.threadIdMap },
|
|
99
|
+
threadData: { ...draft.state.threadData },
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
expect(Object.keys(merged.threadData)).toEqual([draft.mappingId]);
|
|
104
|
+
expect(merged.threadIds).toEqual([draft.id]);
|
|
105
|
+
expect(merged.threadIdMap["remote-1"]).toBe(draft.mappingId);
|
|
106
|
+
expect(merged.threadData[draft.mappingId]?.id).toBe(draft.id);
|
|
107
|
+
expect(merged.threadData[draft.mappingId]?.title).toBe("from the server");
|
|
108
|
+
expect(merged.threadData[draft.mappingId]?.localOrigin).toBe(true);
|
|
109
|
+
const refreshed = merged.threadData[draft.mappingId]!;
|
|
110
|
+
expect(
|
|
111
|
+
refreshed.status === "new" ? undefined : refreshed.initializeTask,
|
|
112
|
+
).toBe(draft.initializeTask);
|
|
113
|
+
expectOneSlotPerIdentity({ ...draft.state, ...merged });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("lists a repeated remote id once", () => {
|
|
117
|
+
const listed = classifyThreads(
|
|
118
|
+
[
|
|
119
|
+
{ status: "regular", remoteId: "a", externalId: "a", title: "first" },
|
|
120
|
+
{ status: "regular", remoteId: "a", externalId: "a", title: "second" },
|
|
121
|
+
],
|
|
122
|
+
{
|
|
123
|
+
threadIds: [],
|
|
124
|
+
archivedThreadIds: [],
|
|
125
|
+
threadIdMap: {},
|
|
126
|
+
threadData: {},
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
expect(listed.threadIds).toEqual(["a"]);
|
|
131
|
+
expect(listed.threadData[createThreadMappingId("a")]?.title).toBe("second");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("deletes every alias of an identity, by either id", () => {
|
|
135
|
+
for (const target of ["remote-1", "local"] as const) {
|
|
136
|
+
const draft = initializedDraft();
|
|
137
|
+
const deleted = updateStatusReducer(
|
|
138
|
+
draft.state,
|
|
139
|
+
target === "local" ? draft.id : target,
|
|
140
|
+
"deleted",
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
expect(deleted.threadData).toEqual({});
|
|
144
|
+
expect(deleted.threadIdMap).toEqual({});
|
|
145
|
+
expect(deleted.threadIds).toEqual([]);
|
|
146
|
+
expect(deleted.archivedThreadIds).toEqual([]);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
37
149
|
});
|
|
@@ -47,26 +47,29 @@ export const LOCAL_THREAD_ID_PREFIX = "__LOCALID_";
|
|
|
47
47
|
export const normalizeCursor = (c: string | undefined): string | undefined =>
|
|
48
48
|
c || undefined;
|
|
49
49
|
|
|
50
|
-
type ClassifyAccumulator = {
|
|
50
|
+
export type ClassifyAccumulator = {
|
|
51
51
|
threadIds: string[];
|
|
52
52
|
archivedThreadIds: string[];
|
|
53
53
|
threadIdMap: Record<string, THREAD_MAPPING_ID>;
|
|
54
54
|
threadData: Record<THREAD_MAPPING_ID, RemoteThreadData>;
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
+
// A slot's `id` is fixed when it is minted: the hook instance manager keys live
|
|
58
|
+
// thread runtimes by it, so renaming a slot would detach the runtime the user is
|
|
59
|
+
// chatting in. A listed thread whose `remoteId` already maps to a slot therefore
|
|
60
|
+
// refreshes that slot instead of minting a second one, and a slot minted by a
|
|
61
|
+
// list() that raced ahead of the remote id collapses when `initialize()` settles.
|
|
62
|
+
// `threadIds`/`archivedThreadIds` carry slot ids, never remote ids.
|
|
57
63
|
export const classifyThreads = (
|
|
58
64
|
threads: readonly RemoteThreadMetadata[],
|
|
59
65
|
acc: ClassifyAccumulator,
|
|
60
66
|
): ClassifyAccumulator => {
|
|
61
|
-
|
|
62
|
-
if (acc.threadIdMap[thread.remoteId] !== undefined) continue;
|
|
67
|
+
const listed = new Set([...acc.threadIds, ...acc.archivedThreadIds]);
|
|
63
68
|
|
|
69
|
+
for (const thread of threads) {
|
|
64
70
|
switch (thread.status) {
|
|
65
71
|
case "regular":
|
|
66
|
-
acc.threadIds.push(thread.remoteId);
|
|
67
|
-
break;
|
|
68
72
|
case "archived":
|
|
69
|
-
acc.archivedThreadIds.push(thread.remoteId);
|
|
70
73
|
break;
|
|
71
74
|
default: {
|
|
72
75
|
const _exhaustiveCheck: never = thread.status;
|
|
@@ -74,20 +77,52 @@ export const classifyThreads = (
|
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
const
|
|
80
|
+
const existingMappingId = acc.threadIdMap[thread.remoteId];
|
|
81
|
+
const existing =
|
|
82
|
+
existingMappingId !== undefined
|
|
83
|
+
? acc.threadData[existingMappingId]
|
|
84
|
+
: undefined;
|
|
85
|
+
const id = existing?.id ?? thread.remoteId;
|
|
86
|
+
const mappingId = existingMappingId ?? createThreadMappingId(id);
|
|
87
|
+
const existingTask =
|
|
88
|
+
existing !== undefined && existing.status !== "new"
|
|
89
|
+
? existing.initializeTask
|
|
90
|
+
: undefined;
|
|
91
|
+
|
|
92
|
+
if (!listed.has(id)) {
|
|
93
|
+
listed.add(id);
|
|
94
|
+
if (thread.status === "regular") {
|
|
95
|
+
acc.threadIds.push(id);
|
|
96
|
+
} else {
|
|
97
|
+
acc.archivedThreadIds.push(id);
|
|
98
|
+
}
|
|
99
|
+
} else if (existing !== undefined && existing.status !== thread.status) {
|
|
100
|
+
if (thread.status === "regular") {
|
|
101
|
+
acc.archivedThreadIds = acc.archivedThreadIds.filter((t) => t !== id);
|
|
102
|
+
acc.threadIds.push(id);
|
|
103
|
+
} else {
|
|
104
|
+
acc.threadIds = acc.threadIds.filter((t) => t !== id);
|
|
105
|
+
acc.archivedThreadIds.push(id);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
acc.threadIdMap[id] = mappingId;
|
|
78
110
|
acc.threadIdMap[thread.remoteId] = mappingId;
|
|
79
111
|
acc.threadData[mappingId] = {
|
|
80
|
-
|
|
112
|
+
...(existing?.localOrigin === true ? { localOrigin: true as const } : {}),
|
|
113
|
+
id,
|
|
81
114
|
remoteId: thread.remoteId,
|
|
82
115
|
externalId: thread.externalId,
|
|
83
116
|
status: thread.status,
|
|
84
117
|
title: thread.title,
|
|
85
118
|
lastMessageAt: thread.lastMessageAt,
|
|
86
119
|
custom: thread.custom,
|
|
87
|
-
initializeTask:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
120
|
+
initializeTask:
|
|
121
|
+
existingTask ??
|
|
122
|
+
Promise.resolve({
|
|
123
|
+
remoteId: thread.remoteId,
|
|
124
|
+
externalId: thread.externalId,
|
|
125
|
+
}),
|
|
91
126
|
};
|
|
92
127
|
}
|
|
93
128
|
return acc;
|
|
@@ -238,7 +273,7 @@ export const updateStatusReducer = (
|
|
|
238
273
|
const data = getThreadData(state, threadIdOrRemoteId);
|
|
239
274
|
if (!data) return state;
|
|
240
275
|
|
|
241
|
-
const { id,
|
|
276
|
+
const { id, status: lastStatus } = data;
|
|
242
277
|
if (lastStatus === newStatus) return state;
|
|
243
278
|
|
|
244
279
|
const newState = { ...state };
|
|
@@ -273,16 +308,20 @@ export const updateStatusReducer = (
|
|
|
273
308
|
newState.archivedThreadIds = [id, ...newState.archivedThreadIds];
|
|
274
309
|
break;
|
|
275
310
|
|
|
276
|
-
case "deleted":
|
|
311
|
+
case "deleted": {
|
|
312
|
+
const mappingId = state.threadIdMap[threadIdOrRemoteId]!;
|
|
277
313
|
newState.threadData = Object.fromEntries(
|
|
278
|
-
Object.entries(newState.threadData).filter(
|
|
314
|
+
Object.entries(newState.threadData).filter(
|
|
315
|
+
([key]) => key !== mappingId,
|
|
316
|
+
),
|
|
279
317
|
);
|
|
280
318
|
newState.threadIdMap = Object.fromEntries(
|
|
281
319
|
Object.entries(newState.threadIdMap).filter(
|
|
282
|
-
([
|
|
320
|
+
([, value]) => value !== mappingId,
|
|
283
321
|
),
|
|
284
322
|
);
|
|
285
323
|
break;
|
|
324
|
+
}
|
|
286
325
|
|
|
287
326
|
default: {
|
|
288
327
|
const _exhaustiveCheck: never = newStatus;
|
|
@@ -78,11 +78,12 @@ export type RemoteThreadListAdapter = {
|
|
|
78
78
|
unstable_Provider?: RemoteThreadListProviderComponent | undefined;
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
|
-
* Hook the `RemoteThreadList` store entry calls once
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
81
|
+
* Hook the `RemoteThreadList` store entry calls once per mounted thread
|
|
82
|
+
* body (the main-thread slot by default; every started thread with
|
|
83
|
+
* `backgroundThreads`), then provides to the `thread` factory. This is not
|
|
84
|
+
* mounted per listed thread. `useRemoteThreadListRuntime` also calls it
|
|
85
|
+
* when `unstable_Provider` is omitted. Resolve `threadListItem` lazily on
|
|
86
|
+
* each adapter call; do not capture it at hook mount. The hook must keep a
|
|
86
87
|
* stable hook count across adapter swaps; a different count throws.
|
|
87
88
|
* Memoize the returned object.
|
|
88
89
|
*
|