@assistant-ui/react-ag-ui 0.0.57 → 0.0.59
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 +16 -0
- package/dist/index.d.ts +2 -2
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts +19 -8
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtime/AgUiThreadRuntimeCore.js +118 -86
- package/dist/runtime/AgUiThreadRuntimeCore.js.map +1 -1
- package/dist/runtime/adapter/conversions.d.ts.map +1 -1
- package/dist/runtime/adapter/conversions.js +34 -11
- package/dist/runtime/adapter/conversions.js.map +1 -1
- package/dist/runtime/adapter/run-aggregator.d.ts +13 -3
- package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
- package/dist/runtime/adapter/run-aggregator.js +385 -119
- package/dist/runtime/adapter/run-aggregator.js.map +1 -1
- package/dist/runtime/adapter/subscriber.d.ts +9 -0
- package/dist/runtime/adapter/subscriber.d.ts.map +1 -1
- package/dist/runtime/adapter/subscriber.js +4 -1
- package/dist/runtime/adapter/subscriber.js.map +1 -1
- package/dist/runtime/adapter/tool-approval.d.ts +3 -1
- package/dist/runtime/adapter/tool-approval.d.ts.map +1 -1
- package/dist/runtime/adapter/tool-approval.js +11 -12
- package/dist/runtime/adapter/tool-approval.js.map +1 -1
- package/dist/runtime/event-parser.d.ts.map +1 -1
- package/dist/runtime/event-parser.js +176 -45
- package/dist/runtime/event-parser.js.map +1 -1
- package/dist/runtime/types.d.ts +67 -1
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/useAgUiRuntime.d.ts.map +1 -1
- package/dist/useAgUiRuntime.js +15 -5
- package/dist/useAgUiRuntime.js.map +1 -1
- package/package.json +9 -9
- package/src/index.ts +1 -0
- package/src/runtime/AgUiThreadRuntimeCore.test.ts +18 -0
- package/src/runtime/AgUiThreadRuntimeCore.ts +204 -120
- package/src/runtime/adapter/conversions.ts +87 -17
- package/src/runtime/adapter/run-aggregator.approval.test.ts +128 -0
- package/src/runtime/adapter/run-aggregator.ts +595 -139
- package/src/runtime/adapter/subscriber.ts +10 -1
- package/src/runtime/adapter/tool-approval.test.ts +75 -0
- package/src/runtime/adapter/tool-approval.ts +17 -12
- package/src/runtime/event-parser.ts +216 -36
- package/src/runtime/types.ts +96 -12
- package/src/useAgUiRuntime.approval.test.tsx +34 -24
- package/src/useAgUiRuntime.queue.test.tsx +54 -0
- package/src/useAgUiRuntime.thread-switch.test.tsx +199 -0
- package/src/useAgUiRuntime.toolDeferral.test.tsx +186 -0
- package/src/useAgUiRuntime.ts +15 -3
package/README.md
CHANGED
|
@@ -33,6 +33,22 @@ export function Provider({ children }: { children: React.ReactNode }) {
|
|
|
33
33
|
}
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
## Subagents
|
|
37
|
+
|
|
38
|
+
An AG-UI backend that runs subagents (the agents-as-tools pattern) emits `SUBAGENT_STARTED` / `SUBAGENT_FINISHED` / `SUBAGENT_ERROR` plus a `subagentRunId` on the events each subagent produces. This adapter groups that activity into one nested assistant message per subagent run and attaches it to the spawning tool call as `ToolCallMessagePart.messages`, joined on `SUBAGENT_STARTED.parentToolCallId`. `PartPrimitive.Messages` renders it without extra wiring, and the subagent's name, description, result, and error code ride on that message's `agui` metadata.
|
|
39
|
+
|
|
40
|
+
A subagent that names no reachable spawning call (`parentToolCallId` is optional, may name a call this run never saw, or two runs may name each other) has nowhere to nest, so its output renders in the parent thread instead of being dropped. This matches the downgrade the protocol's own pre-subagent compatibility middleware performs.
|
|
41
|
+
|
|
42
|
+
A subagent's frontend-executed tool calls work the same way the root agent's do: a call nested on `ToolCallMessagePart.messages` is reachable by `getPendingToolCalls()`, resolves through `addToolResult`, and its result rides the resume as a `tool` record on the spawning assistant record, the same flattened wire shape these calls had before subagent attribution.
|
|
43
|
+
|
|
44
|
+
Approval gates cover nested calls too: a gate that names a subagent-scoped call projects onto the nested part, the frontend tool stays unexecuted while the gate is open, decisions recorded through `respondToToolApproval` land on the nested part, and an undecided gate's result is never exported to the backend.
|
|
45
|
+
|
|
46
|
+
Two limitations are worth knowing before you rely on this:
|
|
47
|
+
|
|
48
|
+
- **Nested structure does not survive a reload.** Thread restore reads the flattened wire shape, so a restored subagent tool call comes back as a root-level part rather than nested under its spawning call. Results and decisions are preserved; only the nesting is not.
|
|
49
|
+
|
|
50
|
+
- **Nested human-in-the-loop is not wired up.** A `SUBAGENT_FINISHED` with a `suspended` outcome marks the nested message `requires-action`, and its `interruptIds` are preserved on the message metadata, but there is no resume path that answers them yet.
|
|
51
|
+
|
|
36
52
|
## See also
|
|
37
53
|
|
|
38
54
|
- `@assistant-ui/react-a2a` for the A2A v1.0 protocol.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AgUiInterrupt, AgUiInterruptReason, AgUiResumeEntry, AgUiRunFinishedOutcome, UseAgUiRuntimeAdapters, UseAgUiRuntimeOptions, UseAgUiThreadListAdapter } from "./runtime/types.js";
|
|
1
|
+
import { AgUiInterrupt, AgUiInterruptReason, AgUiResumeEntry, AgUiResumeTranscript, AgUiRunFinishedOutcome, UseAgUiRuntimeAdapters, UseAgUiRuntimeOptions, UseAgUiThreadListAdapter } from "./runtime/types.js";
|
|
2
2
|
import { useAgUiInterrupts, useAgUiSendA2uiAction, useAgUiSetState, useAgUiState, useAgUiSteerAway, useAgUiSubmitInterruptResponses } from "./hooks.js";
|
|
3
3
|
import { AgUiAssistantRuntime, useAgUiRuntime } from "./useAgUiRuntime.js";
|
|
4
4
|
import { AgUiMessage, FromAgUiMessagesOptions, fromAgUiMessages, toAgUiMessages } from "./runtime/adapter/conversions.js";
|
|
5
|
-
export { type AgUiAssistantRuntime, type AgUiInterrupt, type AgUiInterruptReason, type AgUiMessage, type AgUiResumeEntry, type AgUiRunFinishedOutcome, type FromAgUiMessagesOptions, type UseAgUiRuntimeAdapters, type UseAgUiRuntimeOptions, type UseAgUiThreadListAdapter, fromAgUiMessages, toAgUiMessages, useAgUiInterrupts, useAgUiRuntime, useAgUiSendA2uiAction, useAgUiSetState, useAgUiState, useAgUiSteerAway, useAgUiSubmitInterruptResponses };
|
|
5
|
+
export { type AgUiAssistantRuntime, type AgUiInterrupt, type AgUiInterruptReason, type AgUiMessage, type AgUiResumeEntry, type AgUiResumeTranscript, type AgUiRunFinishedOutcome, type FromAgUiMessagesOptions, type UseAgUiRuntimeAdapters, type UseAgUiRuntimeOptions, type UseAgUiThreadListAdapter, fromAgUiMessages, toAgUiMessages, useAgUiInterrupts, useAgUiRuntime, useAgUiSendA2uiAction, useAgUiSetState, useAgUiState, useAgUiSteerAway, useAgUiSubmitInterruptResponses };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Logger } from "./logger.js";
|
|
2
|
-
import { AgUiInterrupt, AgUiResumeEntry } from "./types.js";
|
|
2
|
+
import { AgUiInterrupt, AgUiResumeEntry, AgUiResumeTranscript } from "./types.js";
|
|
3
3
|
import { AddToolResultOptions, AppendMessage, AssistantRuntime, ChatModelRunOptions, ChatModelRunResult, CreateAppendMessage, ExportedMessageRepository, RespondToToolApprovalOptions, ThreadHistoryAdapter, ThreadMessage } from "@assistant-ui/core";
|
|
4
4
|
import { AbstractAgent } from "@ag-ui/client";
|
|
5
5
|
import { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
@@ -16,6 +16,7 @@ type CoreOptions = {
|
|
|
16
16
|
agent: AbstractAgent;
|
|
17
17
|
logger: Logger;
|
|
18
18
|
showThinking: boolean;
|
|
19
|
+
resumeTranscript?: AgUiResumeTranscript | undefined;
|
|
19
20
|
autoCancelPendingToolCalls?: boolean | undefined;
|
|
20
21
|
onError?: (error: Error) => void;
|
|
21
22
|
onCancel?: () => void;
|
|
@@ -26,6 +27,7 @@ declare class AgUiThreadRuntimeCore {
|
|
|
26
27
|
private agent;
|
|
27
28
|
private logger;
|
|
28
29
|
private showThinking;
|
|
30
|
+
private resumeTranscript;
|
|
29
31
|
private autoCancelPendingToolCalls;
|
|
30
32
|
private onError;
|
|
31
33
|
private onCancel;
|
|
@@ -37,7 +39,6 @@ declare class AgUiThreadRuntimeCore {
|
|
|
37
39
|
private abortController;
|
|
38
40
|
private activeRunAgent;
|
|
39
41
|
private stateSnapshot;
|
|
40
|
-
private pendingError;
|
|
41
42
|
private history;
|
|
42
43
|
private lastRunConfig;
|
|
43
44
|
private readonly assistantHistoryParents;
|
|
@@ -47,8 +48,8 @@ declare class AgUiThreadRuntimeCore {
|
|
|
47
48
|
private _isLoading;
|
|
48
49
|
private _loadPromise;
|
|
49
50
|
private _loadRequested;
|
|
50
|
-
private
|
|
51
|
-
private
|
|
51
|
+
private pendingResume;
|
|
52
|
+
private pendingA2uiResumeOwner;
|
|
52
53
|
private pendingA2uiAction;
|
|
53
54
|
constructor(options: CoreOptions);
|
|
54
55
|
updateOptions(options: Omit<CoreOptions, "notifyUpdate">): void;
|
|
@@ -68,6 +69,8 @@ declare class AgUiThreadRuntimeCore {
|
|
|
68
69
|
runConfig?: RunConfig;
|
|
69
70
|
}): Promise<void>;
|
|
70
71
|
cancel(): Promise<void>;
|
|
72
|
+
private abortActiveRun;
|
|
73
|
+
private supersedeActiveRun;
|
|
71
74
|
resume(config: ResumeRunConfig): Promise<void>;
|
|
72
75
|
resumeInFlightRun(messages: readonly ThreadMessage[]): Promise<void>;
|
|
73
76
|
private assertNoPendingInterrupts;
|
|
@@ -89,9 +92,9 @@ declare class AgUiThreadRuntimeCore {
|
|
|
89
92
|
*/
|
|
90
93
|
private resumeWithResponses;
|
|
91
94
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
+
* A rejected decision reaches the caller through the approval seam, and
|
|
96
|
+
* `onError` in addition, because a consumer watching only the runtime would
|
|
97
|
+
* otherwise miss it. A failure raised by the resumed run itself was already
|
|
95
98
|
* reported by `startRun` before it rethrew, so reporting it here again would
|
|
96
99
|
* give the consumer two notifications for one failure.
|
|
97
100
|
*/
|
|
@@ -117,7 +120,15 @@ declare class AgUiThreadRuntimeCore {
|
|
|
117
120
|
private startRun;
|
|
118
121
|
private consumeResumeStream;
|
|
119
122
|
private buildRunInput;
|
|
120
|
-
|
|
123
|
+
/**
|
|
124
|
+
* An anchor the thread no longer carries falls back to the whole transcript.
|
|
125
|
+
* The anchor is missing exactly when a snapshot import replaced the messages
|
|
126
|
+
* this resume was raised against, and a host that expects a delta tolerates
|
|
127
|
+
* being told more than it knows, while one rebuilding the run from the
|
|
128
|
+
* transcript cannot recover from being told less.
|
|
129
|
+
*/
|
|
130
|
+
private selectRunMessages;
|
|
131
|
+
private clearDeferredContinuations;
|
|
121
132
|
private setRunning;
|
|
122
133
|
private finishRun;
|
|
123
134
|
private insertAssistantPlaceholder;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgUiThreadRuntimeCore.d.ts","names":[],"sources":["../../src/runtime/AgUiThreadRuntimeCore.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"AgUiThreadRuntimeCore.d.ts","names":[],"sources":["../../src/runtime/AgUiThreadRuntimeCore.ts"],"mappings":";;;;;;KAmFK,YAAY,YAAY;KACxB,gBACH,SAAS,wBACN,eAAe;KACf;EACH;EACA;EACA,WAAW;EACX,SAAS;;KAaN;EACH,OAAO;EACP,QAAQ;EACR;EACA,mBAAmB;EACnB;EACA,WAAW,OAAO;EAClB;EACA,UAAU;EACV;;cAoCW;UACH;UACA;UACA;UACA;UACA;UACA;UACA;mBACS;mBACA;UAET;mBACS;UAgBT;UACA;UAGA;UACA;UACA;UACA;mBACS;mBACA;mBACA;mBACA;UACT;UACA;UACA;UACA;UAEA;UACA;EAEI,YAAA,SAAS;EAYrB,cAAc,SAAS,KAAK;EAqB5B,cAAc,SAAS;EAIvB;EAOA,wBAAwB;EAIxB,wBAAwB;EAIxB,YAAY;EAIZ;MAII;EAIJ,mBAAmB;EAsDb,OAAO,SAAS,gBAAgB;UAgB9B;UAQA;EAiBF,KAAK,SAAS,gBAAgB;EAI9B,OACJ,yBACA;IAAU,YAAY;MACrB;EAMG,UAAU;UAWR;UAiBA;EAaF,OAAO,QAAQ,kBAAkB;EAUjC,kBAAkB,mBAAmB,kBAAkB;UAwBrD;UAOA;EAgBR;IACE;IACA,qBAAqB;;EAavB;IACE;IACA;;EAaI,yBACJ,oBAAoB,oBACnB;;;;;;;UAuCW;;;;;;;;EAkBd,YAAY;UAYJ;EAwBF,sBACJ,SAAS,+BACR;EAsEG,UACJ,SAAS,qBACT,qBAAqB,oBACpB;UA4CK;UAkBA;UAiCA;UA2BA;EAmCR,yBAAyB;UAiBjB;EAiBR,cAAc,SAAS;EA+BvB,eAAe,QAAQ;UA6Bf;UAaA;UAiCA;EAUR,sBAAsB,mBAAmB;EA6DzC,kBAAkB,OAAO;EAKzB,SACE,MACI,sBACE,MAAM,kCAAkC;EAOhD;UAKc;UA0PA;UA2DN;;;;;;;;UA4CA;UAaA;UAUA;UAKA;UAOA;UAyBA;UAkEA;UAiDA;UA0BA;UA6BA;UAgEA;UA0EA;UA6DA;UAuDA;UAQA;UAIA;UAKA;UAMA;UAQA;UAsDA;UAqBA"}
|
|
@@ -5,14 +5,13 @@ import { AG_UI_METADATA_NAMESPACE, RunAggregator, isPlainObject, tryParseJSON }
|
|
|
5
5
|
import { fromAgUiMessages, toAgUiMessages, toAgUiTools } from "./adapter/conversions.js";
|
|
6
6
|
import { createAgUiSubscriber } from "./adapter/subscriber.js";
|
|
7
7
|
import { fromThreadMessageLike, generateId, isMcpAppUri } from "@assistant-ui/core";
|
|
8
|
-
import { createMessageRepositorySession, invokeUserCallback } from "@assistant-ui/core/internal";
|
|
8
|
+
import { createMessageRepositorySession, invokeUserCallback, iterateToolCallParts, mapToolCallPartsDeep } from "@assistant-ui/core/internal";
|
|
9
9
|
import jsonpatch from "fast-json-patch";
|
|
10
10
|
//#region src/runtime/AgUiThreadRuntimeCore.ts
|
|
11
11
|
const optimisticPrefix = "__optimistic__";
|
|
12
12
|
const generateOptimisticId = () => `${optimisticPrefix}${generateId()}`;
|
|
13
13
|
const isOptimisticId = (id) => id.startsWith(optimisticPrefix);
|
|
14
14
|
const isResolvedToolCall = (part) => part.type === "tool-call" && "result" in part && part.result !== void 0;
|
|
15
|
-
const symbolResumeShim = Symbol("agui-resume-shim");
|
|
16
15
|
const FALLBACK_USER_STATUS = {
|
|
17
16
|
type: "complete",
|
|
18
17
|
reason: "unknown"
|
|
@@ -37,6 +36,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
37
36
|
agent;
|
|
38
37
|
logger;
|
|
39
38
|
showThinking;
|
|
39
|
+
resumeTranscript;
|
|
40
40
|
autoCancelPendingToolCalls;
|
|
41
41
|
onError;
|
|
42
42
|
onCancel;
|
|
@@ -61,7 +61,6 @@ var AgUiThreadRuntimeCore = class {
|
|
|
61
61
|
abortController = null;
|
|
62
62
|
activeRunAgent = null;
|
|
63
63
|
stateSnapshot;
|
|
64
|
-
pendingError = null;
|
|
65
64
|
history;
|
|
66
65
|
lastRunConfig;
|
|
67
66
|
assistantHistoryParents = /* @__PURE__ */ new Map();
|
|
@@ -71,30 +70,30 @@ var AgUiThreadRuntimeCore = class {
|
|
|
71
70
|
_isLoading = false;
|
|
72
71
|
_loadPromise;
|
|
73
72
|
_loadRequested = false;
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
pendingResume = null;
|
|
74
|
+
pendingA2uiResumeOwner = null;
|
|
76
75
|
pendingA2uiAction;
|
|
77
76
|
constructor(options) {
|
|
78
77
|
this.agent = options.agent;
|
|
79
78
|
this.logger = options.logger;
|
|
80
79
|
this.showThinking = options.showThinking;
|
|
80
|
+
this.resumeTranscript = options.resumeTranscript ?? "full";
|
|
81
81
|
this.autoCancelPendingToolCalls = options.autoCancelPendingToolCalls;
|
|
82
82
|
this.onError = options.onError;
|
|
83
83
|
this.onCancel = options.onCancel;
|
|
84
84
|
this.history = options.history;
|
|
85
85
|
this.notifyUpdate = options.notifyUpdate;
|
|
86
|
-
this.installResumeShim();
|
|
87
86
|
}
|
|
88
87
|
updateOptions(options) {
|
|
89
88
|
this.agent = options.agent;
|
|
90
89
|
this.logger = options.logger;
|
|
91
90
|
this.showThinking = options.showThinking;
|
|
91
|
+
this.resumeTranscript = options.resumeTranscript ?? "full";
|
|
92
92
|
this.autoCancelPendingToolCalls = options.autoCancelPendingToolCalls;
|
|
93
93
|
this.onError = options.onError;
|
|
94
94
|
this.onCancel = options.onCancel;
|
|
95
95
|
const previousHistory = this.history;
|
|
96
96
|
this.history = options.history;
|
|
97
|
-
this.installResumeShim();
|
|
98
97
|
if (this._loadRequested && !this._loadPromise && !previousHistory && options.history && this.session.getMessages().length === 0) this.__internal_load();
|
|
99
98
|
}
|
|
100
99
|
attachRuntime(runtime) {
|
|
@@ -153,12 +152,14 @@ var AgUiThreadRuntimeCore = class {
|
|
|
153
152
|
}
|
|
154
153
|
async append(message) {
|
|
155
154
|
const startRun = message.startRun ?? message.role === "user";
|
|
155
|
+
let ownsThread = true;
|
|
156
156
|
if (startRun) {
|
|
157
157
|
this.assertNoPendingInterrupts();
|
|
158
158
|
this.maybeAutoCancelPendingToolCalls();
|
|
159
|
+
ownsThread = this.supersedeActiveRun();
|
|
159
160
|
}
|
|
160
161
|
const threadMessageId = this.appendEntry(message);
|
|
161
|
-
if (!startRun) return;
|
|
162
|
+
if (!startRun || !ownsThread) return;
|
|
162
163
|
await this.startRun(threadMessageId, message.runConfig);
|
|
163
164
|
}
|
|
164
165
|
maybeAutoCancelPendingToolCalls() {
|
|
@@ -187,12 +188,26 @@ var AgUiThreadRuntimeCore = class {
|
|
|
187
188
|
await this.startRun(parentId, config.runConfig);
|
|
188
189
|
}
|
|
189
190
|
async cancel() {
|
|
190
|
-
|
|
191
|
+
this.abortActiveRun();
|
|
192
|
+
}
|
|
193
|
+
abortActiveRun() {
|
|
194
|
+
const controller = this.abortController;
|
|
195
|
+
if (!controller) return;
|
|
191
196
|
try {
|
|
192
197
|
(this.activeRunAgent ?? this.agent).abortRun();
|
|
193
198
|
} finally {
|
|
194
|
-
|
|
199
|
+
controller.abort();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
supersedeActiveRun() {
|
|
203
|
+
try {
|
|
204
|
+
this.abortActiveRun();
|
|
205
|
+
} catch (error) {
|
|
206
|
+
this.logger.error?.("[agui] agent abortRun failed", error);
|
|
195
207
|
}
|
|
208
|
+
if (this.abortController === null) return true;
|
|
209
|
+
this.logger.debug?.("[agui] onCancel started a replacement run; dropping the superseding send");
|
|
210
|
+
return false;
|
|
196
211
|
}
|
|
197
212
|
async resume(config) {
|
|
198
213
|
this.assertNoPendingInterrupts();
|
|
@@ -234,8 +249,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
234
249
|
const assistant = this.findRequiresActionAssistant("tool-calls");
|
|
235
250
|
if (!assistant) return null;
|
|
236
251
|
const toolCallIds = [];
|
|
237
|
-
for (const part of assistant.content) {
|
|
238
|
-
if (part.type !== "tool-call") continue;
|
|
252
|
+
for (const part of iterateToolCallParts(assistant.content)) {
|
|
239
253
|
if (isResolvedToolCall(part)) continue;
|
|
240
254
|
toolCallIds.push(part.toolCallId);
|
|
241
255
|
}
|
|
@@ -263,12 +277,15 @@ var AgUiThreadRuntimeCore = class {
|
|
|
263
277
|
*/
|
|
264
278
|
async resumeWithResponses(messageId, resume) {
|
|
265
279
|
this.clearPendingInterrupts(messageId, resume);
|
|
266
|
-
await this.startRun(messageId, this.lastRunConfig,
|
|
280
|
+
await this.startRun(messageId, this.lastRunConfig, {
|
|
281
|
+
entries: resume,
|
|
282
|
+
interruptedMessageId: messageId
|
|
283
|
+
});
|
|
267
284
|
}
|
|
268
285
|
/**
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
286
|
+
* A rejected decision reaches the caller through the approval seam, and
|
|
287
|
+
* `onError` in addition, because a consumer watching only the runtime would
|
|
288
|
+
* otherwise miss it. A failure raised by the resumed run itself was already
|
|
272
289
|
* reported by `startRun` before it rethrew, so reporting it here again would
|
|
273
290
|
* give the consumer two notifications for one failure.
|
|
274
291
|
*/
|
|
@@ -293,7 +310,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
293
310
|
const pending = this.getPendingInterrupts();
|
|
294
311
|
if (!pending) throw new Error("[agui] respondToToolApproval: no pending interrupts on this thread");
|
|
295
312
|
const gatedMessage = this.session.tryGetMessage(pending.messageId)?.message;
|
|
296
|
-
if (![...projectAgUiToolApprovals(pending.interrupts, new Set((gatedMessage?.content ?? [])
|
|
313
|
+
if (![...projectAgUiToolApprovals(pending.interrupts, new Set(Array.from(iterateToolCallParts(gatedMessage?.content ?? [])).map((part) => part.toolCallId))).values()].some((approval) => approval.id === options.approvalId)) throw new Error(`[agui] respondToToolApproval: no pending tool-call interrupt for approval id "${options.approvalId}"`);
|
|
297
314
|
this.assertInterruptsAnswerable("respondToToolApproval", pending.interrupts);
|
|
298
315
|
if (!this.session.updateMessage(pending.messageId, (message) => {
|
|
299
316
|
if (message.role !== "assistant") return message;
|
|
@@ -335,7 +352,10 @@ var AgUiThreadRuntimeCore = class {
|
|
|
335
352
|
const normalized = this.toAppendMessage(message);
|
|
336
353
|
this.clearPendingInterrupts(pending.messageId, resume);
|
|
337
354
|
const threadMessageId = this.appendEntry(normalized);
|
|
338
|
-
await this.startRun(threadMessageId, normalized.runConfig,
|
|
355
|
+
await this.startRun(threadMessageId, normalized.runConfig, {
|
|
356
|
+
entries: resume,
|
|
357
|
+
interruptedMessageId: pending.messageId
|
|
358
|
+
});
|
|
339
359
|
}
|
|
340
360
|
resolveSteerAwayResume(interrupts, responses) {
|
|
341
361
|
const responsesById = this.collectInterruptResponses("steerAway", interrupts, responses ?? []);
|
|
@@ -372,7 +392,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
372
392
|
};
|
|
373
393
|
return {
|
|
374
394
|
createdAt: message.createdAt ?? /* @__PURE__ */ new Date(),
|
|
375
|
-
parentId: message.parentId
|
|
395
|
+
parentId: message.parentId === void 0 ? this.session.headId : message.parentId,
|
|
376
396
|
sourceId: message.sourceId ?? null,
|
|
377
397
|
role: message.role ?? "user",
|
|
378
398
|
content: message.content,
|
|
@@ -411,8 +431,8 @@ var AgUiThreadRuntimeCore = class {
|
|
|
411
431
|
for (let index = messages.length - 1; index >= 0; index--) {
|
|
412
432
|
const message = messages[index];
|
|
413
433
|
if (!message || message.role !== "assistant") continue;
|
|
414
|
-
for (const part of message.content) {
|
|
415
|
-
if (part.
|
|
434
|
+
for (const part of iterateToolCallParts(message.content)) {
|
|
435
|
+
if (part.toolCallId !== toolCallId) continue;
|
|
416
436
|
if (!isResolvedToolCall(part)) return message.id;
|
|
417
437
|
fallbackMessageId ??= message.id;
|
|
418
438
|
}
|
|
@@ -423,8 +443,8 @@ var AgUiThreadRuntimeCore = class {
|
|
|
423
443
|
if (this.session.updateMessage(messageId, (message) => {
|
|
424
444
|
if (message.role !== "assistant") return message;
|
|
425
445
|
const assistant = message;
|
|
426
|
-
const content = assistant.content
|
|
427
|
-
if (
|
|
446
|
+
const { content } = mapToolCallPartsDeep(assistant.content, (part) => {
|
|
447
|
+
if (isResolvedToolCall(part)) return part;
|
|
428
448
|
return {
|
|
429
449
|
...part,
|
|
430
450
|
result: { error: "Tool call cancelled by user" },
|
|
@@ -438,12 +458,14 @@ var AgUiThreadRuntimeCore = class {
|
|
|
438
458
|
})) this.notifyUpdate();
|
|
439
459
|
}
|
|
440
460
|
addToolResult(options) {
|
|
441
|
-
|
|
461
|
+
const sessionMessageId = this.session.tryGetMessage(options.messageId) ? options.messageId : this.findMessageIdForToolCall(options.toolCallId);
|
|
462
|
+
if (sessionMessageId === void 0) return;
|
|
463
|
+
if (!this.session.updateMessage(sessionMessageId, (message) => {
|
|
442
464
|
if (message.role !== "assistant") return message;
|
|
443
465
|
const assistant = message;
|
|
444
466
|
let matchedToolCall = false;
|
|
445
|
-
const content = assistant.content
|
|
446
|
-
if (part.
|
|
467
|
+
const { content } = mapToolCallPartsDeep(assistant.content, (part) => {
|
|
468
|
+
if (part.toolCallId !== options.toolCallId) return part;
|
|
447
469
|
matchedToolCall = true;
|
|
448
470
|
return {
|
|
449
471
|
...part,
|
|
@@ -459,7 +481,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
459
481
|
};
|
|
460
482
|
})) return;
|
|
461
483
|
this.notifyUpdate();
|
|
462
|
-
this.maybeResumeAfterToolResults(
|
|
484
|
+
this.maybeResumeAfterToolResults(sessionMessageId);
|
|
463
485
|
}
|
|
464
486
|
sendA2uiAction(action) {
|
|
465
487
|
this.assertNoPendingInterrupts();
|
|
@@ -473,16 +495,21 @@ var AgUiThreadRuntimeCore = class {
|
|
|
473
495
|
delete userAction.type;
|
|
474
496
|
if (!("timestamp" in userAction)) userAction.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
475
497
|
this.pendingA2uiAction = userAction;
|
|
476
|
-
|
|
477
|
-
|
|
498
|
+
const owner = this.abortController;
|
|
499
|
+
if (owner) {
|
|
500
|
+
this.pendingA2uiResumeOwner = owner;
|
|
478
501
|
return;
|
|
479
502
|
}
|
|
480
503
|
this.startResumeRun(parentId);
|
|
481
504
|
}
|
|
482
505
|
maybeResumeAfterToolResults(messageId) {
|
|
483
506
|
if (!this.maybeCompleteAfterToolResults(messageId)) return;
|
|
484
|
-
|
|
485
|
-
|
|
507
|
+
const owner = this.abortController;
|
|
508
|
+
if (owner) {
|
|
509
|
+
this.pendingResume = {
|
|
510
|
+
owner,
|
|
511
|
+
messageId
|
|
512
|
+
};
|
|
486
513
|
return;
|
|
487
514
|
}
|
|
488
515
|
this.startResumeRun(messageId);
|
|
@@ -492,7 +519,12 @@ var AgUiThreadRuntimeCore = class {
|
|
|
492
519
|
if (!message || message.role !== "assistant") return false;
|
|
493
520
|
const assistant = message;
|
|
494
521
|
if (assistant.status?.type !== "requires-action" || assistant.status.reason !== "tool-calls") return false;
|
|
495
|
-
|
|
522
|
+
let allResolved = true;
|
|
523
|
+
for (const part of iterateToolCallParts(assistant.content)) if (!isResolvedToolCall(part)) {
|
|
524
|
+
allResolved = false;
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
if (!allResolved) return false;
|
|
496
528
|
if (!this.session.updateMessage(messageId, (current) => current.role === "assistant" ? {
|
|
497
529
|
...current,
|
|
498
530
|
status: {
|
|
@@ -510,7 +542,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
510
542
|
});
|
|
511
543
|
}
|
|
512
544
|
applyExternalMessages(messages) {
|
|
513
|
-
this.
|
|
545
|
+
this.pendingA2uiResumeOwner = null;
|
|
514
546
|
this.pendingA2uiAction = void 0;
|
|
515
547
|
this.assistantHistoryParents.clear();
|
|
516
548
|
if (messages.length === 0) this.session.clear();
|
|
@@ -548,6 +580,8 @@ var AgUiThreadRuntimeCore = class {
|
|
|
548
580
|
}
|
|
549
581
|
this.snapshotHistoryIds.clear();
|
|
550
582
|
for (const { message } of this.getMessageRepository().messages) this.snapshotHistoryIds.add(message.id);
|
|
583
|
+
const resumeTarget = this.pendingResume?.messageId;
|
|
584
|
+
if (resumeTarget !== void 0 && !this.session.getMessages().some((message) => message.id === resumeTarget)) this.pendingResume = null;
|
|
551
585
|
this.notifyUpdate();
|
|
552
586
|
}
|
|
553
587
|
loadExternalState(state) {
|
|
@@ -563,13 +597,14 @@ var AgUiThreadRuntimeCore = class {
|
|
|
563
597
|
this.notifyUpdate();
|
|
564
598
|
}
|
|
565
599
|
async startRun(parentId, runConfig, resume, resumeStream) {
|
|
600
|
+
if (!this.supersedeActiveRun()) return;
|
|
566
601
|
const normalizedRunConfig = runConfig ?? {};
|
|
567
602
|
this.lastRunConfig = normalizedRunConfig;
|
|
568
603
|
const parent = parentId === null ? void 0 : this.session.tryGetMessage(parentId);
|
|
569
604
|
const shouldEagerlyInsertAssistant = parentId !== null && parent !== void 0 && parentId !== this.session.headId;
|
|
570
605
|
const historicalMessages = [...shouldEagerlyInsertAssistant ? this.session.tryGetMessages(parentId) ?? this.session.getMessages() : this.session.getMessages()];
|
|
571
606
|
const runStartMessageIds = new Set(this.getMessageRepository().messages.map(({ message }) => message.id));
|
|
572
|
-
|
|
607
|
+
let pendingError = null;
|
|
573
608
|
const assistantParentId = parent ? parentId : this.session.headId;
|
|
574
609
|
let assistantMessageId;
|
|
575
610
|
let assistantCollided = false;
|
|
@@ -644,7 +679,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
644
679
|
type: "incomplete",
|
|
645
680
|
reason: "cancelled"
|
|
646
681
|
} });
|
|
647
|
-
await this.consumeResumeStream(resumeStream, {
|
|
682
|
+
pendingError = await this.consumeResumeStream(resumeStream, {
|
|
648
683
|
runConfig: normalizedRunConfig,
|
|
649
684
|
threadId: this.agent.threadId || "main",
|
|
650
685
|
parentId: assistantParentId,
|
|
@@ -653,7 +688,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
653
688
|
ensureAssistant,
|
|
654
689
|
applyUpdate,
|
|
655
690
|
getAssistantMessageId: () => assistantMessageId
|
|
656
|
-
});
|
|
691
|
+
}) ?? null;
|
|
657
692
|
} else {
|
|
658
693
|
const runId = generateId();
|
|
659
694
|
aggregator.handle({
|
|
@@ -667,7 +702,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
667
702
|
logger: this.logger,
|
|
668
703
|
onRunFailed: (error) => {
|
|
669
704
|
if (abortSignal.aborted) return;
|
|
670
|
-
|
|
705
|
+
pendingError = error;
|
|
671
706
|
invokeRuntimeCallback("onError", this.onError, error);
|
|
672
707
|
}
|
|
673
708
|
});
|
|
@@ -686,28 +721,24 @@ var AgUiThreadRuntimeCore = class {
|
|
|
686
721
|
message: err.message
|
|
687
722
|
});
|
|
688
723
|
invokeRuntimeCallback("onError", this.onError, err);
|
|
689
|
-
|
|
724
|
+
pendingError ??= err;
|
|
690
725
|
}
|
|
691
726
|
} finally {
|
|
692
727
|
this.finishRun(abortController);
|
|
693
728
|
}
|
|
694
|
-
if (
|
|
695
|
-
const err =
|
|
729
|
+
if (pendingError) {
|
|
730
|
+
const err = pendingError;
|
|
696
731
|
this.reportedErrors.add(err);
|
|
697
|
-
this.
|
|
698
|
-
this.pendingResumeMessageId = null;
|
|
699
|
-
this.pendingA2uiResume = false;
|
|
700
|
-
this.pendingA2uiAction = void 0;
|
|
732
|
+
this.clearDeferredContinuations(abortController);
|
|
701
733
|
throw err;
|
|
702
734
|
}
|
|
703
|
-
if (this.
|
|
704
|
-
const
|
|
705
|
-
this.
|
|
706
|
-
if (!abortSignal.aborted) this.startResumeRun(
|
|
707
|
-
else this.pendingA2uiAction = void 0;
|
|
735
|
+
if (this.pendingResume?.owner === abortController) {
|
|
736
|
+
const { messageId } = this.pendingResume;
|
|
737
|
+
this.pendingResume = null;
|
|
738
|
+
if (!abortSignal.aborted) this.startResumeRun(messageId);
|
|
708
739
|
}
|
|
709
|
-
if (this.
|
|
710
|
-
this.
|
|
740
|
+
if (this.pendingA2uiResumeOwner === abortController) {
|
|
741
|
+
this.pendingA2uiResumeOwner = null;
|
|
711
742
|
if (!abortSignal.aborted && this.pendingA2uiAction !== void 0) {
|
|
712
743
|
if (this.getPendingInterrupts()) {
|
|
713
744
|
this.pendingA2uiAction = void 0;
|
|
@@ -726,7 +757,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
726
757
|
}
|
|
727
758
|
async consumeResumeStream(stream, ctx) {
|
|
728
759
|
this.pendingA2uiAction = void 0;
|
|
729
|
-
this.
|
|
760
|
+
this.pendingA2uiResumeOwner = null;
|
|
730
761
|
const assistantId = ctx.ensureAssistant();
|
|
731
762
|
const currentId = () => ctx.getAssistantMessageId() ?? assistantId;
|
|
732
763
|
const options = {
|
|
@@ -745,11 +776,11 @@ var AgUiThreadRuntimeCore = class {
|
|
|
745
776
|
};
|
|
746
777
|
try {
|
|
747
778
|
for await (const result of stream(options)) {
|
|
748
|
-
if (ctx.abortSignal.aborted) return;
|
|
779
|
+
if (ctx.abortSignal.aborted) return void 0;
|
|
749
780
|
ctx.applyUpdate(result);
|
|
750
781
|
}
|
|
751
782
|
} catch (error) {
|
|
752
|
-
if (ctx.abortSignal.aborted) return;
|
|
783
|
+
if (ctx.abortSignal.aborted) return void 0;
|
|
753
784
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
754
785
|
ctx.applyUpdate({ status: {
|
|
755
786
|
type: "incomplete",
|
|
@@ -757,10 +788,9 @@ var AgUiThreadRuntimeCore = class {
|
|
|
757
788
|
error: err.message
|
|
758
789
|
} });
|
|
759
790
|
invokeRuntimeCallback("onError", this.onError, err);
|
|
760
|
-
|
|
761
|
-
return;
|
|
791
|
+
return err;
|
|
762
792
|
}
|
|
763
|
-
if (ctx.abortSignal.aborted) return;
|
|
793
|
+
if (ctx.abortSignal.aborted) return void 0;
|
|
764
794
|
const current = this.session.tryGetMessage(currentId())?.message;
|
|
765
795
|
if (!current || current.status?.type === "running") ctx.applyUpdate({ status: {
|
|
766
796
|
type: "complete",
|
|
@@ -769,7 +799,7 @@ var AgUiThreadRuntimeCore = class {
|
|
|
769
799
|
}
|
|
770
800
|
buildRunInput(runId, runConfig, historyMessages, resume) {
|
|
771
801
|
const threadId = this.agent.threadId || "main";
|
|
772
|
-
const messages = toAgUiMessages(historyMessages ?? this.session.getMessages());
|
|
802
|
+
const messages = toAgUiMessages(this.selectRunMessages(historyMessages ?? this.session.getMessages(), resume));
|
|
773
803
|
const context = this.runtime?.thread.getModelContext();
|
|
774
804
|
const input = {
|
|
775
805
|
threadId,
|
|
@@ -787,35 +817,39 @@ var AgUiThreadRuntimeCore = class {
|
|
|
787
817
|
...runConfig?.custom ? { runConfig: runConfig.custom } : {},
|
|
788
818
|
...this.pendingA2uiAction ? { a2uiAction: { userAction: this.pendingA2uiAction } } : {}
|
|
789
819
|
},
|
|
790
|
-
...resume !== void 0 ? { resume } : {}
|
|
820
|
+
...resume !== void 0 ? { resume: resume.entries } : {}
|
|
791
821
|
};
|
|
792
822
|
this.pendingA2uiAction = void 0;
|
|
793
823
|
return input;
|
|
794
824
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
825
|
+
/**
|
|
826
|
+
* An anchor the thread no longer carries falls back to the whole transcript.
|
|
827
|
+
* The anchor is missing exactly when a snapshot import replaced the messages
|
|
828
|
+
* this resume was raised against, and a host that expects a delta tolerates
|
|
829
|
+
* being told more than it knows, while one rebuilding the run from the
|
|
830
|
+
* transcript cannot recover from being told less.
|
|
831
|
+
*/
|
|
832
|
+
selectRunMessages(messages, resume) {
|
|
833
|
+
if (resume === void 0 || this.resumeTranscript !== "appended") return messages;
|
|
834
|
+
const anchor = messages.findIndex((message) => message.id === resume.interruptedMessageId);
|
|
835
|
+
if (anchor === -1) return messages;
|
|
836
|
+
return messages.slice(anchor + 1);
|
|
837
|
+
}
|
|
838
|
+
clearDeferredContinuations(owner) {
|
|
839
|
+
if (this.pendingResume?.owner === owner) this.pendingResume = null;
|
|
840
|
+
if (this.pendingA2uiResumeOwner === owner) {
|
|
841
|
+
this.pendingA2uiResumeOwner = null;
|
|
842
|
+
this.pendingA2uiAction = void 0;
|
|
843
|
+
}
|
|
809
844
|
}
|
|
810
845
|
setRunning(running) {
|
|
811
846
|
this.isRunningFlag = running;
|
|
812
847
|
this.notifyUpdate();
|
|
813
848
|
}
|
|
814
849
|
finishRun(controller) {
|
|
815
|
-
if (this.abortController
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
}
|
|
850
|
+
if (this.abortController !== controller) return;
|
|
851
|
+
this.abortController = null;
|
|
852
|
+
this.activeRunAgent = null;
|
|
819
853
|
this.setRunning(false);
|
|
820
854
|
}
|
|
821
855
|
insertAssistantPlaceholder(parentId, id = generateOptimisticId()) {
|
|
@@ -914,14 +948,12 @@ var AgUiThreadRuntimeCore = class {
|
|
|
914
948
|
}
|
|
915
949
|
preserveToolResults(previous, next) {
|
|
916
950
|
const resolved = /* @__PURE__ */ new Map();
|
|
917
|
-
for (const part of previous) if (
|
|
951
|
+
for (const part of iterateToolCallParts(previous)) if (isResolvedToolCall(part)) resolved.set(part.toolCallId, part);
|
|
918
952
|
if (resolved.size === 0) return next;
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
if (part.type !== "tool-call" || isResolvedToolCall(part)) return part;
|
|
953
|
+
const { content: merged, changed } = mapToolCallPartsDeep(next, (part) => {
|
|
954
|
+
if (isResolvedToolCall(part)) return part;
|
|
922
955
|
const prior = resolved.get(part.toolCallId);
|
|
923
956
|
if (!prior) return part;
|
|
924
|
-
changed = true;
|
|
925
957
|
return {
|
|
926
958
|
...part,
|
|
927
959
|
result: prior.result,
|
|
@@ -996,8 +1028,8 @@ var AgUiThreadRuntimeCore = class {
|
|
|
996
1028
|
const assistant = message;
|
|
997
1029
|
const mcpAppUri = readMcpAppResourceUri(event.mcpResult?._meta);
|
|
998
1030
|
let matchedToolCall = false;
|
|
999
|
-
const content = assistant.content
|
|
1000
|
-
if (part.
|
|
1031
|
+
const { content } = mapToolCallPartsDeep(assistant.content, (part) => {
|
|
1032
|
+
if (part.toolCallId !== event.toolCallId) return part;
|
|
1001
1033
|
matchedToolCall = true;
|
|
1002
1034
|
if (part.mcp?.app !== void 0 && isPlainObject(part.result) && Array.isArray(part.result["content"])) return {
|
|
1003
1035
|
...part,
|
|
@@ -1040,8 +1072,8 @@ var AgUiThreadRuntimeCore = class {
|
|
|
1040
1072
|
if (message.role !== "assistant") return message;
|
|
1041
1073
|
const assistant = message;
|
|
1042
1074
|
let matchedToolCall = false;
|
|
1043
|
-
const content = assistant.content
|
|
1044
|
-
if (part.
|
|
1075
|
+
const { content } = mapToolCallPartsDeep(assistant.content, (part) => {
|
|
1076
|
+
if (part.toolCallId !== toolCallId) return part;
|
|
1045
1077
|
matchedToolCall = true;
|
|
1046
1078
|
return {
|
|
1047
1079
|
...part,
|