@assistant-ui/core 0.3.18 → 0.3.19
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 +13 -3
- package/dist/adapters/speech.js.map +1 -1
- package/dist/adapters/thread-history.d.ts +4 -0
- package/dist/adapters/thread-history.d.ts.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/model-context/frame/host.d.ts.map +1 -1
- package/dist/model-context/frame/host.js +26 -11
- package/dist/model-context/frame/host.js.map +1 -1
- package/dist/model-context/interactable-composer-metadata.js +11 -9
- package/dist/model-context/interactable-composer-metadata.js.map +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.js +45 -7
- package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
- package/dist/react/client/DataRenderers.d.ts.map +1 -1
- package/dist/react/client/DataRenderers.js +20 -15
- package/dist/react/client/DataRenderers.js.map +1 -1
- package/dist/react/client/Interactables.d.ts.map +1 -1
- package/dist/react/client/Interactables.js +11 -5
- package/dist/react/client/Interactables.js.map +1 -1
- package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
- package/dist/react/client/RemoteThreadList.js +3 -2
- package/dist/react/client/RemoteThreadList.js.map +1 -1
- package/dist/react/client/interactable-model-context.d.ts.map +1 -1
- package/dist/react/client/interactable-model-context.js +3 -2
- package/dist/react/client/interactable-model-context.js.map +1 -1
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +2 -2
- package/dist/react/interactables-legacy/useAssistantInteractable.d.ts.map +1 -1
- package/dist/react/interactables-legacy/useAssistantInteractable.js +3 -0
- package/dist/react/interactables-legacy/useAssistantInteractable.js.map +1 -1
- package/dist/react/model-context/useInteractable.d.ts.map +1 -1
- package/dist/react/model-context/useInteractable.js +4 -1
- package/dist/react/model-context/useInteractable.js.map +1 -1
- package/dist/react/model-context/useInteractableVersions.js +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.d.ts +11 -2
- package/dist/react/primitives/message/MessageGroupedParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.js +3 -2
- package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
- package/dist/react/primitives/part/PartMessages.d.ts +1 -0
- package/dist/react/primitives/part/PartMessages.d.ts.map +1 -1
- package/dist/react/primitives/part/PartMessages.js +1 -0
- package/dist/react/primitives/part/PartMessages.js.map +1 -1
- package/dist/react/primitives/thread/ThreadMessages.d.ts.map +1 -1
- package/dist/react/primitives/thread/ThreadMessages.js +6 -5
- package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +2 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +2 -24
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +135 -374
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts +1 -0
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +22 -2
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/auiV0.d.ts +40 -15
- package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/auiV0.js +62 -15
- package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
- package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts +2 -1
- package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js +4 -0
- package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/sdkIdentity.d.ts +6 -0
- package/dist/react/runtimes/cloud/sdkIdentity.d.ts.map +1 -0
- package/dist/react/runtimes/cloud/sdkIdentity.js +9 -0
- package/dist/react/runtimes/cloud/sdkIdentity.js.map +1 -0
- package/dist/react/runtimes/external-message-converter.d.ts +12 -2
- package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
- package/dist/react/runtimes/external-message-converter.js +26 -61
- package/dist/react/runtimes/external-message-converter.js.map +1 -1
- package/dist/react/runtimes/useRemoteThreadListRuntime.js +1 -1
- package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
- package/dist/react/utils/groupParts.d.ts +6 -1
- package/dist/react/utils/groupParts.d.ts.map +1 -1
- package/dist/react/utils/groupParts.js +6 -0
- package/dist/react/utils/groupParts.js.map +1 -1
- package/dist/react/utils/useJSONEqual.d.ts +3 -1
- package/dist/react/utils/useJSONEqual.d.ts.map +1 -1
- package/dist/react/utils/useJSONEqual.js +6 -1
- package/dist/react/utils/useJSONEqual.js.map +1 -1
- package/dist/react/utils/useJSONSchemaDependency.d.ts +7 -0
- package/dist/react/utils/useJSONSchemaDependency.d.ts.map +1 -0
- package/dist/react/utils/useJSONSchemaDependency.js +20 -0
- package/dist/react/utils/useJSONSchemaDependency.js.map +1 -0
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-runtime.js +2 -5
- package/dist/runtime/api/thread-runtime.js.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -0
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js +46 -10
- package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -0
- package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.js +155 -64
- package/dist/runtime/base/base-thread-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/queue/message-queue.d.ts +5 -0
- package/dist/runtime/queue/message-queue.d.ts.map +1 -1
- package/dist/runtime/queue/message-queue.js +61 -6
- package/dist/runtime/queue/message-queue.js.map +1 -1
- package/dist/runtime/utils/auto-status.d.ts +6 -2
- package/dist/runtime/utils/auto-status.d.ts.map +1 -1
- package/dist/runtime/utils/auto-status.js +12 -4
- package/dist/runtime/utils/auto-status.js.map +1 -1
- package/dist/runtime/utils/external-message-conversion.d.ts +16 -4
- package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -1
- package/dist/runtime/utils/external-message-conversion.js +48 -12
- package/dist/runtime/utils/external-message-conversion.js.map +1 -1
- package/dist/runtime/utils/message-repository.js +3 -3
- package/dist/runtime/utils/message-repository.js.map +1 -1
- package/dist/runtime/utils/thread-message-like.d.ts +2 -1
- package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
- package/dist/runtime/utils/thread-message-like.js +4 -2
- package/dist/runtime/utils/thread-message-like.js.map +1 -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 +8 -1
- 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 +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +4 -1
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +2 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
- package/dist/store/clients/external-thread.d.ts.map +1 -1
- package/dist/store/clients/external-thread.js +241 -200
- package/dist/store/clients/external-thread.js.map +1 -1
- package/dist/store/clients/thread-tasks.d.ts +13 -0
- package/dist/store/clients/thread-tasks.d.ts.map +1 -0
- package/dist/store/clients/thread-tasks.js +69 -0
- package/dist/store/clients/thread-tasks.js.map +1 -0
- package/dist/store/index.d.ts +3 -2
- package/dist/store/index.js +3 -3
- package/dist/store/primitive-predicates.d.ts.map +1 -1
- package/dist/store/primitive-predicates.js +4 -4
- package/dist/store/primitive-predicates.js.map +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.js +176 -128
- package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
- package/dist/store/scope-registration.d.ts +2 -0
- package/dist/store/scope-registration.d.ts.map +1 -1
- package/dist/store/scopes/task.d.ts +36 -0
- package/dist/store/scopes/task.d.ts.map +1 -0
- package/dist/store/scopes/task.js +0 -0
- package/dist/store/scopes/thread.d.ts +20 -0
- package/dist/store/scopes/thread.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 -15
- package/dist/subscribable/subscribable.js.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/message.d.ts +8 -1
- package/dist/types/message.d.ts.map +1 -1
- package/dist/types/message.js.map +1 -1
- package/dist/utils/abortable-iterable.d.ts.map +1 -1
- package/dist/utils/abortable-iterable.js +30 -14
- package/dist/utils/abortable-iterable.js.map +1 -1
- package/dist/utils/getGroupStatus.d.ts +18 -2
- package/dist/utils/getGroupStatus.d.ts.map +1 -1
- package/dist/utils/getGroupStatus.js +32 -7
- package/dist/utils/getGroupStatus.js.map +1 -1
- package/dist/utils/wire-media.d.ts.map +1 -1
- package/dist/utils/wire-media.js +2 -2
- package/dist/utils/wire-media.js.map +1 -1
- package/package.json +6 -6
- package/src/adapters/speech.test.ts +236 -0
- package/src/adapters/speech.ts +17 -10
- package/src/adapters/thread-history.ts +3 -0
- package/src/index.ts +1 -0
- package/src/model-context/frame/host.test.ts +113 -1
- package/src/model-context/frame/host.ts +39 -12
- package/src/model-context/interactable-composer-metadata.test.ts +30 -0
- package/src/model-context/interactable-composer-metadata.ts +10 -10
- package/src/react/adapters/LocalStorageThreadListAdapter.test.ts +244 -0
- package/src/react/adapters/LocalStorageThreadListAdapter.tsx +104 -7
- package/src/react/client/DataRenderers.test.tsx +45 -0
- package/src/react/client/DataRenderers.ts +13 -9
- package/src/react/client/Interactables.test.ts +44 -1
- package/src/react/client/Interactables.ts +7 -1
- package/src/react/client/RemoteThreadList.test.ts +41 -1
- package/src/react/client/RemoteThreadList.ts +15 -2
- package/src/react/client/interactable-model-context.test.ts +44 -0
- package/src/react/client/interactable-model-context.ts +3 -2
- package/src/react/index.ts +5 -1
- package/src/react/interactables-legacy/useAssistantInteractable.test.tsx +132 -0
- package/src/react/interactables-legacy/useAssistantInteractable.ts +10 -1
- package/src/react/model-context/useInteractable.test.tsx +177 -0
- package/src/react/model-context/useInteractable.ts +3 -0
- package/src/react/primitive-hooks/useActionBarCopy.test.ts +0 -1
- package/src/react/primitive-hooks/useSuggestionTrigger.test.ts +0 -1
- package/src/react/primitives/message/MessageGroupedParts.test.tsx +96 -0
- package/src/react/primitives/message/MessageGroupedParts.tsx +15 -4
- package/src/react/primitives/part/PartMessages.test.tsx +104 -1
- package/src/react/primitives/part/PartMessages.tsx +1 -0
- package/src/react/primitives/thread/ThreadMessages.test.tsx +142 -0
- package/src/react/primitives/thread/ThreadMessages.tsx +8 -5
- package/src/react/providers/ReadonlyThreadProvider.test.tsx +56 -0
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +7 -1
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +20 -14
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +221 -1
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +166 -512
- package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.test.ts +77 -0
- package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +27 -1
- package/src/react/runtimes/cloud/auiV0.ts +160 -25
- package/src/react/runtimes/cloud/createCloudThreadListAdapter.test.ts +21 -0
- package/src/react/runtimes/cloud/createCloudThreadListAdapter.ts +7 -1
- package/src/react/runtimes/cloud/sdkIdentity.ts +9 -0
- package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +18 -4
- package/src/react/runtimes/external-message-converter.test.ts +136 -1
- package/src/react/runtimes/external-message-converter.ts +39 -93
- package/src/react/runtimes/useRemoteThreadListRuntime.ts +1 -1
- package/src/react/utils/groupParts.ts +12 -1
- package/src/react/utils/useJSONEqual.ts +9 -0
- package/src/react/utils/useJSONSchemaDependency.ts +22 -0
- package/src/runtime/api/thread-runtime.test.ts +58 -0
- package/src/runtime/api/thread-runtime.ts +2 -4
- package/src/runtime/base/base-composer-runtime-core.ts +69 -16
- package/src/runtime/base/base-thread-runtime-core.test.ts +1008 -1
- package/src/runtime/base/base-thread-runtime-core.ts +222 -83
- package/src/runtime/interfaces/thread-runtime-core.ts +6 -0
- package/src/runtime/queue/message-queue.ts +68 -6
- package/src/runtime/utils/auto-status.test.ts +112 -1
- package/src/runtime/utils/auto-status.ts +31 -5
- package/src/runtime/utils/external-message-conversion.test.ts +134 -0
- package/src/runtime/utils/external-message-conversion.ts +122 -31
- package/src/runtime/utils/message-repository.test.ts +49 -0
- package/src/runtime/utils/message-repository.ts +3 -3
- package/src/runtime/utils/thread-message-like.test.ts +52 -0
- package/src/runtime/utils/thread-message-like.ts +4 -0
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +31 -1
- package/src/runtimes/local/local-thread-runtime-core.test.ts +21 -0
- package/src/runtimes/local/local-thread-runtime-core.ts +11 -1
- package/src/runtimes/tool-invocations/EDGE_CASES.md +2 -6
- package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +133 -0
- package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +10 -6
- package/src/store/clients/external-thread.ts +19 -1
- package/src/store/clients/thread-tasks.test.ts +274 -0
- package/src/store/clients/thread-tasks.ts +138 -0
- package/src/store/index.ts +8 -0
- package/src/store/primitive-predicates.test.ts +48 -5
- package/src/store/primitive-predicates.ts +7 -2
- package/src/store/runtime-clients/thread-runtime-client.ts +31 -1
- package/src/store/scope-registration.ts +2 -0
- package/src/store/scopes/task.ts +34 -0
- package/src/store/scopes/thread.ts +16 -0
- package/src/store/thread-tasks.test.tsx +209 -0
- package/src/subscribable/subscribable.test.ts +156 -1
- package/src/subscribable/subscribable.ts +25 -15
- package/src/tests/MessageRepository.test.ts +1 -5
- package/src/tests/auiV0Encode.test.ts +300 -0
- package/src/tests/base-composer-runtime-core-send.test.ts +51 -0
- package/src/tests/base-composer-runtime-core.test.ts +314 -2
- package/src/tests/external-store-thread-runtime-core-adapter.test.ts +151 -0
- package/src/tests/external-thread-attachments.test.tsx +69 -0
- package/src/tests/external-thread-parity.test.tsx +34 -0
- package/src/tests/groupParts.test.ts +71 -0
- package/src/tests/message-queue.test.ts +253 -0
- package/src/tests/remote-thread-list-reactive-threadId.test.ts +51 -0
- package/src/tests/useRemoteThreadListRuntime-controlled.test.tsx +43 -0
- package/src/types/index.ts +1 -0
- package/src/types/message.ts +8 -0
- package/src/utils/abortable-iterable.test.ts +115 -0
- package/src/utils/abortable-iterable.ts +34 -23
- package/src/utils/getGroupStatus.test.ts +70 -4
- package/src/utils/getGroupStatus.ts +35 -12
- package/src/utils/wire-media.test.ts +10 -0
- package/src/utils/wire-media.ts +6 -2
|
@@ -1,9 +1,94 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
convertExternalMessages,
|
|
4
|
+
createExternalMessageConversionCache,
|
|
5
|
+
} from "./external-message-converter";
|
|
3
6
|
import type { useExternalMessageConverter } from "./external-message-converter";
|
|
4
7
|
import { isErrorMessageId } from "../../utils/id";
|
|
5
8
|
|
|
6
9
|
describe("convertExternalMessages", () => {
|
|
10
|
+
it("keeps unchanged messages and rebuilds a replaced one with an opt-in cache", () => {
|
|
11
|
+
type Input = { id: string; role: "user" | "assistant"; text: string };
|
|
12
|
+
const metadata = {};
|
|
13
|
+
const cache = createExternalMessageConversionCache();
|
|
14
|
+
const callback: useExternalMessageConverter.Callback<Input> = (
|
|
15
|
+
message,
|
|
16
|
+
) => ({
|
|
17
|
+
id: message.id,
|
|
18
|
+
role: message.role,
|
|
19
|
+
content: message.text,
|
|
20
|
+
});
|
|
21
|
+
const question: Input = { id: "question", role: "user", text: "hi" };
|
|
22
|
+
const answer: Input = { id: "answer", role: "assistant", text: "hel" };
|
|
23
|
+
|
|
24
|
+
const first = convertExternalMessages(
|
|
25
|
+
[question, answer],
|
|
26
|
+
callback,
|
|
27
|
+
true,
|
|
28
|
+
metadata,
|
|
29
|
+
cache,
|
|
30
|
+
);
|
|
31
|
+
const second = convertExternalMessages(
|
|
32
|
+
[question, { ...answer, text: "hello" }],
|
|
33
|
+
callback,
|
|
34
|
+
true,
|
|
35
|
+
metadata,
|
|
36
|
+
cache,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(second[0]).toBe(first[0]);
|
|
40
|
+
expect(second[1]).not.toBe(first[1]);
|
|
41
|
+
expect(second[1]?.content).toMatchObject([{ type: "text", text: "hello" }]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("invalidates cached messages when the callback or metadata changes", () => {
|
|
45
|
+
const input = { text: "nested" };
|
|
46
|
+
const cache = createExternalMessageConversionCache();
|
|
47
|
+
const withFormat =
|
|
48
|
+
(
|
|
49
|
+
format: (text: string) => string,
|
|
50
|
+
): useExternalMessageConverter.Callback<typeof input> =>
|
|
51
|
+
(message, metadata) => ({
|
|
52
|
+
id: "nested",
|
|
53
|
+
role: "assistant",
|
|
54
|
+
content: format(message.text),
|
|
55
|
+
metadata: { timing: metadata.messageTiming?.["nested"] },
|
|
56
|
+
});
|
|
57
|
+
const callback = withFormat((text) => text);
|
|
58
|
+
const uppercaseCallback = withFormat((text) => text.toUpperCase());
|
|
59
|
+
const metadata = {};
|
|
60
|
+
const timing = { streamStartTime: 1, totalChunks: 1, toolCallCount: 0 };
|
|
61
|
+
|
|
62
|
+
const first = convertExternalMessages(
|
|
63
|
+
[input],
|
|
64
|
+
callback,
|
|
65
|
+
false,
|
|
66
|
+
metadata,
|
|
67
|
+
cache,
|
|
68
|
+
);
|
|
69
|
+
const second = convertExternalMessages(
|
|
70
|
+
[input],
|
|
71
|
+
uppercaseCallback,
|
|
72
|
+
false,
|
|
73
|
+
metadata,
|
|
74
|
+
cache,
|
|
75
|
+
);
|
|
76
|
+
const third = convertExternalMessages(
|
|
77
|
+
[input],
|
|
78
|
+
uppercaseCallback,
|
|
79
|
+
false,
|
|
80
|
+
{ messageTiming: { nested: timing } },
|
|
81
|
+
cache,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
expect(second[0]).not.toBe(first[0]);
|
|
85
|
+
expect(second[0]?.content).toMatchObject([
|
|
86
|
+
{ type: "text", text: "NESTED" },
|
|
87
|
+
]);
|
|
88
|
+
expect(third[0]).not.toBe(second[0]);
|
|
89
|
+
expect(third[0]?.metadata).toMatchObject({ timing });
|
|
90
|
+
});
|
|
91
|
+
|
|
7
92
|
describe("reasoning part merging", () => {
|
|
8
93
|
it("should merge reasoning parts with the same parentId", () => {
|
|
9
94
|
const messages = [
|
|
@@ -205,6 +290,56 @@ describe("convertExternalMessages", () => {
|
|
|
205
290
|
expect((toolCallParts[0] as any).argsText).toBe('{"query":"new"}');
|
|
206
291
|
});
|
|
207
292
|
|
|
293
|
+
it("keeps a merged tool call in its original content position", () => {
|
|
294
|
+
const messages = [
|
|
295
|
+
{
|
|
296
|
+
role: "assistant" as const,
|
|
297
|
+
content: [
|
|
298
|
+
{
|
|
299
|
+
type: "tool-call" as const,
|
|
300
|
+
toolCallId: "tc1",
|
|
301
|
+
toolName: "search",
|
|
302
|
+
args: { query: "old" },
|
|
303
|
+
},
|
|
304
|
+
{ type: "text" as const, text: "after tool" },
|
|
305
|
+
],
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
role: "assistant" as const,
|
|
309
|
+
content: [
|
|
310
|
+
{
|
|
311
|
+
type: "tool-call" as const,
|
|
312
|
+
toolCallId: "tc1",
|
|
313
|
+
toolName: "search",
|
|
314
|
+
args: { query: "new" },
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
role: "tool" as const,
|
|
320
|
+
toolCallId: "tc1",
|
|
321
|
+
result: "found",
|
|
322
|
+
},
|
|
323
|
+
];
|
|
324
|
+
|
|
325
|
+
const result = convertExternalMessages(
|
|
326
|
+
messages,
|
|
327
|
+
(message) => message,
|
|
328
|
+
false,
|
|
329
|
+
{},
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
expect(result[0]!.content).toMatchObject([
|
|
333
|
+
{
|
|
334
|
+
type: "tool-call",
|
|
335
|
+
toolCallId: "tc1",
|
|
336
|
+
args: { query: "new" },
|
|
337
|
+
result: "found",
|
|
338
|
+
},
|
|
339
|
+
{ type: "text", text: "after tool" },
|
|
340
|
+
]);
|
|
341
|
+
});
|
|
342
|
+
|
|
208
343
|
it("should ignore orphaned tool results without throwing", () => {
|
|
209
344
|
const messages = [
|
|
210
345
|
{
|
|
@@ -2,28 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
import { useMemo, useState } from "react";
|
|
4
4
|
import {
|
|
5
|
-
chunkExternalMessages,
|
|
6
|
-
completeExternalMessageConversion,
|
|
7
|
-
convertExternalMessageCallback,
|
|
8
|
-
convertExternalMessageChunk,
|
|
9
5
|
convertExternalMessages as convertExternalMessagesInternal,
|
|
10
|
-
|
|
6
|
+
createExternalMessageConversionCache as createExternalMessageConversionCacheInternal,
|
|
11
7
|
type ExternalMessageConverterCallback,
|
|
12
|
-
type ExternalMessageConverterCallbackResult,
|
|
13
|
-
type ExternalMessageConverterChunk,
|
|
14
8
|
type ExternalMessageConverterMessage,
|
|
15
9
|
type ExternalMessageConverterMetadata,
|
|
10
|
+
type InternalExternalMessageConversionCache,
|
|
16
11
|
type JoinStrategy,
|
|
17
12
|
} from "../../runtime/utils/external-message-conversion";
|
|
18
13
|
import { bindExternalStoreMessage } from "../../runtime/utils/external-store-message";
|
|
19
|
-
import { ThreadMessageConverter } from "../../runtimes/external-store/thread-message-converter";
|
|
20
|
-
import type { ThreadMessage } from "../../types/message";
|
|
21
|
-
|
|
22
|
-
// Generatedness is tracked by identity, not by id shape: a caller-supplied id
|
|
23
|
-
// that happens to match the generated pattern must never be rewritten.
|
|
24
|
-
const generatedFallbackMessages = new WeakSet<object>();
|
|
25
14
|
|
|
26
15
|
export type { JoinStrategy };
|
|
16
|
+
export type ExternalMessageConversionCache = {
|
|
17
|
+
readonly __brand: unique symbol;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates a cache for plain external message conversion. Pass the same cache on every call for one message list, and a source message that has not changed since the previous call converts to the same `ThreadMessage` object.
|
|
22
|
+
*
|
|
23
|
+
* Entries are keyed by source message identity and rebuilt whenever `callback` or `metadata` is a different object than on the previous call, so keep both referentially stable.
|
|
24
|
+
*/
|
|
25
|
+
export const createExternalMessageConversionCache =
|
|
26
|
+
(): ExternalMessageConversionCache =>
|
|
27
|
+
createExternalMessageConversionCacheInternal() as unknown as ExternalMessageConversionCache;
|
|
27
28
|
|
|
28
29
|
export namespace useExternalMessageConverter {
|
|
29
30
|
export type Message = ExternalMessageConverterMessage;
|
|
@@ -31,17 +32,21 @@ export namespace useExternalMessageConverter {
|
|
|
31
32
|
export type Callback<T> = ExternalMessageConverterCallback<T>;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
export const convertExternalMessages
|
|
35
|
+
export const convertExternalMessages = <T extends WeakKey>(
|
|
35
36
|
messages: T[],
|
|
36
37
|
callback: useExternalMessageConverter.Callback<T>,
|
|
37
38
|
isRunning: boolean,
|
|
38
39
|
metadata: useExternalMessageConverter.Metadata,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
cache?: ExternalMessageConversionCache,
|
|
41
|
+
) =>
|
|
42
|
+
convertExternalMessagesInternal(
|
|
43
|
+
messages,
|
|
44
|
+
callback,
|
|
45
|
+
isRunning,
|
|
46
|
+
metadata,
|
|
47
|
+
undefined,
|
|
48
|
+
cache as unknown as InternalExternalMessageConversionCache<T> | undefined,
|
|
49
|
+
);
|
|
45
50
|
|
|
46
51
|
export const useExternalMessageConverter = <T extends WeakKey>({
|
|
47
52
|
callback,
|
|
@@ -56,89 +61,30 @@ export const useExternalMessageConverter = <T extends WeakKey>({
|
|
|
56
61
|
joinStrategy?: JoinStrategy | undefined;
|
|
57
62
|
metadata?: useExternalMessageConverter.Metadata | undefined;
|
|
58
63
|
}) => {
|
|
59
|
-
// The
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// keeping correctness independent of memoization (which React treats as
|
|
64
|
-
// droppable). A "use no memo" opt-out would restore the useMemo semantics
|
|
65
|
-
// but leave cache flushing coupled to memo firing.
|
|
66
|
-
const [caches] = useState(() => ({
|
|
67
|
-
callbackCache: new WeakMap<T, CallbackCacheEntry<T>>(),
|
|
68
|
-
chunkCache: new WeakMap<
|
|
69
|
-
ExternalMessageConverterMessage,
|
|
70
|
-
ExternalMessageConverterChunk<T>
|
|
71
|
-
>(),
|
|
72
|
-
converterCache: new ThreadMessageConverter(),
|
|
73
|
-
}));
|
|
64
|
+
// The cache lives for the component lifetime: React Compiler hoists allocations without reactive dependencies out of useMemo, so entries carry the callback and metadata that produced them instead of being flushed when those change.
|
|
65
|
+
const [cache] = useState(() =>
|
|
66
|
+
createExternalMessageConversionCacheInternal<T>(),
|
|
67
|
+
);
|
|
74
68
|
|
|
75
69
|
const state = useMemo(
|
|
76
70
|
() => ({
|
|
77
71
|
metadata: metadata ?? {},
|
|
78
72
|
callback,
|
|
79
|
-
...caches,
|
|
80
73
|
}),
|
|
81
|
-
[callback, metadata
|
|
74
|
+
[callback, metadata],
|
|
82
75
|
);
|
|
83
76
|
|
|
84
77
|
return useMemo(() => {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
) {
|
|
93
|
-
result = {
|
|
94
|
-
...convertExternalMessageCallback(
|
|
95
|
-
message,
|
|
96
|
-
state.callback,
|
|
97
|
-
state.metadata,
|
|
98
|
-
),
|
|
99
|
-
metadata: state.metadata,
|
|
100
|
-
callback: state.callback,
|
|
101
|
-
};
|
|
102
|
-
state.callbackCache.set(message, result);
|
|
103
|
-
}
|
|
104
|
-
callbackResults.push(result);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const chunks = chunkExternalMessages(callbackResults, joinStrategy).map(
|
|
108
|
-
(message) => {
|
|
109
|
-
const key = message.outputs[0];
|
|
110
|
-
if (!key) return message;
|
|
111
|
-
|
|
112
|
-
const cached = state.chunkCache.get(key);
|
|
113
|
-
if (cached && shallowArrayEqual(cached.outputs, message.outputs)) {
|
|
114
|
-
return cached;
|
|
115
|
-
}
|
|
116
|
-
state.chunkCache.set(key, message);
|
|
117
|
-
return message;
|
|
118
|
-
},
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
const threadMessages = state.converterCache.convertMessages(
|
|
122
|
-
chunks,
|
|
123
|
-
(cache, message, idx) =>
|
|
124
|
-
convertExternalMessageChunk(
|
|
125
|
-
message,
|
|
126
|
-
idx,
|
|
127
|
-
chunks.length,
|
|
128
|
-
isRunning,
|
|
129
|
-
state.metadata.error,
|
|
130
|
-
{
|
|
131
|
-
message: cache,
|
|
132
|
-
generatedFallbackMessages,
|
|
133
|
-
},
|
|
134
|
-
state.metadata.cancelledMessageIds,
|
|
135
|
-
),
|
|
78
|
+
const threadMessages = convertExternalMessagesInternal(
|
|
79
|
+
messages,
|
|
80
|
+
state.callback,
|
|
81
|
+
isRunning,
|
|
82
|
+
state.metadata,
|
|
83
|
+
joinStrategy,
|
|
84
|
+
cache,
|
|
136
85
|
);
|
|
137
86
|
|
|
138
87
|
bindExternalStoreMessage(threadMessages, messages);
|
|
139
|
-
return
|
|
140
|
-
|
|
141
|
-
state.metadata.error,
|
|
142
|
-
);
|
|
143
|
-
}, [state, messages, isRunning, joinStrategy]);
|
|
88
|
+
return threadMessages;
|
|
89
|
+
}, [state, messages, isRunning, joinStrategy, cache]);
|
|
144
90
|
};
|
|
@@ -109,7 +109,7 @@ export const useRemoteThreadListRuntime = (
|
|
|
109
109
|
);
|
|
110
110
|
|
|
111
111
|
const onThreadIdChange = useEffectEvent((threadId: string | undefined) => {
|
|
112
|
-
options.onThreadIdChange?.(threadId);
|
|
112
|
+
return options.onThreadIdChange?.(threadId);
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
const stableOptions = useMemo<RemoteThreadListOptions>(
|
|
@@ -47,8 +47,13 @@ export const GROUPBY_MEMO_KEY: unique symbol = Symbol.for(
|
|
|
47
47
|
* `display: "standalone"`). Resolving the registry-driven cases reads the
|
|
48
48
|
* {@link GroupByContext} passed to the `groupBy` function. Takes precedence
|
|
49
49
|
* over the `"tool-call"` entry.
|
|
50
|
+
*
|
|
51
|
+
* A `"tool-call:<name>"` entry matches tool calls by tool name and takes precedence over the plain `"tool-call"` entry, while `"standalone-tool-call"` still wins first.
|
|
50
52
|
*/
|
|
51
|
-
type GroupPartType =
|
|
53
|
+
type GroupPartType =
|
|
54
|
+
| PartState["type"]
|
|
55
|
+
| "standalone-tool-call"
|
|
56
|
+
| `tool-call:${string}`;
|
|
52
57
|
|
|
53
58
|
/**
|
|
54
59
|
* Build a `groupBy` from a `part.type → group-key path` lookup.
|
|
@@ -63,11 +68,14 @@ type GroupPartType = PartState["type"] | "standalone-tool-call";
|
|
|
63
68
|
* {@link GroupByContext} that `<MessagePrimitive.GroupedParts>` passes to the
|
|
64
69
|
* `groupBy` function — the helper needs nothing threaded into it.
|
|
65
70
|
*
|
|
71
|
+
* A `"tool-call:<name>"` entry matches tool calls by tool name and takes precedence over the plain `"tool-call"` entry, while `"standalone-tool-call"` still wins first.
|
|
72
|
+
*
|
|
66
73
|
* @example
|
|
67
74
|
* ```tsx
|
|
68
75
|
* <MessagePrimitive.GroupedParts
|
|
69
76
|
* groupBy={groupPartByType({
|
|
70
77
|
* reasoning: ["group-thought", "group-reasoning"],
|
|
78
|
+
* "tool-call:task": ["group-subagents"],
|
|
71
79
|
* "tool-call": ["group-thought", "group-tool"],
|
|
72
80
|
* "standalone-tool-call": [],
|
|
73
81
|
* })}
|
|
@@ -91,6 +99,9 @@ export const groupPartByType = <TKey extends `group-${string}`>(
|
|
|
91
99
|
if (isStandalone && lookup["standalone-tool-call"] !== undefined) {
|
|
92
100
|
return lookup["standalone-tool-call"]!;
|
|
93
101
|
}
|
|
102
|
+
const toolCallPath = lookup[`tool-call:${part.toolName}`];
|
|
103
|
+
if (toolCallPath !== undefined) return toolCallPath;
|
|
104
|
+
return lookup["tool-call"] ?? [];
|
|
94
105
|
}
|
|
95
106
|
return lookup[part.type] ?? [];
|
|
96
107
|
}) as ((part: PartState, context?: GroupByContext) => readonly TKey[]) & {
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { useRef } from "react";
|
|
2
|
+
import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
2
3
|
import { isJSONValueEqual } from "../../utils/json/is-json-equal";
|
|
3
4
|
|
|
5
|
+
export function useJSONEqualValue<T extends ReadonlyJSONValue>(value: T): T {
|
|
6
|
+
const prev = useRef(value);
|
|
7
|
+
if (prev.current !== value && !isJSONValueEqual(prev.current, value)) {
|
|
8
|
+
prev.current = value;
|
|
9
|
+
}
|
|
10
|
+
return prev.current;
|
|
11
|
+
}
|
|
12
|
+
|
|
4
13
|
/**
|
|
5
14
|
* Like `useShallow`, but with JSON deep-equality. Use when a selector derives an
|
|
6
15
|
* equal-but-fresh value on every store update — e.g. folding over
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { toJSONSchema } from "assistant-stream";
|
|
3
|
+
import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
4
|
+
import { isJSONValue } from "../../utils/json/is-json";
|
|
5
|
+
import type { Unstable_InteractableStateSchema } from "../types/scopes/interactables";
|
|
6
|
+
import { useJSONEqualValue } from "./useJSONEqual";
|
|
7
|
+
|
|
8
|
+
export function useJSONSchemaDependency(
|
|
9
|
+
schema: Unstable_InteractableStateSchema,
|
|
10
|
+
): ReadonlyJSONValue {
|
|
11
|
+
const normalizedSchema = useMemo(() => {
|
|
12
|
+
try {
|
|
13
|
+
const jsonSchema = toJSONSchema(schema);
|
|
14
|
+
return isJSONValue(jsonSchema) ? jsonSchema : null;
|
|
15
|
+
} catch {
|
|
16
|
+
// Opaque schemas share one fallback so rebuilt instances do not churn registration.
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}, [schema]);
|
|
20
|
+
|
|
21
|
+
return useJSONEqualValue(normalizedSchema);
|
|
22
|
+
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { LocalRuntimeCore } from "../../runtimes/local/local-runtime-core";
|
|
3
3
|
import { ExternalStoreRuntimeCore } from "../../runtimes/external-store/external-store-runtime-core";
|
|
4
|
+
import { ReadonlyThreadRuntimeCore } from "../../runtimes/readonly/ReadonlyThreadRuntimeCore";
|
|
4
5
|
import { AssistantRuntimeImpl } from "./assistant-runtime";
|
|
6
|
+
import {
|
|
7
|
+
ThreadRuntimeImpl,
|
|
8
|
+
type ThreadListItemRuntimeBinding,
|
|
9
|
+
type ThreadRuntimeCoreBinding,
|
|
10
|
+
} from "./thread-runtime";
|
|
5
11
|
|
|
6
12
|
describe("ThreadRuntime.append", () => {
|
|
7
13
|
it.each([
|
|
@@ -76,3 +82,55 @@ describe("ThreadRuntime.append with an external store", () => {
|
|
|
76
82
|
expect(onNew).not.toHaveBeenCalled();
|
|
77
83
|
});
|
|
78
84
|
});
|
|
85
|
+
|
|
86
|
+
describe("ThreadRuntime state subscriptions", () => {
|
|
87
|
+
it("tears down every source before reconnecting after an error", () => {
|
|
88
|
+
const core = new ReadonlyThreadRuntimeCore();
|
|
89
|
+
const cleanupError = new Error("thread cleanup failed");
|
|
90
|
+
const threadCleanup = vi.fn(() => {
|
|
91
|
+
throw cleanupError;
|
|
92
|
+
});
|
|
93
|
+
const itemCleanup = vi.fn();
|
|
94
|
+
let threadSubscriptions = 0;
|
|
95
|
+
let itemSubscriptions = 0;
|
|
96
|
+
const path = {
|
|
97
|
+
ref: "test.thread",
|
|
98
|
+
threadSelector: { type: "main" as const },
|
|
99
|
+
};
|
|
100
|
+
const runtime = new ThreadRuntimeImpl(
|
|
101
|
+
{
|
|
102
|
+
path,
|
|
103
|
+
getState: () => core,
|
|
104
|
+
subscribe: () => {
|
|
105
|
+
threadSubscriptions += 1;
|
|
106
|
+
return threadCleanup;
|
|
107
|
+
},
|
|
108
|
+
outerSubscribe: () => () => {},
|
|
109
|
+
} satisfies ThreadRuntimeCoreBinding,
|
|
110
|
+
{
|
|
111
|
+
path,
|
|
112
|
+
getState: () => ({
|
|
113
|
+
id: "test",
|
|
114
|
+
remoteId: undefined,
|
|
115
|
+
externalId: undefined,
|
|
116
|
+
isMain: true,
|
|
117
|
+
isRunning: false,
|
|
118
|
+
status: "regular",
|
|
119
|
+
title: undefined,
|
|
120
|
+
}),
|
|
121
|
+
subscribe: () => {
|
|
122
|
+
itemSubscriptions += 1;
|
|
123
|
+
return itemCleanup;
|
|
124
|
+
},
|
|
125
|
+
} satisfies ThreadListItemRuntimeBinding,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const unsubscribe = runtime.subscribe(() => {});
|
|
129
|
+
expect(() => unsubscribe()).toThrow(cleanupError);
|
|
130
|
+
expect(itemCleanup).toHaveBeenCalledOnce();
|
|
131
|
+
|
|
132
|
+
runtime.subscribe(() => {});
|
|
133
|
+
expect(threadSubscriptions).toBe(2);
|
|
134
|
+
expect(itemSubscriptions).toBe(2);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from "./message-runtime";
|
|
19
19
|
import { NestedSubscriptionSubject } from "../../subscribable/subscribable";
|
|
20
20
|
import {
|
|
21
|
+
runCleanups,
|
|
21
22
|
ShallowMemoizeSubject,
|
|
22
23
|
SKIP_UPDATE,
|
|
23
24
|
} from "../../subscribable/subscribable";
|
|
@@ -372,10 +373,7 @@ export class ThreadRuntimeImpl implements ThreadRuntime {
|
|
|
372
373
|
subscribe: (callback) => {
|
|
373
374
|
const sub1 = threadBinding.subscribe(callback);
|
|
374
375
|
const sub2 = threadListItemBinding.subscribe(callback);
|
|
375
|
-
return () =>
|
|
376
|
-
sub1();
|
|
377
|
-
sub2();
|
|
378
|
-
};
|
|
376
|
+
return () => runCleanups([sub1, sub2]);
|
|
379
377
|
},
|
|
380
378
|
});
|
|
381
379
|
|
|
@@ -167,7 +167,7 @@ export abstract class BaseComposerRuntimeCore
|
|
|
167
167
|
const adapter = this.getAttachmentAdapter();
|
|
168
168
|
if (adapter) {
|
|
169
169
|
const pending = this._attachments.filter((a) => !isAttachmentComplete(a));
|
|
170
|
-
await Promise.all(pending.map((a) => adapter.remove(a)));
|
|
170
|
+
await Promise.all(pending.map(async (a) => adapter.remove(a)));
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
|
|
@@ -203,6 +203,10 @@ export abstract class BaseComposerRuntimeCore
|
|
|
203
203
|
|
|
204
204
|
public async clearAttachments() {
|
|
205
205
|
this._cancelAllAttachmentAdds();
|
|
206
|
+
if (this._isSending) {
|
|
207
|
+
for (const attachment of this._attachments)
|
|
208
|
+
this._removedDuringSend.add(attachment.id);
|
|
209
|
+
}
|
|
206
210
|
const task = this._onClearAttachments();
|
|
207
211
|
this.setAttachments([]);
|
|
208
212
|
|
|
@@ -213,8 +217,13 @@ export abstract class BaseComposerRuntimeCore
|
|
|
213
217
|
if (!this.canSend || this._isSending) return;
|
|
214
218
|
|
|
215
219
|
if (this._dictationSession) {
|
|
216
|
-
|
|
217
|
-
|
|
220
|
+
try {
|
|
221
|
+
this._dictationSession.cancel();
|
|
222
|
+
} catch (error) {
|
|
223
|
+
console.error("[assistant-ui] Dictation session cancel threw", error);
|
|
224
|
+
} finally {
|
|
225
|
+
this._cleanupDictation();
|
|
226
|
+
}
|
|
218
227
|
}
|
|
219
228
|
|
|
220
229
|
const adapter = this.getAttachmentAdapter();
|
|
@@ -636,14 +645,11 @@ export abstract class BaseComposerRuntimeCore
|
|
|
636
645
|
throw new Error("Dictation adapter not configured");
|
|
637
646
|
}
|
|
638
647
|
|
|
648
|
+
const isReplacing = this._dictationSession !== undefined;
|
|
639
649
|
if (this._dictationSession) {
|
|
640
|
-
for (const unsub of this._dictationUnsubscribes) {
|
|
641
|
-
unsub();
|
|
642
|
-
}
|
|
643
|
-
this._dictationUnsubscribes = [];
|
|
644
650
|
const oldSession = this._dictationSession;
|
|
645
|
-
|
|
646
|
-
this.
|
|
651
|
+
this._cleanupDictation({ notify: false });
|
|
652
|
+
this._stopDictationSession(oldSession);
|
|
647
653
|
}
|
|
648
654
|
|
|
649
655
|
const inputDisabled = adapter.disableInputDuringDictation ?? false;
|
|
@@ -651,7 +657,22 @@ export abstract class BaseComposerRuntimeCore
|
|
|
651
657
|
this._dictationBaseText = this._text;
|
|
652
658
|
this._currentInterimText = "";
|
|
653
659
|
|
|
654
|
-
|
|
660
|
+
let session: DictationAdapter.Session;
|
|
661
|
+
try {
|
|
662
|
+
session = adapter.listen();
|
|
663
|
+
} catch (error) {
|
|
664
|
+
if (isReplacing) {
|
|
665
|
+
try {
|
|
666
|
+
this._notifySubscribers();
|
|
667
|
+
} catch (notifyError) {
|
|
668
|
+
console.error(
|
|
669
|
+
"[assistant-ui] Dictation replacement rollback notification threw",
|
|
670
|
+
notifyError,
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
throw error;
|
|
675
|
+
}
|
|
655
676
|
this._dictationSession = session;
|
|
656
677
|
const sessionId = ++this._dictationSessionIdCounter;
|
|
657
678
|
this._activeDictationSessionId = sessionId;
|
|
@@ -729,27 +750,59 @@ export abstract class BaseComposerRuntimeCore
|
|
|
729
750
|
const session = this._dictationSession;
|
|
730
751
|
const sessionId = this._activeDictationSessionId;
|
|
731
752
|
const cleanup = () => this._cleanupDictation({ sessionId });
|
|
732
|
-
|
|
753
|
+
this._stopDictationSession(session, cleanup);
|
|
733
754
|
}
|
|
734
755
|
|
|
735
|
-
private
|
|
756
|
+
private _stopDictationSession(
|
|
757
|
+
session: DictationAdapter.Session,
|
|
758
|
+
onSettled: () => void = () => {},
|
|
759
|
+
): void {
|
|
760
|
+
let task: Promise<void>;
|
|
761
|
+
try {
|
|
762
|
+
task = session.stop();
|
|
763
|
+
} catch (error) {
|
|
764
|
+
console.error("[assistant-ui] Dictation session stop threw", error);
|
|
765
|
+
onSettled();
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
void task.then(onSettled, (error) => {
|
|
770
|
+
console.error("[assistant-ui] Dictation session stop rejected", error);
|
|
771
|
+
onSettled();
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
private _cleanupDictation(options?: {
|
|
776
|
+
sessionId?: number | undefined;
|
|
777
|
+
notify?: boolean | undefined;
|
|
778
|
+
}): void {
|
|
736
779
|
const isStaleSession =
|
|
737
780
|
options?.sessionId !== undefined &&
|
|
738
781
|
options.sessionId !== this._activeDictationSessionId;
|
|
739
782
|
if (isStaleSession || this._isCleaningDictation) return;
|
|
740
783
|
|
|
741
784
|
this._isCleaningDictation = true;
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
785
|
+
const runCleanup = (cleanup: () => void) => {
|
|
786
|
+
try {
|
|
787
|
+
cleanup();
|
|
788
|
+
} catch (error) {
|
|
789
|
+
console.error("[assistant-ui] Dictation cleanup threw", error);
|
|
745
790
|
}
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
try {
|
|
794
|
+
const unsubscribes = this._dictationUnsubscribes;
|
|
746
795
|
this._dictationUnsubscribes = [];
|
|
747
796
|
this._dictationSession = undefined;
|
|
748
797
|
this._activeDictationSessionId = undefined;
|
|
749
798
|
this._dictation = undefined;
|
|
750
799
|
this._dictationBaseText = "";
|
|
751
800
|
this._currentInterimText = "";
|
|
752
|
-
|
|
801
|
+
|
|
802
|
+
for (const unsubscribe of unsubscribes) runCleanup(unsubscribe);
|
|
803
|
+
if (options?.notify !== false) {
|
|
804
|
+
runCleanup(() => this._notifySubscribers());
|
|
805
|
+
}
|
|
753
806
|
} finally {
|
|
754
807
|
this._isCleaningDictation = false;
|
|
755
808
|
}
|