@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,7 +1,8 @@
|
|
|
1
|
-
import { describe, it,
|
|
1
|
+
import { beforeEach, describe, expect, it, onTestFinished, vi } from "vitest";
|
|
2
2
|
import { BaseComposerRuntimeCore } from "../runtime/base/base-composer-runtime-core";
|
|
3
3
|
import type { AttachmentAdapter } from "../adapters/attachment";
|
|
4
4
|
import type { DictationAdapter } from "../adapters/speech";
|
|
5
|
+
import { WebSpeechDictationAdapter } from "../adapters/speech";
|
|
5
6
|
import type { CreateAttachment, PendingAttachment } from "../types/attachment";
|
|
6
7
|
import type { AppendMessage } from "../types/message";
|
|
7
8
|
import type { SendOptions } from "../runtime/interfaces/composer-runtime-core";
|
|
@@ -75,6 +76,71 @@ describe("BaseComposerRuntimeCore", () => {
|
|
|
75
76
|
expect(listener).not.toHaveBeenCalled();
|
|
76
77
|
});
|
|
77
78
|
|
|
79
|
+
it.each(["", "Existing text"])(
|
|
80
|
+
"replaces the complete browser dictation preview after %j",
|
|
81
|
+
async (baseText) => {
|
|
82
|
+
vi.useFakeTimers();
|
|
83
|
+
onTestFinished(() => {
|
|
84
|
+
vi.unstubAllGlobals();
|
|
85
|
+
vi.useRealTimers();
|
|
86
|
+
});
|
|
87
|
+
const listeners = new Map<string, EventListener>();
|
|
88
|
+
class Recognition {
|
|
89
|
+
addEventListener(type: string, listener: EventListener) {
|
|
90
|
+
listeners.set(type, listener);
|
|
91
|
+
}
|
|
92
|
+
start() {}
|
|
93
|
+
stop() {
|
|
94
|
+
listeners.get("end")!(new Event("end"));
|
|
95
|
+
}
|
|
96
|
+
abort() {
|
|
97
|
+
this.stop();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
vi.stubGlobal("window", { SpeechRecognition: Recognition });
|
|
101
|
+
composer.setDictationAdapter(new WebSpeechDictationAdapter());
|
|
102
|
+
composer.setText(baseText);
|
|
103
|
+
composer.startDictation();
|
|
104
|
+
onTestFinished(() => composer.stopDictation());
|
|
105
|
+
const emit = (results: [string, boolean][], resultIndex = 0) => {
|
|
106
|
+
listeners.get("result")!({
|
|
107
|
+
resultIndex,
|
|
108
|
+
results: results.map(([transcript, isFinal]) => ({
|
|
109
|
+
0: { transcript },
|
|
110
|
+
isFinal,
|
|
111
|
+
})),
|
|
112
|
+
} as unknown as Event);
|
|
113
|
+
};
|
|
114
|
+
const prefix = baseText ? `${baseText} ` : "";
|
|
115
|
+
|
|
116
|
+
emit([
|
|
117
|
+
["hello ", false],
|
|
118
|
+
["world", false],
|
|
119
|
+
]);
|
|
120
|
+
expect(composer.text).toBe(`${prefix}hello world`);
|
|
121
|
+
emit([["hello ", false]], 1);
|
|
122
|
+
expect(composer.text).toBe(`${prefix}hello `);
|
|
123
|
+
emit([]);
|
|
124
|
+
expect(composer.text).toBe(baseText);
|
|
125
|
+
emit([
|
|
126
|
+
["hello ", true],
|
|
127
|
+
["world", false],
|
|
128
|
+
]);
|
|
129
|
+
expect(composer.text).toBe(`${prefix}hello world`);
|
|
130
|
+
emit(
|
|
131
|
+
[
|
|
132
|
+
["hello ", true],
|
|
133
|
+
["world", true],
|
|
134
|
+
],
|
|
135
|
+
1,
|
|
136
|
+
);
|
|
137
|
+
expect(composer.text).toBe(`${prefix}hello world`);
|
|
138
|
+
expect(composer.dictation?.transcript).toBeUndefined();
|
|
139
|
+
composer.stopDictation();
|
|
140
|
+
await Promise.resolve();
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
|
|
78
144
|
it("isEmpty returns true for whitespace-only text", () => {
|
|
79
145
|
composer.setText(" ");
|
|
80
146
|
expect(composer.isEmpty).toBe(true);
|
|
@@ -103,6 +169,56 @@ describe("BaseComposerRuntimeCore", () => {
|
|
|
103
169
|
expect(listener).not.toHaveBeenCalled();
|
|
104
170
|
});
|
|
105
171
|
|
|
172
|
+
describe.each(["clearAttachments", "reset"] as const)(
|
|
173
|
+
"%s cleanup",
|
|
174
|
+
(method) => {
|
|
175
|
+
it.each(["first throw", "later throw", "rejection", "multiple failures"])(
|
|
176
|
+
"attempts all pending removals after %s and still rejects",
|
|
177
|
+
async (failure) => {
|
|
178
|
+
const error = new Error("removal failed");
|
|
179
|
+
const remove = vi.fn((attachment: PendingAttachment) => {
|
|
180
|
+
const fails =
|
|
181
|
+
attachment.id === (failure === "later throw" ? "two" : "one");
|
|
182
|
+
if (fails) {
|
|
183
|
+
if (failure === "rejection") return Promise.reject(error);
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
if (failure === "multiple failures" && attachment.id === "two") {
|
|
187
|
+
return Promise.reject(new Error("another failure"));
|
|
188
|
+
}
|
|
189
|
+
return Promise.resolve();
|
|
190
|
+
});
|
|
191
|
+
composer.setAttachmentAdapter({
|
|
192
|
+
accept: "*",
|
|
193
|
+
add: vi.fn(),
|
|
194
|
+
send: vi.fn(),
|
|
195
|
+
remove,
|
|
196
|
+
});
|
|
197
|
+
composer.setAttachments([
|
|
198
|
+
makePendingAttachment("one"),
|
|
199
|
+
{
|
|
200
|
+
id: "complete",
|
|
201
|
+
type: "file",
|
|
202
|
+
name: "saved.txt",
|
|
203
|
+
contentType: "text/plain",
|
|
204
|
+
content: [],
|
|
205
|
+
status: { type: "complete" },
|
|
206
|
+
},
|
|
207
|
+
makePendingAttachment("two"),
|
|
208
|
+
makePendingAttachment("three"),
|
|
209
|
+
]);
|
|
210
|
+
|
|
211
|
+
await expect(composer[method]()).rejects.toBe(error);
|
|
212
|
+
|
|
213
|
+
expect(
|
|
214
|
+
remove.mock.calls.map(([attachment]) => attachment.id),
|
|
215
|
+
).toEqual(["one", "two", "three"]);
|
|
216
|
+
expect(composer.attachments).toEqual([]);
|
|
217
|
+
},
|
|
218
|
+
);
|
|
219
|
+
},
|
|
220
|
+
);
|
|
221
|
+
|
|
106
222
|
it("reset keeps discarded text out of later dictation results", async () => {
|
|
107
223
|
let emitSpeech!: (result: DictationAdapter.Result) => void;
|
|
108
224
|
composer.setDictationAdapter({
|
|
@@ -226,9 +342,14 @@ describe("BaseComposerRuntimeCore", () => {
|
|
|
226
342
|
});
|
|
227
343
|
|
|
228
344
|
it("handles rejected dictation shutdowns", async () => {
|
|
345
|
+
const consoleError = vi
|
|
346
|
+
.spyOn(console, "error")
|
|
347
|
+
.mockImplementation(() => {});
|
|
348
|
+
onTestFinished(() => consoleError.mockRestore());
|
|
349
|
+
const stopError = new Error("shutdown failed");
|
|
229
350
|
const session: DictationAdapter.Session = {
|
|
230
351
|
status: { type: "running" },
|
|
231
|
-
stop: vi.fn().mockRejectedValue(
|
|
352
|
+
stop: vi.fn().mockRejectedValue(stopError),
|
|
232
353
|
cancel: vi.fn(),
|
|
233
354
|
onSpeechStart: vi.fn(() => () => {}),
|
|
234
355
|
onSpeechEnd: vi.fn(() => () => {}),
|
|
@@ -248,11 +369,202 @@ describe("BaseComposerRuntimeCore", () => {
|
|
|
248
369
|
expect(session.stop).toHaveBeenCalledOnce();
|
|
249
370
|
expect(composer.dictation).toBeUndefined();
|
|
250
371
|
expect(unhandledRejection).not.toHaveBeenCalled();
|
|
372
|
+
expect(consoleError).toHaveBeenCalledWith(
|
|
373
|
+
"[assistant-ui] Dictation session stop rejected",
|
|
374
|
+
stopError,
|
|
375
|
+
);
|
|
251
376
|
} finally {
|
|
252
377
|
process.off("unhandledRejection", unhandledRejection);
|
|
253
378
|
}
|
|
254
379
|
});
|
|
255
380
|
|
|
381
|
+
it("handles synchronously throwing dictation shutdowns", () => {
|
|
382
|
+
const consoleError = vi
|
|
383
|
+
.spyOn(console, "error")
|
|
384
|
+
.mockImplementation(() => {});
|
|
385
|
+
onTestFinished(() => consoleError.mockRestore());
|
|
386
|
+
const stopError = new Error("shutdown failed");
|
|
387
|
+
const session: DictationAdapter.Session = {
|
|
388
|
+
status: { type: "running" },
|
|
389
|
+
stop: vi.fn(() => {
|
|
390
|
+
throw stopError;
|
|
391
|
+
}),
|
|
392
|
+
cancel: vi.fn(),
|
|
393
|
+
onSpeechStart: vi.fn(() => () => {}),
|
|
394
|
+
onSpeechEnd: vi.fn(() => () => {}),
|
|
395
|
+
onSpeech: vi.fn(() => () => {}),
|
|
396
|
+
};
|
|
397
|
+
composer.setDictationAdapter({ listen: () => session });
|
|
398
|
+
composer.startDictation();
|
|
399
|
+
|
|
400
|
+
expect(() => composer.stopDictation()).not.toThrow();
|
|
401
|
+
|
|
402
|
+
expect(composer.dictation).toBeUndefined();
|
|
403
|
+
expect(consoleError).toHaveBeenCalledWith(
|
|
404
|
+
"[assistant-ui] Dictation session stop threw",
|
|
405
|
+
stopError,
|
|
406
|
+
);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it("finishes dictation cleanup when an unsubscribe throws", async () => {
|
|
410
|
+
const cleanupError = new Error("cleanup failed");
|
|
411
|
+
const laterCleanup = vi.fn();
|
|
412
|
+
const consoleError = vi
|
|
413
|
+
.spyOn(console, "error")
|
|
414
|
+
.mockImplementation(() => {});
|
|
415
|
+
onTestFinished(() => consoleError.mockRestore());
|
|
416
|
+
const session: DictationAdapter.Session = {
|
|
417
|
+
status: { type: "running" },
|
|
418
|
+
stop: vi.fn().mockResolvedValue(undefined),
|
|
419
|
+
cancel: vi.fn(),
|
|
420
|
+
onSpeech: vi.fn(() => () => {
|
|
421
|
+
throw cleanupError;
|
|
422
|
+
}),
|
|
423
|
+
onSpeechStart: vi.fn(() => laterCleanup),
|
|
424
|
+
onSpeechEnd: vi.fn(() => () => {}),
|
|
425
|
+
};
|
|
426
|
+
composer.setDictationAdapter({ listen: () => session });
|
|
427
|
+
|
|
428
|
+
composer.startDictation();
|
|
429
|
+
composer.stopDictation();
|
|
430
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
431
|
+
|
|
432
|
+
expect(laterCleanup).toHaveBeenCalledOnce();
|
|
433
|
+
expect(composer.dictation).toBeUndefined();
|
|
434
|
+
expect(consoleError).toHaveBeenCalledWith(
|
|
435
|
+
"[assistant-ui] Dictation cleanup threw",
|
|
436
|
+
cleanupError,
|
|
437
|
+
);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it("sends after a dictation unsubscribe throws", async () => {
|
|
441
|
+
const cleanupError = new Error("cleanup failed");
|
|
442
|
+
const cancelError = new Error("cancel failed");
|
|
443
|
+
const consoleError = vi
|
|
444
|
+
.spyOn(console, "error")
|
|
445
|
+
.mockImplementation(() => {});
|
|
446
|
+
onTestFinished(() => consoleError.mockRestore());
|
|
447
|
+
composer.setDictationAdapter({
|
|
448
|
+
listen: () => ({
|
|
449
|
+
status: { type: "running" },
|
|
450
|
+
stop: vi.fn().mockResolvedValue(undefined),
|
|
451
|
+
cancel: vi.fn(() => {
|
|
452
|
+
throw cancelError;
|
|
453
|
+
}),
|
|
454
|
+
onSpeech: () => () => {
|
|
455
|
+
throw cleanupError;
|
|
456
|
+
},
|
|
457
|
+
onSpeechStart: () => () => {},
|
|
458
|
+
onSpeechEnd: () => () => {},
|
|
459
|
+
}),
|
|
460
|
+
});
|
|
461
|
+
composer.setText("send me");
|
|
462
|
+
composer.startDictation();
|
|
463
|
+
|
|
464
|
+
await composer.send();
|
|
465
|
+
|
|
466
|
+
expect(composer.sentMessages).toHaveLength(1);
|
|
467
|
+
expect(composer.sentMessages[0]?.content).toEqual([
|
|
468
|
+
{ type: "text", text: "send me" },
|
|
469
|
+
]);
|
|
470
|
+
expect(consoleError).toHaveBeenCalledWith(
|
|
471
|
+
"[assistant-ui] Dictation session cancel threw",
|
|
472
|
+
cancelError,
|
|
473
|
+
);
|
|
474
|
+
expect(consoleError).toHaveBeenCalledWith(
|
|
475
|
+
"[assistant-ui] Dictation cleanup threw",
|
|
476
|
+
cleanupError,
|
|
477
|
+
);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it("starts replacement dictation when the old session stop throws", () => {
|
|
481
|
+
const consoleError = vi
|
|
482
|
+
.spyOn(console, "error")
|
|
483
|
+
.mockImplementation(() => {});
|
|
484
|
+
onTestFinished(() => consoleError.mockRestore());
|
|
485
|
+
const stopError = new Error("shutdown failed");
|
|
486
|
+
const firstSession: DictationAdapter.Session = {
|
|
487
|
+
status: { type: "running" },
|
|
488
|
+
stop: vi.fn(() => {
|
|
489
|
+
throw stopError;
|
|
490
|
+
}),
|
|
491
|
+
cancel: vi.fn(),
|
|
492
|
+
onSpeech: () => () => {},
|
|
493
|
+
onSpeechStart: () => () => {},
|
|
494
|
+
onSpeechEnd: () => () => {},
|
|
495
|
+
};
|
|
496
|
+
const secondSession: DictationAdapter.Session = {
|
|
497
|
+
...firstSession,
|
|
498
|
+
stop: vi.fn().mockResolvedValue(undefined),
|
|
499
|
+
};
|
|
500
|
+
const listen = vi
|
|
501
|
+
.fn()
|
|
502
|
+
.mockReturnValueOnce(firstSession)
|
|
503
|
+
.mockReturnValueOnce(secondSession);
|
|
504
|
+
composer.setDictationAdapter({ listen });
|
|
505
|
+
composer.startDictation();
|
|
506
|
+
|
|
507
|
+
expect(() => composer.startDictation()).not.toThrow();
|
|
508
|
+
|
|
509
|
+
expect(listen).toHaveBeenCalledTimes(2);
|
|
510
|
+
expect(composer.dictation).toBeDefined();
|
|
511
|
+
expect(consoleError).toHaveBeenCalledWith(
|
|
512
|
+
"[assistant-ui] Dictation session stop threw",
|
|
513
|
+
stopError,
|
|
514
|
+
);
|
|
515
|
+
composer.stopDictation();
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
it("publishes cleared state when replacement session creation throws", () => {
|
|
519
|
+
const listenError = new Error("listen failed");
|
|
520
|
+
const firstSession: DictationAdapter.Session = {
|
|
521
|
+
status: { type: "running" },
|
|
522
|
+
stop: vi.fn().mockResolvedValue(undefined),
|
|
523
|
+
cancel: vi.fn(),
|
|
524
|
+
onSpeech: () => () => {},
|
|
525
|
+
onSpeechStart: () => () => {},
|
|
526
|
+
onSpeechEnd: () => () => {},
|
|
527
|
+
};
|
|
528
|
+
const listen = vi
|
|
529
|
+
.fn()
|
|
530
|
+
.mockReturnValueOnce(firstSession)
|
|
531
|
+
.mockImplementationOnce(() => {
|
|
532
|
+
throw listenError;
|
|
533
|
+
});
|
|
534
|
+
composer.setDictationAdapter({ listen });
|
|
535
|
+
const states: Array<string | undefined> = [];
|
|
536
|
+
composer.subscribe(() => states.push(composer.dictation?.status.type));
|
|
537
|
+
composer.startDictation();
|
|
538
|
+
states.length = 0;
|
|
539
|
+
|
|
540
|
+
expect(() => composer.startDictation()).toThrow(listenError);
|
|
541
|
+
|
|
542
|
+
expect(composer.dictation).toBeUndefined();
|
|
543
|
+
expect(states).toEqual([undefined]);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it("replaces dictation without publishing an intermediate cleared state", async () => {
|
|
547
|
+
const session = (): DictationAdapter.Session => ({
|
|
548
|
+
status: { type: "running" },
|
|
549
|
+
stop: vi.fn().mockResolvedValue(undefined),
|
|
550
|
+
cancel: vi.fn(),
|
|
551
|
+
onSpeech: () => () => {},
|
|
552
|
+
onSpeechStart: () => () => {},
|
|
553
|
+
onSpeechEnd: () => () => {},
|
|
554
|
+
});
|
|
555
|
+
composer.setDictationAdapter({ listen: session });
|
|
556
|
+
const states: Array<string | undefined> = [];
|
|
557
|
+
composer.subscribe(() => states.push(composer.dictation?.status.type));
|
|
558
|
+
composer.startDictation();
|
|
559
|
+
states.length = 0;
|
|
560
|
+
|
|
561
|
+
composer.startDictation();
|
|
562
|
+
|
|
563
|
+
expect(states).toEqual(["running"]);
|
|
564
|
+
composer.stopDictation();
|
|
565
|
+
await Promise.resolve();
|
|
566
|
+
});
|
|
567
|
+
|
|
256
568
|
describe("CreateAttachment (external source)", () => {
|
|
257
569
|
const makeCreateAttachment = (
|
|
258
570
|
overrides?: Partial<CreateAttachment>,
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
hasUpcomingMessage,
|
|
5
5
|
} from "../runtimes/external-store/external-store-thread-runtime-core";
|
|
6
6
|
import type { ExternalStoreAdapter } from "../runtimes/external-store/external-store-adapter";
|
|
7
|
+
import type { RealtimeVoiceAdapter } from "../adapters/voice";
|
|
7
8
|
import type { ModelContextProvider } from "../model-context/types";
|
|
8
9
|
import type { AppendMessage, ThreadMessage } from "../types/message";
|
|
9
10
|
import { createMessageQueue } from "../runtime/queue/message-queue";
|
|
@@ -1117,6 +1118,44 @@ describe("ExternalStoreThreadRuntimeCore adapter contract", () => {
|
|
|
1117
1118
|
).resolves.toBeUndefined();
|
|
1118
1119
|
});
|
|
1119
1120
|
|
|
1121
|
+
it("emits a decision after an approval callback accepts", async () => {
|
|
1122
|
+
const message: ThreadMessage = {
|
|
1123
|
+
...createAssistantMessage("assistant-1"),
|
|
1124
|
+
status: { type: "requires-action", reason: "tool-calls" },
|
|
1125
|
+
content: [
|
|
1126
|
+
{
|
|
1127
|
+
type: "tool-call",
|
|
1128
|
+
toolCallId: "tool-1",
|
|
1129
|
+
toolName: "review",
|
|
1130
|
+
args: {},
|
|
1131
|
+
argsText: "{}",
|
|
1132
|
+
approval: { id: "approval-1" },
|
|
1133
|
+
},
|
|
1134
|
+
],
|
|
1135
|
+
} as ThreadMessage;
|
|
1136
|
+
const core = new ExternalStoreThreadRuntimeCore(
|
|
1137
|
+
contextProvider,
|
|
1138
|
+
createBaseAdapter({
|
|
1139
|
+
messages: [message],
|
|
1140
|
+
onRespondToToolApproval: () => {},
|
|
1141
|
+
}),
|
|
1142
|
+
);
|
|
1143
|
+
const answered = vi.fn();
|
|
1144
|
+
core.unstable_on("toolApprovalAnswered", answered);
|
|
1145
|
+
|
|
1146
|
+
await core.respondToToolApproval({
|
|
1147
|
+
approvalId: "approval-1",
|
|
1148
|
+
approved: true,
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
expect(answered).toHaveBeenCalledWith({
|
|
1152
|
+
messageId: "assistant-1",
|
|
1153
|
+
toolCallId: "tool-1",
|
|
1154
|
+
toolName: "review",
|
|
1155
|
+
approved: true,
|
|
1156
|
+
});
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1120
1159
|
it("handles rejected onCancel callbacks", async () => {
|
|
1121
1160
|
const error = new Error("cancel failed");
|
|
1122
1161
|
const consoleError = vi
|
|
@@ -1262,3 +1301,115 @@ describe("ExternalStoreThreadRuntimeCore adapter contract", () => {
|
|
|
1262
1301
|
});
|
|
1263
1302
|
});
|
|
1264
1303
|
});
|
|
1304
|
+
|
|
1305
|
+
describe("ExternalStoreThreadRuntimeCore voice transcripts", () => {
|
|
1306
|
+
const createVoiceAdapter = () => {
|
|
1307
|
+
let transcriptCallback:
|
|
1308
|
+
| ((transcript: RealtimeVoiceAdapter.TranscriptItem) => void)
|
|
1309
|
+
| undefined;
|
|
1310
|
+
const session: RealtimeVoiceAdapter.Session = {
|
|
1311
|
+
status: { type: "running" },
|
|
1312
|
+
isMuted: false,
|
|
1313
|
+
disconnect: vi.fn(),
|
|
1314
|
+
mute: vi.fn(),
|
|
1315
|
+
unmute: vi.fn(),
|
|
1316
|
+
onStatusChange: () => () => {},
|
|
1317
|
+
onTranscript: (callback) => {
|
|
1318
|
+
transcriptCallback = callback;
|
|
1319
|
+
return () => {
|
|
1320
|
+
transcriptCallback = undefined;
|
|
1321
|
+
};
|
|
1322
|
+
},
|
|
1323
|
+
onModeChange: () => () => {},
|
|
1324
|
+
onVolumeChange: () => () => {},
|
|
1325
|
+
};
|
|
1326
|
+
return {
|
|
1327
|
+
adapter: { connect: () => session } satisfies RealtimeVoiceAdapter,
|
|
1328
|
+
emitTranscript: (transcript: RealtimeVoiceAdapter.TranscriptItem) =>
|
|
1329
|
+
transcriptCallback?.(transcript),
|
|
1330
|
+
};
|
|
1331
|
+
};
|
|
1332
|
+
|
|
1333
|
+
const createVoiceCore = () => {
|
|
1334
|
+
const voiceAdapter = createVoiceAdapter();
|
|
1335
|
+
const onNew = vi.fn(async () => {});
|
|
1336
|
+
const onEdit = vi.fn(async () => {});
|
|
1337
|
+
const onReload = vi.fn(async () => {});
|
|
1338
|
+
const onResume = vi.fn(async () => {});
|
|
1339
|
+
const core = new ExternalStoreThreadRuntimeCore(
|
|
1340
|
+
createContextProvider(),
|
|
1341
|
+
createBaseAdapter({
|
|
1342
|
+
onNew,
|
|
1343
|
+
onEdit,
|
|
1344
|
+
onReload,
|
|
1345
|
+
onResume,
|
|
1346
|
+
adapters: { voice: voiceAdapter.adapter },
|
|
1347
|
+
}),
|
|
1348
|
+
);
|
|
1349
|
+
core.connectVoice();
|
|
1350
|
+
voiceAdapter.emitTranscript({
|
|
1351
|
+
role: "assistant",
|
|
1352
|
+
text: "Hello",
|
|
1353
|
+
isFinal: true,
|
|
1354
|
+
});
|
|
1355
|
+
return {
|
|
1356
|
+
core,
|
|
1357
|
+
onNew,
|
|
1358
|
+
onEdit,
|
|
1359
|
+
onReload,
|
|
1360
|
+
onResume,
|
|
1361
|
+
transcriptId: core.messages[0]!.id,
|
|
1362
|
+
};
|
|
1363
|
+
};
|
|
1364
|
+
|
|
1365
|
+
it("rejects an edit resubmission of a transcript before reaching the store", async () => {
|
|
1366
|
+
const { core, onNew, onEdit, transcriptId } = createVoiceCore();
|
|
1367
|
+
try {
|
|
1368
|
+
const edit: AppendMessage = {
|
|
1369
|
+
parentId: null,
|
|
1370
|
+
sourceId: transcriptId,
|
|
1371
|
+
role: "user",
|
|
1372
|
+
content: [{ type: "text", text: "again" }],
|
|
1373
|
+
attachments: [],
|
|
1374
|
+
metadata: { custom: {} },
|
|
1375
|
+
createdAt: new Date(),
|
|
1376
|
+
runConfig: {},
|
|
1377
|
+
};
|
|
1378
|
+
|
|
1379
|
+
await expect(core.append(edit)).rejects.toThrow(
|
|
1380
|
+
"Voice transcript messages cannot be edited",
|
|
1381
|
+
);
|
|
1382
|
+
await expect(core.append({ ...edit, startRun: false })).rejects.toThrow(
|
|
1383
|
+
"Voice transcript messages cannot be edited",
|
|
1384
|
+
);
|
|
1385
|
+
expect(onNew).not.toHaveBeenCalled();
|
|
1386
|
+
expect(onEdit).not.toHaveBeenCalled();
|
|
1387
|
+
} finally {
|
|
1388
|
+
core.disconnectVoice();
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
it("rejects reloading a transcript before reaching the store", async () => {
|
|
1393
|
+
const { core, onReload, onResume, transcriptId } = createVoiceCore();
|
|
1394
|
+
try {
|
|
1395
|
+
await expect(
|
|
1396
|
+
core.startRun({
|
|
1397
|
+
parentId: null,
|
|
1398
|
+
sourceId: transcriptId,
|
|
1399
|
+
runConfig: {},
|
|
1400
|
+
}),
|
|
1401
|
+
).rejects.toThrow("Voice transcript messages cannot be reloaded");
|
|
1402
|
+
await expect(
|
|
1403
|
+
core.resumeRun({
|
|
1404
|
+
parentId: null,
|
|
1405
|
+
sourceId: transcriptId,
|
|
1406
|
+
runConfig: {},
|
|
1407
|
+
}),
|
|
1408
|
+
).rejects.toThrow("Voice transcript messages cannot be reloaded");
|
|
1409
|
+
expect(onReload).not.toHaveBeenCalled();
|
|
1410
|
+
expect(onResume).not.toHaveBeenCalled();
|
|
1411
|
+
} finally {
|
|
1412
|
+
core.disconnectVoice();
|
|
1413
|
+
}
|
|
1414
|
+
});
|
|
1415
|
+
});
|
|
@@ -76,6 +76,75 @@ const renderThread = () => {
|
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
describe("ExternalThread attachments", () => {
|
|
79
|
+
describe.each(["clearAttachments", "reset"] as const)(
|
|
80
|
+
"%s cleanup",
|
|
81
|
+
(method) => {
|
|
82
|
+
it.each(["first throw", "later throw", "rejection", "multiple failures"])(
|
|
83
|
+
"attempts all pending removals after %s and still rejects",
|
|
84
|
+
async (failure) => {
|
|
85
|
+
const error = new Error("removal failed");
|
|
86
|
+
const remove = vi.fn((attachment: PendingAttachment) => {
|
|
87
|
+
const fails =
|
|
88
|
+
attachment.id === (failure === "later throw" ? "two" : "one");
|
|
89
|
+
if (fails) {
|
|
90
|
+
if (failure === "rejection") return Promise.reject(error);
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
if (failure === "multiple failures" && attachment.id === "two") {
|
|
94
|
+
return Promise.reject(new Error("another failure"));
|
|
95
|
+
}
|
|
96
|
+
return Promise.resolve();
|
|
97
|
+
});
|
|
98
|
+
const aui = renderThreadWithProps({
|
|
99
|
+
attachmentAdapter: {
|
|
100
|
+
accept: "*",
|
|
101
|
+
add: async ({ file }) => ({
|
|
102
|
+
id: file.name,
|
|
103
|
+
type: "file",
|
|
104
|
+
name: file.name,
|
|
105
|
+
contentType: "text/plain",
|
|
106
|
+
file,
|
|
107
|
+
status: { type: "requires-action", reason: "composer-send" },
|
|
108
|
+
}),
|
|
109
|
+
send: vi.fn(),
|
|
110
|
+
remove,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
const composer = () => aui().thread().composer();
|
|
114
|
+
await act(async () => {
|
|
115
|
+
await composer().addAttachment(
|
|
116
|
+
new File(["data"], "one", { type: "text/plain" }),
|
|
117
|
+
);
|
|
118
|
+
await composer().addAttachment({
|
|
119
|
+
id: "complete",
|
|
120
|
+
name: "saved.txt",
|
|
121
|
+
contentType: "text/plain",
|
|
122
|
+
content: [],
|
|
123
|
+
});
|
|
124
|
+
await composer().addAttachment(
|
|
125
|
+
new File(["data"], "two", { type: "text/plain" }),
|
|
126
|
+
);
|
|
127
|
+
await composer().addAttachment(
|
|
128
|
+
new File(["data"], "three", { type: "text/plain" }),
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
await waitFor(() =>
|
|
132
|
+
expect(composer().getState().attachments).toHaveLength(4),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
await act(async () => {
|
|
136
|
+
await expect(composer()[method]()).rejects.toBe(error);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
expect(
|
|
140
|
+
remove.mock.calls.map(([attachment]) => attachment.id),
|
|
141
|
+
).toEqual(["one", "two", "three"]);
|
|
142
|
+
expect(composer().getState().attachments).toEqual([]);
|
|
143
|
+
},
|
|
144
|
+
);
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
|
|
79
148
|
it("uses generated IDs unless a prepared attachment supplies one", async () => {
|
|
80
149
|
const aui = renderThread();
|
|
81
150
|
mockGenerateId
|
|
@@ -203,6 +203,40 @@ describe("ExternalThread part status", () => {
|
|
|
203
203
|
});
|
|
204
204
|
});
|
|
205
205
|
|
|
206
|
+
describe("ExternalThread tasks", () => {
|
|
207
|
+
it("derives nested conversations and resolves their task scope", () => {
|
|
208
|
+
const { aui } = renderThread({
|
|
209
|
+
messages: [
|
|
210
|
+
assistantMessageWithContent({ type: "running" }, [
|
|
211
|
+
{
|
|
212
|
+
type: "tool-call",
|
|
213
|
+
toolCallId: "delegate-1",
|
|
214
|
+
toolName: "delegate",
|
|
215
|
+
args: {},
|
|
216
|
+
argsText: "{}",
|
|
217
|
+
messages: [
|
|
218
|
+
assistantMessageWithContent(
|
|
219
|
+
{ type: "running" },
|
|
220
|
+
[],
|
|
221
|
+
"nested-message-1",
|
|
222
|
+
),
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
]),
|
|
226
|
+
],
|
|
227
|
+
isRunning: true,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const [task] = aui().thread.getState().tasks;
|
|
231
|
+
expect(task).toMatchObject({
|
|
232
|
+
id: "delegate-1",
|
|
233
|
+
toolName: "delegate",
|
|
234
|
+
messageId: "a1",
|
|
235
|
+
});
|
|
236
|
+
expect(aui().thread.task({ id: "delegate-1" }).getState()).toBe(task);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
206
240
|
describe("ExternalThread unset optional callbacks", () => {
|
|
207
241
|
it("throws a capability error when the callback prop is not set", () => {
|
|
208
242
|
const { aui } = renderThread({
|