@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
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
type ToolCallMessagePart,
|
|
12
12
|
type ToolModelContentPart,
|
|
13
13
|
} from "@assistant-ui/core";
|
|
14
|
+
import type { JoinStrategy } from "@assistant-ui/core/react";
|
|
15
|
+
import type { ContentPart } from "@ag-ui/client";
|
|
14
16
|
import {
|
|
15
17
|
applyA2uiOperations,
|
|
16
18
|
surfaceToPresentToolCall,
|
|
@@ -18,7 +20,7 @@ import {
|
|
|
18
20
|
type A2uiSurfaceState,
|
|
19
21
|
} from "@assistant-ui/generative-ui/a2ui";
|
|
20
22
|
import jsonpatch, { type Operation } from "fast-json-patch";
|
|
21
|
-
import { readMcpAppResourceUri } from "../mcp-tool-result";
|
|
23
|
+
import { readMcpAppResourceUri, toolContentText } from "../mcp-tool-result";
|
|
22
24
|
import { projectAgUiToolApprovals } from "./tool-approval";
|
|
23
25
|
import {
|
|
24
26
|
AG_UI_METADATA_NAMESPACE,
|
|
@@ -176,10 +178,11 @@ export const isPlainObject = (
|
|
|
176
178
|
|
|
177
179
|
export type RunAggregatorOptions = {
|
|
178
180
|
showThinking: boolean;
|
|
181
|
+
joinStrategy?: JoinStrategy | undefined;
|
|
179
182
|
logger: Logger;
|
|
180
183
|
emit: Emit;
|
|
181
184
|
onServerMessageId?: (messageId: string) => void;
|
|
182
|
-
onTextMessageStart?: (messageId: string) => void;
|
|
185
|
+
onTextMessageStart?: (messageId: string, openToolCallIds: string[]) => void;
|
|
183
186
|
};
|
|
184
187
|
|
|
185
188
|
/**
|
|
@@ -195,14 +198,16 @@ export type RunAggregatorOptions = {
|
|
|
195
198
|
export class RunAggregator {
|
|
196
199
|
private readonly emitUpdate: Emit;
|
|
197
200
|
private readonly showThinking: boolean;
|
|
201
|
+
private readonly joinStrategy: JoinStrategy | undefined;
|
|
198
202
|
private readonly logger: Logger;
|
|
199
203
|
private readonly onServerMessageId: ((messageId: string) => void) | undefined;
|
|
200
204
|
private readonly onTextMessageStart:
|
|
201
|
-
| ((messageId: string) => void)
|
|
205
|
+
| ((messageId: string, openToolCallIds: string[]) => void)
|
|
202
206
|
| undefined;
|
|
203
207
|
|
|
204
208
|
private status: ChatModelRunResult["status"] | undefined;
|
|
205
209
|
private interrupts: AgUiInterrupt[] | undefined;
|
|
210
|
+
private pendingToolCallIds: string[] | undefined;
|
|
206
211
|
private readonly textParts = new Map<string, { buffer: string }>();
|
|
207
212
|
private readonly activeTextMessageIdByScope = new Map<string, string>();
|
|
208
213
|
private readonly reasoningParts = new Map<
|
|
@@ -211,20 +216,14 @@ export class RunAggregator {
|
|
|
211
216
|
>();
|
|
212
217
|
private readonly reasoningSignatures = new Map<string, string>();
|
|
213
218
|
private readonly reasoningSignatureIds = new Map<string, string>();
|
|
214
|
-
// Signatures captured while thinking is hidden have no block to live on;
|
|
215
|
-
// they are transport state that must survive to the next run input. The
|
|
216
|
-
// claim rules mirror the visible path: an id-carrying block, or an open
|
|
217
|
-
// anonymous one.
|
|
218
219
|
private readonly hiddenSignatures = new Map<string, string>();
|
|
219
220
|
private readonly hiddenSignatureAnchors = new Map<string, number>();
|
|
220
221
|
private readonly hiddenBlockAnchors = new Map<string, number>();
|
|
221
|
-
private hiddenAnonymousAnchor: number | undefined;
|
|
222
222
|
private readonly hiddenReasoningIds = new Set<string>();
|
|
223
|
-
private
|
|
223
|
+
private hiddenActiveReasoningId: string | undefined;
|
|
224
224
|
private hasEmittedOpaqueReasoning = false;
|
|
225
225
|
private readonly loggedDroppedOpaqueIds = new Set<string>();
|
|
226
226
|
private readonly reasoningMessageIds = new Map<string, string>();
|
|
227
|
-
private readonly anonymousReasoningKeys = new Set<string>();
|
|
228
227
|
private readonly activeReasoningKeyByScope = new Map<string, string>();
|
|
229
228
|
private readonly subagentRuns = new Map<string, SubagentRunState>();
|
|
230
229
|
// The nesting graph only moves when a run is announced or a tool call is
|
|
@@ -236,7 +235,6 @@ export class RunAggregator {
|
|
|
236
235
|
reachable: Set<string>;
|
|
237
236
|
}
|
|
238
237
|
| undefined;
|
|
239
|
-
private reasoningPartCounter = 0;
|
|
240
238
|
private readonly toolCalls = new Map<string, ToolCallState>();
|
|
241
239
|
private readonly a2uiBuckets = new Map<string, A2uiState>();
|
|
242
240
|
private readonly a2uiToolCallIds = new Set<string>();
|
|
@@ -259,6 +257,7 @@ export class RunAggregator {
|
|
|
259
257
|
constructor(options: RunAggregatorOptions) {
|
|
260
258
|
this.emitUpdate = options.emit;
|
|
261
259
|
this.showThinking = options.showThinking;
|
|
260
|
+
this.joinStrategy = options.joinStrategy;
|
|
262
261
|
this.logger = options.logger;
|
|
263
262
|
this.onServerMessageId = options.onServerMessageId;
|
|
264
263
|
this.onTextMessageStart = options.onTextMessageStart;
|
|
@@ -284,6 +283,13 @@ export class RunAggregator {
|
|
|
284
283
|
break;
|
|
285
284
|
}
|
|
286
285
|
case "RUN_FINISHED": {
|
|
286
|
+
if (event.outcome?.type === "cancelled") {
|
|
287
|
+
this.interrupts = undefined;
|
|
288
|
+
this.status = { type: "incomplete", reason: "cancelled" };
|
|
289
|
+
this.closeOpenSubagentRuns(this.status);
|
|
290
|
+
this.emit();
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
287
293
|
if (event.outcome?.type === "interrupt") {
|
|
288
294
|
this.interrupts = event.outcome.interrupts;
|
|
289
295
|
this.status = { type: "requires-action", reason: "interrupt" };
|
|
@@ -296,13 +302,27 @@ export class RunAggregator {
|
|
|
296
302
|
// a root call is: getPendingToolCalls and addToolResult walk
|
|
297
303
|
// ToolCallMessagePart.messages, so any unresolved call keeps the run
|
|
298
304
|
// resumable.
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
305
|
+
const pendingToolCallIds =
|
|
306
|
+
event.outcome?.type === "success"
|
|
307
|
+
? event.outcome.pendingToolCallIds
|
|
308
|
+
: undefined;
|
|
309
|
+
const hasUnresolvedToolCalls =
|
|
310
|
+
pendingToolCallIds && pendingToolCallIds.length > 0
|
|
311
|
+
? pendingToolCallIds.some((id) => {
|
|
312
|
+
const call = this.toolCalls.get(id);
|
|
313
|
+
return call !== undefined && call.result === undefined;
|
|
314
|
+
})
|
|
315
|
+
: Array.from(this.toolCalls.values()).some(
|
|
316
|
+
(tc) => tc.result === undefined,
|
|
317
|
+
);
|
|
302
318
|
|
|
303
319
|
this.status = hasUnresolvedToolCalls
|
|
304
320
|
? { type: "requires-action", reason: "tool-calls" }
|
|
305
321
|
: { type: "complete", reason: "unknown" };
|
|
322
|
+
this.pendingToolCallIds =
|
|
323
|
+
hasUnresolvedToolCalls && pendingToolCallIds?.length
|
|
324
|
+
? pendingToolCallIds
|
|
325
|
+
: undefined;
|
|
306
326
|
this.closeOpenSubagentRuns(this.status);
|
|
307
327
|
this.emit();
|
|
308
328
|
break;
|
|
@@ -344,9 +364,8 @@ export class RunAggregator {
|
|
|
344
364
|
this.emit();
|
|
345
365
|
break;
|
|
346
366
|
}
|
|
347
|
-
case "TEXT_MESSAGE_CONTENT":
|
|
348
|
-
|
|
349
|
-
const incomingId = "messageId" in event ? event.messageId : undefined;
|
|
367
|
+
case "TEXT_MESSAGE_CONTENT": {
|
|
368
|
+
const incomingId = event.messageId;
|
|
350
369
|
const scope = this.scopeOf(event);
|
|
351
370
|
if (scope === ROOT_SCOPE) {
|
|
352
371
|
this.beginDistinctTextMessage(incomingId);
|
|
@@ -386,13 +405,11 @@ export class RunAggregator {
|
|
|
386
405
|
break;
|
|
387
406
|
}
|
|
388
407
|
|
|
389
|
-
case "THINKING_START":
|
|
390
|
-
case "THINKING_TEXT_MESSAGE_START":
|
|
391
408
|
case "REASONING_START":
|
|
392
409
|
case "REASONING_MESSAGE_START":
|
|
393
410
|
this.handleReasoningStart(
|
|
394
|
-
this.scopeOf(
|
|
395
|
-
|
|
411
|
+
this.scopeOf(event),
|
|
412
|
+
event.messageId,
|
|
396
413
|
event.type === "REASONING_MESSAGE_START",
|
|
397
414
|
event.timestamp,
|
|
398
415
|
);
|
|
@@ -400,61 +417,42 @@ export class RunAggregator {
|
|
|
400
417
|
case "REASONING_ENCRYPTED_VALUE":
|
|
401
418
|
if (event.subtype === "message") {
|
|
402
419
|
const scope = this.scopeOf(event);
|
|
403
|
-
// entityId names any message, not necessarily a reasoning one, so an
|
|
404
|
-
// unmatched id may only claim a block that has no id to contradict it.
|
|
405
|
-
const active = this.activeReasoningKeyByScope.get(scope);
|
|
406
420
|
const entityKey = this.partKey(scope, event.entityId);
|
|
407
|
-
const key =
|
|
408
|
-
|
|
421
|
+
const key =
|
|
422
|
+
this.showThinking && this.reasoningParts.has(entityKey)
|
|
409
423
|
? entityKey
|
|
410
|
-
:
|
|
411
|
-
? active
|
|
412
|
-
: undefined
|
|
413
|
-
: undefined;
|
|
424
|
+
: undefined;
|
|
414
425
|
if (key !== undefined) {
|
|
415
426
|
this.reasoningSignatures.set(key, event.encryptedValue);
|
|
416
427
|
this.reasoningSignatureIds.set(key, event.entityId);
|
|
417
428
|
this.emit();
|
|
418
429
|
} else if (
|
|
419
430
|
!this.showThinking &&
|
|
420
|
-
// Hidden-reasoning bookkeeping stays root-only for this task — a
|
|
421
|
-
// subagent's opaque/hidden reasoning signatures are not preserved.
|
|
422
431
|
scope === ROOT_SCOPE &&
|
|
423
432
|
event.entityId.trim().length > 0 &&
|
|
424
433
|
event.encryptedValue.trim().length > 0 &&
|
|
425
|
-
|
|
426
|
-
this.hiddenActiveReasoning === "anonymous")
|
|
434
|
+
this.hiddenReasoningIds.has(event.entityId)
|
|
427
435
|
) {
|
|
428
436
|
this.hiddenSignatures.set(event.entityId, event.encryptedValue);
|
|
429
437
|
this.hiddenSignatureAnchors.set(
|
|
430
438
|
event.entityId,
|
|
431
439
|
this.hiddenBlockAnchors.get(event.entityId) ??
|
|
432
|
-
this.hiddenAnonymousAnchor ??
|
|
433
440
|
this.partOrder.length,
|
|
434
441
|
);
|
|
435
442
|
this.emit();
|
|
436
443
|
}
|
|
437
444
|
}
|
|
438
445
|
break;
|
|
439
|
-
case "THINKING_TEXT_MESSAGE_CONTENT":
|
|
440
|
-
this.handleReasoningContent(ROOT_SCOPE, event.delta);
|
|
441
|
-
this.totalChunks++;
|
|
442
|
-
this.recordFirstToken();
|
|
443
|
-
break;
|
|
444
446
|
case "REASONING_MESSAGE_CONTENT":
|
|
445
447
|
this.handleReasoningContent(
|
|
446
448
|
this.scopeOf(event),
|
|
447
449
|
event.delta,
|
|
448
|
-
|
|
450
|
+
event.messageId,
|
|
449
451
|
true,
|
|
450
452
|
);
|
|
451
453
|
this.totalChunks++;
|
|
452
454
|
this.recordFirstToken();
|
|
453
455
|
break;
|
|
454
|
-
case "THINKING_TEXT_MESSAGE_END":
|
|
455
|
-
case "THINKING_END":
|
|
456
|
-
this.handleReasoningEnd(ROOT_SCOPE, event.timestamp);
|
|
457
|
-
break;
|
|
458
456
|
case "REASONING_MESSAGE_END":
|
|
459
457
|
case "REASONING_END":
|
|
460
458
|
this.handleReasoningEnd(this.scopeOf(event), event.timestamp);
|
|
@@ -474,14 +472,7 @@ export class RunAggregator {
|
|
|
474
472
|
this.emit();
|
|
475
473
|
break;
|
|
476
474
|
}
|
|
477
|
-
case "TOOL_CALL_ARGS":
|
|
478
|
-
case "TOOL_CALL_CHUNK": {
|
|
479
|
-
if (
|
|
480
|
-
event.type === "TOOL_CALL_CHUNK" &&
|
|
481
|
-
this.scopeOf(event) === ROOT_SCOPE
|
|
482
|
-
) {
|
|
483
|
-
this.reportServerMessageId(event.parentMessageId);
|
|
484
|
-
}
|
|
475
|
+
case "TOOL_CALL_ARGS": {
|
|
485
476
|
if (!event.delta) break;
|
|
486
477
|
this.appendToolArgs(event.toolCallId, event.delta);
|
|
487
478
|
this.emit();
|
|
@@ -809,15 +800,12 @@ export class RunAggregator {
|
|
|
809
800
|
this.hiddenSignatures.clear();
|
|
810
801
|
this.hiddenSignatureAnchors.clear();
|
|
811
802
|
this.hiddenBlockAnchors.clear();
|
|
812
|
-
this.hiddenAnonymousAnchor = undefined;
|
|
813
803
|
this.hiddenReasoningIds.clear();
|
|
814
|
-
this.
|
|
804
|
+
this.hiddenActiveReasoningId = undefined;
|
|
815
805
|
this.loggedDroppedOpaqueIds.clear();
|
|
816
|
-
this.anonymousReasoningKeys.clear();
|
|
817
806
|
this.activeReasoningKeyByScope.clear();
|
|
818
807
|
this.nestingMemo = undefined;
|
|
819
808
|
this.subagentRuns.clear();
|
|
820
|
-
this.reasoningPartCounter = 0;
|
|
821
809
|
this.toolCalls.clear();
|
|
822
810
|
this.a2uiBuckets.clear();
|
|
823
811
|
this.a2uiToolCallIds.clear();
|
|
@@ -851,6 +839,7 @@ export class RunAggregator {
|
|
|
851
839
|
|
|
852
840
|
private beginDistinctTextMessage(messageId: string | undefined): void {
|
|
853
841
|
if (
|
|
842
|
+
this.joinStrategy === "concat-content" ||
|
|
854
843
|
!messageId ||
|
|
855
844
|
this.lastTextMessageId === undefined ||
|
|
856
845
|
this.lastTextMessageId === messageId ||
|
|
@@ -862,10 +851,11 @@ export class RunAggregator {
|
|
|
862
851
|
if (carried.parts.length > 0 || carried.hiddenSignatures.length > 0) {
|
|
863
852
|
this.emit();
|
|
864
853
|
}
|
|
865
|
-
const
|
|
854
|
+
const openToolCallIds = Array.from(this.toolCalls.values())
|
|
855
|
+
.filter((call) => call.result === undefined)
|
|
856
|
+
.map((call) => call.toolCallId);
|
|
866
857
|
this.resetMessageParts();
|
|
867
|
-
this.onTextMessageStart(messageId);
|
|
868
|
-
this.reasoningPartCounter = reasoningPartCounter;
|
|
858
|
+
this.onTextMessageStart(messageId, openToolCallIds);
|
|
869
859
|
this.restoreReasoning(carried);
|
|
870
860
|
}
|
|
871
861
|
|
|
@@ -876,7 +866,6 @@ export class RunAggregator {
|
|
|
876
866
|
signature: string | undefined;
|
|
877
867
|
signatureId: string | undefined;
|
|
878
868
|
reasoningId: string | undefined;
|
|
879
|
-
anonymous: boolean;
|
|
880
869
|
}[] = [];
|
|
881
870
|
let last = this.partOrder.at(-1);
|
|
882
871
|
while (
|
|
@@ -891,7 +880,6 @@ export class RunAggregator {
|
|
|
891
880
|
signature: this.reasoningSignatures.get(last.key),
|
|
892
881
|
signatureId: this.reasoningSignatureIds.get(last.key),
|
|
893
882
|
reasoningId: this.reasoningMessageIds.get(last.key),
|
|
894
|
-
anonymous: this.anonymousReasoningKeys.has(last.key),
|
|
895
883
|
});
|
|
896
884
|
last = this.partOrder.at(-1);
|
|
897
885
|
}
|
|
@@ -906,22 +894,17 @@ export class RunAggregator {
|
|
|
906
894
|
const hiddenBlockIds = Array.from(this.hiddenBlockAnchors)
|
|
907
895
|
.filter(([, anchor]) => anchor >= cut)
|
|
908
896
|
.map(([id]) => id);
|
|
909
|
-
const
|
|
910
|
-
this.hiddenAnonymousAnchor !== undefined &&
|
|
911
|
-
this.hiddenAnonymousAnchor >= cut;
|
|
912
|
-
const activeCarried =
|
|
913
|
-
(this.hiddenActiveReasoning === "identified" &&
|
|
914
|
-
hiddenBlockIds.length > 0) ||
|
|
915
|
-
(this.hiddenActiveReasoning === "anonymous" && hiddenAnonymous);
|
|
897
|
+
const hiddenActiveReasoningId = this.hiddenActiveReasoningId;
|
|
916
898
|
return {
|
|
917
899
|
parts,
|
|
918
900
|
activeKey: this.activeReasoningKeyByScope.get(ROOT_SCOPE),
|
|
919
901
|
hiddenSignatures,
|
|
920
902
|
hiddenBlockIds,
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
903
|
+
hiddenActiveReasoningId:
|
|
904
|
+
hiddenActiveReasoningId !== undefined &&
|
|
905
|
+
hiddenBlockIds.includes(hiddenActiveReasoningId)
|
|
906
|
+
? hiddenActiveReasoningId
|
|
907
|
+
: undefined,
|
|
925
908
|
};
|
|
926
909
|
}
|
|
927
910
|
|
|
@@ -937,7 +920,6 @@ export class RunAggregator {
|
|
|
937
920
|
this.reasoningSignatureIds.set(part.key, part.signatureId);
|
|
938
921
|
if (part.reasoningId !== undefined)
|
|
939
922
|
this.reasoningMessageIds.set(part.key, part.reasoningId);
|
|
940
|
-
if (part.anonymous) this.anonymousReasoningKeys.add(part.key);
|
|
941
923
|
}
|
|
942
924
|
if (
|
|
943
925
|
carried.activeKey !== undefined &&
|
|
@@ -953,8 +935,7 @@ export class RunAggregator {
|
|
|
953
935
|
this.hiddenReasoningIds.add(id);
|
|
954
936
|
this.hiddenBlockAnchors.set(id, 0);
|
|
955
937
|
}
|
|
956
|
-
|
|
957
|
-
this.hiddenActiveReasoning = carried.hiddenActiveReasoning;
|
|
938
|
+
this.hiddenActiveReasoningId = carried.hiddenActiveReasoningId;
|
|
958
939
|
}
|
|
959
940
|
|
|
960
941
|
private generateTextKey(): string {
|
|
@@ -1113,12 +1094,13 @@ export class RunAggregator {
|
|
|
1113
1094
|
private finishToolCall(
|
|
1114
1095
|
scope: string,
|
|
1115
1096
|
id: string,
|
|
1116
|
-
content: string,
|
|
1097
|
+
content: string | ContentPart[],
|
|
1117
1098
|
isError?: boolean,
|
|
1118
1099
|
toolMessageId?: string,
|
|
1119
1100
|
mcpResult?: Record<string, unknown>,
|
|
1120
1101
|
) {
|
|
1121
1102
|
if (!id) return;
|
|
1103
|
+
const modelText = toolContentText(content);
|
|
1122
1104
|
let entry = this.toolCalls.get(id);
|
|
1123
1105
|
if (!entry) {
|
|
1124
1106
|
entry = {
|
|
@@ -1143,7 +1125,7 @@ export class RunAggregator {
|
|
|
1143
1125
|
}
|
|
1144
1126
|
if (mcpResult !== undefined && !entry.snapshotResultApplied) {
|
|
1145
1127
|
entry.result = mcpResult;
|
|
1146
|
-
entry.modelContent = [{ type: "text", text:
|
|
1128
|
+
entry.modelContent = [{ type: "text", text: modelText }];
|
|
1147
1129
|
entry.snapshotResultApplied = true;
|
|
1148
1130
|
entry.isError = isError;
|
|
1149
1131
|
if (entry.mcpAppResourceUri === undefined) {
|
|
@@ -1152,13 +1134,16 @@ export class RunAggregator {
|
|
|
1152
1134
|
}
|
|
1153
1135
|
} else if (entry.snapshotResultApplied) {
|
|
1154
1136
|
if (entry.modelContent === undefined) {
|
|
1155
|
-
entry.modelContent = [{ type: "text", text:
|
|
1137
|
+
entry.modelContent = [{ type: "text", text: modelText }];
|
|
1156
1138
|
}
|
|
1157
1139
|
if (entry.isError === undefined) {
|
|
1158
1140
|
entry.isError = isError;
|
|
1159
1141
|
}
|
|
1160
1142
|
} else {
|
|
1161
|
-
entry.result = tryParseJSON(content);
|
|
1143
|
+
entry.result = Array.isArray(content) ? content : tryParseJSON(content);
|
|
1144
|
+
if (Array.isArray(content)) {
|
|
1145
|
+
delete entry.modelContent;
|
|
1146
|
+
}
|
|
1162
1147
|
entry.isError = isError;
|
|
1163
1148
|
}
|
|
1164
1149
|
if (toolMessageId) {
|
|
@@ -1443,15 +1428,6 @@ export class RunAggregator {
|
|
|
1443
1428
|
const snapshot = this.buildParts(ROOT_SCOPE, 0, ctx);
|
|
1444
1429
|
const { opaqueCandidates, lastMaterializedIndex } = root;
|
|
1445
1430
|
|
|
1446
|
-
// An anonymous claim promotes the signature's entityId to a wire message
|
|
1447
|
-
// id; when that id actually names a text message or the adopted assistant
|
|
1448
|
-
// message id (which can also arrive via TOOL_CALL_START.parentMessageId),
|
|
1449
|
-
// replaying it as its own record would put two wire records under one id,
|
|
1450
|
-
// so such entries are dropped instead — the same outcome the visible
|
|
1451
|
-
// path's claim guard produces for a signature that is "not for this
|
|
1452
|
-
// block". Entries with no materialized part after their anchor trail the
|
|
1453
|
-
// assistant record, matching the snapshot path's anchor/after
|
|
1454
|
-
// bookkeeping.
|
|
1455
1431
|
const toolMessageIds = new Set<string>();
|
|
1456
1432
|
for (const call of this.toolCalls.values()) {
|
|
1457
1433
|
if (call.toolMessageId) toolMessageIds.add(call.toolMessageId);
|
|
@@ -1483,11 +1459,20 @@ export class RunAggregator {
|
|
|
1483
1459
|
const timing = this.getTiming();
|
|
1484
1460
|
const metadata = {
|
|
1485
1461
|
...(timing ? { timing } : {}),
|
|
1486
|
-
...(this.interrupts ||
|
|
1462
|
+
...(this.interrupts ||
|
|
1463
|
+
(this.status?.type === "requires-action" &&
|
|
1464
|
+
this.status.reason === "tool-calls" &&
|
|
1465
|
+
this.pendingToolCallIds) ||
|
|
1466
|
+
includeOpaque
|
|
1487
1467
|
? {
|
|
1488
1468
|
custom: {
|
|
1489
1469
|
[AG_UI_METADATA_NAMESPACE]: {
|
|
1490
1470
|
...(this.interrupts ? { interrupts: this.interrupts } : {}),
|
|
1471
|
+
...(this.status?.type === "requires-action" &&
|
|
1472
|
+
this.status.reason === "tool-calls" &&
|
|
1473
|
+
this.pendingToolCallIds
|
|
1474
|
+
? { pendingToolCallIds: this.pendingToolCallIds }
|
|
1475
|
+
: {}),
|
|
1491
1476
|
...(includeOpaque
|
|
1492
1477
|
? { opaqueReasoning: publishableOpaqueReasoning }
|
|
1493
1478
|
: {}),
|
|
@@ -1547,38 +1532,23 @@ export class RunAggregator {
|
|
|
1547
1532
|
|
|
1548
1533
|
private handleReasoningStart(
|
|
1549
1534
|
scope: string,
|
|
1550
|
-
messageId
|
|
1535
|
+
messageId: string,
|
|
1551
1536
|
isMessageId = false,
|
|
1552
1537
|
timestamp?: number,
|
|
1553
1538
|
): void {
|
|
1554
1539
|
if (!this.showThinking) {
|
|
1555
|
-
// Hidden-reasoning bookkeeping stays root-only for this task — a
|
|
1556
|
-
// subagent's opaque/hidden reasoning signatures are not preserved.
|
|
1557
1540
|
if (scope !== ROOT_SCOPE) return;
|
|
1558
1541
|
const anchor = this.partOrder.length;
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
} else {
|
|
1563
|
-
this.hiddenReasoningIds.add(messageId);
|
|
1564
|
-
this.hiddenBlockAnchors.set(messageId, anchor);
|
|
1565
|
-
this.hiddenActiveReasoning = "identified";
|
|
1566
|
-
}
|
|
1542
|
+
this.hiddenReasoningIds.add(messageId);
|
|
1543
|
+
this.hiddenBlockAnchors.set(messageId, anchor);
|
|
1544
|
+
this.hiddenActiveReasoningId = messageId;
|
|
1567
1545
|
return;
|
|
1568
1546
|
}
|
|
1569
1547
|
// A reasoning block acts as a boundary: anonymous text arriving after it
|
|
1570
1548
|
// should be a new part, not appended to any pre-reasoning text — scoped.
|
|
1571
1549
|
this.activeTextMessageIdByScope.delete(scope);
|
|
1572
|
-
const key = this.partKey(
|
|
1573
|
-
|
|
1574
|
-
messageId ?? `__auto-reasoning-${++this.reasoningPartCounter}`,
|
|
1575
|
-
);
|
|
1576
|
-
// Two different questions: which id may be replayed as a ReasoningMessage.id
|
|
1577
|
-
// (only the message-scoped aliases carry one), and which block an unmatched
|
|
1578
|
-
// signature may claim (any block opened without an id at all).
|
|
1579
|
-
if (messageId === undefined) {
|
|
1580
|
-
this.anonymousReasoningKeys.add(key);
|
|
1581
|
-
} else if (isMessageId) {
|
|
1550
|
+
const key = this.partKey(scope, messageId);
|
|
1551
|
+
if (isMessageId) {
|
|
1582
1552
|
this.reasoningMessageIds.set(key, messageId);
|
|
1583
1553
|
}
|
|
1584
1554
|
if (!this.reasoningParts.has(key)) {
|
|
@@ -1606,7 +1576,7 @@ export class RunAggregator {
|
|
|
1606
1576
|
private handleReasoningContent(
|
|
1607
1577
|
scope: string,
|
|
1608
1578
|
delta: string,
|
|
1609
|
-
messageId
|
|
1579
|
+
messageId: string,
|
|
1610
1580
|
isMessageId = false,
|
|
1611
1581
|
): void {
|
|
1612
1582
|
if (!delta) return;
|
|
@@ -1614,7 +1584,7 @@ export class RunAggregator {
|
|
|
1614
1584
|
// Content without a preceding START still names the block (the visible
|
|
1615
1585
|
// path opens it lazily); register it so a signature can claim it.
|
|
1616
1586
|
if (scope !== ROOT_SCOPE) return;
|
|
1617
|
-
if (this.
|
|
1587
|
+
if (this.hiddenActiveReasoningId === undefined) {
|
|
1618
1588
|
this.handleReasoningStart(scope, messageId, isMessageId);
|
|
1619
1589
|
}
|
|
1620
1590
|
return;
|
|
@@ -1632,7 +1602,7 @@ export class RunAggregator {
|
|
|
1632
1602
|
|
|
1633
1603
|
private handleReasoningEnd(scope: string, timestamp?: number): void {
|
|
1634
1604
|
if (!this.showThinking) {
|
|
1635
|
-
if (scope === ROOT_SCOPE) this.
|
|
1605
|
+
if (scope === ROOT_SCOPE) this.hiddenActiveReasoningId = undefined;
|
|
1636
1606
|
return;
|
|
1637
1607
|
}
|
|
1638
1608
|
const key = this.activeReasoningKeyByScope.get(scope);
|
|
@@ -7,16 +7,9 @@ import type { Logger } from "../logger";
|
|
|
7
7
|
type Dispatch = (event: AgUiEvent) => void;
|
|
8
8
|
|
|
9
9
|
type Subscriber = {
|
|
10
|
-
onEvent?: (payload: { event: unknown }) => void;
|
|
11
10
|
onTextMessageStartEvent?: (payload: { event: unknown }) => void;
|
|
12
11
|
onTextMessageContentEvent?: (payload: { event: unknown }) => void;
|
|
13
12
|
onTextMessageEndEvent?: (payload: { event: unknown }) => void;
|
|
14
|
-
onTextMessageChunkEvent?: (payload: { event: unknown }) => void;
|
|
15
|
-
onThinkingStartEvent?: (payload: { event: unknown }) => void;
|
|
16
|
-
onThinkingEndEvent?: (payload: { event: unknown }) => void;
|
|
17
|
-
onThinkingTextMessageStartEvent?: (payload: { event: unknown }) => void;
|
|
18
|
-
onThinkingTextMessageContentEvent?: (payload: { event: unknown }) => void;
|
|
19
|
-
onThinkingTextMessageEndEvent?: (payload: { event: unknown }) => void;
|
|
20
13
|
onReasoningStartEvent?: (payload: { event: unknown }) => void;
|
|
21
14
|
onReasoningEndEvent?: (payload: { event: unknown }) => void;
|
|
22
15
|
onReasoningMessageStartEvent?: (payload: { event: unknown }) => void;
|
|
@@ -26,7 +19,6 @@ type Subscriber = {
|
|
|
26
19
|
onToolCallStartEvent?: (payload: { event: unknown }) => void;
|
|
27
20
|
onToolCallArgsEvent?: (payload: { event: unknown }) => void;
|
|
28
21
|
onToolCallEndEvent?: (payload: { event: unknown }) => void;
|
|
29
|
-
onToolCallChunkEvent?: (payload: { event: unknown }) => void;
|
|
30
22
|
onToolCallResultEvent?: (payload: { event: unknown }) => void;
|
|
31
23
|
onActivitySnapshotEvent?: (payload: { event: unknown }) => void;
|
|
32
24
|
onActivityDeltaEvent?: (payload: { event: unknown }) => void;
|
|
@@ -62,17 +54,7 @@ const ensureEvent = (
|
|
|
62
54
|
logger?: Logger,
|
|
63
55
|
): AgUiEvent | null => {
|
|
64
56
|
const parseOptions = logger ? { logger } : undefined;
|
|
65
|
-
|
|
66
|
-
if (raw && typeof raw === "object") {
|
|
67
|
-
const payload = raw as Record<string, unknown>;
|
|
68
|
-
if (typeof payload.type === "string") {
|
|
69
|
-
parsed = parseAgUiEvent(payload, parseOptions);
|
|
70
|
-
} else {
|
|
71
|
-
parsed = parseAgUiEvent({ type, ...payload }, parseOptions);
|
|
72
|
-
}
|
|
73
|
-
} else {
|
|
74
|
-
parsed = parseAgUiEvent({ type }, parseOptions);
|
|
75
|
-
}
|
|
57
|
+
const parsed = parseAgUiEvent(raw, parseOptions);
|
|
76
58
|
return parsed && parsed.type === type ? parsed : null;
|
|
77
59
|
};
|
|
78
60
|
|
|
@@ -94,35 +76,12 @@ export const createAgUiSubscriber = (
|
|
|
94
76
|
dispatch(event);
|
|
95
77
|
};
|
|
96
78
|
return {
|
|
97
|
-
onEvent: ({ event }) => {
|
|
98
|
-
const typeCandidate =
|
|
99
|
-
event && typeof event === "object"
|
|
100
|
-
? (event as Record<string, unknown>).type
|
|
101
|
-
: undefined;
|
|
102
|
-
if (typeof typeCandidate === "string") {
|
|
103
|
-
// Typed handlers will receive this via the discriminated callbacks; avoid duplicates.
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
const parsed = parseAgUiEvent(event, logger ? { logger } : undefined);
|
|
107
|
-
if (parsed) dispatch(parsed);
|
|
108
|
-
},
|
|
109
79
|
onTextMessageStartEvent: ({ event }) =>
|
|
110
80
|
dispatchIfValid(event, "TEXT_MESSAGE_START"),
|
|
111
81
|
onTextMessageContentEvent: ({ event }) =>
|
|
112
82
|
dispatchIfValid(event, "TEXT_MESSAGE_CONTENT"),
|
|
113
83
|
onTextMessageEndEvent: ({ event }) =>
|
|
114
84
|
dispatchIfValid(event, "TEXT_MESSAGE_END"),
|
|
115
|
-
onTextMessageChunkEvent: ({ event }) =>
|
|
116
|
-
dispatchIfValid(event, "TEXT_MESSAGE_CHUNK"),
|
|
117
|
-
onThinkingStartEvent: ({ event }) =>
|
|
118
|
-
dispatchIfValid(event, "THINKING_START"),
|
|
119
|
-
onThinkingEndEvent: ({ event }) => dispatchIfValid(event, "THINKING_END"),
|
|
120
|
-
onThinkingTextMessageStartEvent: ({ event }) =>
|
|
121
|
-
dispatchIfValid(event, "THINKING_TEXT_MESSAGE_START"),
|
|
122
|
-
onThinkingTextMessageContentEvent: ({ event }) =>
|
|
123
|
-
dispatchIfValid(event, "THINKING_TEXT_MESSAGE_CONTENT"),
|
|
124
|
-
onThinkingTextMessageEndEvent: ({ event }) =>
|
|
125
|
-
dispatchIfValid(event, "THINKING_TEXT_MESSAGE_END"),
|
|
126
85
|
onReasoningStartEvent: ({ event }) =>
|
|
127
86
|
dispatchIfValid(event, "REASONING_START"),
|
|
128
87
|
onReasoningEndEvent: ({ event }) => dispatchIfValid(event, "REASONING_END"),
|
|
@@ -139,8 +98,6 @@ export const createAgUiSubscriber = (
|
|
|
139
98
|
onToolCallArgsEvent: ({ event }) =>
|
|
140
99
|
dispatchIfValid(event, "TOOL_CALL_ARGS"),
|
|
141
100
|
onToolCallEndEvent: ({ event }) => dispatchIfValid(event, "TOOL_CALL_END"),
|
|
142
|
-
onToolCallChunkEvent: ({ event }) =>
|
|
143
|
-
dispatchIfValid(event, "TOOL_CALL_CHUNK"),
|
|
144
101
|
onToolCallResultEvent: ({ event }) =>
|
|
145
102
|
dispatchIfValid(event, "TOOL_CALL_RESULT"),
|
|
146
103
|
onActivitySnapshotEvent: ({ event }) =>
|