@assistant-ui/core 0.3.5 → 0.3.7
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 +4 -4
- 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/RuntimeAdapter.js +1 -1
- package/dist/react/RuntimeAdapter.js.map +1 -1
- package/dist/react/client/Interactables.js +1 -1
- package/dist/react/client/Interactables.js.map +1 -1
- package/dist/react/client/Tools.d.ts +1 -1
- package/dist/react/client/Tools.js +1 -1
- package/dist/react/client/Tools.js.map +1 -1
- package/dist/react/index.d.ts +9 -9
- package/dist/react/index.js +4 -4
- package/dist/react/model-context/interactableTool.d.ts +1 -1
- package/dist/react/model-context/useAssistantTool.d.ts +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 +3 -2
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/createMessageConverter.d.ts +1 -1
- package/dist/react/runtimes/createRuntimeExtras.d.ts +4 -1
- package/dist/react/runtimes/createRuntimeExtras.d.ts.map +1 -1
- package/dist/react/runtimes/createRuntimeExtras.js +6 -17
- package/dist/react/runtimes/createRuntimeExtras.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/react/types/scopes/dataRenderers.d.ts +1 -1
- package/dist/react/types/scopes/interactables.d.ts +1 -1
- package/dist/react/types/scopes/tools.d.ts +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/runtime-extras-brand.d.ts +21 -0
- package/dist/runtime/utils/runtime-extras-brand.d.ts.map +1 -0
- package/dist/runtime/utils/runtime-extras-brand.js +37 -0
- package/dist/runtime/utils/runtime-extras-brand.js.map +1 -0
- 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/clients/runtime-adapter.d.ts.map +1 -1
- package/dist/store/clients/runtime-adapter.js +1 -1
- package/dist/store/clients/runtime-adapter.js.map +1 -1
- package/dist/store/clients/suggestions.js +1 -1
- package/dist/store/clients/suggestions.js.map +1 -1
- package/dist/store/clients/thread-message-client.js +1 -1
- package/dist/store/clients/thread-message-client.js.map +1 -1
- package/dist/store/index.d.ts +10 -1
- package/dist/store/index.js +7 -1
- package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.js +12 -5
- package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/message-runtime-client.js +1 -1
- package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -1
- package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-list-runtime-client.js +1 -1
- package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.js +1 -1
- package/dist/store/runtime-clients/thread-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 +15 -15
- 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/RuntimeAdapter.test.ts +85 -0
- package/src/react/RuntimeAdapter.ts +1 -1
- package/src/react/client/Interactables.test.ts +3 -2
- package/src/react/client/Interactables.ts +4 -4
- package/src/react/client/Tools.ts +4 -4
- package/src/react/index.ts +1 -0
- 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 +19 -4
- package/src/react/runtimes/createRuntimeExtras.test.ts +14 -1
- package/src/react/runtimes/createRuntimeExtras.ts +15 -26
- 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/runtime-extras-brand.ts +47 -0
- 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/clients/runtime-adapter.ts +2 -5
- package/src/store/clients/suggestions.ts +1 -1
- package/src/store/clients/thread-message-client.ts +1 -1
- package/src/store/index.ts +31 -0
- package/src/store/runtime-clients/composer-runtime-client.ts +15 -10
- package/src/store/runtime-clients/message-runtime-client.ts +2 -5
- package/src/store/runtime-clients/thread-list-item-runtime-client.ts +2 -1
- package/src/store/runtime-clients/thread-list-runtime-client.ts +2 -5
- package/src/store/runtime-clients/thread-runtime-client.ts +2 -2
- package/src/store/scopes/composer.ts +6 -4
- package/src/store/scopes/queue-item.ts +8 -1
- package/src/tests/external-store-thread-runtime-core.test.ts +56 -13
- package/src/tests/framework-neutral-boundary.test.ts +257 -0
- package/src/tests/message-queue.test.ts +494 -34
- package/src/tests/thread-message-like.test.ts +66 -0
- package/LICENSE +0 -21
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AppendMessage,
|
|
3
|
+
FileMessagePart,
|
|
4
|
+
TextMessagePart,
|
|
5
|
+
} from "../../types/message";
|
|
2
6
|
import {
|
|
3
7
|
EMPTY_QUEUE_ITEMS,
|
|
4
8
|
type QueueItemState,
|
|
5
9
|
} from "../../store/scopes/queue-item";
|
|
6
10
|
import { generateId } from "../../utils/id";
|
|
7
11
|
import { getThreadMessageText } from "../../utils/text";
|
|
8
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
ExternalThreadQueueAdapter,
|
|
14
|
+
QueuePlacement,
|
|
15
|
+
} from "./external-thread-queue-adapter";
|
|
9
16
|
|
|
10
17
|
export type MessageQueueDriver = {
|
|
11
18
|
run: (message: AppendMessage, options: { steer: boolean }) => void;
|
|
12
|
-
/** When omitted,
|
|
19
|
+
/** When omitted, steering degrades to "process next" instead of interrupting. */
|
|
13
20
|
cancel?: (() => void) | undefined;
|
|
14
21
|
};
|
|
15
22
|
|
|
@@ -19,95 +26,239 @@ export type MessageQueueController = {
|
|
|
19
26
|
notifyBusy: () => void;
|
|
20
27
|
/** Advances to the next pending message; call on the run's falling edge. */
|
|
21
28
|
notifyIdle: () => void;
|
|
29
|
+
/**
|
|
30
|
+
* Pauses queue advance for a user-initiated cancel, so the cancelled run's
|
|
31
|
+
* settle keeps the pending items instead of dispatching the next one; the
|
|
32
|
+
* next explicit send or run start re-arms draining. Call before aborting
|
|
33
|
+
* the run.
|
|
34
|
+
*/
|
|
35
|
+
notifyCancelled: () => void;
|
|
36
|
+
/** Empties both lanes without dispatching. */
|
|
37
|
+
clear: () => void;
|
|
22
38
|
subscribe: (callback: () => void) => () => void;
|
|
23
39
|
};
|
|
24
40
|
|
|
41
|
+
type Lane = "queue" | "steer";
|
|
42
|
+
|
|
43
|
+
const getQueueItemParts = (
|
|
44
|
+
message: AppendMessage,
|
|
45
|
+
): readonly (FileMessagePart | TextMessagePart)[] => {
|
|
46
|
+
// attachment-derived text is the file body, not user-authored prose;
|
|
47
|
+
// project only the file/image content of attachments
|
|
48
|
+
const source = [
|
|
49
|
+
...message.content,
|
|
50
|
+
...(message.attachments ?? []).flatMap((attachment) =>
|
|
51
|
+
(attachment.content ?? []).filter((part) => part.type !== "text"),
|
|
52
|
+
),
|
|
53
|
+
];
|
|
54
|
+
const parts: (FileMessagePart | TextMessagePart)[] = [];
|
|
55
|
+
for (const part of source) {
|
|
56
|
+
if (part.type === "file" || part.type === "text") {
|
|
57
|
+
parts.push(part);
|
|
58
|
+
} else if (part.type === "image") {
|
|
59
|
+
parts.push({
|
|
60
|
+
type: "file",
|
|
61
|
+
data: part.image,
|
|
62
|
+
mimeType: "image/*",
|
|
63
|
+
...(part.filename !== undefined && { filename: part.filename }),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return parts;
|
|
68
|
+
};
|
|
69
|
+
|
|
25
70
|
export const createMessageQueue = (
|
|
26
71
|
driver: MessageQueueDriver,
|
|
27
72
|
): MessageQueueController => {
|
|
28
|
-
let
|
|
73
|
+
let lanes: Record<Lane, readonly QueueItemState[]> = {
|
|
74
|
+
queue: EMPTY_QUEUE_ITEMS,
|
|
75
|
+
steer: EMPTY_QUEUE_ITEMS,
|
|
76
|
+
};
|
|
29
77
|
const messages = new Map<string, AppendMessage>();
|
|
30
78
|
const subscribers = new Set<() => void>();
|
|
31
79
|
|
|
32
80
|
let running = false;
|
|
81
|
+
let paused = false;
|
|
33
82
|
// swallow the cancelled run's settle when steering so it does not double-advance
|
|
34
83
|
let suppressIdle = 0;
|
|
84
|
+
// settles from cancelled runs that must drop `running` without advancing
|
|
85
|
+
let cancelSettles = 0;
|
|
35
86
|
|
|
36
87
|
const notify = () => {
|
|
37
88
|
for (const callback of subscribers) callback();
|
|
38
89
|
};
|
|
39
90
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
adapter.items = next;
|
|
91
|
+
const setLanes = (next: Record<Lane, readonly QueueItemState[]>) => {
|
|
92
|
+
lanes = next;
|
|
93
|
+
adapter.items = next.queue;
|
|
94
|
+
adapter.steerItems = next.steer;
|
|
43
95
|
notify();
|
|
44
96
|
};
|
|
45
97
|
|
|
98
|
+
const toItem = (id: string, message: AppendMessage): QueueItemState => ({
|
|
99
|
+
id,
|
|
100
|
+
prompt: getThreadMessageText(message),
|
|
101
|
+
parts: getQueueItemParts(message),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const laneOf = (queueItemId: string): Lane | undefined => {
|
|
105
|
+
if (lanes.steer.some((item) => item.id === queueItemId)) return "steer";
|
|
106
|
+
if (lanes.queue.some((item) => item.id === queueItemId)) return "queue";
|
|
107
|
+
return undefined;
|
|
108
|
+
};
|
|
109
|
+
|
|
46
110
|
const advance = () => {
|
|
47
|
-
if (running ||
|
|
48
|
-
const
|
|
111
|
+
if (running || paused) return;
|
|
112
|
+
const lane: Lane = lanes.steer.length > 0 ? "steer" : "queue";
|
|
113
|
+
const head = lanes[lane][0];
|
|
114
|
+
if (!head) return;
|
|
49
115
|
const message = messages.get(head.id);
|
|
50
116
|
messages.delete(head.id);
|
|
51
|
-
|
|
117
|
+
setLanes({ ...lanes, [lane]: lanes[lane].slice(1) });
|
|
52
118
|
if (!message) return;
|
|
53
119
|
running = true;
|
|
54
120
|
driver.run(message, { steer: false });
|
|
55
121
|
};
|
|
56
122
|
|
|
57
|
-
const
|
|
123
|
+
const interrupt = (message: AppendMessage) => {
|
|
124
|
+
paused = false;
|
|
125
|
+
// the interrupted run settles exactly once, whether or not it was
|
|
126
|
+
// already cancel-notified
|
|
127
|
+
suppressIdle += Math.max(cancelSettles, 1);
|
|
128
|
+
cancelSettles = 0;
|
|
129
|
+
driver.cancel!();
|
|
130
|
+
running = true;
|
|
131
|
+
driver.run(message, { steer: true });
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const push = (lane: Lane, message: AppendMessage) => {
|
|
135
|
+
paused = false;
|
|
58
136
|
const id = generateId();
|
|
59
|
-
const prompt = getThreadMessageText(message);
|
|
60
137
|
messages.set(id, message);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
138
|
+
setLanes({ ...lanes, [lane]: [...lanes[lane], toItem(id, message)] });
|
|
139
|
+
advance();
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const enqueue = (message: AppendMessage) => {
|
|
143
|
+
push("queue", message);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const steer = (message: AppendMessage) => {
|
|
147
|
+
if (running && driver.cancel) {
|
|
148
|
+
interrupt(message);
|
|
149
|
+
return;
|
|
66
150
|
}
|
|
151
|
+
push("steer", message);
|
|
67
152
|
};
|
|
68
153
|
|
|
69
|
-
const
|
|
70
|
-
|
|
154
|
+
const move = (queueItemId: string, placement: QueuePlacement) => {
|
|
155
|
+
const fromLane = laneOf(queueItemId);
|
|
156
|
+
if (!fromLane) throw new Error(`Unknown queue item "${queueItemId}".`);
|
|
157
|
+
const toLane = placement.lane ?? fromLane;
|
|
158
|
+
|
|
159
|
+
const item = lanes[fromLane].find((i) => i.id === queueItemId)!;
|
|
160
|
+
const dest = (toLane === fromLane ? lanes[fromLane] : lanes[toLane]).filter(
|
|
161
|
+
(i) => i.id !== queueItemId,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const anchorIndex = (anchor: string) => {
|
|
165
|
+
if (anchor === queueItemId)
|
|
166
|
+
throw new Error(`Queue item "${queueItemId}" cannot anchor itself.`);
|
|
167
|
+
const index = dest.findIndex((i) => i.id === anchor);
|
|
168
|
+
if (index === -1)
|
|
169
|
+
throw new Error(`Unknown anchor "${anchor}" in lane "${toLane}".`);
|
|
170
|
+
return index;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const { insertAfter, insertBefore } = placement;
|
|
174
|
+
let index: number;
|
|
175
|
+
if (insertAfter === undefined && insertBefore === undefined) {
|
|
176
|
+
index =
|
|
177
|
+
toLane === fromLane
|
|
178
|
+
? lanes[fromLane].findIndex((i) => i.id === queueItemId)
|
|
179
|
+
: dest.length;
|
|
180
|
+
} else if (insertAfter !== undefined && insertBefore !== undefined) {
|
|
181
|
+
const after = insertAfter === null ? -1 : anchorIndex(insertAfter);
|
|
182
|
+
const before =
|
|
183
|
+
insertBefore === null ? dest.length : anchorIndex(insertBefore);
|
|
184
|
+
if (before !== after + 1)
|
|
185
|
+
throw new Error(
|
|
186
|
+
`insertAfter "${insertAfter}" and insertBefore "${insertBefore}" are not adjacent in lane "${toLane}".`,
|
|
187
|
+
);
|
|
188
|
+
index = after + 1;
|
|
189
|
+
} else if (insertAfter !== undefined) {
|
|
190
|
+
index = insertAfter === null ? 0 : anchorIndex(insertAfter) + 1;
|
|
191
|
+
} else {
|
|
192
|
+
index = insertBefore === null ? dest.length : anchorIndex(insertBefore!);
|
|
193
|
+
}
|
|
71
194
|
|
|
72
|
-
|
|
195
|
+
// placement and immediate dispatch cannot coexist: an anchored move into
|
|
196
|
+
// the steer lane places without interrupting; only an unanchored one
|
|
197
|
+
// cancels the live run and dispatches
|
|
198
|
+
if (
|
|
199
|
+
insertAfter === undefined &&
|
|
200
|
+
insertBefore === undefined &&
|
|
201
|
+
toLane === "steer" &&
|
|
202
|
+
fromLane !== "steer" &&
|
|
203
|
+
running &&
|
|
204
|
+
driver.cancel
|
|
205
|
+
) {
|
|
73
206
|
const message = messages.get(queueItemId)!;
|
|
74
207
|
messages.delete(queueItemId);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
208
|
+
setLanes({
|
|
209
|
+
queue: lanes.queue.filter((i) => i.id !== queueItemId),
|
|
210
|
+
steer: lanes.steer,
|
|
211
|
+
});
|
|
212
|
+
interrupt(message);
|
|
80
213
|
return;
|
|
81
214
|
}
|
|
82
215
|
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
216
|
+
const nextDest = [...dest.slice(0, index), item, ...dest.slice(index)];
|
|
217
|
+
const next = { ...lanes, [toLane]: nextDest };
|
|
218
|
+
if (toLane !== fromLane)
|
|
219
|
+
next[fromLane] = lanes[fromLane].filter((i) => i.id !== queueItemId);
|
|
220
|
+
setLanes(next);
|
|
86
221
|
advance();
|
|
87
222
|
};
|
|
88
223
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
224
|
+
const edit = (queueItemId: string, message: AppendMessage) => {
|
|
225
|
+
const lane = laneOf(queueItemId);
|
|
226
|
+
if (!lane) throw new Error(`Unknown queue item "${queueItemId}".`);
|
|
227
|
+
messages.set(queueItemId, message);
|
|
228
|
+
setLanes({
|
|
229
|
+
...lanes,
|
|
230
|
+
[lane]: lanes[lane].map((item) =>
|
|
231
|
+
item.id === queueItemId ? toItem(queueItemId, message) : item,
|
|
232
|
+
),
|
|
233
|
+
});
|
|
92
234
|
};
|
|
93
235
|
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
236
|
+
const remove = (queueItemId: string) => {
|
|
237
|
+
if (!messages.delete(queueItemId)) return;
|
|
238
|
+
setLanes({
|
|
239
|
+
queue: lanes.queue.filter((item) => item.id !== queueItemId),
|
|
240
|
+
steer: lanes.steer.filter((item) => item.id !== queueItemId),
|
|
241
|
+
});
|
|
98
242
|
};
|
|
99
243
|
|
|
100
244
|
const adapter: ExternalThreadQueueAdapter = {
|
|
101
|
-
items,
|
|
245
|
+
items: lanes.queue,
|
|
246
|
+
steerItems: lanes.steer,
|
|
102
247
|
enqueue,
|
|
103
|
-
steer
|
|
248
|
+
steer,
|
|
249
|
+
move,
|
|
250
|
+
edit,
|
|
104
251
|
remove,
|
|
105
|
-
clear,
|
|
106
252
|
};
|
|
107
253
|
|
|
108
254
|
return {
|
|
109
255
|
adapter,
|
|
110
256
|
notifyBusy: () => {
|
|
257
|
+
paused = false;
|
|
258
|
+
// a cancelled run's settle that is still outstanding belongs to a run
|
|
259
|
+
// this new one replaces; swallow it entirely
|
|
260
|
+
suppressIdle += cancelSettles;
|
|
261
|
+
cancelSettles = 0;
|
|
111
262
|
running = true;
|
|
112
263
|
},
|
|
113
264
|
notifyIdle: () => {
|
|
@@ -115,9 +266,20 @@ export const createMessageQueue = (
|
|
|
115
266
|
suppressIdle--;
|
|
116
267
|
return;
|
|
117
268
|
}
|
|
269
|
+
if (cancelSettles > 0) cancelSettles--;
|
|
118
270
|
running = false;
|
|
119
271
|
advance();
|
|
120
272
|
},
|
|
273
|
+
notifyCancelled: () => {
|
|
274
|
+
if (running && cancelSettles === 0) {
|
|
275
|
+
paused = true;
|
|
276
|
+
cancelSettles = 1;
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
clear: () => {
|
|
280
|
+
messages.clear();
|
|
281
|
+
setLanes({ queue: EMPTY_QUEUE_ITEMS, steer: EMPTY_QUEUE_ITEMS });
|
|
282
|
+
},
|
|
121
283
|
subscribe: (callback) => {
|
|
122
284
|
subscribers.add(callback);
|
|
123
285
|
return () => {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** @deprecated Internal API for external-store adapter authors. Not part of the public API; may change or be removed without notice. */
|
|
2
|
+
export type RuntimeExtrasBrand<T extends object> = {
|
|
3
|
+
provide: (value: T) => T;
|
|
4
|
+
is: (extras: unknown) => extras is T;
|
|
5
|
+
tryGet: (extras: unknown) => T | undefined;
|
|
6
|
+
assert: (extras: unknown) => T;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates the framework-neutral half of a runtime extras channel: the brand
|
|
11
|
+
* symbol with its provide/read primitives. A runtime factory provides extras
|
|
12
|
+
* through the brand on any framework; the React entry wraps the same brand
|
|
13
|
+
* with a `use()` hook. The symbol is per-instance, so the adapter package
|
|
14
|
+
* creates the brand once and every binding imports that instance.
|
|
15
|
+
*
|
|
16
|
+
* @deprecated Internal API for external-store adapter authors. Not part of the public API; may change or be removed without notice.
|
|
17
|
+
*/
|
|
18
|
+
export const createRuntimeExtrasBrand = <T extends object>(
|
|
19
|
+
runtimeName: string,
|
|
20
|
+
): RuntimeExtrasBrand<T> => {
|
|
21
|
+
const brand = Symbol(`${runtimeName} extras`);
|
|
22
|
+
|
|
23
|
+
const is = (extras: unknown): extras is T =>
|
|
24
|
+
typeof extras === "object" && extras !== null && brand in extras;
|
|
25
|
+
|
|
26
|
+
const assert = (extras: unknown): T => {
|
|
27
|
+
if (!is(extras))
|
|
28
|
+
throw new Error(
|
|
29
|
+
`The current thread is not backed by the ${runtimeName} runtime.`,
|
|
30
|
+
);
|
|
31
|
+
return extras;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const provide = (value: T): T => {
|
|
35
|
+
Object.defineProperty(value, brand, {
|
|
36
|
+
value: true,
|
|
37
|
+
enumerable: false,
|
|
38
|
+
configurable: true,
|
|
39
|
+
});
|
|
40
|
+
return value;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const tryGet = (extras: unknown): T | undefined =>
|
|
44
|
+
is(extras) ? extras : undefined;
|
|
45
|
+
|
|
46
|
+
return { provide, is, tryGet, assert };
|
|
47
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parsePartialJsonObject } from "assistant-stream/utils";
|
|
2
2
|
import { generateId } from "../../utils/id";
|
|
3
|
+
import { parseDataUrl } from "../../utils/data-url";
|
|
3
4
|
import type {
|
|
4
5
|
ReasoningMessagePart,
|
|
5
6
|
SourceMessagePart,
|
|
@@ -130,10 +131,7 @@ export const fromThreadMessageLike = (
|
|
|
130
131
|
...rest
|
|
131
132
|
}: ImageMessagePart): ImageMessagePart | null => {
|
|
132
133
|
if (typeof image !== "string") return null;
|
|
133
|
-
|
|
134
|
-
/^data:image\/(png|jpeg|jpg|gif|webp|svg\+xml);base64,(.*)$/i,
|
|
135
|
-
);
|
|
136
|
-
if (dataUri) {
|
|
134
|
+
if (parseDataUrl(image)?.mimeType.startsWith("image/")) {
|
|
137
135
|
return { ...rest, image };
|
|
138
136
|
}
|
|
139
137
|
if (/^(https:\/\/|blob:)/i.test(image)) {
|
|
@@ -27,6 +27,7 @@ import type {
|
|
|
27
27
|
RuntimeCapabilities,
|
|
28
28
|
ThreadRuntimeCore,
|
|
29
29
|
} from "../../runtime/interfaces/thread-runtime-core";
|
|
30
|
+
import type { QueuePlacement } from "../../runtime/queue/external-thread-queue-adapter";
|
|
30
31
|
import { BaseThreadRuntimeCore } from "../../runtime/base/base-thread-runtime-core";
|
|
31
32
|
import type { ModelContextProvider } from "../../model-context/types";
|
|
32
33
|
import {
|
|
@@ -470,7 +471,9 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
470
471
|
// Buffering does not start a run, so the tool-abort below must wait until
|
|
471
472
|
// the queue flushes. By then the prior run (and its tools) has settled.
|
|
472
473
|
if (!isEdit && this._store.queue) {
|
|
473
|
-
|
|
474
|
+
if (message.steer ?? this._store.isRunning ?? false)
|
|
475
|
+
this._store.queue.steer(message);
|
|
476
|
+
else this._store.queue.enqueue(message);
|
|
474
477
|
return;
|
|
475
478
|
}
|
|
476
479
|
|
|
@@ -487,7 +490,6 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
487
490
|
if (isEdit) {
|
|
488
491
|
if (!this._store.onEdit)
|
|
489
492
|
throw new Error("Runtime does not support editing messages.");
|
|
490
|
-
this._store.queue?.clear("edit");
|
|
491
493
|
await this._store.onEdit(message);
|
|
492
494
|
} else {
|
|
493
495
|
await this._store.onNew(message);
|
|
@@ -520,8 +522,12 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
520
522
|
return this._store?.queue?.items ?? EMPTY_QUEUE_ITEMS;
|
|
521
523
|
}
|
|
522
524
|
|
|
523
|
-
public
|
|
524
|
-
this._store?.queue?.
|
|
525
|
+
public getSteerQueueItems() {
|
|
526
|
+
return this._store?.queue?.steerItems ?? EMPTY_QUEUE_ITEMS;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
public moveQueueItem(queueItemId: string, placement: QueuePlacement) {
|
|
530
|
+
this._store?.queue?.move(queueItemId, placement);
|
|
525
531
|
}
|
|
526
532
|
|
|
527
533
|
public removeQueueItem(queueItemId: string) {
|
|
@@ -532,8 +538,6 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
532
538
|
if (!this._store.onReload)
|
|
533
539
|
throw new Error("Runtime does not support reloading messages.");
|
|
534
540
|
|
|
535
|
-
this._store.queue?.clear("reload");
|
|
536
|
-
|
|
537
541
|
// Auto-abort in-flight client-side tool executions when a run reloads;
|
|
538
542
|
// any results that land afterward would target a turn that no longer
|
|
539
543
|
// exists. See `append` above for full rationale.
|
|
@@ -579,8 +583,6 @@ export class ExternalStoreThreadRuntimeCore
|
|
|
579
583
|
if (!this._store.onCancel)
|
|
580
584
|
throw new Error("Runtime does not support cancelling runs.");
|
|
581
585
|
|
|
582
|
-
this._store.queue?.clear("cancel-run");
|
|
583
|
-
|
|
584
586
|
// Abort any in-flight client-side tool executions. Fire-and-forget —
|
|
585
587
|
// the abort resolves once executions settle, but we don't gate the
|
|
586
588
|
// cancel on it.
|
|
@@ -32,4 +32,19 @@ export type LocalRuntimeOptionsBase = {
|
|
|
32
32
|
* `composer.queue` and sent once the run settles. Steering runs it next.
|
|
33
33
|
*/
|
|
34
34
|
unstable_enableMessageQueue?: boolean | undefined;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Auto-clear the message queue when the thread rewinds (message edit).
|
|
38
|
+
* Defaults to `true`.
|
|
39
|
+
* @deprecated Removal after 2026-11-05 — the queue will always survive rewinds.
|
|
40
|
+
*/
|
|
41
|
+
unstable_queueClearOnRewind?: boolean | undefined;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Auto-clear the message queue when the user cancels the run. Defaults to
|
|
45
|
+
* `true`. When `false`, cancel pauses the queue and keeps the pending
|
|
46
|
+
* items; the next send drains them.
|
|
47
|
+
* @deprecated Removal after 2026-11-05 — cancel will always pause the queue and keep the items.
|
|
48
|
+
*/
|
|
49
|
+
unstable_queueClearOnCancel?: boolean | undefined;
|
|
35
50
|
};
|
|
@@ -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,
|