@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
|
@@ -36,7 +36,10 @@ import type { RealtimeVoiceAdapter } from "../../adapters/voice";
|
|
|
36
36
|
import type { ThreadMessageLike } from "../utils/thread-message-like";
|
|
37
37
|
import { notifyEventListeners } from "../../utils/notify-event-listeners";
|
|
38
38
|
import { gateInteractableComposerMetadata } from "../../model-context/interactable-composer-metadata";
|
|
39
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
BaseSubscribable,
|
|
41
|
+
notifySubscribers,
|
|
42
|
+
} from "../../subscribable/subscribable";
|
|
40
43
|
|
|
41
44
|
type BaseThreadAdapters = {
|
|
42
45
|
speech?: SpeechSynthesisAdapter | undefined;
|
|
@@ -168,7 +171,16 @@ export abstract class BaseThreadRuntimeCore
|
|
|
168
171
|
public getEditComposer(messageId: string) {
|
|
169
172
|
return this._editComposers.get(messageId);
|
|
170
173
|
}
|
|
174
|
+
protected _isVoiceMessage(messageId: string | null) {
|
|
175
|
+
return (
|
|
176
|
+
messageId !== null && this._voiceMessages.some((m) => m.id === messageId)
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
171
180
|
public beginEdit(messageId: string) {
|
|
181
|
+
if (this._isVoiceMessage(messageId)) {
|
|
182
|
+
throw new Error("Voice transcript messages cannot be edited");
|
|
183
|
+
}
|
|
172
184
|
if (this._editComposers.has(messageId))
|
|
173
185
|
throw new Error("Edit already in progress");
|
|
174
186
|
|
|
@@ -227,11 +239,27 @@ export abstract class BaseThreadRuntimeCore
|
|
|
227
239
|
notifyEventListeners(subscribers, payload, `Thread runtime "${event}"`);
|
|
228
240
|
}
|
|
229
241
|
|
|
242
|
+
protected _notifyToolApprovalAnswered(
|
|
243
|
+
messageId: string,
|
|
244
|
+
toolCallId: string,
|
|
245
|
+
toolName: string,
|
|
246
|
+
approved: boolean,
|
|
247
|
+
) {
|
|
248
|
+
this._notifyEventSubscribers("toolApprovalAnswered", {
|
|
249
|
+
messageId,
|
|
250
|
+
toolCallId,
|
|
251
|
+
toolName,
|
|
252
|
+
approved,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
230
256
|
public submitFeedback({ messageId, type }: SubmitFeedbackOptions) {
|
|
231
257
|
const adapter = this.adapters?.feedback;
|
|
232
258
|
if (!adapter) throw new Error("Feedback adapter not configured");
|
|
233
259
|
|
|
234
|
-
const
|
|
260
|
+
const entry = this.getMessageById(messageId);
|
|
261
|
+
if (!entry) throw new Error(`Message not found: ${messageId}`);
|
|
262
|
+
const { message, parentId } = entry;
|
|
235
263
|
adapter.submit({ message, type });
|
|
236
264
|
|
|
237
265
|
if (message.role === "assistant") {
|
|
@@ -242,7 +270,18 @@ export abstract class BaseThreadRuntimeCore
|
|
|
242
270
|
submittedFeedback: { type },
|
|
243
271
|
},
|
|
244
272
|
};
|
|
245
|
-
this.
|
|
273
|
+
const voiceIdx = this._voiceMessages.findIndex(
|
|
274
|
+
(voiceMessage) => voiceMessage.id === messageId,
|
|
275
|
+
);
|
|
276
|
+
if (voiceIdx === -1) {
|
|
277
|
+
this.repository.addOrUpdateMessage(parentId, updatedMessage);
|
|
278
|
+
} else {
|
|
279
|
+
this._voiceMessages[voiceIdx] = updatedMessage;
|
|
280
|
+
if (this._currentAssistantMsg === message) {
|
|
281
|
+
this._currentAssistantMsg = updatedMessage as ThreadAssistantMessage;
|
|
282
|
+
}
|
|
283
|
+
this._markVoiceMessagesDirty();
|
|
284
|
+
}
|
|
246
285
|
}
|
|
247
286
|
|
|
248
287
|
this._notifySubscribers();
|
|
@@ -255,36 +294,80 @@ export abstract class BaseThreadRuntimeCore
|
|
|
255
294
|
const adapter = this.adapters?.speech;
|
|
256
295
|
if (!adapter) throw new Error("Speech adapter not configured");
|
|
257
296
|
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
297
|
+
const entry = this.getMessageById(messageId);
|
|
298
|
+
if (!entry) throw new Error(`Message not found: ${messageId}`);
|
|
299
|
+
const { message } = entry;
|
|
261
300
|
|
|
262
|
-
const
|
|
263
|
-
|
|
301
|
+
const previousStop = this._stopSpeaking;
|
|
302
|
+
let utterance: SpeechSynthesisAdapter.Utterance;
|
|
303
|
+
try {
|
|
304
|
+
previousStop?.();
|
|
305
|
+
utterance = adapter.speak(getThreadMessageText(message));
|
|
306
|
+
} catch (error) {
|
|
307
|
+
if (previousStop && !this._stopSpeaking) {
|
|
308
|
+
try {
|
|
309
|
+
this._notifySubscribers();
|
|
310
|
+
} catch (notificationError) {
|
|
311
|
+
console.error(
|
|
312
|
+
"[assistant-ui] Speech rollback notification threw",
|
|
313
|
+
notificationError,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
throw error;
|
|
318
|
+
}
|
|
319
|
+
let unsub: Unsubscribe | undefined;
|
|
320
|
+
const clear = () => {
|
|
321
|
+
this._stopSpeaking = undefined;
|
|
322
|
+
this.speech = undefined;
|
|
323
|
+
const cleanup = unsub;
|
|
324
|
+
unsub = undefined;
|
|
325
|
+
cleanup?.();
|
|
326
|
+
};
|
|
327
|
+
const stop = () => {
|
|
328
|
+
if (this._stopSpeaking !== stop) return;
|
|
329
|
+
try {
|
|
330
|
+
clear();
|
|
331
|
+
} finally {
|
|
332
|
+
utterance.cancel();
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
const update = () => {
|
|
336
|
+
if (this._stopSpeaking !== stop) return;
|
|
264
337
|
if (utterance.status.type === "ended") {
|
|
265
|
-
this.
|
|
266
|
-
this.speech = undefined;
|
|
338
|
+
notifySubscribers([clear, () => this._notifySubscribers()]);
|
|
267
339
|
} else {
|
|
268
340
|
this.speech = { messageId, status: utterance.status };
|
|
341
|
+
this._notifySubscribers();
|
|
269
342
|
}
|
|
270
|
-
this._notifySubscribers();
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
this.speech = { messageId, status: utterance.status };
|
|
274
|
-
this._notifySubscribers();
|
|
275
|
-
|
|
276
|
-
this._stopSpeaking = () => {
|
|
277
|
-
utterance.cancel();
|
|
278
|
-
unsub();
|
|
279
|
-
this.speech = undefined;
|
|
280
|
-
this._stopSpeaking = undefined;
|
|
281
343
|
};
|
|
344
|
+
|
|
345
|
+
this._stopSpeaking = stop;
|
|
346
|
+
try {
|
|
347
|
+
unsub = utterance.subscribe(update);
|
|
348
|
+
if (this._stopSpeaking !== stop) {
|
|
349
|
+
unsub();
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
update();
|
|
353
|
+
} catch (error) {
|
|
354
|
+
if (this._stopSpeaking === stop) {
|
|
355
|
+
try {
|
|
356
|
+
notifySubscribers([stop, () => this._notifySubscribers()]);
|
|
357
|
+
} catch (cleanupError) {
|
|
358
|
+
console.error(
|
|
359
|
+
"[assistant-ui] Speech rollback cleanup threw",
|
|
360
|
+
cleanupError,
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
throw error;
|
|
365
|
+
}
|
|
282
366
|
}
|
|
283
367
|
|
|
284
368
|
public stopSpeaking() {
|
|
285
369
|
if (!this._stopSpeaking) throw new Error("No message is being spoken");
|
|
286
|
-
this._stopSpeaking();
|
|
287
|
-
this._notifySubscribers();
|
|
370
|
+
notifySubscribers([this._stopSpeaking, () => this._notifySubscribers()]);
|
|
288
371
|
}
|
|
289
372
|
|
|
290
373
|
private _voiceSession: RealtimeVoiceAdapter.Session | undefined;
|
|
@@ -305,67 +388,111 @@ export abstract class BaseThreadRuntimeCore
|
|
|
305
388
|
const adapter = this.adapters?.voice;
|
|
306
389
|
if (!adapter) throw new Error("Voice adapter not configured");
|
|
307
390
|
|
|
308
|
-
|
|
391
|
+
try {
|
|
392
|
+
this.disconnectVoice();
|
|
393
|
+
} catch (error) {
|
|
394
|
+
console.error(
|
|
395
|
+
"[assistant-ui] Voice cleanup threw before reconnect",
|
|
396
|
+
error,
|
|
397
|
+
);
|
|
398
|
+
}
|
|
309
399
|
|
|
310
400
|
const session = adapter.connect({});
|
|
311
401
|
this._voiceSession = session;
|
|
312
402
|
const unsubs: Array<() => void> = [];
|
|
403
|
+
this._voiceUnsubs = unsubs;
|
|
313
404
|
|
|
314
|
-
|
|
405
|
+
// The cleanup-list identity preserves ownership after an ended status clears the session.
|
|
406
|
+
const finishDetachedSetup = () => {
|
|
407
|
+
if (this._voiceSession === session && this._voiceUnsubs === unsubs) {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
315
410
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
411
|
+
try {
|
|
412
|
+
notifySubscribers(unsubs.splice(0));
|
|
413
|
+
} catch (error) {
|
|
414
|
+
console.error(
|
|
415
|
+
"[assistant-ui] Detached voice setup cleanup threw",
|
|
416
|
+
error,
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
return true;
|
|
320
420
|
};
|
|
321
|
-
this._voiceVolume = 0;
|
|
322
|
-
this._notifySubscribers();
|
|
323
421
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (status.type === "ended") {
|
|
327
|
-
this._finishVoiceAssistantMessage();
|
|
328
|
-
this._voiceSession = undefined;
|
|
329
|
-
this.voice = undefined;
|
|
330
|
-
} else {
|
|
331
|
-
this.voice = {
|
|
332
|
-
status,
|
|
333
|
-
isMuted: session.isMuted,
|
|
334
|
-
mode: currentMode,
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
this._notifySubscribers();
|
|
338
|
-
}),
|
|
339
|
-
);
|
|
422
|
+
try {
|
|
423
|
+
let currentMode: RealtimeVoiceAdapter.Mode = "listening";
|
|
340
424
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
425
|
+
this.voice = {
|
|
426
|
+
status: session.status,
|
|
427
|
+
isMuted: session.isMuted,
|
|
428
|
+
mode: currentMode,
|
|
429
|
+
};
|
|
430
|
+
this._voiceVolume = 0;
|
|
431
|
+
this._notifySubscribers();
|
|
432
|
+
if (finishDetachedSetup()) return;
|
|
433
|
+
|
|
434
|
+
unsubs.push(
|
|
435
|
+
session.onStatusChange((status) => {
|
|
436
|
+
if (status.type === "ended") {
|
|
437
|
+
this._finishVoiceAssistantMessage();
|
|
438
|
+
this._voiceSession = undefined;
|
|
439
|
+
this.voice = undefined;
|
|
440
|
+
} else {
|
|
441
|
+
this.voice = {
|
|
442
|
+
status,
|
|
443
|
+
isMuted: session.isMuted,
|
|
444
|
+
mode: currentMode,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
346
447
|
this._notifySubscribers();
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
)
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
448
|
+
}),
|
|
449
|
+
);
|
|
450
|
+
if (finishDetachedSetup()) return;
|
|
451
|
+
|
|
452
|
+
unsubs.push(
|
|
453
|
+
session.onModeChange((mode) => {
|
|
454
|
+
currentMode = mode;
|
|
455
|
+
if (this.voice) {
|
|
456
|
+
this.voice = { ...this.voice, mode };
|
|
457
|
+
this._notifySubscribers();
|
|
458
|
+
}
|
|
459
|
+
}),
|
|
460
|
+
);
|
|
461
|
+
if (finishDetachedSetup()) return;
|
|
462
|
+
|
|
463
|
+
unsubs.push(
|
|
464
|
+
session.onVolumeChange((volume) => {
|
|
465
|
+
this._voiceVolume = volume;
|
|
466
|
+
notifyEventListeners(
|
|
467
|
+
this._voiceVolumeSubscribers,
|
|
468
|
+
undefined,
|
|
469
|
+
"Voice volume",
|
|
470
|
+
);
|
|
471
|
+
}),
|
|
472
|
+
);
|
|
473
|
+
if (finishDetachedSetup()) return;
|
|
367
474
|
|
|
368
|
-
|
|
475
|
+
unsubs.push(
|
|
476
|
+
session.onTranscript((transcript) => {
|
|
477
|
+
this._handleVoiceTranscript(transcript);
|
|
478
|
+
}),
|
|
479
|
+
);
|
|
480
|
+
finishDetachedSetup();
|
|
481
|
+
} catch (error) {
|
|
482
|
+
if (this._voiceSession === session && this._voiceUnsubs === unsubs) {
|
|
483
|
+
try {
|
|
484
|
+
this.disconnectVoice();
|
|
485
|
+
} catch (cleanupError) {
|
|
486
|
+
console.error(
|
|
487
|
+
"[assistant-ui] Voice rollback cleanup threw",
|
|
488
|
+
cleanupError,
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
} else {
|
|
492
|
+
finishDetachedSetup();
|
|
493
|
+
}
|
|
494
|
+
throw error;
|
|
495
|
+
}
|
|
369
496
|
}
|
|
370
497
|
|
|
371
498
|
private _currentAssistantMsg: ThreadAssistantMessage | null = null;
|
|
@@ -384,7 +511,7 @@ export abstract class BaseThreadRuntimeCore
|
|
|
384
511
|
id: generateId(),
|
|
385
512
|
role: "user",
|
|
386
513
|
content: [{ type: "text", text: transcript.text }],
|
|
387
|
-
metadata: { custom: {} },
|
|
514
|
+
metadata: { modality: "voice", custom: {} },
|
|
388
515
|
createdAt: new Date(),
|
|
389
516
|
status: { type: "complete", reason: "unknown" },
|
|
390
517
|
attachments: [],
|
|
@@ -407,6 +534,7 @@ export abstract class BaseThreadRuntimeCore
|
|
|
407
534
|
unstable_annotations: [],
|
|
408
535
|
unstable_data: [],
|
|
409
536
|
steps: [],
|
|
537
|
+
modality: "voice",
|
|
410
538
|
custom: {},
|
|
411
539
|
},
|
|
412
540
|
status,
|
|
@@ -448,22 +576,33 @@ export abstract class BaseThreadRuntimeCore
|
|
|
448
576
|
}
|
|
449
577
|
|
|
450
578
|
public disconnectVoice() {
|
|
451
|
-
this._finishVoiceAssistantMessage();
|
|
452
579
|
this._currentAssistantMsg = null;
|
|
453
|
-
|
|
580
|
+
// Drain the shared list in place so reentrant setup cannot release the same handles again.
|
|
581
|
+
const unsubs = this._voiceUnsubs.splice(0);
|
|
454
582
|
this._voiceUnsubs = [];
|
|
455
|
-
this._voiceSession
|
|
583
|
+
const session = this._voiceSession;
|
|
456
584
|
this._voiceSession = undefined;
|
|
457
585
|
this.voice = undefined;
|
|
458
586
|
this._voiceVolume = 0;
|
|
459
|
-
|
|
460
|
-
this.
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
);
|
|
587
|
+
const stopSpeaking =
|
|
588
|
+
this.speech && this._isVoiceMessage(this.speech.messageId)
|
|
589
|
+
? this._stopSpeaking
|
|
590
|
+
: undefined;
|
|
464
591
|
this._voiceMessages = [];
|
|
465
592
|
this._markVoiceMessagesDirty();
|
|
466
|
-
|
|
593
|
+
|
|
594
|
+
notifySubscribers([
|
|
595
|
+
...unsubs,
|
|
596
|
+
...(stopSpeaking ? [stopSpeaking] : []),
|
|
597
|
+
...(session ? [() => session.disconnect()] : []),
|
|
598
|
+
() =>
|
|
599
|
+
notifyEventListeners(
|
|
600
|
+
this._voiceVolumeSubscribers,
|
|
601
|
+
undefined,
|
|
602
|
+
"Voice volume",
|
|
603
|
+
),
|
|
604
|
+
() => this._notifySubscribers(),
|
|
605
|
+
]);
|
|
467
606
|
}
|
|
468
607
|
|
|
469
608
|
public muteVoice() {
|
|
@@ -98,6 +98,12 @@ export type SubmittedFeedback = {
|
|
|
98
98
|
};
|
|
99
99
|
|
|
100
100
|
export type ThreadRuntimeEventPayload = {
|
|
101
|
+
toolApprovalAnswered: {
|
|
102
|
+
messageId: string;
|
|
103
|
+
toolCallId: string;
|
|
104
|
+
toolName: string;
|
|
105
|
+
approved: boolean;
|
|
106
|
+
};
|
|
101
107
|
/**
|
|
102
108
|
* @deprecated State-derivable. Observe `state.isRunning` flipping to `true`
|
|
103
109
|
* via `subscribe` + `getState` instead. Note: this event fires at the
|
|
@@ -13,6 +13,11 @@ import type {
|
|
|
13
13
|
} from "./external-thread-queue-adapter";
|
|
14
14
|
|
|
15
15
|
export type MessageQueueDriver = {
|
|
16
|
+
/**
|
|
17
|
+
* A synchronous throw is treated as a run that never started and restores
|
|
18
|
+
* the message. A driver that already started work must call `notifyBusy`
|
|
19
|
+
* before throwing.
|
|
20
|
+
*/
|
|
16
21
|
run: (message: AppendMessage, options: { steer: boolean }) => void;
|
|
17
22
|
/** When omitted, steering degrades to "process next" instead of interrupting. */
|
|
18
23
|
cancel?: (() => void) | undefined;
|
|
@@ -38,6 +43,12 @@ export type MessageQueueController = {
|
|
|
38
43
|
|
|
39
44
|
type Lane = "queue" | "steer";
|
|
40
45
|
|
|
46
|
+
type DispatchItem = {
|
|
47
|
+
id: string;
|
|
48
|
+
item: QueueItemState;
|
|
49
|
+
message: AppendMessage;
|
|
50
|
+
};
|
|
51
|
+
|
|
41
52
|
const getQueueItemParts = (
|
|
42
53
|
message: AppendMessage,
|
|
43
54
|
): readonly (FileMessagePart | TextMessagePart)[] => {
|
|
@@ -83,6 +94,7 @@ export const createMessageQueue = (
|
|
|
83
94
|
// settles from cancelled runs that must drop `running` without advancing
|
|
84
95
|
let cancelSettles = 0;
|
|
85
96
|
let interrupting = false;
|
|
97
|
+
let busyEdges = 0;
|
|
86
98
|
|
|
87
99
|
const notify = () => {
|
|
88
100
|
notifyEventListeners(subscribers, undefined, "Message queue");
|
|
@@ -101,6 +113,19 @@ export const createMessageQueue = (
|
|
|
101
113
|
parts: getQueueItemParts(message),
|
|
102
114
|
});
|
|
103
115
|
|
|
116
|
+
const restore = (lane: Lane, dispatch: DispatchItem, index = 0) => {
|
|
117
|
+
paused = true;
|
|
118
|
+
messages.set(dispatch.id, dispatch.message);
|
|
119
|
+
setLanes({
|
|
120
|
+
...lanes,
|
|
121
|
+
[lane]: [
|
|
122
|
+
...lanes[lane].slice(0, index),
|
|
123
|
+
dispatch.item,
|
|
124
|
+
...lanes[lane].slice(index),
|
|
125
|
+
],
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
|
|
104
129
|
const laneOf = (queueItemId: string): Lane | undefined => {
|
|
105
130
|
if (lanes.steer.some((item) => item.id === queueItemId)) return "steer";
|
|
106
131
|
if (lanes.queue.some((item) => item.id === queueItemId)) return "queue";
|
|
@@ -116,27 +141,61 @@ export const createMessageQueue = (
|
|
|
116
141
|
messages.delete(head.id);
|
|
117
142
|
setLanes({ ...lanes, [lane]: lanes[lane].slice(1) });
|
|
118
143
|
if (!message) return;
|
|
144
|
+
const dispatch = { id: head.id, item: head, message };
|
|
119
145
|
running = true;
|
|
120
|
-
|
|
146
|
+
const busyEdgesBeforeRun = busyEdges;
|
|
147
|
+
try {
|
|
148
|
+
driver.run(dispatchTransform(message), { steer: false });
|
|
149
|
+
} catch (error) {
|
|
150
|
+
if (busyEdges === busyEdgesBeforeRun) {
|
|
151
|
+
running = false;
|
|
152
|
+
restore(lane, dispatch);
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
121
156
|
};
|
|
122
157
|
|
|
123
|
-
const interrupt = (
|
|
158
|
+
const interrupt = (
|
|
159
|
+
dispatch: DispatchItem,
|
|
160
|
+
restoreLane: Lane = "steer",
|
|
161
|
+
restoreIndex = 0,
|
|
162
|
+
) => {
|
|
124
163
|
paused = false;
|
|
125
164
|
// the interrupted run settles exactly once, whether or not it was
|
|
126
165
|
// already cancel-notified
|
|
127
166
|
suppressIdle += Math.max(cancelSettles, 1);
|
|
128
167
|
cancelSettles = 0;
|
|
168
|
+
const restoreInterrupted = (replacementStarted = false) => {
|
|
169
|
+
if (replacementStarted) return;
|
|
170
|
+
// The live count distinguishes an outstanding cancellation settle
|
|
171
|
+
// from one delivered synchronously by cancel().
|
|
172
|
+
const pendingSettles = suppressIdle;
|
|
173
|
+
suppressIdle = Math.max(pendingSettles - 1, 0);
|
|
174
|
+
cancelSettles = pendingSettles > 0 ? 1 : 0;
|
|
175
|
+
running = pendingSettles > 0;
|
|
176
|
+
restore(restoreLane, dispatch, restoreIndex);
|
|
177
|
+
};
|
|
129
178
|
// a driver whose cancel routes through the runtime notifies this queue
|
|
130
179
|
// back; the interrupt already accounted for that settle and is dispatching
|
|
131
180
|
// in its place
|
|
132
181
|
interrupting = true;
|
|
133
182
|
try {
|
|
134
183
|
driver.cancel!();
|
|
184
|
+
} catch (error) {
|
|
185
|
+
restoreInterrupted();
|
|
186
|
+
throw error;
|
|
135
187
|
} finally {
|
|
136
188
|
interrupting = false;
|
|
137
189
|
}
|
|
138
190
|
running = true;
|
|
139
|
-
|
|
191
|
+
const busyEdgesBeforeRun = busyEdges;
|
|
192
|
+
try {
|
|
193
|
+
driver.run(dispatchTransform(dispatch.message), { steer: true });
|
|
194
|
+
} catch (error) {
|
|
195
|
+
const replacementStarted = busyEdges !== busyEdgesBeforeRun;
|
|
196
|
+
restoreInterrupted(replacementStarted);
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
140
199
|
};
|
|
141
200
|
|
|
142
201
|
const push = (lane: Lane, message: AppendMessage) => {
|
|
@@ -153,7 +212,8 @@ export const createMessageQueue = (
|
|
|
153
212
|
|
|
154
213
|
const steer = (message: AppendMessage) => {
|
|
155
214
|
if (running && driver.cancel) {
|
|
156
|
-
|
|
215
|
+
const id = generateId();
|
|
216
|
+
interrupt({ id, item: toItem(id, message), message });
|
|
157
217
|
return;
|
|
158
218
|
}
|
|
159
219
|
push("steer", message);
|
|
@@ -164,7 +224,8 @@ export const createMessageQueue = (
|
|
|
164
224
|
if (!fromLane) throw new Error(`Unknown queue item "${queueItemId}".`);
|
|
165
225
|
const toLane = placement.lane ?? fromLane;
|
|
166
226
|
|
|
167
|
-
const
|
|
227
|
+
const fromIndex = lanes[fromLane].findIndex((i) => i.id === queueItemId);
|
|
228
|
+
const item = lanes[fromLane][fromIndex]!;
|
|
168
229
|
const dest = (toLane === fromLane ? lanes[fromLane] : lanes[toLane]).filter(
|
|
169
230
|
(i) => i.id !== queueItemId,
|
|
170
231
|
);
|
|
@@ -217,7 +278,7 @@ export const createMessageQueue = (
|
|
|
217
278
|
queue: lanes.queue.filter((i) => i.id !== queueItemId),
|
|
218
279
|
steer: lanes.steer,
|
|
219
280
|
});
|
|
220
|
-
interrupt(message);
|
|
281
|
+
interrupt({ id: queueItemId, item, message }, fromLane, fromIndex);
|
|
221
282
|
return;
|
|
222
283
|
}
|
|
223
284
|
|
|
@@ -280,6 +341,7 @@ export const createMessageQueue = (
|
|
|
280
341
|
suppressIdle += cancelSettles;
|
|
281
342
|
cancelSettles = 0;
|
|
282
343
|
running = true;
|
|
344
|
+
busyEdges++;
|
|
283
345
|
},
|
|
284
346
|
notifyIdle: () => {
|
|
285
347
|
if (suppressIdle > 0) {
|
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import type { MessageStatus, ThreadMessage } from "../../types/message";
|
|
3
|
+
import {
|
|
4
|
+
getAutoStatus,
|
|
5
|
+
getContentAutoStatus,
|
|
6
|
+
isBackgroundToolCall,
|
|
7
|
+
} from "./auto-status";
|
|
8
|
+
|
|
9
|
+
const nestedAssistantMessage = (status: MessageStatus): ThreadMessage => ({
|
|
10
|
+
id: "nested-assistant",
|
|
11
|
+
createdAt: new Date(0),
|
|
12
|
+
role: "assistant",
|
|
13
|
+
content: [],
|
|
14
|
+
status,
|
|
15
|
+
metadata: {
|
|
16
|
+
unstable_state: {},
|
|
17
|
+
unstable_annotations: [],
|
|
18
|
+
unstable_data: [],
|
|
19
|
+
steps: [],
|
|
20
|
+
custom: {},
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const pendingToolCall = (messages?: readonly ThreadMessage[]) => ({
|
|
25
|
+
type: "tool-call" as const,
|
|
26
|
+
toolName: "delegate",
|
|
27
|
+
args: {},
|
|
28
|
+
argsText: "",
|
|
29
|
+
messages,
|
|
30
|
+
});
|
|
3
31
|
|
|
4
32
|
describe("getAutoStatus", () => {
|
|
5
33
|
it("reports a cancelled message as incomplete", () => {
|
|
@@ -54,4 +82,87 @@ describe("getAutoStatus", () => {
|
|
|
54
82
|
).toMatchObject(expected);
|
|
55
83
|
},
|
|
56
84
|
);
|
|
85
|
+
|
|
86
|
+
it.each([true, false])(
|
|
87
|
+
"keeps a background tool call running when isLast is %s",
|
|
88
|
+
(isLast) => {
|
|
89
|
+
expect(
|
|
90
|
+
getContentAutoStatus(
|
|
91
|
+
[pendingToolCall([nestedAssistantMessage({ type: "running" })])],
|
|
92
|
+
isLast,
|
|
93
|
+
false,
|
|
94
|
+
),
|
|
95
|
+
).toMatchObject({ type: "running" });
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
it("reads the trailing nested message, not an earlier running one", () => {
|
|
100
|
+
expect(
|
|
101
|
+
isBackgroundToolCall(
|
|
102
|
+
pendingToolCall([
|
|
103
|
+
nestedAssistantMessage({ type: "running" }),
|
|
104
|
+
nestedAssistantMessage({ type: "complete", reason: "unknown" }),
|
|
105
|
+
]),
|
|
106
|
+
),
|
|
107
|
+
).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("lets cancellation clear a background tool call", () => {
|
|
111
|
+
expect(
|
|
112
|
+
getAutoStatus(true, false, false, true, undefined, true, true),
|
|
113
|
+
).toMatchObject({ type: "requires-action", reason: "tool-calls" });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("keeps a settled nested tool call pending", () => {
|
|
117
|
+
expect(
|
|
118
|
+
getContentAutoStatus(
|
|
119
|
+
[
|
|
120
|
+
pendingToolCall([
|
|
121
|
+
nestedAssistantMessage({ type: "complete", reason: "unknown" }),
|
|
122
|
+
]),
|
|
123
|
+
],
|
|
124
|
+
true,
|
|
125
|
+
false,
|
|
126
|
+
),
|
|
127
|
+
).toMatchObject({ type: "requires-action", reason: "tool-calls" });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("keeps an interrupted tool call ahead of a background tool call", () => {
|
|
131
|
+
expect(
|
|
132
|
+
getContentAutoStatus(
|
|
133
|
+
[
|
|
134
|
+
{
|
|
135
|
+
...pendingToolCall(),
|
|
136
|
+
interrupt: { type: "human", payload: {} },
|
|
137
|
+
},
|
|
138
|
+
pendingToolCall([nestedAssistantMessage({ type: "running" })]),
|
|
139
|
+
],
|
|
140
|
+
false,
|
|
141
|
+
false,
|
|
142
|
+
),
|
|
143
|
+
).toMatchObject({ type: "requires-action", reason: "interrupt" });
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it.each([
|
|
147
|
+
["a result", { ...pendingToolCall(), result: {} }],
|
|
148
|
+
["no nested messages", pendingToolCall()],
|
|
149
|
+
[
|
|
150
|
+
"only a user nested message",
|
|
151
|
+
pendingToolCall([
|
|
152
|
+
{
|
|
153
|
+
id: "nested-user",
|
|
154
|
+
createdAt: new Date(0),
|
|
155
|
+
role: "user",
|
|
156
|
+
content: [],
|
|
157
|
+
attachments: [],
|
|
158
|
+
metadata: { custom: {} },
|
|
159
|
+
},
|
|
160
|
+
]),
|
|
161
|
+
],
|
|
162
|
+
])(
|
|
163
|
+
"does not treat a tool call with %s as a background call",
|
|
164
|
+
(_label, call) => {
|
|
165
|
+
expect(isBackgroundToolCall(call)).toBe(false);
|
|
166
|
+
},
|
|
167
|
+
);
|
|
57
168
|
});
|