@assistant-ui/core 0.3.4 → 0.3.6
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/index.d.ts +3 -3
- package/dist/index.js.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/react/AssistantProvider.d.ts +3 -2
- package/dist/react/AssistantProvider.d.ts.map +1 -1
- package/dist/react/AssistantProvider.js +19 -26
- package/dist/react/AssistantProvider.js.map +1 -1
- package/dist/react/AssistantRuntimeProvider.d.ts +7 -2
- package/dist/react/AssistantRuntimeProvider.d.ts.map +1 -1
- package/dist/react/AssistantRuntimeProvider.js +9 -7
- package/dist/react/AssistantRuntimeProvider.js.map +1 -1
- package/dist/react/providers/AttachmentByIndexProvider.d.ts.map +1 -1
- package/dist/react/providers/AttachmentByIndexProvider.js +33 -39
- package/dist/react/providers/AttachmentByIndexProvider.js.map +1 -1
- package/dist/react/providers/ChainOfThoughtByIndicesProvider.d.ts.map +1 -1
- package/dist/react/providers/ChainOfThoughtByIndicesProvider.js +15 -20
- package/dist/react/providers/ChainOfThoughtByIndicesProvider.js.map +1 -1
- package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js +17 -20
- package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js.map +1 -1
- package/dist/react/providers/MessageByIdProvider.js +28 -38
- package/dist/react/providers/MessageByIdProvider.js.map +1 -1
- package/dist/react/providers/MessageByIndexProvider.js +28 -38
- package/dist/react/providers/MessageByIndexProvider.js.map +1 -1
- package/dist/react/providers/PartByIndexProvider.js +17 -20
- package/dist/react/providers/PartByIndexProvider.js.map +1 -1
- package/dist/react/providers/QueueItemByIndexProvider.js +21 -21
- package/dist/react/providers/QueueItemByIndexProvider.js.map +1 -1
- package/dist/react/providers/ReadonlyThreadProvider.js +24 -32
- package/dist/react/providers/ReadonlyThreadProvider.js.map +1 -1
- package/dist/react/providers/SuggestionByIndexProvider.js +17 -20
- package/dist/react/providers/SuggestionByIndexProvider.js.map +1 -1
- package/dist/react/providers/TextMessagePartProvider.d.ts.map +1 -1
- package/dist/react/providers/TextMessagePartProvider.js +16 -19
- package/dist/react/providers/TextMessagePartProvider.js.map +1 -1
- package/dist/react/providers/ThreadListItemByIndexProvider.js +17 -20
- package/dist/react/providers/ThreadListItemByIndexProvider.js.map +1 -1
- package/dist/react/providers/ThreadListItemRuntimeProvider.js +15 -18
- package/dist/react/providers/ThreadListItemRuntimeProvider.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +7 -3
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +5 -2
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +4 -3
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/useLocalRuntime.d.ts +3 -1
- package/dist/react/runtimes/useLocalRuntime.d.ts.map +1 -1
- package/dist/react/runtimes/useLocalRuntime.js +4 -2
- package/dist/react/runtimes/useLocalRuntime.js.map +1 -1
- package/dist/runtime/api/composer-runtime.d.ts +5 -1
- package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
- package/dist/runtime/api/composer-runtime.js +8 -1
- package/dist/runtime/api/composer-runtime.js.map +1 -1
- package/dist/runtime/api/message-runtime.js +1 -1
- package/dist/runtime/api/message-runtime.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts +4 -2
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts +2 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +3 -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 +14 -3
- package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
- package/dist/runtime/interfaces/composer-runtime-core.d.ts +2 -1
- package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts +3 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/queue/external-thread-queue-adapter.d.ts +19 -6
- package/dist/runtime/queue/external-thread-queue-adapter.d.ts.map +1 -1
- package/dist/runtime/queue/message-queue.d.ts +10 -1
- package/dist/runtime/queue/message-queue.d.ts.map +1 -1
- package/dist/runtime/queue/message-queue.js +138 -36
- package/dist/runtime/queue/message-queue.js.map +1 -1
- package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
- package/dist/runtime/utils/thread-message-like.js +2 -1
- package/dist/runtime/utils/thread-message-like.js.map +1 -1
- package/dist/runtimes/external-store/external-store-adapter.d.ts +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +3 -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 +7 -6
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/local-runtime-options.d.ts +13 -0
- package/dist/runtimes/local/local-runtime-options.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts +5 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +30 -14
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.js +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
- package/dist/runtimes/remote-thread-list/types.d.ts +6 -3
- package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.js +11 -4
- package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
- package/dist/store/scopes/composer.d.ts +7 -3
- package/dist/store/scopes/composer.d.ts.map +1 -1
- package/dist/store/scopes/queue-item.d.ts +10 -0
- package/dist/store/scopes/queue-item.d.ts.map +1 -1
- package/dist/store/scopes/queue-item.js.map +1 -1
- package/package.json +6 -6
- package/src/index.ts +5 -1
- package/src/react/AssistantProvider.tsx +20 -18
- package/src/react/AssistantRuntimeProvider.test.tsx +55 -1
- package/src/react/AssistantRuntimeProvider.tsx +12 -2
- package/src/react/providers/AttachmentByIndexProvider.tsx +15 -7
- package/src/react/providers/ChainOfThoughtByIndicesProvider.tsx +12 -4
- package/src/react/providers/ChainOfThoughtPartByIndexProvider.tsx +8 -4
- package/src/react/providers/MessageByIdProvider.tsx +8 -4
- package/src/react/providers/MessageByIndexProvider.tsx +8 -4
- package/src/react/providers/PartByIndexProvider.tsx +8 -4
- package/src/react/providers/QueueItemByIndexProvider.tsx +9 -5
- package/src/react/providers/ReadonlyThreadProvider.tsx +9 -4
- package/src/react/providers/SuggestionByIndexProvider.tsx +8 -4
- package/src/react/providers/TextMessagePartProvider.tsx +13 -4
- package/src/react/providers/ThreadListItemByIndexProvider.tsx +8 -4
- package/src/react/providers/ThreadListItemRuntimeProvider.tsx +8 -4
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +23 -5
- package/src/react/runtimes/useLocalRuntime.ts +4 -0
- package/src/runtime/api/composer-runtime.ts +11 -2
- package/src/runtime/api/message-runtime.test.ts +25 -0
- package/src/runtime/api/message-runtime.ts +1 -1
- package/src/runtime/base/base-composer-runtime-core.ts +5 -1
- package/src/runtime/base/default-thread-composer-runtime-core.ts +27 -3
- package/src/runtime/interfaces/composer-runtime-core.ts +2 -1
- package/src/runtime/interfaces/thread-runtime-core.ts +3 -1
- package/src/runtime/queue/external-thread-queue-adapter.ts +19 -3
- package/src/runtime/queue/message-queue.ts +200 -38
- package/src/runtime/utils/thread-message-like.ts +2 -4
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +10 -8
- package/src/runtimes/local/local-runtime-options.ts +15 -0
- package/src/runtimes/local/local-thread-runtime-core.test.ts +43 -0
- package/src/runtimes/local/local-thread-runtime-core.ts +48 -13
- package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +1 -1
- package/src/runtimes/remote-thread-list/empty-thread-core.ts +1 -1
- package/src/runtimes/remote-thread-list/types.ts +9 -2
- package/src/store/runtime-clients/composer-runtime-client.ts +13 -5
- package/src/store/scopes/composer.ts +6 -4
- package/src/store/scopes/queue-item.ts +8 -1
- package/src/tests/RemoteThreadListThreadListRuntimeCore-switchToThread-order.test.ts +31 -0
- package/src/tests/external-store-thread-runtime-core.test.ts +56 -13
- package/src/tests/framework-neutral-boundary.test.ts +221 -0
- package/src/tests/message-queue.test.ts +494 -34
- package/src/tests/thread-message-like.test.ts +66 -0
|
@@ -958,6 +958,49 @@ describe("LocalThreadRuntimeCore tool approval persistence", () => {
|
|
|
958
958
|
});
|
|
959
959
|
});
|
|
960
960
|
|
|
961
|
+
it("does not persist an existing falsy tool result again", async () => {
|
|
962
|
+
const { history, updated } = createHistory();
|
|
963
|
+
const twoHumanTools: ChatModelRunResult = {
|
|
964
|
+
content: [
|
|
965
|
+
{
|
|
966
|
+
...toolCallPart("send_email"),
|
|
967
|
+
toolCallId: "call-1",
|
|
968
|
+
},
|
|
969
|
+
{
|
|
970
|
+
...toolCallPart("send_email"),
|
|
971
|
+
toolCallId: "call-2",
|
|
972
|
+
},
|
|
973
|
+
],
|
|
974
|
+
status: { type: "requires-action", reason: "tool-calls" },
|
|
975
|
+
};
|
|
976
|
+
const thread = createThread(
|
|
977
|
+
{
|
|
978
|
+
async run() {
|
|
979
|
+
return twoHumanTools;
|
|
980
|
+
},
|
|
981
|
+
},
|
|
982
|
+
{ history },
|
|
983
|
+
);
|
|
984
|
+
|
|
985
|
+
await thread.append(userMessage("send two emails"));
|
|
986
|
+
await flush();
|
|
987
|
+
|
|
988
|
+
const options = {
|
|
989
|
+
messageId: thread.messages.at(-1)!.id,
|
|
990
|
+
toolName: "send_email",
|
|
991
|
+
toolCallId: "call-1",
|
|
992
|
+
result: false,
|
|
993
|
+
isError: false,
|
|
994
|
+
};
|
|
995
|
+
thread.addToolResult(options);
|
|
996
|
+
await flush();
|
|
997
|
+
expect(updated).toHaveLength(1);
|
|
998
|
+
|
|
999
|
+
thread.addToolResult(options);
|
|
1000
|
+
await flush();
|
|
1001
|
+
expect(updated).toHaveLength(1);
|
|
1002
|
+
});
|
|
1003
|
+
|
|
961
1004
|
it("persists a multi-step run once instead of writing intermediate steps", async () => {
|
|
962
1005
|
const { history, appended, updated } = createHistory();
|
|
963
1006
|
const runs: ChatModelRunOptions[] = [];
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
createMessageQueue,
|
|
29
29
|
type MessageQueueController,
|
|
30
30
|
} from "../../runtime/queue/message-queue";
|
|
31
|
+
import type { QueuePlacement } from "../../runtime/queue/external-thread-queue-adapter";
|
|
31
32
|
import {
|
|
32
33
|
EMPTY_QUEUE_ITEMS,
|
|
33
34
|
type QueueItemState,
|
|
@@ -68,6 +69,8 @@ export class LocalThreadRuntimeCore
|
|
|
68
69
|
|
|
69
70
|
private _queue: MessageQueueController | null = null;
|
|
70
71
|
private _queueRunInFlight = false;
|
|
72
|
+
private _activeRun: { cancelled: boolean } | null = null;
|
|
73
|
+
private _runGeneration = 0;
|
|
71
74
|
|
|
72
75
|
private _historyWrites = new Map<string, Promise<void>>();
|
|
73
76
|
|
|
@@ -206,17 +209,23 @@ export class LocalThreadRuntimeCore
|
|
|
206
209
|
// release the queue when the dispatch settles, even if it rejects
|
|
207
210
|
// before reaching startRun's finally, so a failure can't deadlock it
|
|
208
211
|
this._queueRunInFlight = true;
|
|
209
|
-
|
|
212
|
+
const generation = this._runGeneration;
|
|
213
|
+
// the tail may have moved since the message was enqueued
|
|
214
|
+
void this._runAppend({
|
|
215
|
+
...message,
|
|
216
|
+
parentId: this.messages.at(-1)?.id ?? null,
|
|
217
|
+
})
|
|
210
218
|
.finally(() => {
|
|
211
219
|
this._queueRunInFlight = false;
|
|
212
|
-
|
|
220
|
+
// a dispatch that failed before starting a run settles here;
|
|
221
|
+
// runs that did start release from _runLoop
|
|
222
|
+
if (this._runGeneration === generation) this._queue?.notifyIdle();
|
|
213
223
|
})
|
|
214
224
|
.catch(() => {});
|
|
215
225
|
},
|
|
216
226
|
});
|
|
217
227
|
this._queue.subscribe(() => this._notifySubscribers());
|
|
218
228
|
} else if (!canQueue && this._queue) {
|
|
219
|
-
this._queue.adapter.clear("cancel-run");
|
|
220
229
|
this._queue = null;
|
|
221
230
|
}
|
|
222
231
|
if (this.capabilities.queue !== canQueue) {
|
|
@@ -271,10 +280,17 @@ export class LocalThreadRuntimeCore
|
|
|
271
280
|
const isTail = message.parentId === (this.messages.at(-1)?.id ?? null);
|
|
272
281
|
const willRun = message.startRun ?? message.role === "user";
|
|
273
282
|
if (this._queue && willRun && isTail) {
|
|
274
|
-
|
|
283
|
+
if (message.steer ?? this._queueRunInFlight)
|
|
284
|
+
this._queue.adapter.steer(message);
|
|
285
|
+
else this._queue.adapter.enqueue(message);
|
|
275
286
|
return;
|
|
276
287
|
}
|
|
277
|
-
if (
|
|
288
|
+
if (
|
|
289
|
+
this._queue &&
|
|
290
|
+
!isTail &&
|
|
291
|
+
(this._options.unstable_queueClearOnRewind ?? true)
|
|
292
|
+
)
|
|
293
|
+
this._queue.clear();
|
|
278
294
|
return this._runAppend(message);
|
|
279
295
|
}
|
|
280
296
|
|
|
@@ -284,8 +300,12 @@ export class LocalThreadRuntimeCore
|
|
|
284
300
|
return this._queue?.adapter.items ?? EMPTY_QUEUE_ITEMS;
|
|
285
301
|
}
|
|
286
302
|
|
|
287
|
-
public
|
|
288
|
-
this._queue?.adapter.
|
|
303
|
+
public getSteerQueueItems(): readonly QueueItemState[] {
|
|
304
|
+
return this._queue?.adapter.steerItems ?? EMPTY_QUEUE_ITEMS;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
public moveQueueItem(queueItemId: string, placement: QueuePlacement): void {
|
|
308
|
+
this._queue?.adapter.move(queueItemId, placement);
|
|
289
309
|
}
|
|
290
310
|
|
|
291
311
|
public removeQueueItem(queueItemId: string): void {
|
|
@@ -397,6 +417,10 @@ export class LocalThreadRuntimeCore
|
|
|
397
417
|
message.status?.type === "requires-action" &&
|
|
398
418
|
this._options.adapters.history?.update !== undefined;
|
|
399
419
|
|
|
420
|
+
const run = { cancelled: false };
|
|
421
|
+
this._activeRun = run;
|
|
422
|
+
this._runGeneration++;
|
|
423
|
+
|
|
400
424
|
try {
|
|
401
425
|
// mark busy for runs not started through the queue (regenerate, resume)
|
|
402
426
|
this._queue?.notifyBusy();
|
|
@@ -417,9 +441,12 @@ export class LocalThreadRuntimeCore
|
|
|
417
441
|
} while (shouldContinue(message, this._options.unstable_humanToolNames));
|
|
418
442
|
} finally {
|
|
419
443
|
this._notifyEventSubscribers("runEnd", {});
|
|
420
|
-
//
|
|
421
|
-
//
|
|
422
|
-
|
|
444
|
+
// the settle belongs to this run only while it is still the active run
|
|
445
|
+
// or was cancelled (the engine expects a cancelled run's settle); a run
|
|
446
|
+
// superseded by a newer one stays silent
|
|
447
|
+
const active = this._activeRun === run;
|
|
448
|
+
if (active) this._activeRun = null;
|
|
449
|
+
if (active || run.cancelled) {
|
|
423
450
|
queueMicrotask(() => this._queue?.notifyIdle());
|
|
424
451
|
}
|
|
425
452
|
}
|
|
@@ -644,7 +671,8 @@ export class LocalThreadRuntimeCore
|
|
|
644
671
|
}
|
|
645
672
|
|
|
646
673
|
public detach() {
|
|
647
|
-
|
|
674
|
+
// drop the queue so pending items cannot dispatch on a detached thread
|
|
675
|
+
this._queue = null;
|
|
648
676
|
const error = new AbortError(true);
|
|
649
677
|
this.abortController?.abort(error);
|
|
650
678
|
this.abortController = null;
|
|
@@ -653,7 +681,14 @@ export class LocalThreadRuntimeCore
|
|
|
653
681
|
}
|
|
654
682
|
|
|
655
683
|
public cancelRun() {
|
|
656
|
-
this._queue
|
|
684
|
+
if (this._queue) {
|
|
685
|
+
if (this._options.unstable_queueClearOnCancel ?? true) {
|
|
686
|
+
this._queue.clear();
|
|
687
|
+
} else {
|
|
688
|
+
this._queue.notifyCancelled();
|
|
689
|
+
if (this._activeRun) this._activeRun.cancelled = true;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
657
692
|
const error = new AbortError(false);
|
|
658
693
|
this.abortController?.abort(error);
|
|
659
694
|
this.abortController = null;
|
|
@@ -681,7 +716,7 @@ export class LocalThreadRuntimeCore
|
|
|
681
716
|
if (c.type !== "tool-call") return c;
|
|
682
717
|
if (c.toolCallId !== toolCallId) return c;
|
|
683
718
|
found = true;
|
|
684
|
-
if (
|
|
719
|
+
if (c.result === undefined) added = true;
|
|
685
720
|
return {
|
|
686
721
|
...c,
|
|
687
722
|
result,
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import type { ComponentType, PropsWithChildren } from "react";
|
|
2
1
|
import type { ThreadMessage } from "../../types/message";
|
|
3
2
|
import type { AssistantRuntime } from "../../runtime/api/assistant-runtime";
|
|
4
3
|
import type { AssistantStream } from "assistant-stream";
|
|
5
4
|
|
|
5
|
+
/* oxlint-disable typescript/no-explicit-any -- structural stand-in for ComponentType without depending on react types */
|
|
6
|
+
type RemoteThreadListProviderProps = { children?: any };
|
|
7
|
+
|
|
8
|
+
export type RemoteThreadListProviderComponent =
|
|
9
|
+
| ((props: RemoteThreadListProviderProps) => any)
|
|
10
|
+
| (new (props: RemoteThreadListProviderProps) => any);
|
|
11
|
+
/* oxlint-enable typescript/no-explicit-any */
|
|
12
|
+
|
|
6
13
|
export type RemoteThreadInitializeResponse = {
|
|
7
14
|
remoteId: string;
|
|
8
15
|
externalId?: string | undefined;
|
|
@@ -54,7 +61,7 @@ export type RemoteThreadListAdapter = {
|
|
|
54
61
|
* is unsupported and leaves thread context unavailable to downstream
|
|
55
62
|
* consumers. Load data inside an always-mounted child instead.
|
|
56
63
|
*/
|
|
57
|
-
unstable_Provider?:
|
|
64
|
+
unstable_Provider?: RemoteThreadListProviderComponent | undefined;
|
|
58
65
|
};
|
|
59
66
|
|
|
60
67
|
export type RemoteThreadListOptions = {
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
} from "../../runtime/api/composer-runtime";
|
|
13
13
|
import type { ComposerState } from "../scopes/composer";
|
|
14
14
|
import type { QueueItemState } from "../scopes/queue-item";
|
|
15
|
+
import type { QueuePlacement } from "../../runtime/queue/external-thread-queue-adapter";
|
|
15
16
|
import { AttachmentRuntimeClient } from "./attachment-runtime-client";
|
|
16
17
|
import { useSubscribable } from "./useSubscribable";
|
|
17
18
|
|
|
@@ -40,16 +41,17 @@ const ComposerAttachmentClientByIndex = resource(
|
|
|
40
41
|
|
|
41
42
|
const useQueueItemClient = ({
|
|
42
43
|
item,
|
|
43
|
-
|
|
44
|
+
onMove,
|
|
44
45
|
onRemove,
|
|
45
46
|
}: {
|
|
46
47
|
item: QueueItemState;
|
|
47
|
-
|
|
48
|
+
onMove: (placement: QueuePlacement) => void;
|
|
48
49
|
onRemove: () => void;
|
|
49
50
|
}): ClientOutput<"queueItem"> => {
|
|
50
51
|
return {
|
|
51
52
|
getState: () => item,
|
|
52
|
-
steer:
|
|
53
|
+
steer: () => onMove({ lane: "steer", insertAfter: null }),
|
|
54
|
+
move: onMove,
|
|
53
55
|
remove: onRemove,
|
|
54
56
|
};
|
|
55
57
|
};
|
|
@@ -121,7 +123,7 @@ const useComposerClient = ({
|
|
|
121
123
|
item.id,
|
|
122
124
|
QueueItemClient({
|
|
123
125
|
item,
|
|
124
|
-
|
|
126
|
+
onMove: (placement) => runtime.moveQueueItem(item.id, placement),
|
|
125
127
|
onRemove: () => runtime.removeQueueItem(item.id),
|
|
126
128
|
}),
|
|
127
129
|
),
|
|
@@ -171,7 +173,13 @@ const useComposerClient = ({
|
|
|
171
173
|
return attachments.get(selector);
|
|
172
174
|
}
|
|
173
175
|
},
|
|
174
|
-
queueItem: (selector) =>
|
|
176
|
+
queueItem: (selector) => {
|
|
177
|
+
if ("id" in selector) {
|
|
178
|
+
return queueItems.get({ key: selector.id });
|
|
179
|
+
} else {
|
|
180
|
+
return queueItems.get(selector);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
175
183
|
__internal_getRuntime: () => runtime,
|
|
176
184
|
};
|
|
177
185
|
};
|
|
@@ -13,8 +13,10 @@ import type { QueueItemState, QueueItemMethods } from "./queue-item";
|
|
|
13
13
|
|
|
14
14
|
export type ComposerSendOptions = SendOptions & {
|
|
15
15
|
/**
|
|
16
|
-
* Whether to steer (
|
|
17
|
-
*
|
|
16
|
+
* Whether to steer (process this message next, interrupting the current run
|
|
17
|
+
* when the runtime supports it). Defaults to true while a queued run is in
|
|
18
|
+
* flight and false when idle. Pass false to queue behind the pending
|
|
19
|
+
* messages.
|
|
18
20
|
*/
|
|
19
21
|
steer?: boolean;
|
|
20
22
|
};
|
|
@@ -88,9 +90,9 @@ export type ComposerMethods = {
|
|
|
88
90
|
setQuote(quote: QuoteInfo | undefined): void;
|
|
89
91
|
|
|
90
92
|
/**
|
|
91
|
-
* Access a queue item by index.
|
|
93
|
+
* Access a queue item by index or id.
|
|
92
94
|
*/
|
|
93
|
-
queueItem(selector: { index: number }): QueueItemMethods;
|
|
95
|
+
queueItem(selector: { index: number } | { id: string }): QueueItemMethods;
|
|
94
96
|
|
|
95
97
|
__internal_getRuntime?(): ComposerRuntime;
|
|
96
98
|
};
|
|
@@ -1,19 +1,26 @@
|
|
|
1
|
+
import type { FileMessagePart, TextMessagePart } from "../../types/message";
|
|
2
|
+
import type { QueuePlacement } from "../../runtime/queue/external-thread-queue-adapter";
|
|
3
|
+
|
|
1
4
|
export type QueueItemState = {
|
|
2
5
|
readonly id: string;
|
|
6
|
+
/** @deprecated Derive from the text parts of `parts` instead. Removal after 2026-11-05. */
|
|
3
7
|
readonly prompt: string;
|
|
8
|
+
readonly parts: readonly (FileMessagePart | TextMessagePart)[];
|
|
4
9
|
};
|
|
5
10
|
|
|
6
11
|
export const EMPTY_QUEUE_ITEMS: readonly QueueItemState[] = Object.freeze([]);
|
|
7
12
|
|
|
8
13
|
export type QueueItemMethods = {
|
|
9
14
|
getState(): QueueItemState;
|
|
15
|
+
/** @deprecated Use `move({ lane: "steer", insertAfter: null })` instead. Removal after 2026-11-05. */
|
|
10
16
|
steer(): void;
|
|
17
|
+
move(placement: QueuePlacement): void;
|
|
11
18
|
remove(): void;
|
|
12
19
|
};
|
|
13
20
|
|
|
14
21
|
export type QueueItemMeta = {
|
|
15
22
|
source: "composer";
|
|
16
|
-
query: { index: number };
|
|
23
|
+
query: { type: "index"; index: number } | { type: "id"; id: string };
|
|
17
24
|
};
|
|
18
25
|
|
|
19
26
|
export type QueueItemClientSchema = {
|
|
@@ -64,6 +64,37 @@ describe("RemoteThreadListThreadListRuntimeCore.switchToThread order", () => {
|
|
|
64
64
|
expect(core.mainThreadId).toBe(core.getItemById("thread-c")?.id);
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
+
it("handles a stopped initial runtime start", async () => {
|
|
68
|
+
const rejections: unknown[] = [];
|
|
69
|
+
const onUnhandledRejection = (reason: unknown) => {
|
|
70
|
+
rejections.push(reason);
|
|
71
|
+
};
|
|
72
|
+
const priorListeners = process.listeners("unhandledRejection");
|
|
73
|
+
process.removeAllListeners("unhandledRejection");
|
|
74
|
+
process.on("unhandledRejection", onUnhandledRejection);
|
|
75
|
+
try {
|
|
76
|
+
const core = createCore(makeAdapter());
|
|
77
|
+
const newThreadId = core.newThreadId;
|
|
78
|
+
if (!newThreadId) throw new Error("Expected an initial new thread");
|
|
79
|
+
const runtimeId = core.getItemById(newThreadId)?.id;
|
|
80
|
+
if (!runtimeId) throw new Error("Expected an initial runtime id");
|
|
81
|
+
|
|
82
|
+
(
|
|
83
|
+
core as unknown as {
|
|
84
|
+
_hookManager: { stopThreadRuntime: (id: string) => void };
|
|
85
|
+
}
|
|
86
|
+
)._hookManager.stopThreadRuntime(runtimeId);
|
|
87
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
88
|
+
} finally {
|
|
89
|
+
process.removeListener("unhandledRejection", onUnhandledRejection);
|
|
90
|
+
for (const listener of priorListeners) {
|
|
91
|
+
process.on("unhandledRejection", listener);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
expect(rejections).toEqual([]);
|
|
96
|
+
});
|
|
97
|
+
|
|
67
98
|
it("invalidates an earlier switch while a new thread request is pending", async () => {
|
|
68
99
|
const initializeThread = deferred<{
|
|
69
100
|
remoteId: string;
|
|
@@ -832,10 +832,12 @@ describe("ExternalStoreThreadRuntimeCore - initialize event replay", () => {
|
|
|
832
832
|
describe("ExternalStoreThreadRuntimeCore - message queue", () => {
|
|
833
833
|
const makeQueue = () => ({
|
|
834
834
|
items: [] as never[],
|
|
835
|
+
steerItems: [] as never[],
|
|
835
836
|
enqueue: vi.fn(),
|
|
836
837
|
steer: vi.fn(),
|
|
838
|
+
move: vi.fn(),
|
|
839
|
+
edit: vi.fn(),
|
|
837
840
|
remove: vi.fn(),
|
|
838
|
-
clear: vi.fn(),
|
|
839
841
|
});
|
|
840
842
|
|
|
841
843
|
const appendMessage = (
|
|
@@ -866,7 +868,7 @@ describe("ExternalStoreThreadRuntimeCore - message queue", () => {
|
|
|
866
868
|
expect(withoutQueue.capabilities.queue).toBe(false);
|
|
867
869
|
});
|
|
868
870
|
|
|
869
|
-
it("routes a tail append through queue
|
|
871
|
+
it("routes a tail append through the queue adapter instead of onNew", async () => {
|
|
870
872
|
const queue = makeQueue();
|
|
871
873
|
const onNew = vi.fn();
|
|
872
874
|
const runtime = new ExternalStoreThreadRuntimeCore(
|
|
@@ -877,8 +879,48 @@ describe("ExternalStoreThreadRuntimeCore - message queue", () => {
|
|
|
877
879
|
await runtime.append(appendMessage({ steer: true }));
|
|
878
880
|
|
|
879
881
|
expect(onNew).not.toHaveBeenCalled();
|
|
882
|
+
expect(queue.enqueue).not.toHaveBeenCalled();
|
|
883
|
+
expect(queue.steer).toHaveBeenCalledTimes(1);
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
it("defaults a mid-run send to steer and an idle send to enqueue", async () => {
|
|
887
|
+
const queue = makeQueue();
|
|
888
|
+
const running = new ExternalStoreThreadRuntimeCore(
|
|
889
|
+
mockContextProvider,
|
|
890
|
+
makeStore({ queue, onNew: vi.fn(), isRunning: true }),
|
|
891
|
+
);
|
|
892
|
+
await running.append(
|
|
893
|
+
appendMessage({ parentId: running.messages.at(-1)?.id ?? null }),
|
|
894
|
+
);
|
|
895
|
+
expect(queue.steer).toHaveBeenCalledTimes(1);
|
|
896
|
+
expect(queue.enqueue).not.toHaveBeenCalled();
|
|
897
|
+
|
|
898
|
+
const idleQueue = makeQueue();
|
|
899
|
+
const idle = new ExternalStoreThreadRuntimeCore(
|
|
900
|
+
mockContextProvider,
|
|
901
|
+
makeStore({ queue: idleQueue, onNew: vi.fn(), isRunning: false }),
|
|
902
|
+
);
|
|
903
|
+
await idle.append(
|
|
904
|
+
appendMessage({ parentId: idle.messages.at(-1)?.id ?? null }),
|
|
905
|
+
);
|
|
906
|
+
expect(idleQueue.enqueue).toHaveBeenCalledTimes(1);
|
|
907
|
+
expect(idleQueue.steer).not.toHaveBeenCalled();
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
it("queues behind pending items when steer is explicitly false mid-run", async () => {
|
|
911
|
+
const queue = makeQueue();
|
|
912
|
+
const runtime = new ExternalStoreThreadRuntimeCore(
|
|
913
|
+
mockContextProvider,
|
|
914
|
+
makeStore({ queue, onNew: vi.fn(), isRunning: true }),
|
|
915
|
+
);
|
|
916
|
+
await runtime.append(
|
|
917
|
+
appendMessage({
|
|
918
|
+
steer: false,
|
|
919
|
+
parentId: runtime.messages.at(-1)?.id ?? null,
|
|
920
|
+
}),
|
|
921
|
+
);
|
|
880
922
|
expect(queue.enqueue).toHaveBeenCalledTimes(1);
|
|
881
|
-
expect(queue.
|
|
923
|
+
expect(queue.steer).not.toHaveBeenCalled();
|
|
882
924
|
});
|
|
883
925
|
|
|
884
926
|
it("does not abort in-flight tools when buffering a queued send", async () => {
|
|
@@ -913,7 +955,7 @@ describe("ExternalStoreThreadRuntimeCore - message queue", () => {
|
|
|
913
955
|
expect(abort).toHaveBeenCalledTimes(1);
|
|
914
956
|
});
|
|
915
957
|
|
|
916
|
-
it("
|
|
958
|
+
it("keeps the queue on cancel, reload, and edit", async () => {
|
|
917
959
|
const queue = makeQueue();
|
|
918
960
|
const runtime = new ExternalStoreThreadRuntimeCore(
|
|
919
961
|
mockContextProvider,
|
|
@@ -926,29 +968,30 @@ describe("ExternalStoreThreadRuntimeCore - message queue", () => {
|
|
|
926
968
|
);
|
|
927
969
|
|
|
928
970
|
runtime.cancelRun();
|
|
929
|
-
expect(queue.clear).toHaveBeenCalledWith("cancel-run");
|
|
930
|
-
|
|
931
971
|
await runtime.startRun({ parentId: null, sourceId: null, runConfig: {} });
|
|
932
|
-
expect(queue.clear).toHaveBeenCalledWith("reload");
|
|
933
|
-
|
|
934
972
|
// a non-tail parentId routes to the edit branch
|
|
935
973
|
await runtime.append(appendMessage({ parentId: "not-the-tail" }));
|
|
936
|
-
|
|
974
|
+
|
|
975
|
+
expect(queue.remove).not.toHaveBeenCalled();
|
|
976
|
+
expect(queue.move).not.toHaveBeenCalled();
|
|
937
977
|
});
|
|
938
978
|
|
|
939
|
-
it("delegates
|
|
979
|
+
it("delegates queue reads / move / remove to the adapter", () => {
|
|
940
980
|
const queue = makeQueue();
|
|
941
|
-
const items = [{ id: "q1", prompt: "queued" }];
|
|
981
|
+
const items = [{ id: "q1", prompt: "queued", parts: [] }];
|
|
982
|
+
const steerItems = [{ id: "s1", prompt: "steered", parts: [] }];
|
|
942
983
|
queue.items = items as never;
|
|
984
|
+
queue.steerItems = steerItems as never;
|
|
943
985
|
const runtime = new ExternalStoreThreadRuntimeCore(
|
|
944
986
|
mockContextProvider,
|
|
945
987
|
makeStore({ queue }),
|
|
946
988
|
);
|
|
947
989
|
|
|
948
990
|
expect(runtime.getQueueItems()).toBe(items);
|
|
949
|
-
runtime.
|
|
991
|
+
expect(runtime.getSteerQueueItems()).toBe(steerItems);
|
|
992
|
+
runtime.moveQueueItem("q1", { lane: "steer" });
|
|
950
993
|
runtime.removeQueueItem("q1");
|
|
951
|
-
expect(queue.
|
|
994
|
+
expect(queue.move).toHaveBeenCalledWith("q1", { lane: "steer" });
|
|
952
995
|
expect(queue.remove).toHaveBeenCalledWith("q1");
|
|
953
996
|
});
|
|
954
997
|
});
|