@assistant-ui/react-ag-ui 0.0.71 → 0.1.1
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/README.md +1 -1
- package/dist/agUiExtras.js +1 -2
- package/dist/hooks.js +1 -2
- package/dist/runtime/{AgUiThreadRuntimeCore.d.ts → AgUiThreadController.d.ts} +6 -12
- package/dist/runtime/AgUiThreadController.d.ts.map +1 -0
- package/dist/runtime/{AgUiThreadRuntimeCore.js → AgUiThreadController.js} +567 -194
- package/dist/runtime/AgUiThreadController.js.map +1 -0
- package/dist/runtime/adapter/conversions.d.ts +4 -4
- package/dist/runtime/adapter/conversions.d.ts.map +1 -1
- package/dist/runtime/adapter/conversions.js +120 -35
- package/dist/runtime/adapter/conversions.js.map +1 -1
- package/dist/runtime/adapter/replay-content.d.ts +9 -0
- package/dist/runtime/adapter/replay-content.d.ts.map +1 -0
- package/dist/runtime/adapter/replay-content.js +185 -0
- package/dist/runtime/adapter/replay-content.js.map +1 -0
- package/dist/runtime/adapter/run-aggregator.d.ts +3 -5
- package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
- package/dist/runtime/adapter/run-aggregator.js +48 -63
- package/dist/runtime/adapter/run-aggregator.js.map +1 -1
- package/dist/runtime/adapter/subscriber.d.ts +0 -24
- package/dist/runtime/adapter/subscriber.d.ts.map +1 -1
- package/dist/runtime/adapter/subscriber.js +2 -24
- package/dist/runtime/adapter/subscriber.js.map +1 -1
- package/dist/runtime/adapter/tool-approval.d.ts +2 -0
- package/dist/runtime/adapter/tool-approval.d.ts.map +1 -1
- package/dist/runtime/adapter/tool-approval.js +23 -6
- package/dist/runtime/adapter/tool-approval.js.map +1 -1
- package/dist/runtime/agUiThreadState.d.ts +60 -0
- package/dist/runtime/agUiThreadState.d.ts.map +1 -0
- package/dist/runtime/agUiThreadState.js +94 -0
- package/dist/runtime/agUiThreadState.js.map +1 -0
- package/dist/runtime/event-parser.d.ts.map +1 -1
- package/dist/runtime/event-parser.js +46 -50
- package/dist/runtime/event-parser.js.map +1 -1
- package/dist/runtime/interrupt-internals.js +1 -2
- package/dist/runtime/logger.js +1 -2
- package/dist/runtime/mcp-tool-result.d.ts +3 -1
- package/dist/runtime/mcp-tool-result.d.ts.map +1 -1
- package/dist/runtime/mcp-tool-result.js +7 -5
- package/dist/runtime/mcp-tool-result.js.map +1 -1
- package/dist/runtime/types.d.ts +11 -35
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js +1 -2
- package/dist/runtime/types.js.map +1 -1
- package/dist/useAgUiRuntime.js +3 -4
- package/dist/useAgUiRuntime.js.map +1 -1
- package/package.json +12 -11
- package/src/runtime/AgUiThreadController.deferred-results.test.ts +548 -0
- package/src/runtime/{AgUiThreadRuntimeCore.interrupt-cancel.test.ts → AgUiThreadController.interrupt-cancel.test.ts} +4 -4
- package/src/runtime/AgUiThreadController.replay.test.ts +3932 -0
- package/src/runtime/{AgUiThreadRuntimeCore.test.ts → AgUiThreadController.test.ts} +114 -6
- package/src/runtime/{AgUiThreadRuntimeCore.ts → AgUiThreadController.ts} +1037 -236
- package/src/runtime/adapter/conversions.content.test.ts +149 -5
- package/src/runtime/adapter/conversions.tools.test.ts +2 -1
- package/src/runtime/adapter/conversions.ts +249 -62
- package/src/runtime/adapter/replay-content.test.ts +235 -0
- package/src/runtime/adapter/replay-content.ts +306 -0
- package/src/runtime/adapter/run-aggregator.ts +79 -114
- package/src/runtime/adapter/subscriber.ts +1 -44
- package/src/runtime/adapter/tool-approval.ts +41 -3
- package/src/runtime/agUiThreadState.test.ts +224 -0
- package/src/runtime/agUiThreadState.ts +139 -0
- package/src/runtime/event-parser.ts +40 -58
- package/src/runtime/mcp-tool-result.ts +16 -2
- package/src/runtime/types.ts +16 -28
- package/src/useAgUiRuntime.fast-refresh.test.tsx +3 -3
- package/src/useAgUiRuntime.thread-switch.test.tsx +4 -4
- package/src/useAgUiRuntime.ts +3 -3
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +0 -1
- package/dist/runtime/AgUiThreadRuntimeCore.js.map +0 -1
|
@@ -1,18 +1,57 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { createAgUiThreadState, reduceAgUiThreadState } from "./agUiThreadState.js";
|
|
3
|
+
import { readMcpAppResourceUri, toolContentText } from "./mcp-tool-result.js";
|
|
3
4
|
import { AG_UI_METADATA_NAMESPACE } from "./types.js";
|
|
4
|
-
import { buildToolApprovalResume, projectAgUiToolApprovals, withSettledToolApprovals, withToolApprovalDecision } from "./adapter/tool-approval.js";
|
|
5
|
+
import { buildToolApprovalResume, preserveToolApprovalDecision, projectAgUiToolApprovals, withSettledToolApprovals, withToolApprovalDecision } from "./adapter/tool-approval.js";
|
|
5
6
|
import { RunAggregator, isPlainObject, tryParseJSON } from "./adapter/run-aggregator.js";
|
|
6
7
|
import { fromAgUiMessages, toAgUiMessages, toAgUiTools } from "./adapter/conversions.js";
|
|
7
8
|
import { createAgUiSubscriber } from "./adapter/subscriber.js";
|
|
9
|
+
import { createReplayContentMerger, groupReplayToolContent, indexReplayToolOwners, preserveToolResult } from "./adapter/replay-content.js";
|
|
8
10
|
import { fromThreadMessageLike, generateId, isMcpAppUri } from "@assistant-ui/core";
|
|
9
|
-
import { appendToolInteraction, createMessageRepositorySession, invokeUserCallback, iterateToolCallParts, mapToolCallPartsDeep } from "@assistant-ui/core/internal";
|
|
11
|
+
import { appendToolInteraction, createHistoryWriteChain, createMessageRepositorySession, invokeUserCallback, iterateToolCallParts, mapToolCallPartsDeep } from "@assistant-ui/core/internal";
|
|
10
12
|
import jsonpatch from "fast-json-patch";
|
|
11
|
-
|
|
13
|
+
// src/runtime/AgUiThreadController.ts
|
|
12
14
|
const optimisticPrefix = "__optimistic__";
|
|
13
15
|
const generateOptimisticId = () => `${optimisticPrefix}${generateId()}`;
|
|
14
16
|
const isOptimisticId = (id) => id.startsWith(optimisticPrefix);
|
|
15
17
|
const isResolvedToolCall = (part) => part.type === "tool-call" && "result" in part && part.result !== void 0;
|
|
18
|
+
const hasResolvedToolCalls = (message) => {
|
|
19
|
+
if (message.role !== "assistant") return false;
|
|
20
|
+
for (const part of iterateToolCallParts(message.content)) if (isResolvedToolCall(part)) return true;
|
|
21
|
+
return false;
|
|
22
|
+
};
|
|
23
|
+
const listedPendingToolCallIds = (assistant) => {
|
|
24
|
+
const ids = assistant.metadata.custom[AG_UI_METADATA_NAMESPACE]?.pendingToolCallIds;
|
|
25
|
+
return ids === void 0 ? void 0 : new Set(ids);
|
|
26
|
+
};
|
|
27
|
+
const withoutPendingToolCallIds = (metadata) => {
|
|
28
|
+
const namespace = metadata.custom[AG_UI_METADATA_NAMESPACE];
|
|
29
|
+
if (!namespace || !("pendingToolCallIds" in namespace)) return metadata;
|
|
30
|
+
const { pendingToolCallIds: _pendingToolCallIds, ...remainingNamespace } = namespace;
|
|
31
|
+
const { [AG_UI_METADATA_NAMESPACE]: _namespace, ...remainingCustom } = metadata.custom;
|
|
32
|
+
return {
|
|
33
|
+
...metadata,
|
|
34
|
+
custom: {
|
|
35
|
+
...remainingCustom,
|
|
36
|
+
...Object.keys(remainingNamespace).length > 0 ? { [AG_UI_METADATA_NAMESPACE]: remainingNamespace } : {}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
const withPendingToolCallIds = (metadata, ids) => ({
|
|
41
|
+
...metadata,
|
|
42
|
+
custom: {
|
|
43
|
+
...metadata.custom,
|
|
44
|
+
[AG_UI_METADATA_NAMESPACE]: {
|
|
45
|
+
...metadata.custom[AG_UI_METADATA_NAMESPACE],
|
|
46
|
+
pendingToolCallIds: [...ids]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
const actionableToolCallIds = (message) => {
|
|
51
|
+
if (message?.role !== "assistant" || message.status?.type !== "requires-action" || message.status.reason !== "tool-calls") return /* @__PURE__ */ new Set();
|
|
52
|
+
const permitted = listedPendingToolCallIds(message);
|
|
53
|
+
return new Set(Array.from(iterateToolCallParts(message.content)).filter((part) => !isResolvedToolCall(part) && (!permitted || permitted.has(part.toolCallId))).map((part) => part.toolCallId));
|
|
54
|
+
};
|
|
16
55
|
const isActivityPart = (part) => part.type === "data" && part.name.startsWith("agui-activity/");
|
|
17
56
|
const FALLBACK_USER_STATUS = {
|
|
18
57
|
type: "complete",
|
|
@@ -34,7 +73,7 @@ const mergeAgUiNamespace = (current, incoming) => {
|
|
|
34
73
|
};
|
|
35
74
|
return merged;
|
|
36
75
|
};
|
|
37
|
-
var
|
|
76
|
+
var AgUiThreadController = class {
|
|
38
77
|
agent;
|
|
39
78
|
logger;
|
|
40
79
|
showThinking;
|
|
@@ -63,24 +102,16 @@ var AgUiThreadRuntimeCore = class {
|
|
|
63
102
|
headId: repository.headId
|
|
64
103
|
};
|
|
65
104
|
} });
|
|
66
|
-
|
|
105
|
+
state = createAgUiThreadState();
|
|
67
106
|
abortController = null;
|
|
68
107
|
activeRunAgent = null;
|
|
69
|
-
stateSnapshot;
|
|
70
108
|
history;
|
|
71
|
-
lastRunConfig;
|
|
72
109
|
assistantHistoryParents = /* @__PURE__ */ new Map();
|
|
73
110
|
snapshotHistoryIds = /* @__PURE__ */ new Set();
|
|
74
111
|
persistedHistoryIds = /* @__PURE__ */ new Set();
|
|
75
|
-
historyWrites =
|
|
76
|
-
_isLoading = false;
|
|
112
|
+
historyWrites = createHistoryWriteChain();
|
|
77
113
|
_historyImportPromise;
|
|
78
|
-
historyMutationGeneration = 0;
|
|
79
114
|
_loadPromise;
|
|
80
|
-
_loadRequested = false;
|
|
81
|
-
pendingResume = null;
|
|
82
|
-
pendingA2uiResumeOwner = null;
|
|
83
|
-
pendingA2uiAction;
|
|
84
115
|
constructor(options) {
|
|
85
116
|
this.agent = options.agent;
|
|
86
117
|
this.logger = options.logger;
|
|
@@ -99,11 +130,14 @@ var AgUiThreadRuntimeCore = class {
|
|
|
99
130
|
return () => this.listeners.delete(listener);
|
|
100
131
|
};
|
|
101
132
|
getSnapshot = () => this.snapshot ??= {
|
|
102
|
-
isLoading: this.
|
|
103
|
-
isRunning: this.
|
|
133
|
+
isLoading: this.state.isLoading,
|
|
134
|
+
isRunning: this.state.isRunning,
|
|
104
135
|
messageRepository: this.session.export(),
|
|
105
|
-
state: this.stateSnapshot
|
|
136
|
+
state: this.state.stateSnapshot
|
|
106
137
|
};
|
|
138
|
+
dispatch(action) {
|
|
139
|
+
this.state = reduceAgUiThreadState(this.state, action);
|
|
140
|
+
}
|
|
107
141
|
publishUpdate() {
|
|
108
142
|
this.snapshot = void 0;
|
|
109
143
|
for (const listener of this.listeners) listener();
|
|
@@ -120,7 +154,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
120
154
|
this.onCancel = options.onCancel;
|
|
121
155
|
const previousHistory = this.history;
|
|
122
156
|
this.history = options.history;
|
|
123
|
-
if (this.
|
|
157
|
+
if (this.state.loadRequested && !this._loadPromise && !previousHistory && options.history && this.session.getMessages().length === 0) this.__internal_load();
|
|
124
158
|
}
|
|
125
159
|
attachRuntime(runtime) {
|
|
126
160
|
this.runtime = runtime;
|
|
@@ -138,16 +172,16 @@ var AgUiThreadRuntimeCore = class {
|
|
|
138
172
|
return this.session.export();
|
|
139
173
|
}
|
|
140
174
|
getState() {
|
|
141
|
-
return this.stateSnapshot;
|
|
175
|
+
return this.state.stateSnapshot;
|
|
142
176
|
}
|
|
143
177
|
isRunning() {
|
|
144
|
-
return this.
|
|
178
|
+
return this.state.isRunning;
|
|
145
179
|
}
|
|
146
180
|
get isLoading() {
|
|
147
|
-
return this.
|
|
181
|
+
return this.state.isLoading;
|
|
148
182
|
}
|
|
149
183
|
__internal_load() {
|
|
150
|
-
this.
|
|
184
|
+
this.dispatch({ type: "loadRequested" });
|
|
151
185
|
if (this._loadPromise) return this._loadPromise;
|
|
152
186
|
if (!this.history) return Promise.resolve();
|
|
153
187
|
const promise = this.history.load().then((repo) => {
|
|
@@ -161,7 +195,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
161
195
|
if (repo.state !== void 0) this.loadExternalState(repo.state);
|
|
162
196
|
return repo;
|
|
163
197
|
});
|
|
164
|
-
this.
|
|
198
|
+
this.dispatch({ type: "loadStarted" });
|
|
165
199
|
this._historyImportPromise = promise.then(() => {
|
|
166
200
|
this._historyImportPromise = void 0;
|
|
167
201
|
}, () => {
|
|
@@ -171,13 +205,13 @@ var AgUiThreadRuntimeCore = class {
|
|
|
171
205
|
if (repo?.unstable_resume) {
|
|
172
206
|
const parentId = repo.headId ?? this.session.headId;
|
|
173
207
|
const resumeStream = this.history?.resume?.bind(this.history);
|
|
174
|
-
await this.startRun(parentId, this.lastRunConfig, void 0, resumeStream);
|
|
208
|
+
await this.startRun(parentId, this.state.lastRunConfig, void 0, resumeStream);
|
|
175
209
|
}
|
|
176
210
|
}).catch((error) => {
|
|
177
211
|
this.logger.error?.("[agui] failed to load history", error);
|
|
178
212
|
this.reportError(error);
|
|
179
213
|
}).finally(() => {
|
|
180
|
-
this.
|
|
214
|
+
this.dispatch({ type: "loadFinished" });
|
|
181
215
|
this.publishUpdate();
|
|
182
216
|
});
|
|
183
217
|
this.publishUpdate();
|
|
@@ -186,15 +220,15 @@ var AgUiThreadRuntimeCore = class {
|
|
|
186
220
|
waitForHistoryImport() {
|
|
187
221
|
const historyImport = this._historyImportPromise;
|
|
188
222
|
if (!historyImport) return void 0;
|
|
189
|
-
const generation = this.historyMutationGeneration;
|
|
190
|
-
return historyImport.then(() => generation === this.historyMutationGeneration && !this.isThreadSwitching?.());
|
|
223
|
+
const generation = this.state.historyMutationGeneration;
|
|
224
|
+
return historyImport.then(() => generation === this.state.historyMutationGeneration && !this.isThreadSwitching?.());
|
|
191
225
|
}
|
|
192
226
|
deferUntilHistoryImported(callback) {
|
|
193
227
|
const historyImport = this._historyImportPromise;
|
|
194
228
|
if (!historyImport) return false;
|
|
195
|
-
const generation = this.historyMutationGeneration;
|
|
229
|
+
const generation = this.state.historyMutationGeneration;
|
|
196
230
|
historyImport.then(() => {
|
|
197
|
-
if (generation !== this.historyMutationGeneration || this.isThreadSwitching?.()) return;
|
|
231
|
+
if (generation !== this.state.historyMutationGeneration || this.isThreadSwitching?.()) return;
|
|
198
232
|
try {
|
|
199
233
|
callback();
|
|
200
234
|
} catch (error) {
|
|
@@ -207,9 +241,9 @@ var AgUiThreadRuntimeCore = class {
|
|
|
207
241
|
const historyImport = this._historyImportPromise;
|
|
208
242
|
if (historyImport) {
|
|
209
243
|
const wasAtTail = message.parentId === this.session.headId;
|
|
210
|
-
const generation = this.historyMutationGeneration;
|
|
244
|
+
const generation = this.state.historyMutationGeneration;
|
|
211
245
|
await historyImport;
|
|
212
|
-
if (generation !== this.historyMutationGeneration || this.isThreadSwitching?.()) return;
|
|
246
|
+
if (generation !== this.state.historyMutationGeneration || this.isThreadSwitching?.()) return;
|
|
213
247
|
if (wasAtTail) message = {
|
|
214
248
|
...message,
|
|
215
249
|
parentId: this.session.headId
|
|
@@ -235,10 +269,10 @@ var AgUiThreadRuntimeCore = class {
|
|
|
235
269
|
}
|
|
236
270
|
maybeAutoCancelPendingToolCalls() {
|
|
237
271
|
if (this.autoCancelPendingToolCalls === false) return;
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
272
|
+
for (const assistant of this.getPendingToolCallMessages()) {
|
|
273
|
+
this.cancelUnresolvedToolCalls(assistant.id);
|
|
274
|
+
this.maybeCompleteAfterToolResults(assistant.id);
|
|
275
|
+
}
|
|
242
276
|
}
|
|
243
277
|
appendEntry(message) {
|
|
244
278
|
const threadMessage = this.toThreadMessage(message);
|
|
@@ -260,7 +294,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
260
294
|
await this.startRun(parentId, config.runConfig);
|
|
261
295
|
}
|
|
262
296
|
async cancel() {
|
|
263
|
-
this.
|
|
297
|
+
this.dispatch({ type: "historyMutationAdvanced" });
|
|
264
298
|
this.abortActiveRun();
|
|
265
299
|
}
|
|
266
300
|
abortActiveRun() {
|
|
@@ -286,7 +320,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
286
320
|
const historyImport = this.waitForHistoryImport();
|
|
287
321
|
if (historyImport && !await historyImport) return;
|
|
288
322
|
this.assertNoPendingInterrupts();
|
|
289
|
-
await this.startRun(config.parentId, config.runConfig ?? this.lastRunConfig, void 0, config.stream);
|
|
323
|
+
await this.startRun(config.parentId, config.runConfig ?? this.state.lastRunConfig, void 0, config.stream, true);
|
|
290
324
|
}
|
|
291
325
|
async resumeInFlightRun(messages) {
|
|
292
326
|
const historyImport = this.waitForHistoryImport();
|
|
@@ -300,7 +334,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
300
334
|
}
|
|
301
335
|
const parentId = messages.at(-1)?.id ?? null;
|
|
302
336
|
try {
|
|
303
|
-
await this.startRun(parentId, this.lastRunConfig, void 0, resumeStream);
|
|
337
|
+
await this.startRun(parentId, this.state.lastRunConfig, void 0, resumeStream);
|
|
304
338
|
} catch {}
|
|
305
339
|
}
|
|
306
340
|
assertNoPendingInterrupts() {
|
|
@@ -308,9 +342,20 @@ var AgUiThreadRuntimeCore = class {
|
|
|
308
342
|
throw new Error("[agui] cannot start a new run while interrupts are pending; resolve them with submitInterruptResponses()");
|
|
309
343
|
}
|
|
310
344
|
findRequiresActionAssistant(reason) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
345
|
+
return this.getTrailingAssistantTurn().findLast((message) => message.status?.type === "requires-action" && message.status.reason === reason) ?? null;
|
|
346
|
+
}
|
|
347
|
+
getTrailingAssistantTurn() {
|
|
348
|
+
const messages = this.getMessages();
|
|
349
|
+
const turn = [];
|
|
350
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
351
|
+
const message = messages[index];
|
|
352
|
+
if (message?.role !== "assistant") break;
|
|
353
|
+
turn.unshift(message);
|
|
354
|
+
}
|
|
355
|
+
return turn;
|
|
356
|
+
}
|
|
357
|
+
getPendingToolCallMessages() {
|
|
358
|
+
return this.getTrailingAssistantTurn().filter((message) => message.status?.type === "requires-action" && message.status.reason === "tool-calls");
|
|
314
359
|
}
|
|
315
360
|
getPendingInterrupts() {
|
|
316
361
|
const assistant = this.findRequiresActionAssistant("interrupt");
|
|
@@ -323,16 +368,18 @@ var AgUiThreadRuntimeCore = class {
|
|
|
323
368
|
};
|
|
324
369
|
}
|
|
325
370
|
getPendingToolCalls() {
|
|
326
|
-
const assistant = this.findRequiresActionAssistant("tool-calls");
|
|
327
|
-
if (!assistant) return null;
|
|
328
371
|
const toolCallIds = [];
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
372
|
+
let messageId;
|
|
373
|
+
for (const assistant of this.getPendingToolCallMessages()) {
|
|
374
|
+
const listed = listedPendingToolCallIds(assistant);
|
|
375
|
+
for (const part of iterateToolCallParts(assistant.content)) {
|
|
376
|
+
if (isResolvedToolCall(part) || listed && !listed.has(part.toolCallId)) continue;
|
|
377
|
+
messageId ??= assistant.id;
|
|
378
|
+
toolCallIds.push(part.toolCallId);
|
|
379
|
+
}
|
|
332
380
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
messageId: assistant.id,
|
|
381
|
+
return messageId === void 0 ? null : {
|
|
382
|
+
messageId,
|
|
336
383
|
toolCallIds
|
|
337
384
|
};
|
|
338
385
|
}
|
|
@@ -345,7 +392,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
345
392
|
const openIds = pending.interrupts.map((i) => i.id);
|
|
346
393
|
const missing = openIds.filter((id) => !responsesById.has(id));
|
|
347
394
|
if (missing.length > 0) throw new Error(`[agui] submitInterruptResponses: missing responses for open interrupts: ${missing.join(", ")}`);
|
|
348
|
-
this.assertInterruptsAnswerable("submitInterruptResponses", pending.interrupts);
|
|
395
|
+
this.assertInterruptsAnswerable("submitInterruptResponses", pending.interrupts, responsesById);
|
|
349
396
|
await this.resumeWithResponses(pending.messageId, openIds.map((id) => responsesById.get(id)));
|
|
350
397
|
}
|
|
351
398
|
/**
|
|
@@ -355,8 +402,9 @@ var AgUiThreadRuntimeCore = class {
|
|
|
355
402
|
* unretryable.
|
|
356
403
|
*/
|
|
357
404
|
async resumeWithResponses(messageId, resume) {
|
|
405
|
+
const parentId = this.session.headId ?? messageId;
|
|
358
406
|
this.clearPendingInterrupts(messageId, resume);
|
|
359
|
-
await this.startRun(
|
|
407
|
+
await this.startRun(parentId, this.state.lastRunConfig, {
|
|
360
408
|
entries: resume,
|
|
361
409
|
interruptedMessageId: messageId
|
|
362
410
|
});
|
|
@@ -375,15 +423,15 @@ var AgUiThreadRuntimeCore = class {
|
|
|
375
423
|
}
|
|
376
424
|
invokeRuntimeCallback("onError", this.onError, error instanceof Error ? error : new Error(String(error)));
|
|
377
425
|
}
|
|
378
|
-
assertInterruptsAnswerable(method, interrupts) {
|
|
426
|
+
assertInterruptsAnswerable(method, interrupts, responsesById) {
|
|
379
427
|
const now = Date.now();
|
|
380
428
|
for (const interrupt of interrupts) {
|
|
381
429
|
if (!interrupt.expiresAt) continue;
|
|
382
430
|
const expiry = new Date(interrupt.expiresAt).getTime();
|
|
383
431
|
if (Number.isNaN(expiry)) throw new Error(`[agui] ${method}: interrupt ${interrupt.id} has malformed expiresAt "${interrupt.expiresAt}"`);
|
|
384
|
-
if (expiry <= now) throw new Error(`[agui] ${method}: interrupt ${interrupt.id} expired at ${interrupt.expiresAt}`);
|
|
432
|
+
if (expiry <= now && responsesById?.get(interrupt.id)?.status !== "cancelled") throw new Error(`[agui] ${method}: interrupt ${interrupt.id} expired at ${interrupt.expiresAt}`);
|
|
385
433
|
}
|
|
386
|
-
if (this.
|
|
434
|
+
if (this.state.isRunning) throw new Error(`[agui] ${method}: a run is already in progress`);
|
|
387
435
|
}
|
|
388
436
|
async respondToToolApproval(options) {
|
|
389
437
|
const historyImport = this.waitForHistoryImport();
|
|
@@ -415,12 +463,13 @@ var AgUiThreadRuntimeCore = class {
|
|
|
415
463
|
if (historyImport && !await historyImport) return;
|
|
416
464
|
const pending = this.getPendingInterrupts();
|
|
417
465
|
if (!pending) {
|
|
418
|
-
|
|
419
|
-
if (pendingTools) {
|
|
466
|
+
if (this.getPendingToolCalls()) {
|
|
420
467
|
if (responses?.length) throw new Error("[agui] steerAway: responses are only valid for pending interrupts");
|
|
421
|
-
if (this.
|
|
422
|
-
this.
|
|
423
|
-
|
|
468
|
+
if (this.state.isRunning) throw new Error("[agui] steerAway: a run is already in progress");
|
|
469
|
+
for (const assistant of this.getPendingToolCallMessages()) {
|
|
470
|
+
this.cancelUnresolvedToolCalls(assistant.id);
|
|
471
|
+
this.maybeCompleteAfterToolResults(assistant.id);
|
|
472
|
+
}
|
|
424
473
|
const normalized = this.toAppendMessage(message);
|
|
425
474
|
const threadMessageId = this.appendEntry(normalized);
|
|
426
475
|
await this.startRun(threadMessageId, normalized.runConfig);
|
|
@@ -431,7 +480,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
431
480
|
return;
|
|
432
481
|
}
|
|
433
482
|
const resume = this.resolveSteerAwayResume(pending.interrupts, responses);
|
|
434
|
-
if (this.
|
|
483
|
+
if (this.state.isRunning) throw new Error("[agui] steerAway: a run is already in progress");
|
|
435
484
|
const normalized = this.toAppendMessage(message);
|
|
436
485
|
this.clearPendingInterrupts(pending.messageId, resume);
|
|
437
486
|
const threadMessageId = this.appendEntry(normalized);
|
|
@@ -597,7 +646,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
597
646
|
const message = this.session.tryGetMessage(sessionMessageId)?.message;
|
|
598
647
|
const history = this.history;
|
|
599
648
|
if (!message || !history?.update || !this.isPersistableStatus(message.status)) return;
|
|
600
|
-
await this.
|
|
649
|
+
await this.historyWrites.chain(sessionMessageId, () => history.update({
|
|
601
650
|
parentId: item.parentId,
|
|
602
651
|
message
|
|
603
652
|
}));
|
|
@@ -614,25 +663,42 @@ var AgUiThreadRuntimeCore = class {
|
|
|
614
663
|
const userAction = { ...action };
|
|
615
664
|
delete userAction.type;
|
|
616
665
|
if (!("timestamp" in userAction)) userAction.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
617
|
-
this.
|
|
666
|
+
this.dispatch({
|
|
667
|
+
type: "a2uiActionDeferred",
|
|
668
|
+
action: userAction
|
|
669
|
+
});
|
|
618
670
|
const owner = this.abortController;
|
|
619
671
|
if (owner) {
|
|
620
|
-
this.
|
|
672
|
+
this.dispatch({
|
|
673
|
+
type: "a2uiResumeDeferred",
|
|
674
|
+
owner
|
|
675
|
+
});
|
|
621
676
|
return;
|
|
622
677
|
}
|
|
623
678
|
this.startResumeRun(parentId);
|
|
624
679
|
}
|
|
625
|
-
maybeResumeAfterToolResults(messageId) {
|
|
680
|
+
maybeResumeAfterToolResults(messageId, accountedResultIds) {
|
|
626
681
|
if (!this.maybeCompleteAfterToolResults(messageId)) return;
|
|
682
|
+
if (accountedResultIds) {
|
|
683
|
+
const message = this.session.tryGetMessage(messageId)?.message;
|
|
684
|
+
if (message?.role !== "assistant" || !Array.from(iterateToolCallParts(message.content)).some((part) => isResolvedToolCall(part) && !accountedResultIds.has(part.toolCallId))) return;
|
|
685
|
+
}
|
|
686
|
+
if (this.getPendingInterrupts()) return;
|
|
687
|
+
const turn = this.getTrailingAssistantTurn();
|
|
688
|
+
if (!turn.some((message) => message.id === messageId)) return;
|
|
627
689
|
const owner = this.abortController;
|
|
628
690
|
if (owner) {
|
|
629
|
-
this.
|
|
691
|
+
this.dispatch({
|
|
692
|
+
type: "resumeDeferred",
|
|
630
693
|
owner,
|
|
631
694
|
messageId
|
|
632
|
-
};
|
|
695
|
+
});
|
|
633
696
|
return;
|
|
634
697
|
}
|
|
635
|
-
this.
|
|
698
|
+
if (this.getPendingToolCalls()) return;
|
|
699
|
+
const headId = turn.at(-1)?.id;
|
|
700
|
+
if (headId === void 0) return;
|
|
701
|
+
this.startResumeRun(headId);
|
|
636
702
|
}
|
|
637
703
|
maybeCompleteAfterToolResults(messageId) {
|
|
638
704
|
const message = this.session.tryGetMessage(messageId)?.message;
|
|
@@ -640,9 +706,13 @@ var AgUiThreadRuntimeCore = class {
|
|
|
640
706
|
const assistant = message;
|
|
641
707
|
if (assistant.status?.type !== "requires-action" || assistant.status.reason !== "tool-calls") return false;
|
|
642
708
|
let allResolved = true;
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
709
|
+
const listed = listedPendingToolCallIds(assistant);
|
|
710
|
+
for (const part of iterateToolCallParts(assistant.content)) {
|
|
711
|
+
if (listed && !listed.has(part.toolCallId)) continue;
|
|
712
|
+
if (!isResolvedToolCall(part)) {
|
|
713
|
+
allResolved = false;
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
646
716
|
}
|
|
647
717
|
if (!allResolved) return false;
|
|
648
718
|
if (!this.session.updateMessage(messageId, (current) => current.role === "assistant" ? {
|
|
@@ -650,14 +720,15 @@ var AgUiThreadRuntimeCore = class {
|
|
|
650
720
|
status: {
|
|
651
721
|
type: "complete",
|
|
652
722
|
reason: "unknown"
|
|
653
|
-
}
|
|
723
|
+
},
|
|
724
|
+
metadata: withoutPendingToolCallIds(current.metadata)
|
|
654
725
|
} : current)) return false;
|
|
655
726
|
this.publishUpdate();
|
|
656
727
|
this.persistAssistantHistory(messageId);
|
|
657
728
|
return true;
|
|
658
729
|
}
|
|
659
730
|
startResumeRun(messageId) {
|
|
660
|
-
this.startRun(messageId, this.lastRunConfig).catch((error) => {
|
|
731
|
+
this.startRun(messageId, this.state.lastRunConfig).catch((error) => {
|
|
661
732
|
this.reportError(error);
|
|
662
733
|
});
|
|
663
734
|
}
|
|
@@ -674,8 +745,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
674
745
|
}
|
|
675
746
|
};
|
|
676
747
|
});
|
|
677
|
-
this.
|
|
678
|
-
this.pendingA2uiAction = void 0;
|
|
748
|
+
this.dispatch({ type: "a2uiContinuationsCleared" });
|
|
679
749
|
this.assistantHistoryParents.clear();
|
|
680
750
|
if (messages.length === 0) this.session.clear();
|
|
681
751
|
else {
|
|
@@ -683,6 +753,10 @@ var AgUiThreadRuntimeCore = class {
|
|
|
683
753
|
let lastAppliedId = null;
|
|
684
754
|
let hardReplace = false;
|
|
685
755
|
const seen = /* @__PURE__ */ new Set();
|
|
756
|
+
const retainToolResults = (existing, message) => existing?.role === "assistant" && message.role === "assistant" ? {
|
|
757
|
+
...message,
|
|
758
|
+
content: this.preserveToolResults(existing.content, message.content)
|
|
759
|
+
} : message;
|
|
686
760
|
for (const message of messages) {
|
|
687
761
|
if (seen.has(message.id)) continue;
|
|
688
762
|
seen.add(message.id);
|
|
@@ -691,11 +765,12 @@ var AgUiThreadRuntimeCore = class {
|
|
|
691
765
|
hardReplace = true;
|
|
692
766
|
break;
|
|
693
767
|
}
|
|
694
|
-
this.session.addOrUpdateMessage(expectedParentId, message);
|
|
768
|
+
this.session.addOrUpdateMessage(expectedParentId, retainToolResults(existing?.message, message));
|
|
695
769
|
expectedParentId = message.id;
|
|
696
770
|
lastAppliedId = message.id;
|
|
697
771
|
}
|
|
698
772
|
if (hardReplace) {
|
|
773
|
+
const previous = new Map(this.getMessageRepository().messages.map(({ message }) => [message.id, message]));
|
|
699
774
|
this.session.clear();
|
|
700
775
|
expectedParentId = null;
|
|
701
776
|
lastAppliedId = null;
|
|
@@ -703,39 +778,55 @@ var AgUiThreadRuntimeCore = class {
|
|
|
703
778
|
for (const message of messages) {
|
|
704
779
|
if (seen.has(message.id)) continue;
|
|
705
780
|
seen.add(message.id);
|
|
706
|
-
this.session.addOrUpdateMessage(expectedParentId, message);
|
|
781
|
+
this.session.addOrUpdateMessage(expectedParentId, retainToolResults(previous.get(message.id), message));
|
|
707
782
|
expectedParentId = message.id;
|
|
708
783
|
lastAppliedId = message.id;
|
|
709
784
|
}
|
|
710
785
|
}
|
|
711
786
|
this.session.resetHead(lastAppliedId);
|
|
787
|
+
for (const messageId of seen) this.maybeCompleteAfterToolResults(messageId);
|
|
712
788
|
}
|
|
713
789
|
this.snapshotHistoryIds.clear();
|
|
714
790
|
for (const { message } of this.getMessageRepository().messages) this.snapshotHistoryIds.add(message.id);
|
|
715
|
-
const
|
|
716
|
-
if (
|
|
791
|
+
const pendingResume = this.state.pendingResume;
|
|
792
|
+
if (pendingResume) {
|
|
793
|
+
const selectedResultOwnerIds = new Set(this.session.getMessages().filter(hasResolvedToolCalls).map((message) => message.id));
|
|
794
|
+
const retained = pendingResume.messageIds.filter((id) => selectedResultOwnerIds.has(id));
|
|
795
|
+
if (retained.length !== pendingResume.messageIds.length) {
|
|
796
|
+
this.dispatch({ type: "resumeCleared" });
|
|
797
|
+
for (const messageId of retained) this.dispatch({
|
|
798
|
+
type: "resumeDeferred",
|
|
799
|
+
owner: pendingResume.owner,
|
|
800
|
+
messageId
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
}
|
|
717
804
|
this.publishUpdate();
|
|
718
805
|
}
|
|
719
806
|
loadExternalState(state) {
|
|
720
|
-
this.
|
|
807
|
+
this.dispatch({
|
|
808
|
+
type: "stateSnapshot",
|
|
809
|
+
snapshot: state
|
|
810
|
+
});
|
|
721
811
|
this.publishUpdate();
|
|
722
812
|
}
|
|
723
813
|
setState(next) {
|
|
724
|
-
|
|
814
|
+
const snapshot = typeof next === "function" ? next(this.state.stateSnapshot) : next;
|
|
815
|
+
this.dispatch({
|
|
816
|
+
type: "stateSnapshot",
|
|
817
|
+
snapshot
|
|
818
|
+
});
|
|
725
819
|
this.publishUpdate();
|
|
726
820
|
}
|
|
727
821
|
resetState() {
|
|
728
|
-
this.
|
|
822
|
+
this.dispatch({ type: "resetState" });
|
|
729
823
|
this.publishUpdate();
|
|
730
824
|
}
|
|
731
825
|
resetThreadState() {
|
|
732
|
-
this.
|
|
826
|
+
this.dispatch({ type: "historyMutationAdvanced" });
|
|
733
827
|
const controller = this.abortController;
|
|
734
828
|
const activeRunAgent = this.activeRunAgent;
|
|
735
|
-
this.
|
|
736
|
-
this.pendingResume = null;
|
|
737
|
-
this.pendingA2uiResumeOwner = null;
|
|
738
|
-
this.pendingA2uiAction = void 0;
|
|
829
|
+
this.dispatch({ type: "resetThread" });
|
|
739
830
|
if (controller) {
|
|
740
831
|
this.abortController = null;
|
|
741
832
|
this.activeRunAgent = null;
|
|
@@ -749,52 +840,245 @@ var AgUiThreadRuntimeCore = class {
|
|
|
749
840
|
}
|
|
750
841
|
} else this.publishUpdate();
|
|
751
842
|
}
|
|
752
|
-
async startRun(parentId, runConfig, resume, resumeStream) {
|
|
843
|
+
async startRun(parentId, runConfig, resume, resumeStream, replayExistingMessages = false) {
|
|
753
844
|
if (this.isThreadSwitching?.()) return;
|
|
754
845
|
if (!this.supersedeActiveRun()) return;
|
|
846
|
+
const generation = this.state.historyMutationGeneration;
|
|
755
847
|
const normalizedRunConfig = runConfig ?? {};
|
|
756
|
-
this.
|
|
848
|
+
this.dispatch({
|
|
849
|
+
type: "runConfigRecorded",
|
|
850
|
+
runConfig: normalizedRunConfig
|
|
851
|
+
});
|
|
757
852
|
const parent = parentId === null ? void 0 : this.session.tryGetMessage(parentId);
|
|
758
853
|
const shouldEagerlyInsertAssistant = parentId !== null && parent !== void 0 && parentId !== this.session.headId;
|
|
759
854
|
const historicalMessages = [...shouldEagerlyInsertAssistant ? this.session.tryGetMessages(parentId) ?? this.session.getMessages() : this.session.getMessages()];
|
|
760
855
|
const runStartMessageIds = new Set(this.getMessageRepository().messages.map(({ message }) => message.id));
|
|
856
|
+
const selectedMessages = this.session.getMessages();
|
|
857
|
+
const parentIndex = selectedMessages.findIndex((message) => message.id === parentId);
|
|
858
|
+
let replayStart = parentIndex + 1;
|
|
859
|
+
if (selectedMessages[parentIndex]?.role === "assistant") {
|
|
860
|
+
replayStart = parentIndex;
|
|
861
|
+
while (replayStart > 0 && selectedMessages[replayStart - 1]?.role === "assistant") replayStart--;
|
|
862
|
+
}
|
|
863
|
+
const replayMessageIds = [];
|
|
864
|
+
if (replayExistingMessages && !resumeStream && parentIndex >= 0) for (const message of selectedMessages.slice(replayStart)) {
|
|
865
|
+
if (message.role !== "assistant") break;
|
|
866
|
+
replayMessageIds.push(message.id);
|
|
867
|
+
}
|
|
868
|
+
const accountedResultIds = new Set(historicalMessages.flatMap((message) => message.role === "assistant" ? Array.from(iterateToolCallParts(message.content)).filter(isResolvedToolCall).map((part) => part.toolCallId) : []));
|
|
869
|
+
const candidateContexts = /* @__PURE__ */ new Map();
|
|
870
|
+
for (const message of selectedMessages) {
|
|
871
|
+
if (message.role !== "assistant" || !replayMessageIds.includes(message.id)) continue;
|
|
872
|
+
candidateContexts.set(message.id, {
|
|
873
|
+
originalContent: message.content,
|
|
874
|
+
accountedResultIds,
|
|
875
|
+
admittedToolCallIds: actionableToolCallIds(message),
|
|
876
|
+
merge: createReplayContentMerger()
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
let replayContexts = /* @__PURE__ */ new Map();
|
|
880
|
+
let lastReplayedIndex = -1;
|
|
881
|
+
let replayEnded = replayMessageIds.length === 0;
|
|
882
|
+
const replayTail = replayMessageIds.at(-1);
|
|
761
883
|
const joinStrategy = this.joinStrategy;
|
|
762
884
|
const hasRunUserBoundary = parent?.message.role === "user" && !resume;
|
|
763
885
|
let pendingError = null;
|
|
764
886
|
const assistantParentId = parent ? parentId : this.session.headId;
|
|
765
887
|
let assistantMessageId;
|
|
888
|
+
let snapshotAssistantId;
|
|
889
|
+
let replayInterruptMessageId;
|
|
766
890
|
let assistantCollided = false;
|
|
891
|
+
const heldMessages = /* @__PURE__ */ new Map();
|
|
892
|
+
let frameOwnerIds = /* @__PURE__ */ new Set();
|
|
893
|
+
let restrictedPendingToolCallIds;
|
|
894
|
+
let settlingRun = false;
|
|
767
895
|
const ensureAssistant = (allowRecreate = false) => {
|
|
768
896
|
const cached = assistantMessageId;
|
|
769
897
|
if (cached !== void 0 && this.session.tryGetMessage(cached)) return cached;
|
|
770
898
|
if (cached !== void 0 && (assistantCollided || !allowRecreate)) return cached;
|
|
771
899
|
const repositoryHeadId = this.session.headId;
|
|
772
|
-
const parentId = (cached === void 0 || shouldEagerlyInsertAssistant && (repositoryHeadId === null || runStartMessageIds.has(repositoryHeadId))) && assistantParentId && this.session.hasMessage(assistantParentId) ? assistantParentId : repositoryHeadId;
|
|
900
|
+
const parentId = replayContexts.size === 0 && (cached === void 0 || shouldEagerlyInsertAssistant && (repositoryHeadId === null || runStartMessageIds.has(repositoryHeadId))) && (replayMessageIds.length === 0 || repositoryHeadId === null || runStartMessageIds.has(repositoryHeadId)) && assistantParentId && this.session.hasMessage(assistantParentId) ? assistantParentId : repositoryHeadId;
|
|
773
901
|
const created = this.insertAssistantPlaceholder(parentId, cached);
|
|
774
902
|
assistantMessageId = created;
|
|
775
903
|
this.markPendingAssistantHistory(created, parentId);
|
|
776
904
|
return created;
|
|
777
905
|
};
|
|
778
|
-
if (shouldEagerlyInsertAssistant) ensureAssistant();
|
|
906
|
+
if (shouldEagerlyInsertAssistant && replayEnded) ensureAssistant();
|
|
907
|
+
const settleHeldMessages = (terminalStatus) => {
|
|
908
|
+
const pendingMessages = [];
|
|
909
|
+
const producerPending = restrictedPendingToolCallIds;
|
|
910
|
+
const canWaitForTools = terminalStatus.type === "complete" || terminalStatus.type === "requires-action";
|
|
911
|
+
if (producerPending && canWaitForTools) for (const message of this.getTrailingAssistantTurn()) {
|
|
912
|
+
const replay = replayContexts.get(message.id);
|
|
913
|
+
if (message.id === assistantMessageId || heldMessages.has(message.id) || !replay || message.status?.type === "requires-action" && message.status.reason === "interrupt") continue;
|
|
914
|
+
if (replay.admittedToolCallIds.size > 0 || actionableToolCallIds(message).size > 0 || Array.from(iterateToolCallParts(message.content)).some((part) => producerPending.has(part.toolCallId) && !accountedResultIds.has(part.toolCallId))) heldMessages.set(message.id, /* @__PURE__ */ new Set());
|
|
915
|
+
}
|
|
916
|
+
for (const [messageId, openToolCallIds] of heldMessages) {
|
|
917
|
+
const existing = this.session.tryGetMessage(messageId);
|
|
918
|
+
if (existing) this.markPendingAssistantHistory(messageId, existing.parentId);
|
|
919
|
+
let requiresToolResult = false;
|
|
920
|
+
if (this.session.updateMessage(messageId, (message) => {
|
|
921
|
+
if (message.role !== "assistant") return message;
|
|
922
|
+
const assistant = message;
|
|
923
|
+
const pendingToolCallIds = producerPending ? Array.from(iterateToolCallParts(assistant.content)).filter((part) => producerPending.has(part.toolCallId) && (!isResolvedToolCall(part) || openToolCallIds.has(part.toolCallId) || replayContexts.has(messageId) && !accountedResultIds.has(part.toolCallId))).map((part) => part.toolCallId) : Array.from(openToolCallIds);
|
|
924
|
+
const replay = replayContexts.get(messageId);
|
|
925
|
+
if (replay) replay.admittedToolCallIds = new Set(pendingToolCallIds);
|
|
926
|
+
const status = canWaitForTools ? pendingToolCallIds.length > 0 ? {
|
|
927
|
+
type: "requires-action",
|
|
928
|
+
reason: "tool-calls"
|
|
929
|
+
} : {
|
|
930
|
+
type: "complete",
|
|
931
|
+
reason: "unknown"
|
|
932
|
+
} : terminalStatus;
|
|
933
|
+
requiresToolResult = status.type === "requires-action";
|
|
934
|
+
const metadata = withoutPendingToolCallIds(assistant.metadata);
|
|
935
|
+
return {
|
|
936
|
+
...assistant,
|
|
937
|
+
status,
|
|
938
|
+
metadata: status.type === "requires-action" && (restrictedPendingToolCallIds || replayContexts.has(messageId)) ? withPendingToolCallIds(metadata, pendingToolCallIds) : metadata
|
|
939
|
+
};
|
|
940
|
+
})) {
|
|
941
|
+
this.publishUpdate();
|
|
942
|
+
this.persistAssistantHistory(messageId);
|
|
943
|
+
if (requiresToolResult) pendingMessages.push(messageId);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
heldMessages.clear();
|
|
947
|
+
return pendingMessages;
|
|
948
|
+
};
|
|
949
|
+
const accountProducerResults = (content) => {
|
|
950
|
+
for (const part of iterateToolCallParts(content)) if (isResolvedToolCall(part)) accountedResultIds.add(part.toolCallId);
|
|
951
|
+
};
|
|
952
|
+
const reconcileReplaySnapshot = (snapshot, previous) => {
|
|
953
|
+
accountProducerResults(snapshot.content);
|
|
954
|
+
const replay = candidateContexts.get(snapshot.id);
|
|
955
|
+
if (!replay) return snapshot;
|
|
956
|
+
replay.admittedToolCallIds = actionableToolCallIds(snapshot);
|
|
957
|
+
if (heldMessages.has(snapshot.id)) heldMessages.set(snapshot.id, new Set(replay.admittedToolCallIds));
|
|
958
|
+
if (!replayContexts.has(snapshot.id)) return snapshot;
|
|
959
|
+
return previous ? {
|
|
960
|
+
...snapshot,
|
|
961
|
+
content: this.preserveToolResults(previous.content, snapshot.content, replay.originalContent)
|
|
962
|
+
} : snapshot;
|
|
963
|
+
};
|
|
779
964
|
const applyUpdate = (update) => {
|
|
965
|
+
if (!assistantCollided && replayContexts.size && update.content !== void 0) accountProducerResults(update.content);
|
|
966
|
+
let ownerParts;
|
|
967
|
+
let contentOwnerIds;
|
|
780
968
|
const hasStreamContent = Array.isArray(update.content) && update.content.some((part) => part.type !== "data");
|
|
781
|
-
|
|
782
|
-
|
|
969
|
+
if (!replayEnded && assistantMessageId === void 0) {
|
|
970
|
+
if (!update.status || update.status.type === "running") return;
|
|
971
|
+
replayEnded = true;
|
|
972
|
+
if (replayContexts.size === 0 && snapshotAssistantId !== void 0 && this.session.headId === snapshotAssistantId && !hasStreamContent) {
|
|
973
|
+
if (update.status.type !== "complete") this.updateAssistantMessage(snapshotAssistantId, {
|
|
974
|
+
status: update.status,
|
|
975
|
+
...update.metadata ? { metadata: update.metadata } : {}
|
|
976
|
+
});
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
const streamMessageId = assistantMessageId;
|
|
981
|
+
let primaryMessageId = streamMessageId;
|
|
982
|
+
const terminalStatus = update.status;
|
|
983
|
+
const replayInterrupt = replayContexts.size > 0 && terminalStatus?.type === "requires-action" && terminalStatus.reason === "interrupt";
|
|
984
|
+
if (streamMessageId !== void 0 && !assistantCollided && replayContexts.size > 0 && (update.content !== void 0 || replayInterrupt)) {
|
|
985
|
+
const owners = this.getTrailingAssistantTurn().filter((message) => replayContexts.has(message.id));
|
|
986
|
+
const grouped = update.content !== void 0 ? groupReplayToolContent(owners, update.content, streamMessageId) : /* @__PURE__ */ new Map([[streamMessageId, [...update.content ?? []]]]);
|
|
987
|
+
if (update.content !== void 0) {
|
|
988
|
+
const currentOwnerIds = new Set(grouped.keys());
|
|
989
|
+
for (const ownerId of frameOwnerIds) grouped.set(ownerId, grouped.get(ownerId) ?? []);
|
|
990
|
+
contentOwnerIds = new Set(grouped.keys());
|
|
991
|
+
frameOwnerIds = currentOwnerIds;
|
|
992
|
+
}
|
|
993
|
+
ownerParts = grouped;
|
|
994
|
+
if (replayInterrupt) {
|
|
995
|
+
const interrupts = (update.metadata?.custom?.[AG_UI_METADATA_NAMESPACE])?.interrupts;
|
|
996
|
+
const approvals = projectAgUiToolApprovals(interrupts);
|
|
997
|
+
for (const owner of owners) if (Array.from(iterateToolCallParts(owner.content)).some((part) => approvals.has(part.toolCallId))) grouped.set(owner.id, grouped.get(owner.id) ?? []);
|
|
998
|
+
const anchor = owners.find((message) => projectAgUiToolApprovals(interrupts, new Set(Array.from(iterateToolCallParts([...message.content, ...grouped.get(message.id) ?? []])).map((part) => part.toolCallId))).size > 0);
|
|
999
|
+
if (anchor) {
|
|
1000
|
+
primaryMessageId = anchor.id;
|
|
1001
|
+
replayInterruptMessageId = anchor.id;
|
|
1002
|
+
grouped.set(anchor.id, grouped.get(anchor.id) ?? []);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
const pendingMessages = settlingRun && terminalStatus && terminalStatus.type !== "running" ? settleHeldMessages(terminalStatus) : [];
|
|
1007
|
+
const frameMessageId = ensureAssistant(hasStreamContent);
|
|
1008
|
+
const messageId = primaryMessageId ?? frameMessageId;
|
|
1009
|
+
let primaryUpdate = update;
|
|
1010
|
+
if (ownerParts) {
|
|
1011
|
+
const interrupt = terminalStatus?.type === "requires-action" && terminalStatus.reason === "interrupt";
|
|
1012
|
+
const ownerStatus = terminalStatus?.type === "requires-action" ? {
|
|
1013
|
+
type: "complete",
|
|
1014
|
+
reason: "unknown"
|
|
1015
|
+
} : terminalStatus;
|
|
1016
|
+
for (const [ownerId, content] of ownerParts) {
|
|
1017
|
+
const existing = this.session.tryGetMessage(ownerId);
|
|
1018
|
+
if (existing) this.markPendingAssistantHistory(ownerId, existing.parentId);
|
|
1019
|
+
if (ownerId === messageId) continue;
|
|
1020
|
+
this.updateAssistantMessage(ownerId, {
|
|
1021
|
+
...contentOwnerIds?.has(ownerId) ? { content } : {},
|
|
1022
|
+
...ownerStatus ? { status: ownerStatus } : {}
|
|
1023
|
+
}, replayContexts.get(ownerId), interrupt ? /* @__PURE__ */ new Set() : restrictedPendingToolCallIds);
|
|
1024
|
+
}
|
|
1025
|
+
const { content: _content, ...controlUpdate } = update;
|
|
1026
|
+
const content = ownerParts.get(messageId) ?? [];
|
|
1027
|
+
primaryUpdate = {
|
|
1028
|
+
...controlUpdate,
|
|
1029
|
+
...contentOwnerIds?.has(messageId) ? { content } : {},
|
|
1030
|
+
...ownerStatus && !interrupt && replayContexts.has(messageId) ? { status: ownerStatus } : {}
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
const resolved = this.updateAssistantMessage(messageId, primaryUpdate, replayContexts.get(messageId), restrictedPendingToolCallIds);
|
|
1034
|
+
if (messageId === frameMessageId && resolved !== assistantMessageId) assistantMessageId = resolved;
|
|
1035
|
+
for (const messageId of pendingMessages) this.maybeResumeAfterToolResults(messageId, replayMessageIds.includes(messageId) ? accountedResultIds : void 0);
|
|
783
1036
|
};
|
|
784
|
-
const adoptServerMessageId = (serverId, startNewMessage) => {
|
|
1037
|
+
const adoptServerMessageId = (serverId, startNewMessage, openToolCallIds = []) => {
|
|
1038
|
+
const existing = this.session.tryGetMessage(serverId);
|
|
1039
|
+
const replayIndex = replayMessageIds.indexOf(serverId);
|
|
1040
|
+
const adoptsReplay = !replayEnded && !assistantCollided && replayIndex > lastReplayedIndex && existing?.message.role === "assistant" && this.session.getMessages().some((message) => message.id === serverId);
|
|
1041
|
+
if (adoptsReplay && replayContexts.size === 0) {
|
|
1042
|
+
replayContexts = candidateContexts;
|
|
1043
|
+
const selected = this.session.getMessages();
|
|
1044
|
+
let tailId = serverId;
|
|
1045
|
+
for (const message of selected.slice(selected.findIndex((message) => message.id === serverId) + 1)) {
|
|
1046
|
+
if (message.role !== "assistant") break;
|
|
1047
|
+
tailId = message.id;
|
|
1048
|
+
}
|
|
1049
|
+
this.session.selectHead(tailId);
|
|
1050
|
+
}
|
|
785
1051
|
if (startNewMessage && assistantMessageId !== void 0) {
|
|
786
|
-
|
|
1052
|
+
let hasReplayCallsToHold = false;
|
|
1053
|
+
const replay = replayContexts.get(assistantMessageId);
|
|
1054
|
+
if (replay) {
|
|
1055
|
+
const current = this.session.tryGetMessage(assistantMessageId)?.message;
|
|
1056
|
+
if (current?.role === "assistant") {
|
|
1057
|
+
const retainedPending = Array.from(iterateToolCallParts(current.content)).filter((part) => !isResolvedToolCall(part) || replay.admittedToolCallIds.has(part.toolCallId) && !accountedResultIds.has(part.toolCallId));
|
|
1058
|
+
hasReplayCallsToHold = retainedPending.length > 0;
|
|
1059
|
+
openToolCallIds = [.../* @__PURE__ */ new Set([...openToolCallIds, ...retainedPending.filter((part) => replay.admittedToolCallIds.has(part.toolCallId)).map((part) => part.toolCallId)])];
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
if (openToolCallIds.length > 0 || hasReplayCallsToHold) heldMessages.set(assistantMessageId, new Set(openToolCallIds));
|
|
1063
|
+
else applyUpdate({ status: {
|
|
787
1064
|
type: "complete",
|
|
788
1065
|
reason: "unknown"
|
|
789
1066
|
} });
|
|
790
1067
|
const previousId = assistantMessageId;
|
|
791
1068
|
assistantMessageId = void 0;
|
|
792
|
-
if (this.session.tryGetMessage(previousId)) {
|
|
1069
|
+
if (!adoptsReplay && this.session.tryGetMessage(previousId)) {
|
|
793
1070
|
const created = this.insertAssistantPlaceholder(previousId);
|
|
794
1071
|
assistantMessageId = created;
|
|
795
1072
|
this.markPendingAssistantHistory(created, previousId);
|
|
796
1073
|
}
|
|
797
1074
|
}
|
|
1075
|
+
if (adoptsReplay) {
|
|
1076
|
+
assistantMessageId = serverId;
|
|
1077
|
+
lastReplayedIndex = replayIndex;
|
|
1078
|
+
this.markPendingAssistantHistory(serverId, existing.parentId);
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
replayEnded = true;
|
|
798
1082
|
const placeholder = ensureAssistant(true);
|
|
799
1083
|
if (placeholder === serverId) return;
|
|
800
1084
|
const reassigned = this.reassignAssistantId(placeholder, serverId);
|
|
@@ -817,14 +1101,60 @@ var AgUiThreadRuntimeCore = class {
|
|
|
817
1101
|
onServerMessageId: (serverId) => {
|
|
818
1102
|
adoptServerMessageId(serverId, false);
|
|
819
1103
|
},
|
|
820
|
-
onTextMessageStart: (serverId) =>
|
|
1104
|
+
onTextMessageStart: (serverId, openToolCallIds) => {
|
|
1105
|
+
const owners = this.getTrailingAssistantTurn().filter((message) => replayContexts.has(message.id));
|
|
1106
|
+
const toolOwners = indexReplayToolOwners(owners);
|
|
1107
|
+
const frameOpenIds = openToolCallIds.filter((id) => {
|
|
1108
|
+
const ownerId = toolOwners.get(id);
|
|
1109
|
+
if (!ownerId || ownerId === assistantMessageId || assistantCollided) return true;
|
|
1110
|
+
const held = heldMessages.get(ownerId) ?? /* @__PURE__ */ new Set();
|
|
1111
|
+
held.add(id);
|
|
1112
|
+
heldMessages.set(ownerId, held);
|
|
1113
|
+
return false;
|
|
1114
|
+
});
|
|
1115
|
+
for (const ownerId of frameOwnerIds) {
|
|
1116
|
+
const replay = replayContexts.get(ownerId);
|
|
1117
|
+
if (replay) replay.merge = createReplayContentMerger();
|
|
1118
|
+
}
|
|
1119
|
+
frameOwnerIds.clear();
|
|
1120
|
+
adoptServerMessageId(serverId, true, frameOpenIds);
|
|
1121
|
+
}
|
|
821
1122
|
});
|
|
822
1123
|
let runFinished = false;
|
|
823
1124
|
const dispatch = (event) => {
|
|
824
1125
|
if (this.abortController !== abortController) return;
|
|
825
|
-
if (event.type === "RUN_FINISHED")
|
|
826
|
-
|
|
827
|
-
|
|
1126
|
+
if (event.type === "RUN_FINISHED") {
|
|
1127
|
+
runFinished = true;
|
|
1128
|
+
if (event.outcome?.type === "interrupt") replayInterruptMessageId = assistantMessageId ?? replayTail;
|
|
1129
|
+
const ids = event.outcome?.type === "success" ? event.outcome.pendingToolCallIds : void 0;
|
|
1130
|
+
restrictedPendingToolCallIds = ids === void 0 ? void 0 : new Set(ids);
|
|
1131
|
+
}
|
|
1132
|
+
if (event.type === "TOOL_CALL_RESULT") {
|
|
1133
|
+
if (!assistantCollided && candidateContexts.size) accountedResultIds.add(event.toolCallId);
|
|
1134
|
+
for (const openToolCallIds of heldMessages.values()) openToolCallIds.delete(event.toolCallId);
|
|
1135
|
+
}
|
|
1136
|
+
settlingRun = event.type === "RUN_FINISHED" || event.type === "RUN_ERROR" || event.type === "RUN_CANCELLED";
|
|
1137
|
+
const beforeSnapshot = event.type === "MESSAGES_SNAPSHOT" ? this.session.getMessages() : [];
|
|
1138
|
+
try {
|
|
1139
|
+
const nextAssistantMessageId = this.handleEvent(aggregator, event, assistantMessageId, joinStrategy, runStartMessageIds, hasRunUserBoundary, candidateContexts.size ? reconcileReplaySnapshot : void 0);
|
|
1140
|
+
if (event.type === "MESSAGES_SNAPSHOT") {
|
|
1141
|
+
for (const message of beforeSnapshot) {
|
|
1142
|
+
const context = replayContexts.get(message.id);
|
|
1143
|
+
const current = this.session.tryGetMessage(message.id)?.message;
|
|
1144
|
+
if (context && current?.content !== message.content) {
|
|
1145
|
+
context.merge = createReplayContentMerger();
|
|
1146
|
+
frameOwnerIds.delete(message.id);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
assistantMessageId = nextAssistantMessageId;
|
|
1150
|
+
if (!replayEnded && assistantMessageId === void 0) {
|
|
1151
|
+
const tail = this.session.getMessages().at(-1);
|
|
1152
|
+
snapshotAssistantId = tail?.role === "assistant" ? tail.id : void 0;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
} finally {
|
|
1156
|
+
settlingRun = false;
|
|
1157
|
+
}
|
|
828
1158
|
};
|
|
829
1159
|
this.abortController = abortController;
|
|
830
1160
|
const runAgentInstance = this.agent;
|
|
@@ -898,16 +1228,46 @@ var AgUiThreadRuntimeCore = class {
|
|
|
898
1228
|
this.clearDeferredContinuations(abortController);
|
|
899
1229
|
throw err;
|
|
900
1230
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1231
|
+
const pending = replayInterruptMessageId ? this.getPendingInterrupts() : null;
|
|
1232
|
+
if (pending && pending.messageId === replayInterruptMessageId && replayContexts.has(pending.messageId) && !abortSignal.aborted && this.abortController === null && generation === this.state.historyMutationGeneration && !this.isThreadSwitching?.()) {
|
|
1233
|
+
const message = this.session.tryGetMessage(pending.messageId)?.message;
|
|
1234
|
+
const responses = message?.role === "assistant" ? buildToolApprovalResume(message.content, pending.interrupts) : null;
|
|
1235
|
+
if (responses) {
|
|
1236
|
+
this.clearDeferredContinuations(abortController);
|
|
1237
|
+
try {
|
|
1238
|
+
await this.submitInterruptResponses(responses);
|
|
1239
|
+
} catch (error) {
|
|
1240
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1241
|
+
if (!this.reportedErrors.has(err)) {
|
|
1242
|
+
this.reportError(err);
|
|
1243
|
+
this.reportedErrors.add(err);
|
|
1244
|
+
}
|
|
1245
|
+
throw err;
|
|
1246
|
+
}
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
905
1249
|
}
|
|
906
|
-
if (this.
|
|
907
|
-
|
|
908
|
-
|
|
1250
|
+
if (this.state.pendingResume?.owner === abortController) {
|
|
1251
|
+
const { messageIds } = this.state.pendingResume;
|
|
1252
|
+
this.dispatch({ type: "resumeCleared" });
|
|
1253
|
+
if (!abortSignal.aborted && !this.getPendingInterrupts() && !this.getPendingToolCalls()) {
|
|
1254
|
+
const turn = this.getTrailingAssistantTurn();
|
|
1255
|
+
if (turn.some((message) => {
|
|
1256
|
+
if (!messageIds.includes(message.id)) return false;
|
|
1257
|
+
const replay = replayContexts.get(message.id);
|
|
1258
|
+
if (!replay) return hasResolvedToolCalls(message);
|
|
1259
|
+
return Array.from(iterateToolCallParts(message.content)).some((part) => isResolvedToolCall(part) && replay.admittedToolCallIds.has(part.toolCallId) && !accountedResultIds.has(part.toolCallId));
|
|
1260
|
+
})) {
|
|
1261
|
+
const headId = turn.at(-1)?.id;
|
|
1262
|
+
if (headId !== void 0) this.startResumeRun(headId);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
if (this.state.pendingA2uiResumeOwner === abortController) {
|
|
1267
|
+
this.dispatch({ type: "a2uiResumeCleared" });
|
|
1268
|
+
if (!abortSignal.aborted && this.state.pendingA2uiAction !== void 0) {
|
|
909
1269
|
if (this.getPendingInterrupts()) {
|
|
910
|
-
this.
|
|
1270
|
+
this.dispatch({ type: "a2uiActionCleared" });
|
|
911
1271
|
this.logger.debug("[agui] sendA2uiAction: pending interrupts, dropping action");
|
|
912
1272
|
return;
|
|
913
1273
|
}
|
|
@@ -915,15 +1275,14 @@ var AgUiThreadRuntimeCore = class {
|
|
|
915
1275
|
const parentId = this.session.headId;
|
|
916
1276
|
if (parentId !== null) this.startResumeRun(parentId);
|
|
917
1277
|
else {
|
|
918
|
-
this.
|
|
1278
|
+
this.dispatch({ type: "a2uiActionCleared" });
|
|
919
1279
|
this.logger.debug("[agui] sendA2uiAction: no messages to resume, dropping action");
|
|
920
1280
|
}
|
|
921
|
-
} else this.
|
|
1281
|
+
} else this.dispatch({ type: "a2uiActionCleared" });
|
|
922
1282
|
}
|
|
923
1283
|
}
|
|
924
1284
|
async consumeResumeStream(stream, ctx) {
|
|
925
|
-
this.
|
|
926
|
-
this.pendingA2uiResumeOwner = null;
|
|
1285
|
+
this.dispatch({ type: "a2uiContinuationsCleared" });
|
|
927
1286
|
const assistantId = ctx.ensureAssistant();
|
|
928
1287
|
const currentId = () => ctx.getAssistantMessageId() ?? assistantId;
|
|
929
1288
|
const options = {
|
|
@@ -964,8 +1323,8 @@ var AgUiThreadRuntimeCore = class {
|
|
|
964
1323
|
} });
|
|
965
1324
|
}
|
|
966
1325
|
buildRunInput(runId, runConfig, historyMessages, resume) {
|
|
967
|
-
const pendingA2uiAction = this.pendingA2uiAction;
|
|
968
|
-
this.
|
|
1326
|
+
const pendingA2uiAction = this.state.pendingA2uiAction;
|
|
1327
|
+
this.dispatch({ type: "a2uiActionCleared" });
|
|
969
1328
|
const threadId = this.agent.threadId || "main";
|
|
970
1329
|
const messages = toAgUiMessages(this.selectRunMessages(historyMessages ?? this.session.getMessages(), resume));
|
|
971
1330
|
const context = this.runtime?.thread.getModelContext();
|
|
@@ -983,7 +1342,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
983
1342
|
return {
|
|
984
1343
|
threadId,
|
|
985
1344
|
runId,
|
|
986
|
-
state: this.stateSnapshot ?? null,
|
|
1345
|
+
state: this.state.stateSnapshot ?? null,
|
|
987
1346
|
messages,
|
|
988
1347
|
tools: toAgUiTools(context?.tools),
|
|
989
1348
|
context: [...context?.system ? [{
|
|
@@ -1013,14 +1372,11 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1013
1372
|
return messages.slice(anchor + 1);
|
|
1014
1373
|
}
|
|
1015
1374
|
clearDeferredContinuations(owner) {
|
|
1016
|
-
if (this.pendingResume?.owner === owner) this.
|
|
1017
|
-
if (this.pendingA2uiResumeOwner === owner) {
|
|
1018
|
-
this.pendingA2uiResumeOwner = null;
|
|
1019
|
-
this.pendingA2uiAction = void 0;
|
|
1020
|
-
}
|
|
1375
|
+
if (this.state.pendingResume?.owner === owner) this.dispatch({ type: "resumeCleared" });
|
|
1376
|
+
if (this.state.pendingA2uiResumeOwner === owner) this.dispatch({ type: "a2uiContinuationsCleared" });
|
|
1021
1377
|
}
|
|
1022
1378
|
setRunning(running) {
|
|
1023
|
-
this.
|
|
1379
|
+
this.dispatch({ type: running ? "runStarted" : "runFinished" });
|
|
1024
1380
|
this.publishUpdate();
|
|
1025
1381
|
}
|
|
1026
1382
|
finishRun(controller) {
|
|
@@ -1037,7 +1393,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1037
1393
|
status: { type: "running" },
|
|
1038
1394
|
content: [],
|
|
1039
1395
|
metadata: {
|
|
1040
|
-
unstable_state: this.stateSnapshot ?? null,
|
|
1396
|
+
unstable_state: this.state.stateSnapshot ?? null,
|
|
1041
1397
|
unstable_annotations: [],
|
|
1042
1398
|
unstable_data: [],
|
|
1043
1399
|
steps: [],
|
|
@@ -1080,32 +1436,55 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1080
1436
|
ids.delete(oldId);
|
|
1081
1437
|
if (!collidesWithExisting) ids.add(newId);
|
|
1082
1438
|
}
|
|
1083
|
-
const pendingWrite = this.historyWrites.
|
|
1084
|
-
if (pendingWrite) {
|
|
1085
|
-
this.
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
const settle = () => {
|
|
1089
|
-
if (this.historyWrites.get(newId) === pendingWrite) this.historyWrites.delete(newId);
|
|
1090
|
-
};
|
|
1091
|
-
pendingWrite.then(() => {
|
|
1092
|
-
this.persistedHistoryIds.delete(oldId);
|
|
1093
|
-
this.persistedHistoryIds.add(newId);
|
|
1094
|
-
settle();
|
|
1095
|
-
}, settle);
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1439
|
+
const pendingWrite = this.historyWrites.move(oldId, collidesWithExisting ? void 0 : newId);
|
|
1440
|
+
if (pendingWrite && !collidesWithExisting) pendingWrite.then(() => {
|
|
1441
|
+
this.persistedHistoryIds.delete(oldId);
|
|
1442
|
+
this.persistedHistoryIds.add(newId);
|
|
1443
|
+
}, () => {});
|
|
1098
1444
|
this.publishUpdate();
|
|
1099
1445
|
return !collidesWithExisting;
|
|
1100
1446
|
}
|
|
1101
|
-
updateAssistantMessage(messageId, update) {
|
|
1447
|
+
updateAssistantMessage(messageId, update, replay, pendingToolCallIds) {
|
|
1102
1448
|
let latestStatus;
|
|
1103
1449
|
if (!this.session.updateMessage(messageId, (message) => {
|
|
1104
1450
|
if (message.role !== "assistant") return message;
|
|
1105
1451
|
const assistant = message;
|
|
1106
|
-
const
|
|
1452
|
+
const mergedMetadata = update.metadata ? this.mergeAssistantMetadata(assistant.metadata, update.metadata) : assistant.metadata;
|
|
1107
1453
|
latestStatus = update.status ?? assistant.status;
|
|
1108
|
-
|
|
1454
|
+
let content = update.content !== void 0 ? replay ? replay.merge(assistant.content, update.content) : this.preserveToolResults(assistant.content, update.content) : assistant.content;
|
|
1455
|
+
const calls = replay ? Array.from(iterateToolCallParts(content)) : [];
|
|
1456
|
+
const pending = pendingToolCallIds ?? (replay ? /* @__PURE__ */ new Set([...replay.admittedToolCallIds, ...Array.from(iterateToolCallParts(update.content ?? [])).filter((part) => !isResolvedToolCall(part)).map((part) => part.toolCallId)]) : void 0);
|
|
1457
|
+
if (replay && latestStatus?.type !== "running" && pending) replay.admittedToolCallIds = pending;
|
|
1458
|
+
if (replay) {
|
|
1459
|
+
const interrupts = latestStatus?.type === "requires-action" && latestStatus.reason === "interrupt" ? mergedMetadata.custom["agui"]?.interrupts : void 0;
|
|
1460
|
+
const approvals = projectAgUiToolApprovals(interrupts, new Set(calls.map((part) => part.toolCallId)));
|
|
1461
|
+
const originalCalls = new Map(Array.from(iterateToolCallParts(replay.originalContent), (part) => [part.toolCallId, part]));
|
|
1462
|
+
content = mapToolCallPartsDeep(content, (part) => {
|
|
1463
|
+
const approval = approvals.get(part.toolCallId);
|
|
1464
|
+
if (approval) return {
|
|
1465
|
+
...part,
|
|
1466
|
+
approval: preserveToolApprovalDecision(originalCalls.get(part.toolCallId), {
|
|
1467
|
+
...part,
|
|
1468
|
+
approval
|
|
1469
|
+
}) ?? approval
|
|
1470
|
+
};
|
|
1471
|
+
if (!part.approval || part.approval.approved !== void 0 || part.approval.resolution) return part;
|
|
1472
|
+
const { approval: _approval, ...withoutApproval } = part;
|
|
1473
|
+
return withoutApproval;
|
|
1474
|
+
}).content;
|
|
1475
|
+
}
|
|
1476
|
+
if (replay && latestStatus?.type === "complete") {
|
|
1477
|
+
const listed = pending ?? listedPendingToolCallIds({
|
|
1478
|
+
...assistant,
|
|
1479
|
+
metadata: mergedMetadata
|
|
1480
|
+
});
|
|
1481
|
+
if (calls.some((part) => (!isResolvedToolCall(part) || !replay.accountedResultIds.has(part.toolCallId)) && (!listed || listed.has(part.toolCallId)))) latestStatus = {
|
|
1482
|
+
type: "requires-action",
|
|
1483
|
+
reason: "tool-calls"
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
const admission = pending && replay ? withPendingToolCallIds(mergedMetadata, pending) : mergedMetadata;
|
|
1487
|
+
const metadata = latestStatus?.type === "requires-action" && latestStatus.reason === "tool-calls" ? admission : replay && latestStatus?.type === "running" ? mergedMetadata : withoutPendingToolCallIds(admission);
|
|
1109
1488
|
return {
|
|
1110
1489
|
...assistant,
|
|
1111
1490
|
content,
|
|
@@ -1120,24 +1499,25 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1120
1499
|
resolvedMessageId = stableId;
|
|
1121
1500
|
} else this.publishUpdate();
|
|
1122
1501
|
if (this.isPersistableStatus(latestStatus)) this.persistAssistantHistory(resolvedMessageId);
|
|
1123
|
-
this.maybeResumeAfterToolResults(resolvedMessageId);
|
|
1502
|
+
this.maybeResumeAfterToolResults(resolvedMessageId, replay?.accountedResultIds);
|
|
1124
1503
|
return resolvedMessageId;
|
|
1125
1504
|
}
|
|
1126
|
-
preserveToolResults(previous, next) {
|
|
1505
|
+
preserveToolResults(previous, next, decisionSource) {
|
|
1127
1506
|
const preserved = /* @__PURE__ */ new Map();
|
|
1128
1507
|
for (const part of iterateToolCallParts(previous)) if (isResolvedToolCall(part)) preserved.set(part.toolCallId, part);
|
|
1129
|
-
|
|
1508
|
+
const decisions = new Map(Array.from(iterateToolCallParts(decisionSource ?? [])).filter((part) => part.approval?.approved !== void 0).map((part) => [part.toolCallId, part]));
|
|
1509
|
+
if (preserved.size === 0 && decisions.size === 0) return this.preserveToolInteractions(previous, next);
|
|
1130
1510
|
const { content: merged, changed } = mapToolCallPartsDeep(next, (part) => {
|
|
1131
1511
|
const prior = preserved.get(part.toolCallId);
|
|
1132
|
-
|
|
1133
|
-
if (
|
|
1134
|
-
|
|
1135
|
-
result
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
}
|
|
1140
|
-
return
|
|
1512
|
+
const result = prior ? preserveToolResult(prior, part) : part;
|
|
1513
|
+
if (result.approval && decisions.has(part.toolCallId)) {
|
|
1514
|
+
const approval = preserveToolApprovalDecision(decisions.get(part.toolCallId), result);
|
|
1515
|
+
if (approval && approval !== result.approval) return {
|
|
1516
|
+
...result,
|
|
1517
|
+
approval
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
return result;
|
|
1141
1521
|
});
|
|
1142
1522
|
return this.preserveToolInteractions(previous, changed ? merged : next);
|
|
1143
1523
|
}
|
|
@@ -1240,18 +1620,23 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1240
1620
|
custom: incoming.custom ? mergeAgUiNamespace(current.custom, incoming.custom) : current.custom
|
|
1241
1621
|
};
|
|
1242
1622
|
}
|
|
1243
|
-
handleEvent(aggregator, event, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary) {
|
|
1623
|
+
handleEvent(aggregator, event, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary, reconcileSnapshot) {
|
|
1244
1624
|
switch (event.type) {
|
|
1245
1625
|
case "STATE_SNAPSHOT":
|
|
1246
|
-
this.
|
|
1626
|
+
this.dispatch({
|
|
1627
|
+
type: "stateSnapshot",
|
|
1628
|
+
snapshot: event.snapshot
|
|
1629
|
+
});
|
|
1247
1630
|
this.updateActiveAssistantState(activeAssistantId);
|
|
1248
1631
|
break;
|
|
1249
1632
|
case "STATE_DELTA":
|
|
1250
1633
|
if (event.delta.length === 0) break;
|
|
1251
1634
|
try {
|
|
1252
|
-
const
|
|
1253
|
-
|
|
1254
|
-
|
|
1635
|
+
const result = jsonpatch.applyPatch(this.state.stateSnapshot ?? {}, event.delta, true, false);
|
|
1636
|
+
this.dispatch({
|
|
1637
|
+
type: "stateSnapshot",
|
|
1638
|
+
snapshot: result.newDocument
|
|
1639
|
+
});
|
|
1255
1640
|
this.updateActiveAssistantState(activeAssistantId);
|
|
1256
1641
|
} catch (error) {
|
|
1257
1642
|
this.logger.error?.("[agui] failed to apply state delta", error);
|
|
@@ -1261,7 +1646,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1261
1646
|
const previousHeadId = this.session.headId;
|
|
1262
1647
|
const activeAssistant = activeAssistantId === void 0 ? void 0 : this.session.tryGetMessage(activeAssistantId)?.message;
|
|
1263
1648
|
const hasActiveText = activeAssistant?.role === "assistant" && activeAssistant.content.some((part) => part.type === "text");
|
|
1264
|
-
this.importMessagesSnapshot(event.messages, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary);
|
|
1649
|
+
this.importMessagesSnapshot(event.messages, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary, reconcileSnapshot);
|
|
1265
1650
|
const headId = this.session.headId;
|
|
1266
1651
|
if (hasActiveText && headId !== previousHeadId) activeAssistantId = (headId === null ? void 0 : this.session.tryGetMessage(headId)?.message)?.role === "assistant" ? headId ?? void 0 : void 0;
|
|
1267
1652
|
this.updateActiveAssistantState(activeAssistantId);
|
|
@@ -1302,7 +1687,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1302
1687
|
...assistant,
|
|
1303
1688
|
metadata: {
|
|
1304
1689
|
...assistant.metadata,
|
|
1305
|
-
unstable_state: this.stateSnapshot ?? null
|
|
1690
|
+
unstable_state: this.state.stateSnapshot ?? null
|
|
1306
1691
|
}
|
|
1307
1692
|
};
|
|
1308
1693
|
});
|
|
@@ -1313,25 +1698,27 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1313
1698
|
if (message.role !== "assistant") return message;
|
|
1314
1699
|
const assistant = message;
|
|
1315
1700
|
const mcpAppUri = readMcpAppResourceUri(event.mcpResult?._meta);
|
|
1701
|
+
const modelText = toolContentText(event.content);
|
|
1316
1702
|
let matchedToolCall = false;
|
|
1317
1703
|
const { content } = mapToolCallPartsDeep(assistant.content, (part) => {
|
|
1318
1704
|
if (part.toolCallId !== event.toolCallId) return part;
|
|
1319
1705
|
matchedToolCall = true;
|
|
1320
1706
|
if (part.mcp?.app !== void 0 && isPlainObject(part.result) && Array.isArray(part.result["content"])) return {
|
|
1321
1707
|
...part,
|
|
1322
|
-
...part.modelContent === void 0
|
|
1708
|
+
...part.modelContent === void 0 ? { modelContent: [{
|
|
1323
1709
|
type: "text",
|
|
1324
|
-
text:
|
|
1710
|
+
text: modelText
|
|
1325
1711
|
}] } : {},
|
|
1326
1712
|
...part.isError === void 0 ? typeof event.mcpResult?.isError === "boolean" ? { isError: event.mcpResult.isError } : event.role === "tool" ? { isError: false } : {} : {},
|
|
1327
1713
|
...event.messageId ? { unstable_toolMessageId: event.messageId } : {}
|
|
1328
1714
|
};
|
|
1715
|
+
const { modelContent: _modelContent, ...partWithoutModelContent } = part;
|
|
1329
1716
|
return {
|
|
1330
|
-
...part,
|
|
1331
|
-
result: event.mcpResult ??
|
|
1717
|
+
...event.mcpResult === void 0 && Array.isArray(event.content) ? partWithoutModelContent : part,
|
|
1718
|
+
result: event.mcpResult ?? (Array.isArray(event.content) ? event.content : tryParseJSON(event.content)),
|
|
1332
1719
|
...event.mcpResult !== void 0 ? { modelContent: [{
|
|
1333
1720
|
type: "text",
|
|
1334
|
-
text:
|
|
1721
|
+
text: modelText
|
|
1335
1722
|
}] } : {},
|
|
1336
1723
|
...typeof event.mcpResult?.isError === "boolean" ? { isError: event.mcpResult.isError } : event.role === "tool" ? { isError: false } : {},
|
|
1337
1724
|
...part.mcp === void 0 && mcpAppUri !== void 0 ? { mcp: { app: { resourceUri: mcpAppUri } } } : {},
|
|
@@ -1386,7 +1773,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1386
1773
|
this.publishUpdate();
|
|
1387
1774
|
this.maybeCompleteAfterToolResults(messageId);
|
|
1388
1775
|
}
|
|
1389
|
-
importMessagesSnapshot(rawMessages, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary) {
|
|
1776
|
+
importMessagesSnapshot(rawMessages, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary, reconcileSnapshot) {
|
|
1390
1777
|
try {
|
|
1391
1778
|
const activeAssistantItem = activeAssistantId ? this.session.tryGetMessage(activeAssistantId) : void 0;
|
|
1392
1779
|
const activeMessage = activeAssistantItem?.message;
|
|
@@ -1404,10 +1791,14 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1404
1791
|
for (let index = 0; index < converted.length; index++) {
|
|
1405
1792
|
const convertedMessage = converted[index];
|
|
1406
1793
|
const existing = this.session.tryGetMessage(convertedMessage.id)?.message;
|
|
1407
|
-
if (convertedMessage.role === "assistant"
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1794
|
+
if (convertedMessage.role === "assistant") {
|
|
1795
|
+
const previous = existing?.role === "assistant" ? existing : void 0;
|
|
1796
|
+
const snapshot = reconcileSnapshot?.(convertedMessage, previous) ?? convertedMessage;
|
|
1797
|
+
converted[index] = previous ? {
|
|
1798
|
+
...snapshot,
|
|
1799
|
+
content: this.mergeSnapshotAssistantContent(previous.content, snapshot.content, snapshotHasReasoning, snapshotHasActivity)
|
|
1800
|
+
} : snapshot;
|
|
1801
|
+
}
|
|
1411
1802
|
}
|
|
1412
1803
|
if (joinStrategy === "concat-content") {
|
|
1413
1804
|
const activeIndex = converted.findIndex((message) => message.id === activeAssistantId);
|
|
@@ -1501,7 +1892,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1501
1892
|
const wasPersisted = this.persistedHistoryIds.has(messageId);
|
|
1502
1893
|
const update = history.update;
|
|
1503
1894
|
if (update !== void 0 && (wasPersisted || this.snapshotHistoryIds.has(messageId))) {
|
|
1504
|
-
const write = this.
|
|
1895
|
+
const write = this.historyWrites.chain(messageId, () => update.call(history, {
|
|
1505
1896
|
parentId,
|
|
1506
1897
|
message
|
|
1507
1898
|
}));
|
|
@@ -1509,7 +1900,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1509
1900
|
write.then(() => {
|
|
1510
1901
|
this.persistedHistoryIds.add(messageId);
|
|
1511
1902
|
}, (error) => {
|
|
1512
|
-
const pending = this.historyWrites.
|
|
1903
|
+
const pending = this.historyWrites.pending(messageId);
|
|
1513
1904
|
if (pending === void 0 || pending === write) this.assistantHistoryParents.set(messageId, parentId);
|
|
1514
1905
|
this.logger.error?.("[agui] failed to update history entry", error);
|
|
1515
1906
|
});
|
|
@@ -1523,17 +1914,17 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1523
1914
|
if (!write) return;
|
|
1524
1915
|
this.assistantHistoryParents.delete(messageId);
|
|
1525
1916
|
write.then(() => {}, (error) => {
|
|
1526
|
-
const pending = this.historyWrites.
|
|
1917
|
+
const pending = this.historyWrites.pending(messageId);
|
|
1527
1918
|
if (pending === void 0 || pending === write) this.assistantHistoryParents.set(messageId, parentId);
|
|
1528
1919
|
this.logger.error?.("[agui] failed to append history entry", error);
|
|
1529
1920
|
});
|
|
1530
1921
|
}
|
|
1531
1922
|
appendHistoryItem(parentId, message) {
|
|
1532
1923
|
if (!this.history || this.persistedHistoryIds.has(message.id)) return;
|
|
1533
|
-
const pending = this.historyWrites.
|
|
1924
|
+
const pending = this.historyWrites.pending(message.id);
|
|
1534
1925
|
if (pending) return pending;
|
|
1535
1926
|
const append = this.history.append.bind(this.history);
|
|
1536
|
-
const write = this.
|
|
1927
|
+
const write = this.historyWrites.chain(message.id, () => append({
|
|
1537
1928
|
parentId,
|
|
1538
1929
|
message
|
|
1539
1930
|
}));
|
|
@@ -1542,25 +1933,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1542
1933
|
}, () => {});
|
|
1543
1934
|
return write;
|
|
1544
1935
|
}
|
|
1545
|
-
chainHistoryWrite(id, write) {
|
|
1546
|
-
const pending = this.historyWrites.get(id);
|
|
1547
|
-
let next;
|
|
1548
|
-
if (pending) next = pending.then(write, write);
|
|
1549
|
-
else try {
|
|
1550
|
-
next = Promise.resolve(write());
|
|
1551
|
-
} catch (error) {
|
|
1552
|
-
next = Promise.reject(error);
|
|
1553
|
-
}
|
|
1554
|
-
this.historyWrites.set(id, next);
|
|
1555
|
-
next.then(() => {
|
|
1556
|
-
if (this.historyWrites.get(id) === next) this.historyWrites.delete(id);
|
|
1557
|
-
}, () => {
|
|
1558
|
-
if (this.historyWrites.get(id) === next) this.historyWrites.delete(id);
|
|
1559
|
-
});
|
|
1560
|
-
return next;
|
|
1561
|
-
}
|
|
1562
1936
|
};
|
|
1563
|
-
|
|
1564
|
-
export { AgUiThreadRuntimeCore };
|
|
1937
|
+
export { AgUiThreadController };
|
|
1565
1938
|
|
|
1566
|
-
//# sourceMappingURL=
|
|
1939
|
+
//# sourceMappingURL=AgUiThreadController.js.map
|