@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
|
@@ -427,6 +427,27 @@ describe("LocalThreadRuntimeCore state", () => {
|
|
|
427
427
|
});
|
|
428
428
|
|
|
429
429
|
describe("LocalThreadRuntimeCore tool approvals", () => {
|
|
430
|
+
it("emits a decision with its message and tool call IDs", async () => {
|
|
431
|
+
const { thread } = createApprovalThread(
|
|
432
|
+
toolCallResult("send_email", { id: "a1" }),
|
|
433
|
+
);
|
|
434
|
+
const answered = vi.fn();
|
|
435
|
+
thread.unstable_on("toolApprovalAnswered", answered);
|
|
436
|
+
|
|
437
|
+
await thread.append(userMessage("send an email"));
|
|
438
|
+
await flush();
|
|
439
|
+
const messageId = thread.messages.at(-1)?.id;
|
|
440
|
+
|
|
441
|
+
await thread.respondToToolApproval({ approvalId: "a1", approved: false });
|
|
442
|
+
|
|
443
|
+
expect(answered).toHaveBeenCalledWith({
|
|
444
|
+
messageId,
|
|
445
|
+
toolCallId: "call-send_email",
|
|
446
|
+
toolName: "send_email",
|
|
447
|
+
approved: false,
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
|
|
430
451
|
it("pauses the run while an approval is pending, even for unlisted tools", async () => {
|
|
431
452
|
const { thread, runs } = createApprovalThread(
|
|
432
453
|
toolCallResult("deploy", { id: "a1" }),
|
|
@@ -342,6 +342,8 @@ export class LocalThreadRuntimeCore
|
|
|
342
342
|
}
|
|
343
343
|
|
|
344
344
|
public async append(message: AppendMessage): Promise<void> {
|
|
345
|
+
if (this._isVoiceMessage(message.sourceId))
|
|
346
|
+
throw new Error("Voice transcript messages cannot be edited");
|
|
345
347
|
const isTail = message.parentId === (this.messages.at(-1)?.id ?? null);
|
|
346
348
|
const willRun = message.startRun ?? message.role === "user";
|
|
347
349
|
if (this._queue && willRun && isTail) {
|
|
@@ -488,10 +490,12 @@ export class LocalThreadRuntimeCore
|
|
|
488
490
|
}
|
|
489
491
|
|
|
490
492
|
public async startRun(
|
|
491
|
-
{ parentId, runConfig }: StartRunConfig,
|
|
493
|
+
{ parentId, sourceId, runConfig }: StartRunConfig,
|
|
492
494
|
runCallback?: ChatModelAdapter["run"],
|
|
493
495
|
): Promise<void> {
|
|
494
496
|
this.ensureInitialized();
|
|
497
|
+
if (this._isVoiceMessage(sourceId))
|
|
498
|
+
throw new Error("Voice transcript messages cannot be reloaded");
|
|
495
499
|
|
|
496
500
|
// add assistant message
|
|
497
501
|
const id = generateId();
|
|
@@ -961,6 +965,12 @@ export class LocalThreadRuntimeCore
|
|
|
961
965
|
const { parentId } = this.repository.getMessage(message.id);
|
|
962
966
|
this.repository.addOrUpdateMessage(parentId, message);
|
|
963
967
|
this._notifySubscribers();
|
|
968
|
+
this._notifyToolApprovalAnswered(
|
|
969
|
+
message.id,
|
|
970
|
+
target.toolCallId,
|
|
971
|
+
target.toolName,
|
|
972
|
+
approved,
|
|
973
|
+
);
|
|
964
974
|
|
|
965
975
|
if (
|
|
966
976
|
this.repository.headId === message.id &&
|
|
@@ -72,9 +72,7 @@ If the tool also had a frontend `execute`, the executor is short-circuited
|
|
|
72
72
|
via the entry's per-execution skip marker. Single fire.
|
|
73
73
|
|
|
74
74
|
### A.6. Previously-resolved tool's `result` is replaced
|
|
75
|
-
Silently ignored
|
|
76
|
-
re-`setResponse` path and the downstream result-chunk handler. The host
|
|
77
|
-
sees only the first result.
|
|
75
|
+
Silently ignored. A restored entry with a result remains historical even when a live snapshot reserializes its `argsText` or replaces its result. For active entries, `entry.hasResult` short-circuits both the re-`setResponse` path and the downstream result-chunk handler. The host sees only the first result.
|
|
78
76
|
|
|
79
77
|
### A.7. Previously-resolved tool loses its `result` (back to undefined)
|
|
80
78
|
Silently ignored. The entry stays in the resolved phase internally.
|
|
@@ -213,9 +211,7 @@ Silently kept as restored. Recursion into `content.messages` still
|
|
|
213
211
|
happens so any nested live tool calls are processed.
|
|
214
212
|
|
|
215
213
|
### C.3. Restored entry observed in a live snapshot, signature changed
|
|
216
|
-
|
|
217
|
-
`_startActiveEntry`. This is PR #4057's promotion path. `streamCall`
|
|
218
|
-
fires once — its first and only fire for this `toolCallId`.
|
|
214
|
+
A restored entry with a result remains historical regardless of later `argsText` or result changes. An unresolved restored entry is promoted only when a result lands or its `argsText` changes. Complete `argsText` values that parse to equivalent JSON are unchanged. Promotion deletes the restored entry and starts a new active entry via `_startActiveEntry`. `streamCall` fires once, its first and only fire for this `toolCallId`.
|
|
219
215
|
|
|
220
216
|
### C.4. `isLoading` transitions `true → false` while messages are stable
|
|
221
217
|
The next `setState` call sees `isLoading === false` and processes
|
|
@@ -1465,6 +1465,139 @@ describe("ToolInvocationTracker", () => {
|
|
|
1465
1465
|
expect(onResult).not.toHaveBeenCalled();
|
|
1466
1466
|
});
|
|
1467
1467
|
|
|
1468
|
+
it("keeps a resolved restored tool call historical when its snapshot is reserialized", async () => {
|
|
1469
|
+
const execute = vi.fn(async () => ({ forecast: "ok" }));
|
|
1470
|
+
const streamCall = vi.fn();
|
|
1471
|
+
const getTools = () => ({
|
|
1472
|
+
weatherSearch: {
|
|
1473
|
+
parameters: { type: "object", properties: {} },
|
|
1474
|
+
execute,
|
|
1475
|
+
streamCall,
|
|
1476
|
+
} satisfies Tool,
|
|
1477
|
+
});
|
|
1478
|
+
const onResult = vi.fn();
|
|
1479
|
+
const tracker = new ToolInvocationTracker(getTools, {
|
|
1480
|
+
onResult,
|
|
1481
|
+
onStatusesChange: () => {},
|
|
1482
|
+
});
|
|
1483
|
+
|
|
1484
|
+
tracker.setState(
|
|
1485
|
+
createState([
|
|
1486
|
+
createAssistantMessage(
|
|
1487
|
+
'{"query":"London","page":1}',
|
|
1488
|
+
{ query: "London", page: 1 },
|
|
1489
|
+
{ result: { source: "history", revision: 1 } },
|
|
1490
|
+
),
|
|
1491
|
+
]),
|
|
1492
|
+
);
|
|
1493
|
+
|
|
1494
|
+
tracker.setState(
|
|
1495
|
+
createState([
|
|
1496
|
+
createAssistantMessage(
|
|
1497
|
+
'{ "page": 1, "query": "London" }',
|
|
1498
|
+
{ query: "London", page: 1 },
|
|
1499
|
+
{ result: { source: "history", revision: 2 } },
|
|
1500
|
+
),
|
|
1501
|
+
]),
|
|
1502
|
+
);
|
|
1503
|
+
|
|
1504
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
1505
|
+
|
|
1506
|
+
expect(streamCall).not.toHaveBeenCalled();
|
|
1507
|
+
expect(execute).not.toHaveBeenCalled();
|
|
1508
|
+
expect(onResult).not.toHaveBeenCalled();
|
|
1509
|
+
});
|
|
1510
|
+
|
|
1511
|
+
it("promotes unresolved restored tool calls only for real args changes or a landed result", async () => {
|
|
1512
|
+
const equivalentStreamCall = vi.fn();
|
|
1513
|
+
const growingStreamCall = vi.fn();
|
|
1514
|
+
const resolvedStreamCall = vi.fn();
|
|
1515
|
+
const getTools = () => ({
|
|
1516
|
+
equivalentTool: {
|
|
1517
|
+
parameters: { type: "object", properties: {} },
|
|
1518
|
+
streamCall: equivalentStreamCall,
|
|
1519
|
+
} satisfies Tool,
|
|
1520
|
+
growingTool: {
|
|
1521
|
+
parameters: { type: "object", properties: {} },
|
|
1522
|
+
streamCall: growingStreamCall,
|
|
1523
|
+
} satisfies Tool,
|
|
1524
|
+
resolvedTool: {
|
|
1525
|
+
parameters: { type: "object", properties: {} },
|
|
1526
|
+
streamCall: resolvedStreamCall,
|
|
1527
|
+
} satisfies Tool,
|
|
1528
|
+
});
|
|
1529
|
+
const tracker = new ToolInvocationTracker(getTools, {
|
|
1530
|
+
onResult: vi.fn(),
|
|
1531
|
+
onStatusesChange: () => {},
|
|
1532
|
+
});
|
|
1533
|
+
|
|
1534
|
+
tracker.setState(
|
|
1535
|
+
createState([
|
|
1536
|
+
createAssistantMessage(
|
|
1537
|
+
'{"a":1,"b":2}',
|
|
1538
|
+
{ a: 1, b: 2 },
|
|
1539
|
+
{
|
|
1540
|
+
toolCallId: "equivalent",
|
|
1541
|
+
toolName: "equivalentTool",
|
|
1542
|
+
},
|
|
1543
|
+
),
|
|
1544
|
+
createAssistantMessage(
|
|
1545
|
+
'{"query":"Lon',
|
|
1546
|
+
{ query: "Lon" },
|
|
1547
|
+
{
|
|
1548
|
+
toolCallId: "growing",
|
|
1549
|
+
toolName: "growingTool",
|
|
1550
|
+
},
|
|
1551
|
+
),
|
|
1552
|
+
createAssistantMessage(
|
|
1553
|
+
'{"city":"London"}',
|
|
1554
|
+
{ city: "London" },
|
|
1555
|
+
{
|
|
1556
|
+
toolCallId: "resolved",
|
|
1557
|
+
toolName: "resolvedTool",
|
|
1558
|
+
},
|
|
1559
|
+
),
|
|
1560
|
+
]),
|
|
1561
|
+
);
|
|
1562
|
+
|
|
1563
|
+
tracker.setState(
|
|
1564
|
+
createState([
|
|
1565
|
+
createAssistantMessage(
|
|
1566
|
+
'{ "b": 2, "a": 1 }',
|
|
1567
|
+
{ a: 1, b: 2 },
|
|
1568
|
+
{
|
|
1569
|
+
toolCallId: "equivalent",
|
|
1570
|
+
toolName: "equivalentTool",
|
|
1571
|
+
},
|
|
1572
|
+
),
|
|
1573
|
+
createAssistantMessage(
|
|
1574
|
+
'{"query":"London"}',
|
|
1575
|
+
{ query: "London" },
|
|
1576
|
+
{
|
|
1577
|
+
toolCallId: "growing",
|
|
1578
|
+
toolName: "growingTool",
|
|
1579
|
+
},
|
|
1580
|
+
),
|
|
1581
|
+
createAssistantMessage(
|
|
1582
|
+
'{"city":"London"}',
|
|
1583
|
+
{ city: "London" },
|
|
1584
|
+
{
|
|
1585
|
+
toolCallId: "resolved",
|
|
1586
|
+
toolName: "resolvedTool",
|
|
1587
|
+
result: { source: "history" },
|
|
1588
|
+
},
|
|
1589
|
+
),
|
|
1590
|
+
]),
|
|
1591
|
+
);
|
|
1592
|
+
|
|
1593
|
+
await waitFor(() => {
|
|
1594
|
+
expect(growingStreamCall).toHaveBeenCalledTimes(1);
|
|
1595
|
+
expect(resolvedStreamCall).toHaveBeenCalledTimes(1);
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
expect(equivalentStreamCall).not.toHaveBeenCalled();
|
|
1599
|
+
});
|
|
1600
|
+
|
|
1468
1601
|
it("promotes an in-progress tool call from the initial snapshot when it changes", async () => {
|
|
1469
1602
|
const execute = vi.fn(async () => ({ forecast: "ok" }));
|
|
1470
1603
|
const streamCall = vi.fn();
|
|
@@ -825,12 +825,16 @@ export class ToolInvocationTracker {
|
|
|
825
825
|
this._discardedToolCallIds.delete(content.toolCallId);
|
|
826
826
|
|
|
827
827
|
if (entry && !entry.controller) {
|
|
828
|
-
//
|
|
829
|
-
|
|
830
|
-
const
|
|
831
|
-
content.argsText !== entry.argsText
|
|
832
|
-
(
|
|
833
|
-
|
|
828
|
+
// A restored entry with a result remains historical.
|
|
829
|
+
if (entry.hasResult) continue;
|
|
830
|
+
const argsChanged =
|
|
831
|
+
content.argsText !== entry.argsText &&
|
|
832
|
+
!(
|
|
833
|
+
isArgsTextComplete(entry.argsText) &&
|
|
834
|
+
isArgsTextComplete(content.argsText) &&
|
|
835
|
+
isEquivalentCompleteArgsText(entry.argsText, content.argsText)
|
|
836
|
+
);
|
|
837
|
+
if (!argsChanged && content.result === undefined) continue;
|
|
834
838
|
this._entries.delete(content.toolCallId);
|
|
835
839
|
entry = undefined;
|
|
836
840
|
}
|
|
@@ -55,6 +55,7 @@ import { toMessagePartStatus } from "../../utils/normalizePartStatus";
|
|
|
55
55
|
import { generateId } from "../../utils/id";
|
|
56
56
|
import { ModelContext } from "./model-context-client";
|
|
57
57
|
import { ThreadSuggestions } from "./suggestions";
|
|
58
|
+
import { createTaskDeriver, getTaskKey, TaskClient } from "./thread-tasks";
|
|
58
59
|
import { Tools } from "../../react/client/Tools";
|
|
59
60
|
import { DataRenderers } from "../../react/client/DataRenderers";
|
|
60
61
|
import { SingleThreadList } from "./single-thread-list";
|
|
@@ -569,7 +570,7 @@ const useComposerClientResource = ({
|
|
|
569
570
|
await Promise.all(
|
|
570
571
|
removed
|
|
571
572
|
.filter((a) => a.status.type !== "complete")
|
|
572
|
-
.map((a) => attachmentAdapter.remove(a)),
|
|
573
|
+
.map(async (a) => attachmentAdapter.remove(a)),
|
|
573
574
|
);
|
|
574
575
|
};
|
|
575
576
|
|
|
@@ -1025,6 +1026,14 @@ const useExternalThread = ({
|
|
|
1025
1026
|
}),
|
|
1026
1027
|
);
|
|
1027
1028
|
|
|
1029
|
+
const taskDeriver = useMemo(() => createTaskDeriver(), []);
|
|
1030
|
+
const tasks = useMemo(() => taskDeriver(messages), [taskDeriver, messages]);
|
|
1031
|
+
const taskClients = useClientLookup(
|
|
1032
|
+
tasks.map((task) =>
|
|
1033
|
+
withKey(getTaskKey(task), TaskClient({ task }), [task]),
|
|
1034
|
+
),
|
|
1035
|
+
);
|
|
1036
|
+
|
|
1028
1037
|
const handleCancelRun = () => {
|
|
1029
1038
|
// Nothing is aborted without a handler, so pausing the queue would hold
|
|
1030
1039
|
// the pending items against a run that keeps going.
|
|
@@ -1109,6 +1118,7 @@ const useExternalThread = ({
|
|
|
1109
1118
|
queue: hasQueue,
|
|
1110
1119
|
},
|
|
1111
1120
|
messages: messageStates,
|
|
1121
|
+
tasks,
|
|
1112
1122
|
state: threadState ?? {},
|
|
1113
1123
|
suggestions: EMPTY_SUGGESTIONS,
|
|
1114
1124
|
extras,
|
|
@@ -1134,12 +1144,20 @@ const useExternalThread = ({
|
|
|
1134
1144
|
speech,
|
|
1135
1145
|
messageClients.state,
|
|
1136
1146
|
composerClient.state,
|
|
1147
|
+
tasks,
|
|
1137
1148
|
]);
|
|
1138
1149
|
|
|
1139
1150
|
return {
|
|
1140
1151
|
getState: () => state,
|
|
1141
1152
|
composer: () => composerClient.methods,
|
|
1142
1153
|
suggestions: () => suggestionsClient.methods,
|
|
1154
|
+
task: (selector) => {
|
|
1155
|
+
if ("id" in selector) {
|
|
1156
|
+
const task = tasks.find((candidate) => candidate.id === selector.id);
|
|
1157
|
+
return taskClients.get({ key: task ? getTaskKey(task) : selector.id });
|
|
1158
|
+
}
|
|
1159
|
+
return taskClients.get(selector);
|
|
1160
|
+
},
|
|
1143
1161
|
append: (message) => {
|
|
1144
1162
|
const appendMessage: AppendMessage =
|
|
1145
1163
|
typeof message === "string"
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { ThreadMessage, ToolCallMessagePart } from "../../types/message";
|
|
3
|
+
import { createTaskDeriver, getTaskKey } from "./thread-tasks";
|
|
4
|
+
|
|
5
|
+
const assistantMessage = (
|
|
6
|
+
id: string,
|
|
7
|
+
status:
|
|
8
|
+
| { type: "running" }
|
|
9
|
+
| { type: "complete"; reason: "stop" }
|
|
10
|
+
| { type: "incomplete"; reason: "error"; error: unknown },
|
|
11
|
+
content:
|
|
12
|
+
| readonly ToolCallMessagePart[]
|
|
13
|
+
| readonly { type: "text"; text: string }[],
|
|
14
|
+
) =>
|
|
15
|
+
({
|
|
16
|
+
id,
|
|
17
|
+
role: "assistant",
|
|
18
|
+
createdAt: new Date(0),
|
|
19
|
+
content,
|
|
20
|
+
status,
|
|
21
|
+
metadata: {
|
|
22
|
+
unstable_state: {},
|
|
23
|
+
unstable_annotations: [],
|
|
24
|
+
unstable_data: [],
|
|
25
|
+
steps: [],
|
|
26
|
+
custom: {},
|
|
27
|
+
},
|
|
28
|
+
}) as unknown as ThreadMessage;
|
|
29
|
+
|
|
30
|
+
const toolCall = (
|
|
31
|
+
id: string,
|
|
32
|
+
messages?: readonly ThreadMessage[],
|
|
33
|
+
result?: unknown,
|
|
34
|
+
) =>
|
|
35
|
+
({
|
|
36
|
+
type: "tool-call",
|
|
37
|
+
toolCallId: id,
|
|
38
|
+
toolName: "delegate",
|
|
39
|
+
args: { id },
|
|
40
|
+
argsText: JSON.stringify({ id }),
|
|
41
|
+
...(messages !== undefined ? { messages } : undefined),
|
|
42
|
+
...(result !== undefined ? { result } : undefined),
|
|
43
|
+
}) as ToolCallMessagePart;
|
|
44
|
+
|
|
45
|
+
describe("createTaskDeriver", () => {
|
|
46
|
+
it("derives top-level and nested tasks in document order", () => {
|
|
47
|
+
const nestedTask = toolCall("nested-1", []);
|
|
48
|
+
const nestedMessage = assistantMessage(
|
|
49
|
+
"nested-message",
|
|
50
|
+
{ type: "running" },
|
|
51
|
+
[nestedTask],
|
|
52
|
+
);
|
|
53
|
+
const topLevelTask = toolCall("top-level-1", [nestedMessage]);
|
|
54
|
+
const tasks = createTaskDeriver()([
|
|
55
|
+
assistantMessage("outer-message", { type: "running" }, [topLevelTask]),
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
expect(tasks.map((task) => task.id)).toEqual(["top-level-1", "nested-1"]);
|
|
59
|
+
expect(tasks[0]).toMatchObject({
|
|
60
|
+
depth: 0,
|
|
61
|
+
parentTaskId: null,
|
|
62
|
+
messageId: "outer-message",
|
|
63
|
+
});
|
|
64
|
+
expect(tasks[1]).toMatchObject({
|
|
65
|
+
depth: 1,
|
|
66
|
+
parentTaskId: "top-level-1",
|
|
67
|
+
messageId: "nested-message",
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("uses normalized tool call statuses", () => {
|
|
72
|
+
const tasks = createTaskDeriver()([
|
|
73
|
+
assistantMessage("outer-message", { type: "running" }, [
|
|
74
|
+
toolCall("pending", []),
|
|
75
|
+
toolCall("complete", [], "done"),
|
|
76
|
+
]),
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
expect(tasks.map((task) => task.status)).toEqual([
|
|
80
|
+
{ type: "running" },
|
|
81
|
+
{ type: "complete" },
|
|
82
|
+
]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("excludes tool calls without nested messages", () => {
|
|
86
|
+
const tasks = createTaskDeriver()([
|
|
87
|
+
assistantMessage("outer-message", { type: "running" }, [
|
|
88
|
+
toolCall("not-a-task"),
|
|
89
|
+
toolCall("task", []),
|
|
90
|
+
]),
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
expect(tasks.map((task) => task.id)).toEqual(["task"]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("preserves the array identity when an unrelated sibling changes", () => {
|
|
97
|
+
const task = toolCall("delegate-1", []);
|
|
98
|
+
const derive = createTaskDeriver();
|
|
99
|
+
const initial = derive([
|
|
100
|
+
assistantMessage("outer-message", { type: "running" }, [
|
|
101
|
+
{ type: "text", text: "one" },
|
|
102
|
+
task,
|
|
103
|
+
] as unknown as readonly ToolCallMessagePart[]),
|
|
104
|
+
]);
|
|
105
|
+
const updated = derive([
|
|
106
|
+
assistantMessage("outer-message", { type: "running" }, [
|
|
107
|
+
{ type: "text", text: "two" },
|
|
108
|
+
task,
|
|
109
|
+
] as unknown as readonly ToolCallMessagePart[]),
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
expect(updated).toBe(initial);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("creates a fresh entry and array when a nested status changes", () => {
|
|
116
|
+
const nestedTask = toolCall("nested-1", []);
|
|
117
|
+
const derive = createTaskDeriver();
|
|
118
|
+
const initial = derive([
|
|
119
|
+
assistantMessage("outer-message", { type: "running" }, [
|
|
120
|
+
toolCall("top-level-1", [
|
|
121
|
+
assistantMessage("nested-message", { type: "running" }, [nestedTask]),
|
|
122
|
+
]),
|
|
123
|
+
]),
|
|
124
|
+
]);
|
|
125
|
+
const updated = derive([
|
|
126
|
+
assistantMessage("outer-message", { type: "running" }, [
|
|
127
|
+
toolCall("top-level-1", [
|
|
128
|
+
assistantMessage(
|
|
129
|
+
"nested-message",
|
|
130
|
+
{ type: "complete", reason: "stop" },
|
|
131
|
+
[nestedTask],
|
|
132
|
+
),
|
|
133
|
+
]),
|
|
134
|
+
]),
|
|
135
|
+
]);
|
|
136
|
+
|
|
137
|
+
expect(updated).not.toBe(initial);
|
|
138
|
+
expect(updated[1]).not.toBe(initial[1]);
|
|
139
|
+
expect(updated[1]?.status).toMatchObject({ type: "complete" });
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("creates a fresh entry when an incomplete status changes its error", () => {
|
|
143
|
+
const task = toolCall("failing", []);
|
|
144
|
+
const derive = createTaskDeriver();
|
|
145
|
+
const initial = derive([
|
|
146
|
+
assistantMessage(
|
|
147
|
+
"outer-message",
|
|
148
|
+
{ type: "incomplete", reason: "error", error: "first" },
|
|
149
|
+
[task],
|
|
150
|
+
),
|
|
151
|
+
]);
|
|
152
|
+
const updated = derive([
|
|
153
|
+
assistantMessage(
|
|
154
|
+
"outer-message",
|
|
155
|
+
{ type: "incomplete", reason: "error", error: "second" },
|
|
156
|
+
[task],
|
|
157
|
+
),
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
expect(updated).not.toBe(initial);
|
|
161
|
+
expect(updated[0]).not.toBe(initial[0]);
|
|
162
|
+
expect(updated[0]?.status).toMatchObject({
|
|
163
|
+
type: "incomplete",
|
|
164
|
+
reason: "error",
|
|
165
|
+
error: "second",
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("rebuilds an entry when an unchanged part moves to another message", () => {
|
|
170
|
+
const part = toolCall("moving", []);
|
|
171
|
+
const derive = createTaskDeriver();
|
|
172
|
+
const initial = derive([
|
|
173
|
+
assistantMessage("first-message", { type: "running" }, [part]),
|
|
174
|
+
]);
|
|
175
|
+
const moved = derive([
|
|
176
|
+
assistantMessage("second-message", { type: "running" }, [part]),
|
|
177
|
+
]);
|
|
178
|
+
|
|
179
|
+
expect(moved[0]).not.toBe(initial[0]);
|
|
180
|
+
expect(moved[0]?.messageId).toBe("second-message");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("stops descending past the depth cap", () => {
|
|
184
|
+
let content: readonly ToolCallMessagePart[] = [toolCall("leaf", [])];
|
|
185
|
+
for (let level = 40; level > 0; level -= 1) {
|
|
186
|
+
content = [
|
|
187
|
+
toolCall(`level-${level}`, [
|
|
188
|
+
assistantMessage(`message-${level}`, { type: "running" }, content),
|
|
189
|
+
]),
|
|
190
|
+
];
|
|
191
|
+
}
|
|
192
|
+
const tasks = createTaskDeriver()([
|
|
193
|
+
assistantMessage("root", { type: "running" }, content),
|
|
194
|
+
]);
|
|
195
|
+
|
|
196
|
+
expect(Math.max(...tasks.map((task) => task.depth))).toBe(32);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("keeps the array identity when nested conversations repeat a toolCallId", () => {
|
|
200
|
+
const nested = (messageId: string) =>
|
|
201
|
+
assistantMessage(messageId, { type: "complete", reason: "stop" }, [
|
|
202
|
+
toolCall("call_1", [], "ok"),
|
|
203
|
+
]);
|
|
204
|
+
const messages = [
|
|
205
|
+
assistantMessage("outer-message", { type: "complete", reason: "stop" }, [
|
|
206
|
+
toolCall("delegate-a", [nested("nested-a")], "ok"),
|
|
207
|
+
toolCall("delegate-b", [nested("nested-b")], "ok"),
|
|
208
|
+
]),
|
|
209
|
+
];
|
|
210
|
+
const derive = createTaskDeriver();
|
|
211
|
+
const first = derive(messages);
|
|
212
|
+
const second = derive(messages);
|
|
213
|
+
|
|
214
|
+
expect(first.map((task) => task.id)).toEqual([
|
|
215
|
+
"delegate-a",
|
|
216
|
+
"call_1",
|
|
217
|
+
"delegate-b",
|
|
218
|
+
"call_1",
|
|
219
|
+
]);
|
|
220
|
+
expect(second).toBe(first);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("keys tasks by document order when nested payloads repeat every id", () => {
|
|
224
|
+
const replay = () =>
|
|
225
|
+
assistantMessage("nested", { type: "complete", reason: "stop" }, [
|
|
226
|
+
toolCall("call_1", [], "ok"),
|
|
227
|
+
]);
|
|
228
|
+
const messages = [
|
|
229
|
+
assistantMessage("outer-message", { type: "complete", reason: "stop" }, [
|
|
230
|
+
toolCall("delegate-a", [replay()], "ok"),
|
|
231
|
+
toolCall("delegate-b", [replay()], "ok"),
|
|
232
|
+
]),
|
|
233
|
+
];
|
|
234
|
+
const derive = createTaskDeriver();
|
|
235
|
+
const first = derive(messages);
|
|
236
|
+
|
|
237
|
+
expect(new Set(first.map(getTaskKey)).size).toBe(4);
|
|
238
|
+
expect(derive(messages)).toBe(first);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("reads a nested message without a status as complete", () => {
|
|
242
|
+
const unnormalized = {
|
|
243
|
+
...assistantMessage("nested", { type: "running" }, [
|
|
244
|
+
toolCall("call_1", []),
|
|
245
|
+
]),
|
|
246
|
+
status: undefined,
|
|
247
|
+
} as unknown as ThreadMessage;
|
|
248
|
+
const [, nestedTask] = createTaskDeriver()([
|
|
249
|
+
assistantMessage("outer-message", { type: "running" }, [
|
|
250
|
+
toolCall("delegate", [unnormalized], "ok"),
|
|
251
|
+
]),
|
|
252
|
+
]);
|
|
253
|
+
|
|
254
|
+
expect(nestedTask?.status).toEqual({ type: "complete" });
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("preserves unchanged entries when a task is appended", () => {
|
|
258
|
+
const first = toolCall("first", []);
|
|
259
|
+
const derive = createTaskDeriver();
|
|
260
|
+
const initial = derive([
|
|
261
|
+
assistantMessage("outer-message", { type: "running" }, [first]),
|
|
262
|
+
]);
|
|
263
|
+
const updated = derive([
|
|
264
|
+
assistantMessage("outer-message", { type: "running" }, [
|
|
265
|
+
first,
|
|
266
|
+
toolCall("second", []),
|
|
267
|
+
]),
|
|
268
|
+
]);
|
|
269
|
+
|
|
270
|
+
expect(updated).not.toBe(initial);
|
|
271
|
+
expect(updated[0]).toBe(initial[0]);
|
|
272
|
+
expect(updated.map((task) => task.id)).toEqual(["first", "second"]);
|
|
273
|
+
});
|
|
274
|
+
});
|