@assistant-ui/core 0.3.15 → 0.3.16
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/internal.d.ts +7 -2
- package/dist/internal.js +8 -3
- package/dist/model-context/frame/host.d.ts.map +1 -1
- package/dist/model-context/frame/host.js +3 -2
- package/dist/model-context/frame/host.js.map +1 -1
- package/dist/model-context/frame/provider.d.ts +2 -0
- package/dist/model-context/frame/provider.d.ts.map +1 -1
- package/dist/model-context/frame/provider.js +88 -25
- package/dist/model-context/frame/provider.js.map +1 -1
- package/dist/model-context/interactable-composer-metadata.d.ts +1 -1
- package/dist/model-context/interactable-composer-metadata.js +1 -1
- package/dist/model-context/interactable-composer-metadata.js.map +1 -1
- package/dist/model-context/registry.d.ts +2 -2
- package/dist/model-context/registry.d.ts.map +1 -1
- package/dist/model-context/registry.js +9 -28
- package/dist/model-context/registry.js.map +1 -1
- package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
- package/dist/react/client/InMemoryThreadList.js +24 -20
- package/dist/react/client/InMemoryThreadList.js.map +1 -1
- package/dist/react/client/Interactables.d.ts.map +1 -1
- package/dist/react/client/Interactables.js +59 -177
- 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 +23 -56
- package/dist/react/client/RemoteThreadList.js.map +1 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -1
- package/dist/react/interactables-legacy/Interactables.js +56 -174
- package/dist/react/interactables-legacy/Interactables.js.map +1 -1
- package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts +24 -0
- package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -0
- package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +138 -0
- package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -0
- package/dist/react/primitives/composer/ComposerIf.js.map +1 -1
- package/dist/react/primitives/composer/ComposerQueue.d.ts +2 -1
- package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageParts.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +23 -55
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/assistant-transport/runManager.d.ts.map +1 -1
- package/dist/react/runtimes/assistant-transport/runManager.js +2 -12
- package/dist/react/runtimes/assistant-transport/runManager.js.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +3 -10
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +20 -67
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts +12 -0
- package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts.map +1 -0
- package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js +10 -0
- package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js.map +1 -0
- package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js.map +1 -1
- package/dist/react/runtimes/createMessageConverter.d.ts +2 -1
- package/dist/react/runtimes/createMessageConverter.d.ts.map +1 -1
- package/dist/react/runtimes/external-message-converter.d.ts +6 -25
- package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
- package/dist/react/runtimes/external-message-converter.js +33 -233
- package/dist/react/runtimes/external-message-converter.js.map +1 -1
- package/dist/runtime/api/composer-runtime.d.ts +1 -1
- package/dist/runtime/api/composer-runtime.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts +1 -2
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js +14 -29
- package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -4
- package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.js +3 -9
- package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.js +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
- package/dist/runtime/interfaces/composer-runtime-core.d.ts +1 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts +1 -1
- package/dist/runtime/internal.d.ts +2 -1
- package/dist/runtime/internal.js +2 -1
- package/dist/runtime/queue/external-thread-queue-adapter.d.ts +1 -1
- package/dist/runtime/queue/message-queue.d.ts.map +1 -1
- package/dist/runtime/queue/message-queue.js +1 -1
- package/dist/runtime/queue/message-queue.js.map +1 -1
- package/dist/runtime/queue/queue-item.d.ts +12 -0
- package/dist/runtime/queue/queue-item.d.ts.map +1 -0
- package/dist/runtime/queue/queue-item.js +6 -0
- package/dist/runtime/queue/queue-item.js.map +1 -0
- package/dist/runtime/utils/abortable-thread-load.d.ts +15 -0
- package/dist/runtime/utils/abortable-thread-load.d.ts.map +1 -0
- package/dist/runtime/utils/abortable-thread-load.js +35 -0
- package/dist/runtime/utils/abortable-thread-load.js.map +1 -0
- package/dist/runtime/utils/attachment-add-operations.d.ts +18 -0
- package/dist/runtime/utils/attachment-add-operations.d.ts.map +1 -0
- package/dist/runtime/utils/attachment-add-operations.js +43 -0
- package/dist/runtime/utils/attachment-add-operations.js.map +1 -0
- package/dist/runtime/utils/auto-status.d.ts +6 -1
- package/dist/runtime/utils/auto-status.d.ts.map +1 -1
- package/dist/runtime/utils/auto-status.js +7 -1
- package/dist/runtime/utils/auto-status.js.map +1 -1
- package/dist/runtime/utils/external-message-conversion.d.ts +47 -0
- package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -0
- package/dist/runtime/utils/external-message-conversion.js +221 -0
- package/dist/runtime/utils/external-message-conversion.js.map +1 -0
- package/dist/runtime/utils/external-store-message.d.ts +2 -1
- package/dist/runtime/utils/external-store-message.d.ts.map +1 -1
- package/dist/runtime/utils/external-store-message.js +2 -1
- package/dist/runtime/utils/external-store-message.js.map +1 -1
- package/dist/runtime/utils/message-repository-session.d.ts +30 -0
- package/dist/runtime/utils/message-repository-session.d.ts.map +1 -0
- package/dist/runtime/utils/message-repository-session.js +130 -0
- package/dist/runtime/utils/message-repository-session.js.map +1 -0
- package/dist/runtime/utils/message-repository.d.ts.map +1 -1
- package/dist/runtime/utils/message-repository.js +3 -4
- package/dist/runtime/utils/message-repository.js.map +1 -1
- package/dist/runtime/utils/pending-tool-calls.d.ts +20 -0
- package/dist/runtime/utils/pending-tool-calls.d.ts.map +1 -0
- package/dist/runtime/utils/pending-tool-calls.js +22 -0
- package/dist/runtime/utils/pending-tool-calls.js.map +1 -0
- 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.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +2 -5
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +3 -10
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +19 -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 +122 -20
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts +3 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +20 -2
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +12 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.js +77 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +2 -0
- 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 +74 -99
- package/dist/store/clients/external-thread.js.map +1 -1
- package/dist/store/clients/single-thread-list.d.ts.map +1 -1
- package/dist/store/clients/single-thread-list.js +1 -1
- package/dist/store/clients/single-thread-list.js.map +1 -1
- package/dist/store/clients/suggestions.d.ts.map +1 -1
- package/dist/store/clients/suggestions.js +16 -7
- package/dist/store/clients/suggestions.js.map +1 -1
- package/dist/store/index.d.ts +4 -2
- package/dist/store/scope-registration.d.ts +1 -1
- package/dist/store/scopes/composer.d.ts +2 -1
- package/dist/store/scopes/composer.d.ts.map +1 -1
- package/dist/store/scopes/message.d.ts +1 -1
- package/dist/store/scopes/queue-item.d.ts +2 -9
- package/dist/store/scopes/queue-item.d.ts.map +1 -1
- package/dist/store/scopes/queue-item.js +0 -6
- package/dist/types/message.d.ts +7 -3
- package/dist/types/message.d.ts.map +1 -1
- package/dist/types/message.js.map +1 -1
- package/dist/utils/data-url.d.ts +14 -1
- package/dist/utils/data-url.d.ts.map +1 -1
- package/dist/utils/data-url.js +13 -1
- package/dist/utils/data-url.js.map +1 -1
- package/dist/utils/invoke-user-callback.d.ts +5 -0
- package/dist/utils/invoke-user-callback.d.ts.map +1 -0
- package/dist/utils/invoke-user-callback.js +19 -0
- package/dist/utils/invoke-user-callback.js.map +1 -0
- package/dist/utils/normalizePartStatus.js.map +1 -1
- package/package.json +10 -10
- package/src/internal.ts +12 -0
- package/src/model-context/frame/host.test.ts +91 -7
- package/src/model-context/frame/host.ts +7 -2
- package/src/model-context/frame/provider.test.ts +267 -12
- package/src/model-context/frame/provider.ts +113 -31
- package/src/model-context/interactable-composer-metadata.ts +1 -1
- package/src/model-context/registry.test.ts +44 -0
- package/src/model-context/registry.ts +13 -35
- package/src/react/client/InMemoryThreadList.test.tsx +49 -0
- package/src/react/client/InMemoryThreadList.ts +7 -1
- package/src/react/client/Interactables.ts +19 -178
- package/src/react/client/RemoteThreadList.test.ts +96 -0
- package/src/react/client/RemoteThreadList.ts +27 -59
- package/src/react/interactables-legacy/Interactables.ts +20 -177
- package/src/react/interactables-shared/useInteractablePersistenceQueue.test.tsx +161 -0
- package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +206 -0
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +26 -56
- package/src/react/runtimes/assistant-transport/runManager.ts +4 -17
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +92 -0
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +40 -141
- package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.test.ts +37 -0
- package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.ts +18 -0
- package/src/react/runtimes/external-message-converter.test.ts +0 -14
- package/src/react/runtimes/external-message-converter.test.tsx +77 -0
- package/src/react/runtimes/external-message-converter.ts +58 -481
- package/src/runtime/api/composer-runtime.ts +1 -1
- package/src/runtime/api/message-runtime.test.ts +28 -1
- package/src/runtime/base/base-composer-runtime-core.ts +20 -40
- package/src/runtime/base/base-thread-runtime-core.test.ts +16 -0
- package/src/runtime/base/base-thread-runtime-core.ts +6 -11
- package/src/runtime/base/default-thread-composer-runtime-core.ts +1 -4
- package/src/runtime/interfaces/composer-runtime-core.ts +1 -1
- package/src/runtime/interfaces/thread-runtime-core.ts +1 -1
- package/src/runtime/internal.ts +5 -0
- package/src/runtime/queue/external-thread-queue-adapter.ts +1 -1
- package/src/runtime/queue/message-queue.ts +1 -4
- package/src/runtime/queue/queue-item.ts +10 -0
- package/src/runtime/utils/abortable-thread-load.test.ts +87 -0
- package/src/runtime/utils/abortable-thread-load.ts +53 -0
- package/src/runtime/utils/attachment-add-operations.ts +59 -0
- package/src/runtime/utils/auto-status.ts +33 -0
- package/src/runtime/utils/external-message-conversion.test.ts +113 -0
- package/src/runtime/utils/external-message-conversion.ts +494 -0
- package/src/runtime/utils/external-store-message.ts +2 -0
- package/src/runtime/utils/message-repository-session.test.ts +195 -0
- package/src/runtime/utils/message-repository-session.ts +176 -0
- package/src/runtime/utils/message-repository.ts +7 -4
- package/src/runtime/utils/pending-tool-calls.test.ts +57 -0
- package/src/runtime/utils/pending-tool-calls.ts +35 -0
- package/src/runtime/utils/thread-message-like.ts +2 -0
- package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +6 -14
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +175 -28
- package/src/runtimes/local/local-thread-runtime-core.test.ts +172 -1
- package/src/runtimes/local/local-thread-runtime-core.ts +48 -3
- package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +37 -0
- package/src/runtimes/remote-thread-list/remote-thread-state.ts +123 -0
- package/src/runtimes/tool-invocations/EDGE_CASES.md +17 -0
- package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +140 -0
- package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +3 -0
- package/src/store/clients/external-thread.ts +16 -61
- package/src/store/clients/single-thread-list.ts +4 -1
- package/src/store/clients/suggestions.test.ts +31 -0
- package/src/store/clients/suggestions.ts +18 -19
- package/src/store/scopes/message.ts +1 -1
- package/src/store/scopes/queue-item.ts +2 -9
- package/src/tests/MessageRepository.test.ts +77 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +194 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +158 -0
- package/src/tests/external-store-thread-runtime-core-adapter.test.ts +164 -0
- package/src/tests/external-store-thread-runtime-core.test.ts +642 -0
- package/src/tests/external-thread-attachments.test.tsx +173 -0
- package/src/tests/single-thread-list-item.test.tsx +47 -0
- package/src/tests/thread-message-like.test.ts +39 -1
- package/src/types/message.ts +8 -3
- package/src/utils/data-url.test.ts +52 -1
- package/src/utils/data-url.ts +21 -0
- package/src/utils/invoke-user-callback.test.ts +82 -0
- package/src/utils/invoke-user-callback.ts +31 -0
- package/src/utils/normalizePartStatus.ts +1 -1
- package/dist/store/scopes/queue-item.js.map +0 -1
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import type { CompleteAttachment } from "../../types/attachment";
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
ThreadAssistantMessage,
|
|
5
|
+
ToolCallMessagePartStatus,
|
|
6
|
+
} from "../../types/message";
|
|
4
7
|
import type { ThreadRuntimeCoreBinding } from "./thread-runtime";
|
|
5
8
|
import {
|
|
6
9
|
MessageRuntimeImpl,
|
|
@@ -173,6 +176,30 @@ describe("toMessagePartStatus", () => {
|
|
|
173
176
|
});
|
|
174
177
|
});
|
|
175
178
|
|
|
179
|
+
it("preserves incomplete tool-call status on unresolved tool calls", () => {
|
|
180
|
+
const incompleteStatus = {
|
|
181
|
+
type: "incomplete",
|
|
182
|
+
reason: "tool-calls",
|
|
183
|
+
error: { message: "Tool execution did not finish" },
|
|
184
|
+
} satisfies ToolCallMessagePartStatus;
|
|
185
|
+
const message = createAssistantMessage(
|
|
186
|
+
[
|
|
187
|
+
{
|
|
188
|
+
type: "tool-call",
|
|
189
|
+
toolCallId: "call-1",
|
|
190
|
+
toolName: "weather",
|
|
191
|
+
args: {},
|
|
192
|
+
argsText: "{}",
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
incompleteStatus,
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
expect(toMessagePartStatus(message, 0, message.content[0]!)).toEqual(
|
|
199
|
+
incompleteStatus,
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
|
|
176
203
|
it.each([
|
|
177
204
|
["false", false],
|
|
178
205
|
["zero", 0],
|
|
@@ -26,21 +26,17 @@ import type {
|
|
|
26
26
|
} from "../interfaces/composer-runtime-core";
|
|
27
27
|
import type { DictationAdapter } from "../../adapters/speech";
|
|
28
28
|
import type { QueuePlacement } from "../queue/external-thread-queue-adapter";
|
|
29
|
-
import {
|
|
30
|
-
EMPTY_QUEUE_ITEMS,
|
|
31
|
-
type QueueItemState,
|
|
32
|
-
} from "../../store/scopes/queue-item";
|
|
29
|
+
import { EMPTY_QUEUE_ITEMS, type QueueItemState } from "../queue/queue-item";
|
|
33
30
|
import { generateId } from "../../utils/id";
|
|
34
31
|
import { notifyEventListeners } from "../../utils/notify-event-listeners";
|
|
32
|
+
import {
|
|
33
|
+
AttachmentAddOperations,
|
|
34
|
+
drainAttachmentAdd,
|
|
35
|
+
} from "../utils/attachment-add-operations";
|
|
35
36
|
|
|
36
37
|
const isAttachmentComplete = (a: Attachment): a is CompleteAttachment =>
|
|
37
38
|
a.status.type === "complete";
|
|
38
39
|
|
|
39
|
-
type AttachmentAddOperation = {
|
|
40
|
-
cancelled: boolean;
|
|
41
|
-
attachmentIds: Set<string>;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
40
|
export abstract class BaseComposerRuntimeCore
|
|
45
41
|
extends BaseSubscribable
|
|
46
42
|
implements ComposerRuntimeCore
|
|
@@ -152,21 +148,14 @@ export abstract class BaseComposerRuntimeCore
|
|
|
152
148
|
protected _isSending = false;
|
|
153
149
|
private _removedDuringSend = new Set<string>();
|
|
154
150
|
private _sendGeneration = 0;
|
|
155
|
-
private _attachmentAddOperations = new
|
|
151
|
+
private _attachmentAddOperations = new AttachmentAddOperations();
|
|
156
152
|
|
|
157
153
|
private _cancelAttachmentAdd(attachmentId: string) {
|
|
158
|
-
|
|
159
|
-
if (!operation.attachmentIds.has(attachmentId)) continue;
|
|
160
|
-
operation.cancelled = true;
|
|
161
|
-
this._attachmentAddOperations.delete(operation);
|
|
162
|
-
}
|
|
154
|
+
this._attachmentAddOperations.cancel(attachmentId);
|
|
163
155
|
}
|
|
164
156
|
|
|
165
157
|
private _cancelAllAttachmentAdds() {
|
|
166
|
-
|
|
167
|
-
operation.cancelled = true;
|
|
168
|
-
}
|
|
169
|
-
this._attachmentAddOperations.clear();
|
|
158
|
+
this._attachmentAddOperations.cancelAll();
|
|
170
159
|
}
|
|
171
160
|
|
|
172
161
|
private _emptyTextAndAttachments() {
|
|
@@ -466,16 +455,10 @@ export abstract class BaseComposerRuntimeCore
|
|
|
466
455
|
throw err;
|
|
467
456
|
}
|
|
468
457
|
|
|
469
|
-
const operation
|
|
470
|
-
cancelled: false,
|
|
471
|
-
attachmentIds: new Set(),
|
|
472
|
-
};
|
|
473
|
-
const operations = this._attachmentAddOperations;
|
|
474
|
-
operations.add(operation);
|
|
458
|
+
const operation = this._attachmentAddOperations.start();
|
|
475
459
|
const upsertAttachment = (a: PendingAttachment) => {
|
|
476
|
-
if (operation.
|
|
460
|
+
if (!this._attachmentAddOperations.accept(operation, a.id)) return false;
|
|
477
461
|
|
|
478
|
-
operation.attachmentIds.add(a.id);
|
|
479
462
|
const idx = this._attachments.findIndex(
|
|
480
463
|
(attachment) => attachment.id === a.id,
|
|
481
464
|
);
|
|
@@ -494,18 +477,15 @@ export abstract class BaseComposerRuntimeCore
|
|
|
494
477
|
};
|
|
495
478
|
let lastAttachment: PendingAttachment | undefined;
|
|
496
479
|
try {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
lastAttachment =
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
lastAttachment = await promiseOrGenerator;
|
|
505
|
-
upsertAttachment(lastAttachment);
|
|
506
|
-
}
|
|
480
|
+
await drainAttachmentAdd(
|
|
481
|
+
adapter.add({ file: fileOrAttachment }),
|
|
482
|
+
(attachment) => {
|
|
483
|
+
lastAttachment = attachment;
|
|
484
|
+
return upsertAttachment(attachment);
|
|
485
|
+
},
|
|
486
|
+
);
|
|
507
487
|
} catch (e) {
|
|
508
|
-
if (operation
|
|
488
|
+
if (this._attachmentAddOperations.isCancelled(operation)) return;
|
|
509
489
|
if (lastAttachment) {
|
|
510
490
|
upsertAttachment({
|
|
511
491
|
...lastAttachment,
|
|
@@ -524,10 +504,10 @@ export abstract class BaseComposerRuntimeCore
|
|
|
524
504
|
);
|
|
525
505
|
throw e;
|
|
526
506
|
} finally {
|
|
527
|
-
|
|
507
|
+
this._attachmentAddOperations.finish(operation);
|
|
528
508
|
}
|
|
529
509
|
|
|
530
|
-
if (operation
|
|
510
|
+
if (this._attachmentAddOperations.isCancelled(operation)) return;
|
|
531
511
|
if (
|
|
532
512
|
lastAttachment?.status.type === "incomplete" &&
|
|
533
513
|
lastAttachment.status.reason === "error"
|
|
@@ -112,6 +112,22 @@ afterEach(() => {
|
|
|
112
112
|
vi.restoreAllMocks();
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
+
describe("BaseThreadRuntimeCore subscriptions", () => {
|
|
116
|
+
it("notifies later subscribers when an earlier subscriber throws", () => {
|
|
117
|
+
const runtime = new TestRuntime(createVoiceAdapter());
|
|
118
|
+
const error = new Error("subscriber failed");
|
|
119
|
+
const laterSubscriber = vi.fn();
|
|
120
|
+
|
|
121
|
+
runtime.subscribe(() => {
|
|
122
|
+
throw error;
|
|
123
|
+
});
|
|
124
|
+
runtime.subscribe(laterSubscriber);
|
|
125
|
+
|
|
126
|
+
expect(() => runtime.reset()).toThrow(error);
|
|
127
|
+
expect(laterSubscriber).toHaveBeenCalledOnce();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
115
131
|
describe("BaseThreadRuntimeCore voice volume subscriptions", () => {
|
|
116
132
|
it("continues notifying subscribers when one throws", () => {
|
|
117
133
|
const consoleError = vi
|
|
@@ -36,6 +36,7 @@ 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 { BaseSubscribable } from "../../subscribable/subscribable";
|
|
39
40
|
|
|
40
41
|
type BaseThreadAdapters = {
|
|
41
42
|
speech?: SpeechSynthesisAdapter | undefined;
|
|
@@ -44,8 +45,10 @@ type BaseThreadAdapters = {
|
|
|
44
45
|
voice?: RealtimeVoiceAdapter | undefined;
|
|
45
46
|
};
|
|
46
47
|
|
|
47
|
-
export abstract class BaseThreadRuntimeCore
|
|
48
|
-
|
|
48
|
+
export abstract class BaseThreadRuntimeCore
|
|
49
|
+
extends BaseSubscribable
|
|
50
|
+
implements ThreadRuntimeCore
|
|
51
|
+
{
|
|
49
52
|
private _isInitialized = false;
|
|
50
53
|
|
|
51
54
|
protected readonly repository = new MessageRepository();
|
|
@@ -118,6 +121,7 @@ export abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore {
|
|
|
118
121
|
private readonly _contextProvider: ModelContextProvider;
|
|
119
122
|
|
|
120
123
|
constructor(_contextProvider: ModelContextProvider) {
|
|
124
|
+
super();
|
|
121
125
|
this._contextProvider = _contextProvider;
|
|
122
126
|
}
|
|
123
127
|
|
|
@@ -213,10 +217,6 @@ export abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore {
|
|
|
213
217
|
this._notifySubscribers();
|
|
214
218
|
}
|
|
215
219
|
|
|
216
|
-
protected _notifySubscribers() {
|
|
217
|
-
for (const callback of this._subscriptions) callback();
|
|
218
|
-
}
|
|
219
|
-
|
|
220
220
|
public _notifyEventSubscribers<E extends ThreadRuntimeEventType>(
|
|
221
221
|
event: E,
|
|
222
222
|
payload: ThreadRuntimeEventPayload[E],
|
|
@@ -227,11 +227,6 @@ export abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore {
|
|
|
227
227
|
notifyEventListeners(subscribers, payload, `Thread runtime "${event}"`);
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
public subscribe(callback: () => void): Unsubscribe {
|
|
231
|
-
this._subscriptions.add(callback);
|
|
232
|
-
return () => this._subscriptions.delete(callback);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
230
|
public submitFeedback({ messageId, type }: SubmitFeedbackOptions) {
|
|
236
231
|
const adapter = this.adapters?.feedback;
|
|
237
232
|
if (!adapter) throw new Error("Feedback adapter not configured");
|
|
@@ -8,10 +8,7 @@ import type {
|
|
|
8
8
|
import type { ThreadRuntimeCore } from "../interfaces/thread-runtime-core";
|
|
9
9
|
import { getThreadRuntimeCoreIsRunning } from "../api/thread-runtime";
|
|
10
10
|
import type { QueuePlacement } from "../queue/external-thread-queue-adapter";
|
|
11
|
-
import {
|
|
12
|
-
EMPTY_QUEUE_ITEMS,
|
|
13
|
-
type QueueItemState,
|
|
14
|
-
} from "../../store/scopes/queue-item";
|
|
11
|
+
import { EMPTY_QUEUE_ITEMS, type QueueItemState } from "../queue/queue-item";
|
|
15
12
|
import { BaseComposerRuntimeCore } from "./base-composer-runtime-core";
|
|
16
13
|
|
|
17
14
|
const isCancelable = (runtime: Omit<ThreadRuntimeCore, "composer">) => {
|
|
@@ -4,7 +4,7 @@ import type { Attachment, CreateAttachment } from "../../types/attachment";
|
|
|
4
4
|
import type { Unsubscribe } from "../../types/unsubscribe";
|
|
5
5
|
import type { RunConfig } from "../../types/message";
|
|
6
6
|
import type { DictationAdapter } from "../../adapters/speech";
|
|
7
|
-
import type { QueueItemState } from "
|
|
7
|
+
import type { QueueItemState } from "../queue/queue-item";
|
|
8
8
|
import type { QueuePlacement } from "../queue/external-thread-queue-adapter";
|
|
9
9
|
|
|
10
10
|
export type AttachmentAddErrorReason =
|
|
@@ -16,7 +16,7 @@ import type {
|
|
|
16
16
|
EditComposerRuntimeCore,
|
|
17
17
|
ThreadComposerRuntimeCore,
|
|
18
18
|
} from "./composer-runtime-core";
|
|
19
|
-
import type { QueueItemState } from "
|
|
19
|
+
import type { QueueItemState } from "../queue/queue-item";
|
|
20
20
|
import type { QueuePlacement } from "../queue/external-thread-queue-adapter";
|
|
21
21
|
|
|
22
22
|
export type RuntimeCapabilities = {
|
package/src/runtime/internal.ts
CHANGED
|
@@ -56,3 +56,8 @@ export {
|
|
|
56
56
|
MessageRepository,
|
|
57
57
|
} from "./utils/message-repository";
|
|
58
58
|
export type { ExportedMessageRepositoryItem } from "./utils/message-repository";
|
|
59
|
+
export { createMessageRepositorySession } from "./utils/message-repository-session";
|
|
60
|
+
export type {
|
|
61
|
+
MessageRepositorySession,
|
|
62
|
+
MessageRepositorySessionOptions,
|
|
63
|
+
} from "./utils/message-repository-session";
|
|
@@ -3,10 +3,7 @@ import type {
|
|
|
3
3
|
FileMessagePart,
|
|
4
4
|
TextMessagePart,
|
|
5
5
|
} from "../../types/message";
|
|
6
|
-
import {
|
|
7
|
-
EMPTY_QUEUE_ITEMS,
|
|
8
|
-
type QueueItemState,
|
|
9
|
-
} from "../../store/scopes/queue-item";
|
|
6
|
+
import { EMPTY_QUEUE_ITEMS, type QueueItemState } from "./queue-item";
|
|
10
7
|
import { generateId } from "../../utils/id";
|
|
11
8
|
import { notifyEventListeners } from "../../utils/notify-event-listeners";
|
|
12
9
|
import { getThreadMessageText } from "../../utils/text";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FileMessagePart, TextMessagePart } from "../../types/message";
|
|
2
|
+
|
|
3
|
+
export type QueueItemState = {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
/** @deprecated Derive from the text parts of `parts` instead. Removal after 2026-11-05. */
|
|
6
|
+
readonly prompt: string;
|
|
7
|
+
readonly parts: readonly (FileMessagePart | TextMessagePart)[];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const EMPTY_QUEUE_ITEMS: readonly QueueItemState[] = Object.freeze([]);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { createAbortableThreadLoad } from "./abortable-thread-load";
|
|
3
|
+
|
|
4
|
+
const callbacks = () => ({
|
|
5
|
+
onSettled: vi.fn(),
|
|
6
|
+
onInitialError: vi.fn(),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
describe("createAbortableThreadLoad", () => {
|
|
10
|
+
it("defers a reload to an in-flight initial load", async () => {
|
|
11
|
+
let resolveInitial!: () => void;
|
|
12
|
+
const initialLoad = vi.fn(
|
|
13
|
+
() =>
|
|
14
|
+
new Promise<void>((resolve) => {
|
|
15
|
+
resolveInitial = resolve;
|
|
16
|
+
}),
|
|
17
|
+
);
|
|
18
|
+
const reloadLoad = vi.fn(() => Promise.resolve());
|
|
19
|
+
const controller = createAbortableThreadLoad();
|
|
20
|
+
|
|
21
|
+
const initial = controller.run({
|
|
22
|
+
purpose: "initial",
|
|
23
|
+
load: initialLoad,
|
|
24
|
+
...callbacks(),
|
|
25
|
+
});
|
|
26
|
+
const reload = controller.run({
|
|
27
|
+
purpose: "reload",
|
|
28
|
+
load: reloadLoad,
|
|
29
|
+
...callbacks(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(reloadLoad).not.toHaveBeenCalled();
|
|
33
|
+
resolveInitial();
|
|
34
|
+
await Promise.all([initial, reload]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("aborts a superseded load and swallows its failure", async () => {
|
|
38
|
+
let rejectFirst!: (error: Error) => void;
|
|
39
|
+
let firstSignal!: AbortSignal;
|
|
40
|
+
const controller = createAbortableThreadLoad();
|
|
41
|
+
const first = controller.run({
|
|
42
|
+
purpose: "reload",
|
|
43
|
+
load: (signal) => {
|
|
44
|
+
firstSignal = signal;
|
|
45
|
+
return new Promise<void>((_, reject) => {
|
|
46
|
+
rejectFirst = reject;
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
...callbacks(),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const second = controller.run({
|
|
53
|
+
purpose: "initial",
|
|
54
|
+
load: () => Promise.resolve(),
|
|
55
|
+
...callbacks(),
|
|
56
|
+
});
|
|
57
|
+
rejectFirst(new Error("aborted"));
|
|
58
|
+
|
|
59
|
+
expect(firstSignal.aborted).toBe(true);
|
|
60
|
+
await expect(first).resolves.toBeUndefined();
|
|
61
|
+
await second;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("swallows initial failures and surfaces reload failures", async () => {
|
|
65
|
+
const controller = createAbortableThreadLoad();
|
|
66
|
+
const initialCallbacks = callbacks();
|
|
67
|
+
const initialError = new Error("initial");
|
|
68
|
+
|
|
69
|
+
await expect(
|
|
70
|
+
controller.run({
|
|
71
|
+
purpose: "initial",
|
|
72
|
+
load: () => Promise.reject(initialError),
|
|
73
|
+
...initialCallbacks,
|
|
74
|
+
}),
|
|
75
|
+
).resolves.toBeUndefined();
|
|
76
|
+
expect(initialCallbacks.onInitialError).toHaveBeenCalledWith(initialError);
|
|
77
|
+
|
|
78
|
+
const reloadError = new Error("reload");
|
|
79
|
+
await expect(
|
|
80
|
+
controller.run({
|
|
81
|
+
purpose: "reload",
|
|
82
|
+
load: () => Promise.reject(reloadError),
|
|
83
|
+
...callbacks(),
|
|
84
|
+
}),
|
|
85
|
+
).rejects.toBe(reloadError);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export type AbortableThreadLoadPurpose = "initial" | "reload";
|
|
2
|
+
|
|
3
|
+
type AbortableThreadLoadOptions = {
|
|
4
|
+
purpose?: AbortableThreadLoadPurpose;
|
|
5
|
+
load: (signal: AbortSignal) => Promise<void>;
|
|
6
|
+
onSettled: () => void;
|
|
7
|
+
onInitialError: (error: unknown) => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const createAbortableThreadLoad = () => {
|
|
11
|
+
let current: {
|
|
12
|
+
controller: AbortController;
|
|
13
|
+
purpose: AbortableThreadLoadPurpose;
|
|
14
|
+
promise?: Promise<void>;
|
|
15
|
+
} | null = null;
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
abort(purpose?: AbortableThreadLoadPurpose) {
|
|
19
|
+
if (purpose !== undefined && current?.purpose !== purpose) return;
|
|
20
|
+
current?.controller.abort();
|
|
21
|
+
},
|
|
22
|
+
run({
|
|
23
|
+
purpose = "initial",
|
|
24
|
+
load,
|
|
25
|
+
onSettled,
|
|
26
|
+
onInitialError,
|
|
27
|
+
}: AbortableThreadLoadOptions): Promise<void> {
|
|
28
|
+
if (purpose === "reload" && current?.purpose === "initial") {
|
|
29
|
+
return current.promise ?? Promise.resolve();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
current?.controller.abort();
|
|
33
|
+
const controller = new AbortController();
|
|
34
|
+
const record: NonNullable<typeof current> = { controller, purpose };
|
|
35
|
+
current = record;
|
|
36
|
+
|
|
37
|
+
const task = load(controller.signal)
|
|
38
|
+
.catch((error: unknown) => {
|
|
39
|
+
if (controller.signal.aborted) return;
|
|
40
|
+
throw error;
|
|
41
|
+
})
|
|
42
|
+
.finally(() => {
|
|
43
|
+
if (current?.controller === controller) current = null;
|
|
44
|
+
if (controller.signal.aborted) return;
|
|
45
|
+
onSettled();
|
|
46
|
+
});
|
|
47
|
+
record.promise = task;
|
|
48
|
+
|
|
49
|
+
if (purpose === "reload") return task;
|
|
50
|
+
return task.catch(onInitialError);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type AttachmentAddOperation = {
|
|
2
|
+
cancelled: boolean;
|
|
3
|
+
attachmentIds: Set<string>;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export class AttachmentAddOperations {
|
|
7
|
+
private readonly operations = new Set<AttachmentAddOperation>();
|
|
8
|
+
|
|
9
|
+
start() {
|
|
10
|
+
const operation: AttachmentAddOperation = {
|
|
11
|
+
cancelled: false,
|
|
12
|
+
attachmentIds: new Set(),
|
|
13
|
+
};
|
|
14
|
+
this.operations.add(operation);
|
|
15
|
+
return operation;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
accept(operation: AttachmentAddOperation, attachmentId: string) {
|
|
19
|
+
if (operation.cancelled) return false;
|
|
20
|
+
operation.attachmentIds.add(attachmentId);
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
finish(operation: AttachmentAddOperation) {
|
|
25
|
+
this.operations.delete(operation);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
isCancelled(operation: AttachmentAddOperation) {
|
|
29
|
+
return operation.cancelled;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
cancel(attachmentId: string) {
|
|
33
|
+
for (const operation of [...this.operations]) {
|
|
34
|
+
if (!operation.attachmentIds.has(attachmentId)) continue;
|
|
35
|
+
operation.cancelled = true;
|
|
36
|
+
this.operations.delete(operation);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
cancelAll() {
|
|
41
|
+
for (const operation of this.operations) {
|
|
42
|
+
operation.cancelled = true;
|
|
43
|
+
}
|
|
44
|
+
this.operations.clear();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const drainAttachmentAdd = async <T>(
|
|
49
|
+
result: Promise<T> | AsyncIterable<T>,
|
|
50
|
+
accept: (attachment: T) => boolean,
|
|
51
|
+
) => {
|
|
52
|
+
if (Symbol.asyncIterator in result) {
|
|
53
|
+
for await (const attachment of result) {
|
|
54
|
+
if (!accept(attachment)) break;
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
accept(await result);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
import type { MessageStatus } from "../../types/message";
|
|
2
2
|
import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
3
|
+
import type { ThreadMessageLike } from "./thread-message-like";
|
|
4
|
+
|
|
5
|
+
type ThreadMessageLikeContentItem = Exclude<
|
|
6
|
+
ThreadMessageLike["content"],
|
|
7
|
+
string
|
|
8
|
+
>[number];
|
|
9
|
+
|
|
10
|
+
export const isPendingToolCall = (c: ThreadMessageLikeContentItem): boolean =>
|
|
11
|
+
c.type === "tool-call" && c.result === undefined;
|
|
12
|
+
|
|
13
|
+
export const isInterruptedToolCall = (
|
|
14
|
+
c: ThreadMessageLikeContentItem,
|
|
15
|
+
): boolean => {
|
|
16
|
+
if (c.type !== "tool-call" || c.result !== undefined) return false;
|
|
17
|
+
return (
|
|
18
|
+
c.interrupt != null ||
|
|
19
|
+
(c.approval != null &&
|
|
20
|
+
c.approval.approved === undefined &&
|
|
21
|
+
c.approval.resolution === undefined)
|
|
22
|
+
);
|
|
23
|
+
};
|
|
3
24
|
|
|
4
25
|
const symbolAutoStatus = Symbol("autoStatus");
|
|
5
26
|
|
|
@@ -65,3 +86,15 @@ export const getAutoStatus = (
|
|
|
65
86
|
? AUTO_STATUS_PENDING
|
|
66
87
|
: AUTO_STATUS_COMPLETE;
|
|
67
88
|
};
|
|
89
|
+
|
|
90
|
+
export const getContentAutoStatus = (
|
|
91
|
+
content: ThreadMessageLike["content"],
|
|
92
|
+
isLast: boolean,
|
|
93
|
+
isRunning: boolean,
|
|
94
|
+
): MessageStatus =>
|
|
95
|
+
getAutoStatus(
|
|
96
|
+
isLast,
|
|
97
|
+
isRunning,
|
|
98
|
+
typeof content !== "string" && content.some(isInterruptedToolCall),
|
|
99
|
+
typeof content !== "string" && content.some(isPendingToolCall),
|
|
100
|
+
);
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
chunkExternalMessages,
|
|
4
|
+
convertExternalMessageCallback,
|
|
5
|
+
convertExternalMessageChunk,
|
|
6
|
+
type ExternalMessageConverterCallback,
|
|
7
|
+
type ExternalMessageConverterCallbackResult,
|
|
8
|
+
} from "./external-message-conversion";
|
|
9
|
+
|
|
10
|
+
describe("convertExternalMessageCallback", () => {
|
|
11
|
+
it.each([
|
|
12
|
+
["a missing toolCallId", { role: "tool", result: "ok" }],
|
|
13
|
+
["an empty toolCallId", { role: "tool", toolCallId: "", result: "ok" }],
|
|
14
|
+
])("warns and drops a tool message with %s", (_label, message) => {
|
|
15
|
+
const input = { id: "m1" };
|
|
16
|
+
const callback = (() =>
|
|
17
|
+
message) as unknown as ExternalMessageConverterCallback<typeof input>;
|
|
18
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const result = convertExternalMessageCallback(input, callback, {});
|
|
22
|
+
expect(result.outputs).toHaveLength(0);
|
|
23
|
+
expect(warn).toHaveBeenCalledOnce();
|
|
24
|
+
expect(warn.mock.calls[0]![0]).toMatch(
|
|
25
|
+
/dropping a tool result without a toolCallId/,
|
|
26
|
+
);
|
|
27
|
+
} finally {
|
|
28
|
+
warn.mockRestore();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("chunkExternalMessages", () => {
|
|
34
|
+
it("keeps a tool result with a non-joining assistant before starting the next chunk", () => {
|
|
35
|
+
const toolCall = {};
|
|
36
|
+
const toolResult = {};
|
|
37
|
+
const answer = {};
|
|
38
|
+
const callbackResults: ExternalMessageConverterCallbackResult<object>[] = [
|
|
39
|
+
{
|
|
40
|
+
input: toolCall,
|
|
41
|
+
outputs: [
|
|
42
|
+
{
|
|
43
|
+
role: "assistant",
|
|
44
|
+
content: [
|
|
45
|
+
{
|
|
46
|
+
type: "tool-call",
|
|
47
|
+
toolCallId: "call-1",
|
|
48
|
+
toolName: "search",
|
|
49
|
+
args: {},
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
convertConfig: { joinStrategy: "none" },
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
input: toolResult,
|
|
58
|
+
outputs: [
|
|
59
|
+
{
|
|
60
|
+
role: "tool",
|
|
61
|
+
toolCallId: "call-1",
|
|
62
|
+
toolName: "search",
|
|
63
|
+
result: "result",
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
input: answer,
|
|
69
|
+
outputs: [{ role: "assistant", content: "answer" }],
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const chunks = chunkExternalMessages(callbackResults);
|
|
74
|
+
|
|
75
|
+
expect(chunks).toHaveLength(2);
|
|
76
|
+
expect(chunks[0]?.inputs).toEqual([toolCall, toolResult]);
|
|
77
|
+
expect(chunks[0]?.outputs).toEqual([
|
|
78
|
+
callbackResults[0]?.outputs[0],
|
|
79
|
+
callbackResults[1]?.outputs[0],
|
|
80
|
+
]);
|
|
81
|
+
expect(chunks[1]?.inputs).toEqual([answer]);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("convertExternalMessageChunk", () => {
|
|
86
|
+
it("reuses a cached message when the error status is unchanged", () => {
|
|
87
|
+
const input = {};
|
|
88
|
+
const chunk = {
|
|
89
|
+
inputs: [input],
|
|
90
|
+
outputs: [{ role: "assistant" as const, content: "failed" }],
|
|
91
|
+
};
|
|
92
|
+
const generatedFallbackMessages = new WeakSet<object>();
|
|
93
|
+
const first = convertExternalMessageChunk(
|
|
94
|
+
chunk,
|
|
95
|
+
0,
|
|
96
|
+
1,
|
|
97
|
+
false,
|
|
98
|
+
{ message: "failed" },
|
|
99
|
+
{ message: undefined, generatedFallbackMessages },
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const second = convertExternalMessageChunk(
|
|
103
|
+
chunk,
|
|
104
|
+
0,
|
|
105
|
+
1,
|
|
106
|
+
true,
|
|
107
|
+
{ message: "failed" },
|
|
108
|
+
{ message: first, generatedFallbackMessages },
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
expect(second).toBe(first);
|
|
112
|
+
});
|
|
113
|
+
});
|