@assistant-ui/react-ag-ui 0.0.70 → 0.1.0
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/runtime/{AgUiThreadRuntimeCore.d.ts → AgUiThreadController.d.ts} +8 -12
- package/dist/runtime/AgUiThreadController.d.ts.map +1 -0
- package/dist/runtime/{AgUiThreadRuntimeCore.js → AgUiThreadController.js} +302 -162
- 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 +111 -33
- package/dist/runtime/adapter/conversions.js.map +1 -1
- package/dist/runtime/adapter/run-aggregator.d.ts +6 -5
- package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
- package/dist/runtime/adapter/run-aggregator.js +50 -62
- 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 +1 -22
- package/dist/runtime/adapter/subscriber.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 +95 -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 +45 -48
- package/dist/runtime/event-parser.js.map +1 -1
- 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 +6 -3
- package/dist/runtime/mcp-tool-result.js.map +1 -1
- package/dist/runtime/types.d.ts +13 -35
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js.map +1 -1
- package/dist/useAgUiRuntime.d.ts.map +1 -1
- package/dist/useAgUiRuntime.js +4 -2
- package/dist/useAgUiRuntime.js.map +1 -1
- package/package.json +13 -12
- package/src/runtime/{AgUiThreadRuntimeCore.interrupt-cancel.test.ts → AgUiThreadController.interrupt-cancel.test.ts} +4 -4
- package/src/runtime/{AgUiThreadRuntimeCore.test.ts → AgUiThreadController.test.ts} +35 -6
- package/src/runtime/{AgUiThreadRuntimeCore.ts → AgUiThreadController.ts} +471 -169
- package/src/runtime/adapter/conversions.content.test.ts +37 -5
- package/src/runtime/adapter/conversions.tools.test.ts +2 -1
- package/src/runtime/adapter/conversions.ts +235 -58
- package/src/runtime/adapter/run-aggregator.ts +84 -114
- package/src/runtime/adapter/subscriber.ts +1 -44
- package/src/runtime/agUiThreadState.test.ts +205 -0
- package/src/runtime/agUiThreadState.ts +130 -0
- package/src/runtime/event-parser.ts +40 -58
- package/src/runtime/mcp-tool-result.ts +16 -2
- package/src/runtime/types.ts +18 -28
- package/src/useAgUiRuntime.external-store.test.tsx +39 -0
- package/src/useAgUiRuntime.fast-refresh.test.tsx +3 -3
- package/src/useAgUiRuntime.thread-switch.test.tsx +4 -4
- package/src/useAgUiRuntime.ts +5 -3
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +0 -1
- package/dist/runtime/AgUiThreadRuntimeCore.js.map +0 -1
|
@@ -1,18 +1,36 @@
|
|
|
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
5
|
import { buildToolApprovalResume, 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";
|
|
8
9
|
import { fromThreadMessageLike, generateId, isMcpAppUri } from "@assistant-ui/core";
|
|
9
|
-
import { appendToolInteraction, createMessageRepositorySession, invokeUserCallback, iterateToolCallParts, mapToolCallPartsDeep } from "@assistant-ui/core/internal";
|
|
10
|
+
import { appendToolInteraction, createHistoryWriteChain, createMessageRepositorySession, invokeUserCallback, iterateToolCallParts, mapToolCallPartsDeep } from "@assistant-ui/core/internal";
|
|
10
11
|
import jsonpatch from "fast-json-patch";
|
|
11
|
-
//#region src/runtime/
|
|
12
|
+
//#region src/runtime/AgUiThreadController.ts
|
|
12
13
|
const optimisticPrefix = "__optimistic__";
|
|
13
14
|
const generateOptimisticId = () => `${optimisticPrefix}${generateId()}`;
|
|
14
15
|
const isOptimisticId = (id) => id.startsWith(optimisticPrefix);
|
|
15
16
|
const isResolvedToolCall = (part) => part.type === "tool-call" && "result" in part && part.result !== void 0;
|
|
17
|
+
const listedPendingToolCallIds = (assistant) => {
|
|
18
|
+
const ids = assistant.metadata.custom[AG_UI_METADATA_NAMESPACE]?.pendingToolCallIds;
|
|
19
|
+
return ids?.length ? new Set(ids) : void 0;
|
|
20
|
+
};
|
|
21
|
+
const withoutPendingToolCallIds = (metadata) => {
|
|
22
|
+
const namespace = metadata.custom[AG_UI_METADATA_NAMESPACE];
|
|
23
|
+
if (!namespace || !("pendingToolCallIds" in namespace)) return metadata;
|
|
24
|
+
const { pendingToolCallIds: _pendingToolCallIds, ...remainingNamespace } = namespace;
|
|
25
|
+
const { [AG_UI_METADATA_NAMESPACE]: _namespace, ...remainingCustom } = metadata.custom;
|
|
26
|
+
return {
|
|
27
|
+
...metadata,
|
|
28
|
+
custom: {
|
|
29
|
+
...remainingCustom,
|
|
30
|
+
...Object.keys(remainingNamespace).length > 0 ? { [AG_UI_METADATA_NAMESPACE]: remainingNamespace } : {}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
};
|
|
16
34
|
const isActivityPart = (part) => part.type === "data" && part.name.startsWith("agui-activity/");
|
|
17
35
|
const FALLBACK_USER_STATUS = {
|
|
18
36
|
type: "complete",
|
|
@@ -34,10 +52,11 @@ const mergeAgUiNamespace = (current, incoming) => {
|
|
|
34
52
|
};
|
|
35
53
|
return merged;
|
|
36
54
|
};
|
|
37
|
-
var
|
|
55
|
+
var AgUiThreadController = class {
|
|
38
56
|
agent;
|
|
39
57
|
logger;
|
|
40
58
|
showThinking;
|
|
59
|
+
joinStrategy;
|
|
41
60
|
getRunContext;
|
|
42
61
|
resumeTranscript;
|
|
43
62
|
autoCancelPendingToolCalls;
|
|
@@ -62,28 +81,21 @@ var AgUiThreadRuntimeCore = class {
|
|
|
62
81
|
headId: repository.headId
|
|
63
82
|
};
|
|
64
83
|
} });
|
|
65
|
-
|
|
84
|
+
state = createAgUiThreadState();
|
|
66
85
|
abortController = null;
|
|
67
86
|
activeRunAgent = null;
|
|
68
|
-
stateSnapshot;
|
|
69
87
|
history;
|
|
70
|
-
lastRunConfig;
|
|
71
88
|
assistantHistoryParents = /* @__PURE__ */ new Map();
|
|
72
89
|
snapshotHistoryIds = /* @__PURE__ */ new Set();
|
|
73
90
|
persistedHistoryIds = /* @__PURE__ */ new Set();
|
|
74
|
-
historyWrites =
|
|
75
|
-
_isLoading = false;
|
|
91
|
+
historyWrites = createHistoryWriteChain();
|
|
76
92
|
_historyImportPromise;
|
|
77
|
-
historyMutationGeneration = 0;
|
|
78
93
|
_loadPromise;
|
|
79
|
-
_loadRequested = false;
|
|
80
|
-
pendingResume = null;
|
|
81
|
-
pendingA2uiResumeOwner = null;
|
|
82
|
-
pendingA2uiAction;
|
|
83
94
|
constructor(options) {
|
|
84
95
|
this.agent = options.agent;
|
|
85
96
|
this.logger = options.logger;
|
|
86
97
|
this.showThinking = options.showThinking;
|
|
98
|
+
this.joinStrategy = options.joinStrategy;
|
|
87
99
|
this.getRunContext = options.getRunContext;
|
|
88
100
|
this.resumeTranscript = options.resumeTranscript ?? "full";
|
|
89
101
|
this.autoCancelPendingToolCalls = options.autoCancelPendingToolCalls;
|
|
@@ -97,11 +109,14 @@ var AgUiThreadRuntimeCore = class {
|
|
|
97
109
|
return () => this.listeners.delete(listener);
|
|
98
110
|
};
|
|
99
111
|
getSnapshot = () => this.snapshot ??= {
|
|
100
|
-
isLoading: this.
|
|
101
|
-
isRunning: this.
|
|
112
|
+
isLoading: this.state.isLoading,
|
|
113
|
+
isRunning: this.state.isRunning,
|
|
102
114
|
messageRepository: this.session.export(),
|
|
103
|
-
state: this.stateSnapshot
|
|
115
|
+
state: this.state.stateSnapshot
|
|
104
116
|
};
|
|
117
|
+
dispatch(action) {
|
|
118
|
+
this.state = reduceAgUiThreadState(this.state, action);
|
|
119
|
+
}
|
|
105
120
|
publishUpdate() {
|
|
106
121
|
this.snapshot = void 0;
|
|
107
122
|
for (const listener of this.listeners) listener();
|
|
@@ -110,6 +125,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
110
125
|
this.agent = options.agent;
|
|
111
126
|
this.logger = options.logger;
|
|
112
127
|
this.showThinking = options.showThinking;
|
|
128
|
+
this.joinStrategy = options.joinStrategy;
|
|
113
129
|
this.getRunContext = options.getRunContext;
|
|
114
130
|
this.resumeTranscript = options.resumeTranscript ?? "full";
|
|
115
131
|
this.autoCancelPendingToolCalls = options.autoCancelPendingToolCalls;
|
|
@@ -117,7 +133,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
117
133
|
this.onCancel = options.onCancel;
|
|
118
134
|
const previousHistory = this.history;
|
|
119
135
|
this.history = options.history;
|
|
120
|
-
if (this.
|
|
136
|
+
if (this.state.loadRequested && !this._loadPromise && !previousHistory && options.history && this.session.getMessages().length === 0) this.__internal_load();
|
|
121
137
|
}
|
|
122
138
|
attachRuntime(runtime) {
|
|
123
139
|
this.runtime = runtime;
|
|
@@ -135,16 +151,16 @@ var AgUiThreadRuntimeCore = class {
|
|
|
135
151
|
return this.session.export();
|
|
136
152
|
}
|
|
137
153
|
getState() {
|
|
138
|
-
return this.stateSnapshot;
|
|
154
|
+
return this.state.stateSnapshot;
|
|
139
155
|
}
|
|
140
156
|
isRunning() {
|
|
141
|
-
return this.
|
|
157
|
+
return this.state.isRunning;
|
|
142
158
|
}
|
|
143
159
|
get isLoading() {
|
|
144
|
-
return this.
|
|
160
|
+
return this.state.isLoading;
|
|
145
161
|
}
|
|
146
162
|
__internal_load() {
|
|
147
|
-
this.
|
|
163
|
+
this.dispatch({ type: "loadRequested" });
|
|
148
164
|
if (this._loadPromise) return this._loadPromise;
|
|
149
165
|
if (!this.history) return Promise.resolve();
|
|
150
166
|
const promise = this.history.load().then((repo) => {
|
|
@@ -158,7 +174,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
158
174
|
if (repo.state !== void 0) this.loadExternalState(repo.state);
|
|
159
175
|
return repo;
|
|
160
176
|
});
|
|
161
|
-
this.
|
|
177
|
+
this.dispatch({ type: "loadStarted" });
|
|
162
178
|
this._historyImportPromise = promise.then(() => {
|
|
163
179
|
this._historyImportPromise = void 0;
|
|
164
180
|
}, () => {
|
|
@@ -168,13 +184,13 @@ var AgUiThreadRuntimeCore = class {
|
|
|
168
184
|
if (repo?.unstable_resume) {
|
|
169
185
|
const parentId = repo.headId ?? this.session.headId;
|
|
170
186
|
const resumeStream = this.history?.resume?.bind(this.history);
|
|
171
|
-
await this.startRun(parentId, this.lastRunConfig, void 0, resumeStream);
|
|
187
|
+
await this.startRun(parentId, this.state.lastRunConfig, void 0, resumeStream);
|
|
172
188
|
}
|
|
173
189
|
}).catch((error) => {
|
|
174
190
|
this.logger.error?.("[agui] failed to load history", error);
|
|
175
191
|
this.reportError(error);
|
|
176
192
|
}).finally(() => {
|
|
177
|
-
this.
|
|
193
|
+
this.dispatch({ type: "loadFinished" });
|
|
178
194
|
this.publishUpdate();
|
|
179
195
|
});
|
|
180
196
|
this.publishUpdate();
|
|
@@ -183,15 +199,15 @@ var AgUiThreadRuntimeCore = class {
|
|
|
183
199
|
waitForHistoryImport() {
|
|
184
200
|
const historyImport = this._historyImportPromise;
|
|
185
201
|
if (!historyImport) return void 0;
|
|
186
|
-
const generation = this.historyMutationGeneration;
|
|
187
|
-
return historyImport.then(() => generation === this.historyMutationGeneration && !this.isThreadSwitching?.());
|
|
202
|
+
const generation = this.state.historyMutationGeneration;
|
|
203
|
+
return historyImport.then(() => generation === this.state.historyMutationGeneration && !this.isThreadSwitching?.());
|
|
188
204
|
}
|
|
189
205
|
deferUntilHistoryImported(callback) {
|
|
190
206
|
const historyImport = this._historyImportPromise;
|
|
191
207
|
if (!historyImport) return false;
|
|
192
|
-
const generation = this.historyMutationGeneration;
|
|
208
|
+
const generation = this.state.historyMutationGeneration;
|
|
193
209
|
historyImport.then(() => {
|
|
194
|
-
if (generation !== this.historyMutationGeneration || this.isThreadSwitching?.()) return;
|
|
210
|
+
if (generation !== this.state.historyMutationGeneration || this.isThreadSwitching?.()) return;
|
|
195
211
|
try {
|
|
196
212
|
callback();
|
|
197
213
|
} catch (error) {
|
|
@@ -204,9 +220,9 @@ var AgUiThreadRuntimeCore = class {
|
|
|
204
220
|
const historyImport = this._historyImportPromise;
|
|
205
221
|
if (historyImport) {
|
|
206
222
|
const wasAtTail = message.parentId === this.session.headId;
|
|
207
|
-
const generation = this.historyMutationGeneration;
|
|
223
|
+
const generation = this.state.historyMutationGeneration;
|
|
208
224
|
await historyImport;
|
|
209
|
-
if (generation !== this.historyMutationGeneration || this.isThreadSwitching?.()) return;
|
|
225
|
+
if (generation !== this.state.historyMutationGeneration || this.isThreadSwitching?.()) return;
|
|
210
226
|
if (wasAtTail) message = {
|
|
211
227
|
...message,
|
|
212
228
|
parentId: this.session.headId
|
|
@@ -232,10 +248,10 @@ var AgUiThreadRuntimeCore = class {
|
|
|
232
248
|
}
|
|
233
249
|
maybeAutoCancelPendingToolCalls() {
|
|
234
250
|
if (this.autoCancelPendingToolCalls === false) return;
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
251
|
+
for (const assistant of this.getPendingToolCallMessages()) {
|
|
252
|
+
this.cancelUnresolvedToolCalls(assistant.id);
|
|
253
|
+
this.maybeCompleteAfterToolResults(assistant.id);
|
|
254
|
+
}
|
|
239
255
|
}
|
|
240
256
|
appendEntry(message) {
|
|
241
257
|
const threadMessage = this.toThreadMessage(message);
|
|
@@ -257,7 +273,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
257
273
|
await this.startRun(parentId, config.runConfig);
|
|
258
274
|
}
|
|
259
275
|
async cancel() {
|
|
260
|
-
this.
|
|
276
|
+
this.dispatch({ type: "historyMutationAdvanced" });
|
|
261
277
|
this.abortActiveRun();
|
|
262
278
|
}
|
|
263
279
|
abortActiveRun() {
|
|
@@ -283,7 +299,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
283
299
|
const historyImport = this.waitForHistoryImport();
|
|
284
300
|
if (historyImport && !await historyImport) return;
|
|
285
301
|
this.assertNoPendingInterrupts();
|
|
286
|
-
await this.startRun(config.parentId, config.runConfig ?? this.lastRunConfig, void 0, config.stream);
|
|
302
|
+
await this.startRun(config.parentId, config.runConfig ?? this.state.lastRunConfig, void 0, config.stream);
|
|
287
303
|
}
|
|
288
304
|
async resumeInFlightRun(messages) {
|
|
289
305
|
const historyImport = this.waitForHistoryImport();
|
|
@@ -297,7 +313,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
297
313
|
}
|
|
298
314
|
const parentId = messages.at(-1)?.id ?? null;
|
|
299
315
|
try {
|
|
300
|
-
await this.startRun(parentId, this.lastRunConfig, void 0, resumeStream);
|
|
316
|
+
await this.startRun(parentId, this.state.lastRunConfig, void 0, resumeStream);
|
|
301
317
|
} catch {}
|
|
302
318
|
}
|
|
303
319
|
assertNoPendingInterrupts() {
|
|
@@ -309,6 +325,19 @@ var AgUiThreadRuntimeCore = class {
|
|
|
309
325
|
if (!assistant || assistant.status?.type !== "requires-action" || assistant.status.reason !== reason) return null;
|
|
310
326
|
return assistant;
|
|
311
327
|
}
|
|
328
|
+
getTrailingAssistantTurn() {
|
|
329
|
+
const messages = this.getMessages();
|
|
330
|
+
const turn = [];
|
|
331
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
332
|
+
const message = messages[index];
|
|
333
|
+
if (message?.role !== "assistant") break;
|
|
334
|
+
turn.unshift(message);
|
|
335
|
+
}
|
|
336
|
+
return turn;
|
|
337
|
+
}
|
|
338
|
+
getPendingToolCallMessages() {
|
|
339
|
+
return this.getTrailingAssistantTurn().filter((message) => message.status?.type === "requires-action" && message.status.reason === "tool-calls");
|
|
340
|
+
}
|
|
312
341
|
getPendingInterrupts() {
|
|
313
342
|
const assistant = this.findRequiresActionAssistant("interrupt");
|
|
314
343
|
if (!assistant) return null;
|
|
@@ -320,16 +349,18 @@ var AgUiThreadRuntimeCore = class {
|
|
|
320
349
|
};
|
|
321
350
|
}
|
|
322
351
|
getPendingToolCalls() {
|
|
323
|
-
const assistant = this.findRequiresActionAssistant("tool-calls");
|
|
324
|
-
if (!assistant) return null;
|
|
325
352
|
const toolCallIds = [];
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
353
|
+
let messageId;
|
|
354
|
+
for (const assistant of this.getPendingToolCallMessages()) {
|
|
355
|
+
const listed = listedPendingToolCallIds(assistant);
|
|
356
|
+
for (const part of iterateToolCallParts(assistant.content)) {
|
|
357
|
+
if (isResolvedToolCall(part) || listed && !listed.has(part.toolCallId)) continue;
|
|
358
|
+
messageId ??= assistant.id;
|
|
359
|
+
toolCallIds.push(part.toolCallId);
|
|
360
|
+
}
|
|
329
361
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
messageId: assistant.id,
|
|
362
|
+
return messageId === void 0 ? null : {
|
|
363
|
+
messageId,
|
|
333
364
|
toolCallIds
|
|
334
365
|
};
|
|
335
366
|
}
|
|
@@ -342,7 +373,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
342
373
|
const openIds = pending.interrupts.map((i) => i.id);
|
|
343
374
|
const missing = openIds.filter((id) => !responsesById.has(id));
|
|
344
375
|
if (missing.length > 0) throw new Error(`[agui] submitInterruptResponses: missing responses for open interrupts: ${missing.join(", ")}`);
|
|
345
|
-
this.assertInterruptsAnswerable("submitInterruptResponses", pending.interrupts);
|
|
376
|
+
this.assertInterruptsAnswerable("submitInterruptResponses", pending.interrupts, responsesById);
|
|
346
377
|
await this.resumeWithResponses(pending.messageId, openIds.map((id) => responsesById.get(id)));
|
|
347
378
|
}
|
|
348
379
|
/**
|
|
@@ -353,7 +384,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
353
384
|
*/
|
|
354
385
|
async resumeWithResponses(messageId, resume) {
|
|
355
386
|
this.clearPendingInterrupts(messageId, resume);
|
|
356
|
-
await this.startRun(messageId, this.lastRunConfig, {
|
|
387
|
+
await this.startRun(messageId, this.state.lastRunConfig, {
|
|
357
388
|
entries: resume,
|
|
358
389
|
interruptedMessageId: messageId
|
|
359
390
|
});
|
|
@@ -372,15 +403,15 @@ var AgUiThreadRuntimeCore = class {
|
|
|
372
403
|
}
|
|
373
404
|
invokeRuntimeCallback("onError", this.onError, error instanceof Error ? error : new Error(String(error)));
|
|
374
405
|
}
|
|
375
|
-
assertInterruptsAnswerable(method, interrupts) {
|
|
406
|
+
assertInterruptsAnswerable(method, interrupts, responsesById) {
|
|
376
407
|
const now = Date.now();
|
|
377
408
|
for (const interrupt of interrupts) {
|
|
378
409
|
if (!interrupt.expiresAt) continue;
|
|
379
410
|
const expiry = new Date(interrupt.expiresAt).getTime();
|
|
380
411
|
if (Number.isNaN(expiry)) throw new Error(`[agui] ${method}: interrupt ${interrupt.id} has malformed expiresAt "${interrupt.expiresAt}"`);
|
|
381
|
-
if (expiry <= now) throw new Error(`[agui] ${method}: interrupt ${interrupt.id} expired at ${interrupt.expiresAt}`);
|
|
412
|
+
if (expiry <= now && responsesById?.get(interrupt.id)?.status !== "cancelled") throw new Error(`[agui] ${method}: interrupt ${interrupt.id} expired at ${interrupt.expiresAt}`);
|
|
382
413
|
}
|
|
383
|
-
if (this.
|
|
414
|
+
if (this.state.isRunning) throw new Error(`[agui] ${method}: a run is already in progress`);
|
|
384
415
|
}
|
|
385
416
|
async respondToToolApproval(options) {
|
|
386
417
|
const historyImport = this.waitForHistoryImport();
|
|
@@ -412,12 +443,13 @@ var AgUiThreadRuntimeCore = class {
|
|
|
412
443
|
if (historyImport && !await historyImport) return;
|
|
413
444
|
const pending = this.getPendingInterrupts();
|
|
414
445
|
if (!pending) {
|
|
415
|
-
|
|
416
|
-
if (pendingTools) {
|
|
446
|
+
if (this.getPendingToolCalls()) {
|
|
417
447
|
if (responses?.length) throw new Error("[agui] steerAway: responses are only valid for pending interrupts");
|
|
418
|
-
if (this.
|
|
419
|
-
this.
|
|
420
|
-
|
|
448
|
+
if (this.state.isRunning) throw new Error("[agui] steerAway: a run is already in progress");
|
|
449
|
+
for (const assistant of this.getPendingToolCallMessages()) {
|
|
450
|
+
this.cancelUnresolvedToolCalls(assistant.id);
|
|
451
|
+
this.maybeCompleteAfterToolResults(assistant.id);
|
|
452
|
+
}
|
|
421
453
|
const normalized = this.toAppendMessage(message);
|
|
422
454
|
const threadMessageId = this.appendEntry(normalized);
|
|
423
455
|
await this.startRun(threadMessageId, normalized.runConfig);
|
|
@@ -428,7 +460,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
428
460
|
return;
|
|
429
461
|
}
|
|
430
462
|
const resume = this.resolveSteerAwayResume(pending.interrupts, responses);
|
|
431
|
-
if (this.
|
|
463
|
+
if (this.state.isRunning) throw new Error("[agui] steerAway: a run is already in progress");
|
|
432
464
|
const normalized = this.toAppendMessage(message);
|
|
433
465
|
this.clearPendingInterrupts(pending.messageId, resume);
|
|
434
466
|
const threadMessageId = this.appendEntry(normalized);
|
|
@@ -594,7 +626,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
594
626
|
const message = this.session.tryGetMessage(sessionMessageId)?.message;
|
|
595
627
|
const history = this.history;
|
|
596
628
|
if (!message || !history?.update || !this.isPersistableStatus(message.status)) return;
|
|
597
|
-
await this.
|
|
629
|
+
await this.historyWrites.chain(sessionMessageId, () => history.update({
|
|
598
630
|
parentId: item.parentId,
|
|
599
631
|
message
|
|
600
632
|
}));
|
|
@@ -611,25 +643,38 @@ var AgUiThreadRuntimeCore = class {
|
|
|
611
643
|
const userAction = { ...action };
|
|
612
644
|
delete userAction.type;
|
|
613
645
|
if (!("timestamp" in userAction)) userAction.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
614
|
-
this.
|
|
646
|
+
this.dispatch({
|
|
647
|
+
type: "a2uiActionDeferred",
|
|
648
|
+
action: userAction
|
|
649
|
+
});
|
|
615
650
|
const owner = this.abortController;
|
|
616
651
|
if (owner) {
|
|
617
|
-
this.
|
|
652
|
+
this.dispatch({
|
|
653
|
+
type: "a2uiResumeDeferred",
|
|
654
|
+
owner
|
|
655
|
+
});
|
|
618
656
|
return;
|
|
619
657
|
}
|
|
620
658
|
this.startResumeRun(parentId);
|
|
621
659
|
}
|
|
622
660
|
maybeResumeAfterToolResults(messageId) {
|
|
623
661
|
if (!this.maybeCompleteAfterToolResults(messageId)) return;
|
|
662
|
+
if (this.getPendingInterrupts()) return;
|
|
663
|
+
const turn = this.getTrailingAssistantTurn();
|
|
664
|
+
if (!turn.some((message) => message.id === messageId)) return;
|
|
665
|
+
if (this.getPendingToolCalls()) return;
|
|
666
|
+
const headId = turn.at(-1)?.id;
|
|
667
|
+
if (headId === void 0) return;
|
|
624
668
|
const owner = this.abortController;
|
|
625
669
|
if (owner) {
|
|
626
|
-
this.
|
|
670
|
+
this.dispatch({
|
|
671
|
+
type: "resumeDeferred",
|
|
627
672
|
owner,
|
|
628
|
-
messageId
|
|
629
|
-
};
|
|
673
|
+
messageId: headId
|
|
674
|
+
});
|
|
630
675
|
return;
|
|
631
676
|
}
|
|
632
|
-
this.startResumeRun(
|
|
677
|
+
this.startResumeRun(headId);
|
|
633
678
|
}
|
|
634
679
|
maybeCompleteAfterToolResults(messageId) {
|
|
635
680
|
const message = this.session.tryGetMessage(messageId)?.message;
|
|
@@ -637,9 +682,13 @@ var AgUiThreadRuntimeCore = class {
|
|
|
637
682
|
const assistant = message;
|
|
638
683
|
if (assistant.status?.type !== "requires-action" || assistant.status.reason !== "tool-calls") return false;
|
|
639
684
|
let allResolved = true;
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
685
|
+
const listed = listedPendingToolCallIds(assistant);
|
|
686
|
+
for (const part of iterateToolCallParts(assistant.content)) {
|
|
687
|
+
if (listed && !listed.has(part.toolCallId)) continue;
|
|
688
|
+
if (!isResolvedToolCall(part)) {
|
|
689
|
+
allResolved = false;
|
|
690
|
+
break;
|
|
691
|
+
}
|
|
643
692
|
}
|
|
644
693
|
if (!allResolved) return false;
|
|
645
694
|
if (!this.session.updateMessage(messageId, (current) => current.role === "assistant" ? {
|
|
@@ -647,14 +696,15 @@ var AgUiThreadRuntimeCore = class {
|
|
|
647
696
|
status: {
|
|
648
697
|
type: "complete",
|
|
649
698
|
reason: "unknown"
|
|
650
|
-
}
|
|
699
|
+
},
|
|
700
|
+
metadata: withoutPendingToolCallIds(current.metadata)
|
|
651
701
|
} : current)) return false;
|
|
652
702
|
this.publishUpdate();
|
|
653
703
|
this.persistAssistantHistory(messageId);
|
|
654
704
|
return true;
|
|
655
705
|
}
|
|
656
706
|
startResumeRun(messageId) {
|
|
657
|
-
this.startRun(messageId, this.lastRunConfig).catch((error) => {
|
|
707
|
+
this.startRun(messageId, this.state.lastRunConfig).catch((error) => {
|
|
658
708
|
this.reportError(error);
|
|
659
709
|
});
|
|
660
710
|
}
|
|
@@ -671,8 +721,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
671
721
|
}
|
|
672
722
|
};
|
|
673
723
|
});
|
|
674
|
-
this.
|
|
675
|
-
this.pendingA2uiAction = void 0;
|
|
724
|
+
this.dispatch({ type: "a2uiContinuationsCleared" });
|
|
676
725
|
this.assistantHistoryParents.clear();
|
|
677
726
|
if (messages.length === 0) this.session.clear();
|
|
678
727
|
else {
|
|
@@ -709,30 +758,34 @@ var AgUiThreadRuntimeCore = class {
|
|
|
709
758
|
}
|
|
710
759
|
this.snapshotHistoryIds.clear();
|
|
711
760
|
for (const { message } of this.getMessageRepository().messages) this.snapshotHistoryIds.add(message.id);
|
|
712
|
-
const resumeTarget = this.pendingResume?.messageId;
|
|
713
|
-
if (resumeTarget !== void 0 && !this.session.getMessages().some((message) => message.id === resumeTarget)) this.
|
|
761
|
+
const resumeTarget = this.state.pendingResume?.messageId;
|
|
762
|
+
if (resumeTarget !== void 0 && !this.session.getMessages().some((message) => message.id === resumeTarget)) this.dispatch({ type: "resumeCleared" });
|
|
714
763
|
this.publishUpdate();
|
|
715
764
|
}
|
|
716
765
|
loadExternalState(state) {
|
|
717
|
-
this.
|
|
766
|
+
this.dispatch({
|
|
767
|
+
type: "stateSnapshot",
|
|
768
|
+
snapshot: state
|
|
769
|
+
});
|
|
718
770
|
this.publishUpdate();
|
|
719
771
|
}
|
|
720
772
|
setState(next) {
|
|
721
|
-
|
|
773
|
+
const snapshot = typeof next === "function" ? next(this.state.stateSnapshot) : next;
|
|
774
|
+
this.dispatch({
|
|
775
|
+
type: "stateSnapshot",
|
|
776
|
+
snapshot
|
|
777
|
+
});
|
|
722
778
|
this.publishUpdate();
|
|
723
779
|
}
|
|
724
780
|
resetState() {
|
|
725
|
-
this.
|
|
781
|
+
this.dispatch({ type: "resetState" });
|
|
726
782
|
this.publishUpdate();
|
|
727
783
|
}
|
|
728
784
|
resetThreadState() {
|
|
729
|
-
this.
|
|
785
|
+
this.dispatch({ type: "historyMutationAdvanced" });
|
|
730
786
|
const controller = this.abortController;
|
|
731
787
|
const activeRunAgent = this.activeRunAgent;
|
|
732
|
-
this.
|
|
733
|
-
this.pendingResume = null;
|
|
734
|
-
this.pendingA2uiResumeOwner = null;
|
|
735
|
-
this.pendingA2uiAction = void 0;
|
|
788
|
+
this.dispatch({ type: "resetThread" });
|
|
736
789
|
if (controller) {
|
|
737
790
|
this.abortController = null;
|
|
738
791
|
this.activeRunAgent = null;
|
|
@@ -750,15 +803,23 @@ var AgUiThreadRuntimeCore = class {
|
|
|
750
803
|
if (this.isThreadSwitching?.()) return;
|
|
751
804
|
if (!this.supersedeActiveRun()) return;
|
|
752
805
|
const normalizedRunConfig = runConfig ?? {};
|
|
753
|
-
this.
|
|
806
|
+
this.dispatch({
|
|
807
|
+
type: "runConfigRecorded",
|
|
808
|
+
runConfig: normalizedRunConfig
|
|
809
|
+
});
|
|
754
810
|
const parent = parentId === null ? void 0 : this.session.tryGetMessage(parentId);
|
|
755
811
|
const shouldEagerlyInsertAssistant = parentId !== null && parent !== void 0 && parentId !== this.session.headId;
|
|
756
812
|
const historicalMessages = [...shouldEagerlyInsertAssistant ? this.session.tryGetMessages(parentId) ?? this.session.getMessages() : this.session.getMessages()];
|
|
757
813
|
const runStartMessageIds = new Set(this.getMessageRepository().messages.map(({ message }) => message.id));
|
|
814
|
+
const joinStrategy = this.joinStrategy;
|
|
815
|
+
const hasRunUserBoundary = parent?.message.role === "user" && !resume;
|
|
758
816
|
let pendingError = null;
|
|
759
817
|
const assistantParentId = parent ? parentId : this.session.headId;
|
|
760
818
|
let assistantMessageId;
|
|
761
819
|
let assistantCollided = false;
|
|
820
|
+
const heldMessages = /* @__PURE__ */ new Map();
|
|
821
|
+
let restrictedPendingToolCallIds;
|
|
822
|
+
let settlingRun = false;
|
|
762
823
|
const ensureAssistant = (allowRecreate = false) => {
|
|
763
824
|
const cached = assistantMessageId;
|
|
764
825
|
if (cached !== void 0 && this.session.tryGetMessage(cached)) return cached;
|
|
@@ -771,14 +832,58 @@ var AgUiThreadRuntimeCore = class {
|
|
|
771
832
|
return created;
|
|
772
833
|
};
|
|
773
834
|
if (shouldEagerlyInsertAssistant) ensureAssistant();
|
|
835
|
+
const settleHeldMessages = (terminalStatus) => {
|
|
836
|
+
const pendingMessages = [];
|
|
837
|
+
for (const [messageId, openToolCallIds] of heldMessages) {
|
|
838
|
+
let requiresToolResult = false;
|
|
839
|
+
if (this.session.updateMessage(messageId, (message) => {
|
|
840
|
+
if (message.role !== "assistant") return message;
|
|
841
|
+
const assistant = message;
|
|
842
|
+
const pendingToolCallIds = Array.from(openToolCallIds).filter((id) => !restrictedPendingToolCallIds || restrictedPendingToolCallIds.has(id));
|
|
843
|
+
const status = terminalStatus.type === "complete" || terminalStatus.type === "requires-action" ? pendingToolCallIds.length > 0 ? {
|
|
844
|
+
type: "requires-action",
|
|
845
|
+
reason: "tool-calls"
|
|
846
|
+
} : {
|
|
847
|
+
type: "complete",
|
|
848
|
+
reason: "unknown"
|
|
849
|
+
} : terminalStatus;
|
|
850
|
+
requiresToolResult = status.type === "requires-action";
|
|
851
|
+
const metadata = withoutPendingToolCallIds(assistant.metadata);
|
|
852
|
+
return {
|
|
853
|
+
...assistant,
|
|
854
|
+
status,
|
|
855
|
+
metadata: status.type === "requires-action" && restrictedPendingToolCallIds ? {
|
|
856
|
+
...metadata,
|
|
857
|
+
custom: {
|
|
858
|
+
...metadata.custom,
|
|
859
|
+
["agui"]: {
|
|
860
|
+
...metadata.custom["agui"],
|
|
861
|
+
pendingToolCallIds
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
} : metadata
|
|
865
|
+
};
|
|
866
|
+
})) {
|
|
867
|
+
this.publishUpdate();
|
|
868
|
+
this.persistAssistantHistory(messageId);
|
|
869
|
+
if (requiresToolResult) pendingMessages.push(messageId);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
heldMessages.clear();
|
|
873
|
+
return pendingMessages;
|
|
874
|
+
};
|
|
774
875
|
const applyUpdate = (update) => {
|
|
876
|
+
const terminalStatus = update.status;
|
|
877
|
+
const pendingMessages = settlingRun && terminalStatus && terminalStatus.type !== "running" ? settleHeldMessages(terminalStatus) : [];
|
|
775
878
|
const hasStreamContent = Array.isArray(update.content) && update.content.some((part) => part.type !== "data");
|
|
776
879
|
const resolved = this.updateAssistantMessage(ensureAssistant(hasStreamContent), update);
|
|
777
880
|
if (resolved !== assistantMessageId) assistantMessageId = resolved;
|
|
881
|
+
for (const messageId of pendingMessages) this.maybeResumeAfterToolResults(messageId);
|
|
778
882
|
};
|
|
779
|
-
const adoptServerMessageId = (serverId, startNewMessage) => {
|
|
883
|
+
const adoptServerMessageId = (serverId, startNewMessage, openToolCallIds = []) => {
|
|
780
884
|
if (startNewMessage && assistantMessageId !== void 0) {
|
|
781
|
-
|
|
885
|
+
if (openToolCallIds.length > 0) heldMessages.set(assistantMessageId, new Set(openToolCallIds));
|
|
886
|
+
else applyUpdate({ status: {
|
|
782
887
|
type: "complete",
|
|
783
888
|
reason: "unknown"
|
|
784
889
|
} });
|
|
@@ -806,19 +911,30 @@ var AgUiThreadRuntimeCore = class {
|
|
|
806
911
|
const abortSignal = abortController.signal;
|
|
807
912
|
const aggregator = new RunAggregator({
|
|
808
913
|
showThinking: this.showThinking,
|
|
914
|
+
joinStrategy,
|
|
809
915
|
logger: this.logger,
|
|
810
916
|
emit: applyUpdate,
|
|
811
917
|
onServerMessageId: (serverId) => {
|
|
812
918
|
adoptServerMessageId(serverId, false);
|
|
813
919
|
},
|
|
814
|
-
onTextMessageStart: (serverId) => adoptServerMessageId(serverId, true)
|
|
920
|
+
onTextMessageStart: (serverId, openToolCallIds) => adoptServerMessageId(serverId, true, openToolCallIds)
|
|
815
921
|
});
|
|
816
922
|
let runFinished = false;
|
|
817
923
|
const dispatch = (event) => {
|
|
818
924
|
if (this.abortController !== abortController) return;
|
|
819
|
-
if (event.type === "RUN_FINISHED")
|
|
820
|
-
|
|
821
|
-
|
|
925
|
+
if (event.type === "RUN_FINISHED") {
|
|
926
|
+
runFinished = true;
|
|
927
|
+
const ids = event.outcome?.type === "success" ? event.outcome.pendingToolCallIds : void 0;
|
|
928
|
+
restrictedPendingToolCallIds = ids?.length ? new Set(ids) : void 0;
|
|
929
|
+
}
|
|
930
|
+
if (event.type === "TOOL_CALL_RESULT") for (const openToolCallIds of heldMessages.values()) openToolCallIds.delete(event.toolCallId);
|
|
931
|
+
settlingRun = event.type === "RUN_FINISHED" || event.type === "RUN_ERROR" || event.type === "RUN_CANCELLED";
|
|
932
|
+
try {
|
|
933
|
+
const nextAssistantMessageId = this.handleEvent(aggregator, event, assistantMessageId, joinStrategy, runStartMessageIds, hasRunUserBoundary);
|
|
934
|
+
if (event.type === "MESSAGES_SNAPSHOT") assistantMessageId = nextAssistantMessageId;
|
|
935
|
+
} finally {
|
|
936
|
+
settlingRun = false;
|
|
937
|
+
}
|
|
822
938
|
};
|
|
823
939
|
this.abortController = abortController;
|
|
824
940
|
const runAgentInstance = this.agent;
|
|
@@ -892,16 +1008,16 @@ var AgUiThreadRuntimeCore = class {
|
|
|
892
1008
|
this.clearDeferredContinuations(abortController);
|
|
893
1009
|
throw err;
|
|
894
1010
|
}
|
|
895
|
-
if (this.pendingResume?.owner === abortController) {
|
|
896
|
-
const { messageId } = this.pendingResume;
|
|
897
|
-
this.
|
|
1011
|
+
if (this.state.pendingResume?.owner === abortController) {
|
|
1012
|
+
const { messageId } = this.state.pendingResume;
|
|
1013
|
+
this.dispatch({ type: "resumeCleared" });
|
|
898
1014
|
if (!abortSignal.aborted) this.startResumeRun(messageId);
|
|
899
1015
|
}
|
|
900
|
-
if (this.pendingA2uiResumeOwner === abortController) {
|
|
901
|
-
this.
|
|
902
|
-
if (!abortSignal.aborted && this.pendingA2uiAction !== void 0) {
|
|
1016
|
+
if (this.state.pendingA2uiResumeOwner === abortController) {
|
|
1017
|
+
this.dispatch({ type: "a2uiResumeCleared" });
|
|
1018
|
+
if (!abortSignal.aborted && this.state.pendingA2uiAction !== void 0) {
|
|
903
1019
|
if (this.getPendingInterrupts()) {
|
|
904
|
-
this.
|
|
1020
|
+
this.dispatch({ type: "a2uiActionCleared" });
|
|
905
1021
|
this.logger.debug("[agui] sendA2uiAction: pending interrupts, dropping action");
|
|
906
1022
|
return;
|
|
907
1023
|
}
|
|
@@ -909,15 +1025,14 @@ var AgUiThreadRuntimeCore = class {
|
|
|
909
1025
|
const parentId = this.session.headId;
|
|
910
1026
|
if (parentId !== null) this.startResumeRun(parentId);
|
|
911
1027
|
else {
|
|
912
|
-
this.
|
|
1028
|
+
this.dispatch({ type: "a2uiActionCleared" });
|
|
913
1029
|
this.logger.debug("[agui] sendA2uiAction: no messages to resume, dropping action");
|
|
914
1030
|
}
|
|
915
|
-
} else this.
|
|
1031
|
+
} else this.dispatch({ type: "a2uiActionCleared" });
|
|
916
1032
|
}
|
|
917
1033
|
}
|
|
918
1034
|
async consumeResumeStream(stream, ctx) {
|
|
919
|
-
this.
|
|
920
|
-
this.pendingA2uiResumeOwner = null;
|
|
1035
|
+
this.dispatch({ type: "a2uiContinuationsCleared" });
|
|
921
1036
|
const assistantId = ctx.ensureAssistant();
|
|
922
1037
|
const currentId = () => ctx.getAssistantMessageId() ?? assistantId;
|
|
923
1038
|
const options = {
|
|
@@ -958,8 +1073,8 @@ var AgUiThreadRuntimeCore = class {
|
|
|
958
1073
|
} });
|
|
959
1074
|
}
|
|
960
1075
|
buildRunInput(runId, runConfig, historyMessages, resume) {
|
|
961
|
-
const pendingA2uiAction = this.pendingA2uiAction;
|
|
962
|
-
this.
|
|
1076
|
+
const pendingA2uiAction = this.state.pendingA2uiAction;
|
|
1077
|
+
this.dispatch({ type: "a2uiActionCleared" });
|
|
963
1078
|
const threadId = this.agent.threadId || "main";
|
|
964
1079
|
const messages = toAgUiMessages(this.selectRunMessages(historyMessages ?? this.session.getMessages(), resume));
|
|
965
1080
|
const context = this.runtime?.thread.getModelContext();
|
|
@@ -977,7 +1092,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
977
1092
|
return {
|
|
978
1093
|
threadId,
|
|
979
1094
|
runId,
|
|
980
|
-
state: this.stateSnapshot ?? null,
|
|
1095
|
+
state: this.state.stateSnapshot ?? null,
|
|
981
1096
|
messages,
|
|
982
1097
|
tools: toAgUiTools(context?.tools),
|
|
983
1098
|
context: [...context?.system ? [{
|
|
@@ -1007,14 +1122,11 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1007
1122
|
return messages.slice(anchor + 1);
|
|
1008
1123
|
}
|
|
1009
1124
|
clearDeferredContinuations(owner) {
|
|
1010
|
-
if (this.pendingResume?.owner === owner) this.
|
|
1011
|
-
if (this.pendingA2uiResumeOwner === owner) {
|
|
1012
|
-
this.pendingA2uiResumeOwner = null;
|
|
1013
|
-
this.pendingA2uiAction = void 0;
|
|
1014
|
-
}
|
|
1125
|
+
if (this.state.pendingResume?.owner === owner) this.dispatch({ type: "resumeCleared" });
|
|
1126
|
+
if (this.state.pendingA2uiResumeOwner === owner) this.dispatch({ type: "a2uiContinuationsCleared" });
|
|
1015
1127
|
}
|
|
1016
1128
|
setRunning(running) {
|
|
1017
|
-
this.
|
|
1129
|
+
this.dispatch({ type: running ? "runStarted" : "runFinished" });
|
|
1018
1130
|
this.publishUpdate();
|
|
1019
1131
|
}
|
|
1020
1132
|
finishRun(controller) {
|
|
@@ -1031,7 +1143,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1031
1143
|
status: { type: "running" },
|
|
1032
1144
|
content: [],
|
|
1033
1145
|
metadata: {
|
|
1034
|
-
unstable_state: this.stateSnapshot ?? null,
|
|
1146
|
+
unstable_state: this.state.stateSnapshot ?? null,
|
|
1035
1147
|
unstable_annotations: [],
|
|
1036
1148
|
unstable_data: [],
|
|
1037
1149
|
steps: [],
|
|
@@ -1074,21 +1186,11 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1074
1186
|
ids.delete(oldId);
|
|
1075
1187
|
if (!collidesWithExisting) ids.add(newId);
|
|
1076
1188
|
}
|
|
1077
|
-
const pendingWrite = this.historyWrites.
|
|
1078
|
-
if (pendingWrite) {
|
|
1079
|
-
this.
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
const settle = () => {
|
|
1083
|
-
if (this.historyWrites.get(newId) === pendingWrite) this.historyWrites.delete(newId);
|
|
1084
|
-
};
|
|
1085
|
-
pendingWrite.then(() => {
|
|
1086
|
-
this.persistedHistoryIds.delete(oldId);
|
|
1087
|
-
this.persistedHistoryIds.add(newId);
|
|
1088
|
-
settle();
|
|
1089
|
-
}, settle);
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1189
|
+
const pendingWrite = this.historyWrites.move(oldId, collidesWithExisting ? void 0 : newId);
|
|
1190
|
+
if (pendingWrite && !collidesWithExisting) pendingWrite.then(() => {
|
|
1191
|
+
this.persistedHistoryIds.delete(oldId);
|
|
1192
|
+
this.persistedHistoryIds.add(newId);
|
|
1193
|
+
}, () => {});
|
|
1092
1194
|
this.publishUpdate();
|
|
1093
1195
|
return !collidesWithExisting;
|
|
1094
1196
|
}
|
|
@@ -1097,8 +1199,9 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1097
1199
|
if (!this.session.updateMessage(messageId, (message) => {
|
|
1098
1200
|
if (message.role !== "assistant") return message;
|
|
1099
1201
|
const assistant = message;
|
|
1100
|
-
const
|
|
1202
|
+
const mergedMetadata = update.metadata ? this.mergeAssistantMetadata(assistant.metadata, update.metadata) : assistant.metadata;
|
|
1101
1203
|
latestStatus = update.status ?? assistant.status;
|
|
1204
|
+
const metadata = latestStatus?.type === "requires-action" && latestStatus.reason === "tool-calls" ? mergedMetadata : withoutPendingToolCallIds(mergedMetadata);
|
|
1102
1205
|
const content = update.content !== void 0 ? this.preserveToolResults(assistant.content, update.content) : assistant.content;
|
|
1103
1206
|
return {
|
|
1104
1207
|
...assistant,
|
|
@@ -1234,18 +1337,23 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1234
1337
|
custom: incoming.custom ? mergeAgUiNamespace(current.custom, incoming.custom) : current.custom
|
|
1235
1338
|
};
|
|
1236
1339
|
}
|
|
1237
|
-
handleEvent(aggregator, event, activeAssistantId) {
|
|
1340
|
+
handleEvent(aggregator, event, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary) {
|
|
1238
1341
|
switch (event.type) {
|
|
1239
1342
|
case "STATE_SNAPSHOT":
|
|
1240
|
-
this.
|
|
1343
|
+
this.dispatch({
|
|
1344
|
+
type: "stateSnapshot",
|
|
1345
|
+
snapshot: event.snapshot
|
|
1346
|
+
});
|
|
1241
1347
|
this.updateActiveAssistantState(activeAssistantId);
|
|
1242
1348
|
break;
|
|
1243
1349
|
case "STATE_DELTA":
|
|
1244
1350
|
if (event.delta.length === 0) break;
|
|
1245
1351
|
try {
|
|
1246
|
-
const
|
|
1247
|
-
|
|
1248
|
-
|
|
1352
|
+
const result = jsonpatch.applyPatch(this.state.stateSnapshot ?? {}, event.delta, true, false);
|
|
1353
|
+
this.dispatch({
|
|
1354
|
+
type: "stateSnapshot",
|
|
1355
|
+
snapshot: result.newDocument
|
|
1356
|
+
});
|
|
1249
1357
|
this.updateActiveAssistantState(activeAssistantId);
|
|
1250
1358
|
} catch (error) {
|
|
1251
1359
|
this.logger.error?.("[agui] failed to apply state delta", error);
|
|
@@ -1255,7 +1363,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1255
1363
|
const previousHeadId = this.session.headId;
|
|
1256
1364
|
const activeAssistant = activeAssistantId === void 0 ? void 0 : this.session.tryGetMessage(activeAssistantId)?.message;
|
|
1257
1365
|
const hasActiveText = activeAssistant?.role === "assistant" && activeAssistant.content.some((part) => part.type === "text");
|
|
1258
|
-
this.importMessagesSnapshot(event.messages, activeAssistantId);
|
|
1366
|
+
this.importMessagesSnapshot(event.messages, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary);
|
|
1259
1367
|
const headId = this.session.headId;
|
|
1260
1368
|
if (hasActiveText && headId !== previousHeadId) activeAssistantId = (headId === null ? void 0 : this.session.tryGetMessage(headId)?.message)?.role === "assistant" ? headId ?? void 0 : void 0;
|
|
1261
1369
|
this.updateActiveAssistantState(activeAssistantId);
|
|
@@ -1296,7 +1404,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1296
1404
|
...assistant,
|
|
1297
1405
|
metadata: {
|
|
1298
1406
|
...assistant.metadata,
|
|
1299
|
-
unstable_state: this.stateSnapshot ?? null
|
|
1407
|
+
unstable_state: this.state.stateSnapshot ?? null
|
|
1300
1408
|
}
|
|
1301
1409
|
};
|
|
1302
1410
|
});
|
|
@@ -1307,25 +1415,27 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1307
1415
|
if (message.role !== "assistant") return message;
|
|
1308
1416
|
const assistant = message;
|
|
1309
1417
|
const mcpAppUri = readMcpAppResourceUri(event.mcpResult?._meta);
|
|
1418
|
+
const modelText = toolContentText(event.content);
|
|
1310
1419
|
let matchedToolCall = false;
|
|
1311
1420
|
const { content } = mapToolCallPartsDeep(assistant.content, (part) => {
|
|
1312
1421
|
if (part.toolCallId !== event.toolCallId) return part;
|
|
1313
1422
|
matchedToolCall = true;
|
|
1314
1423
|
if (part.mcp?.app !== void 0 && isPlainObject(part.result) && Array.isArray(part.result["content"])) return {
|
|
1315
1424
|
...part,
|
|
1316
|
-
...part.modelContent === void 0
|
|
1425
|
+
...part.modelContent === void 0 ? { modelContent: [{
|
|
1317
1426
|
type: "text",
|
|
1318
|
-
text:
|
|
1427
|
+
text: modelText
|
|
1319
1428
|
}] } : {},
|
|
1320
1429
|
...part.isError === void 0 ? typeof event.mcpResult?.isError === "boolean" ? { isError: event.mcpResult.isError } : event.role === "tool" ? { isError: false } : {} : {},
|
|
1321
1430
|
...event.messageId ? { unstable_toolMessageId: event.messageId } : {}
|
|
1322
1431
|
};
|
|
1432
|
+
const { modelContent: _modelContent, ...partWithoutModelContent } = part;
|
|
1323
1433
|
return {
|
|
1324
|
-
...part,
|
|
1325
|
-
result: event.mcpResult ??
|
|
1434
|
+
...event.mcpResult === void 0 && Array.isArray(event.content) ? partWithoutModelContent : part,
|
|
1435
|
+
result: event.mcpResult ?? (Array.isArray(event.content) ? event.content : tryParseJSON(event.content)),
|
|
1326
1436
|
...event.mcpResult !== void 0 ? { modelContent: [{
|
|
1327
1437
|
type: "text",
|
|
1328
|
-
text:
|
|
1438
|
+
text: modelText
|
|
1329
1439
|
}] } : {},
|
|
1330
1440
|
...typeof event.mcpResult?.isError === "boolean" ? { isError: event.mcpResult.isError } : event.role === "tool" ? { isError: false } : {},
|
|
1331
1441
|
...part.mcp === void 0 && mcpAppUri !== void 0 ? { mcp: { app: { resourceUri: mcpAppUri } } } : {},
|
|
@@ -1380,7 +1490,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1380
1490
|
this.publishUpdate();
|
|
1381
1491
|
this.maybeCompleteAfterToolResults(messageId);
|
|
1382
1492
|
}
|
|
1383
|
-
importMessagesSnapshot(rawMessages, activeAssistantId) {
|
|
1493
|
+
importMessagesSnapshot(rawMessages, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary) {
|
|
1384
1494
|
try {
|
|
1385
1495
|
const activeAssistantItem = activeAssistantId ? this.session.tryGetMessage(activeAssistantId) : void 0;
|
|
1386
1496
|
const activeMessage = activeAssistantItem?.message;
|
|
@@ -1403,6 +1513,53 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1403
1513
|
content: this.mergeSnapshotAssistantContent(existing.content, convertedMessage.content, snapshotHasReasoning, snapshotHasActivity)
|
|
1404
1514
|
};
|
|
1405
1515
|
}
|
|
1516
|
+
if (joinStrategy === "concat-content") {
|
|
1517
|
+
const activeIndex = converted.findIndex((message) => message.id === activeAssistantId);
|
|
1518
|
+
const lastKnownIndex = converted.findLastIndex((message) => runStartMessageIds.has(message.id));
|
|
1519
|
+
const lastUserIndex = hasRunUserBoundary ? converted.findLastIndex((message) => message.role === "user") : -1;
|
|
1520
|
+
const startIndex = activeIndex >= 0 ? activeIndex : lastKnownIndex >= 0 || lastUserIndex >= 0 ? Math.max(lastKnownIndex, lastUserIndex) + 1 : -1;
|
|
1521
|
+
if (startIndex >= 0 && converted[startIndex]?.role === "assistant") {
|
|
1522
|
+
let endIndex = startIndex + 1;
|
|
1523
|
+
while (converted[endIndex]?.role === "assistant") endIndex++;
|
|
1524
|
+
const runMessages = converted.slice(startIndex, endIndex);
|
|
1525
|
+
const includesStreamedPrefix = activeIndex < 0 && activeAssistant?.content.some((part) => part.type !== "data");
|
|
1526
|
+
if (includesStreamedPrefix && activeAssistant) runMessages.unshift(activeAssistant);
|
|
1527
|
+
if (runMessages.length > 1) {
|
|
1528
|
+
const [first, ...rest] = runMessages;
|
|
1529
|
+
let content = runMessages.flatMap((message) => message.content);
|
|
1530
|
+
if (includesStreamedPrefix) {
|
|
1531
|
+
content = [...first.content];
|
|
1532
|
+
let cursor = 0;
|
|
1533
|
+
const keyOf = (part) => part.type === "tool-call" ? `tool:${part.toolCallId}` : part.type === "text" || part.type === "reasoning" ? `${part.type}:${part.text}` : void 0;
|
|
1534
|
+
for (const message of rest) for (const part of message.content) {
|
|
1535
|
+
const key = keyOf(part);
|
|
1536
|
+
const match = key === void 0 ? -1 : content.findIndex((previous, index) => index >= cursor && keyOf(previous) === key);
|
|
1537
|
+
if (match < 0) {
|
|
1538
|
+
content.push(part);
|
|
1539
|
+
cursor = content.length;
|
|
1540
|
+
} else {
|
|
1541
|
+
if (part.type === "tool-call") content[match] = this.preserveToolResults([content[match]], [part])[0];
|
|
1542
|
+
cursor = match + 1;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
converted.splice(startIndex, endIndex - startIndex, {
|
|
1547
|
+
...first,
|
|
1548
|
+
content,
|
|
1549
|
+
status: rest.at(-1).status,
|
|
1550
|
+
metadata: rest.reduce((metadata, message) => ({
|
|
1551
|
+
...metadata,
|
|
1552
|
+
...message.metadata,
|
|
1553
|
+
unstable_state: message.metadata.unstable_state,
|
|
1554
|
+
unstable_annotations: [...metadata.unstable_annotations, ...message.metadata.unstable_annotations],
|
|
1555
|
+
unstable_data: [...metadata.unstable_data, ...message.metadata.unstable_data],
|
|
1556
|
+
steps: [...metadata.steps, ...message.metadata.steps],
|
|
1557
|
+
custom: mergeAgUiNamespace(metadata.custom, message.metadata.custom)
|
|
1558
|
+
}), first.metadata)
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1406
1563
|
const snapshotContainsActiveAssistant = converted.some((message) => message.id === activeAssistant?.id);
|
|
1407
1564
|
const preservesActiveAssistant = activeAssistant !== void 0 && !snapshotContainsActiveAssistant && (activeAssistant.metadata.isOptimistic !== true || converted.at(-1)?.role !== "assistant" || activeAssistantParentId !== void 0 && converted.at(-1)?.id === activeAssistantParentId);
|
|
1408
1565
|
if (preservesActiveAssistant) converted.push(activeAssistant);
|
|
@@ -1448,7 +1605,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1448
1605
|
const wasPersisted = this.persistedHistoryIds.has(messageId);
|
|
1449
1606
|
const update = history.update;
|
|
1450
1607
|
if (update !== void 0 && (wasPersisted || this.snapshotHistoryIds.has(messageId))) {
|
|
1451
|
-
const write = this.
|
|
1608
|
+
const write = this.historyWrites.chain(messageId, () => update.call(history, {
|
|
1452
1609
|
parentId,
|
|
1453
1610
|
message
|
|
1454
1611
|
}));
|
|
@@ -1456,7 +1613,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1456
1613
|
write.then(() => {
|
|
1457
1614
|
this.persistedHistoryIds.add(messageId);
|
|
1458
1615
|
}, (error) => {
|
|
1459
|
-
const pending = this.historyWrites.
|
|
1616
|
+
const pending = this.historyWrites.pending(messageId);
|
|
1460
1617
|
if (pending === void 0 || pending === write) this.assistantHistoryParents.set(messageId, parentId);
|
|
1461
1618
|
this.logger.error?.("[agui] failed to update history entry", error);
|
|
1462
1619
|
});
|
|
@@ -1470,17 +1627,17 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1470
1627
|
if (!write) return;
|
|
1471
1628
|
this.assistantHistoryParents.delete(messageId);
|
|
1472
1629
|
write.then(() => {}, (error) => {
|
|
1473
|
-
const pending = this.historyWrites.
|
|
1630
|
+
const pending = this.historyWrites.pending(messageId);
|
|
1474
1631
|
if (pending === void 0 || pending === write) this.assistantHistoryParents.set(messageId, parentId);
|
|
1475
1632
|
this.logger.error?.("[agui] failed to append history entry", error);
|
|
1476
1633
|
});
|
|
1477
1634
|
}
|
|
1478
1635
|
appendHistoryItem(parentId, message) {
|
|
1479
1636
|
if (!this.history || this.persistedHistoryIds.has(message.id)) return;
|
|
1480
|
-
const pending = this.historyWrites.
|
|
1637
|
+
const pending = this.historyWrites.pending(message.id);
|
|
1481
1638
|
if (pending) return pending;
|
|
1482
1639
|
const append = this.history.append.bind(this.history);
|
|
1483
|
-
const write = this.
|
|
1640
|
+
const write = this.historyWrites.chain(message.id, () => append({
|
|
1484
1641
|
parentId,
|
|
1485
1642
|
message
|
|
1486
1643
|
}));
|
|
@@ -1489,25 +1646,8 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1489
1646
|
}, () => {});
|
|
1490
1647
|
return write;
|
|
1491
1648
|
}
|
|
1492
|
-
chainHistoryWrite(id, write) {
|
|
1493
|
-
const pending = this.historyWrites.get(id);
|
|
1494
|
-
let next;
|
|
1495
|
-
if (pending) next = pending.then(write, write);
|
|
1496
|
-
else try {
|
|
1497
|
-
next = Promise.resolve(write());
|
|
1498
|
-
} catch (error) {
|
|
1499
|
-
next = Promise.reject(error);
|
|
1500
|
-
}
|
|
1501
|
-
this.historyWrites.set(id, next);
|
|
1502
|
-
next.then(() => {
|
|
1503
|
-
if (this.historyWrites.get(id) === next) this.historyWrites.delete(id);
|
|
1504
|
-
}, () => {
|
|
1505
|
-
if (this.historyWrites.get(id) === next) this.historyWrites.delete(id);
|
|
1506
|
-
});
|
|
1507
|
-
return next;
|
|
1508
|
-
}
|
|
1509
1649
|
};
|
|
1510
1650
|
//#endregion
|
|
1511
|
-
export {
|
|
1651
|
+
export { AgUiThreadController };
|
|
1512
1652
|
|
|
1513
|
-
//# sourceMappingURL=
|
|
1653
|
+
//# sourceMappingURL=AgUiThreadController.js.map
|