@assistant-ui/react-ag-ui 0.0.34 → 0.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts +8 -2
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtime/AgUiThreadRuntimeCore.js +158 -52
- package/dist/runtime/AgUiThreadRuntimeCore.js.map +1 -1
- package/dist/runtime/adapter/conversions.d.ts +12 -4
- package/dist/runtime/adapter/conversions.d.ts.map +1 -1
- package/dist/runtime/adapter/conversions.js +65 -30
- package/dist/runtime/adapter/conversions.js.map +1 -1
- package/dist/runtime/adapter/run-aggregator.d.ts +1 -0
- package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
- package/dist/runtime/adapter/run-aggregator.js +17 -0
- package/dist/runtime/adapter/run-aggregator.js.map +1 -1
- package/dist/runtime/adapter/subscriber.d.ts +3 -0
- package/dist/runtime/adapter/subscriber.d.ts.map +1 -1
- package/dist/runtime/adapter/subscriber.js +1 -0
- package/dist/runtime/adapter/subscriber.js.map +1 -1
- package/dist/runtime/event-parser.js +9 -0
- package/dist/runtime/event-parser.js.map +1 -1
- package/dist/runtime/types.d.ts +4 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/useAgUiRuntime.js.map +1 -1
- package/package.json +9 -8
- package/src/index.ts +2 -0
- package/src/runtime/AgUiThreadRuntimeCore.ts +252 -87
- package/src/runtime/adapter/conversions.ts +87 -47
- package/src/runtime/adapter/run-aggregator.ts +30 -5
- package/src/runtime/adapter/subscriber.ts +3 -0
- package/src/runtime/event-parser.ts +9 -0
- package/src/runtime/types.ts +5 -0
|
@@ -5,13 +5,16 @@ import type {
|
|
|
5
5
|
AddToolResultOptions,
|
|
6
6
|
AppendMessage,
|
|
7
7
|
AssistantRuntime,
|
|
8
|
+
ChatModelRunOptions,
|
|
8
9
|
ChatModelRunResult,
|
|
9
10
|
MessageStatus,
|
|
10
11
|
ThreadAssistantMessage,
|
|
11
12
|
ThreadHistoryAdapter,
|
|
12
13
|
ThreadMessage,
|
|
14
|
+
ToolCallMessagePart,
|
|
13
15
|
} from "@assistant-ui/core";
|
|
14
16
|
import type { HttpAgent } from "@ag-ui/client";
|
|
17
|
+
import jsonpatch, { type Operation } from "fast-json-patch";
|
|
15
18
|
import type { Logger } from "./logger";
|
|
16
19
|
import type { AgUiEvent, AgUiInterrupt, AgUiResumeEntry } from "./types";
|
|
17
20
|
import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
@@ -34,11 +37,14 @@ const isOptimisticId = (id: string) => id.startsWith(optimisticPrefix);
|
|
|
34
37
|
const symbolResumeShim = Symbol("agui-resume-shim");
|
|
35
38
|
|
|
36
39
|
type RunConfig = NonNullable<AppendMessage["runConfig"]>;
|
|
40
|
+
type ResumeStream = (
|
|
41
|
+
options: ChatModelRunOptions,
|
|
42
|
+
) => AsyncGenerator<ChatModelRunResult, void, unknown>;
|
|
37
43
|
type ResumeRunConfig = {
|
|
38
44
|
parentId: string | null;
|
|
39
45
|
sourceId: string | null;
|
|
40
46
|
runConfig: RunConfig;
|
|
41
|
-
stream?:
|
|
47
|
+
stream?: ResumeStream;
|
|
42
48
|
};
|
|
43
49
|
|
|
44
50
|
type CoreOptions = {
|
|
@@ -73,6 +79,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
73
79
|
private readonly recordedHistoryIds = new Set<string>();
|
|
74
80
|
private _isLoading = false;
|
|
75
81
|
private _loadPromise: Promise<void> | undefined;
|
|
82
|
+
private pendingResumeMessageId: string | null = null;
|
|
76
83
|
|
|
77
84
|
constructor(options: CoreOptions) {
|
|
78
85
|
this.agent = options.agent;
|
|
@@ -135,7 +142,13 @@ export class AgUiThreadRuntimeCore {
|
|
|
135
142
|
|
|
136
143
|
if (repo.unstable_resume) {
|
|
137
144
|
const parentId = repo.headId ?? messages.at(-1)?.id ?? null;
|
|
138
|
-
|
|
145
|
+
const resumeStream = this.history?.resume?.bind(this.history);
|
|
146
|
+
await this.startRun(
|
|
147
|
+
parentId,
|
|
148
|
+
this.lastRunConfig,
|
|
149
|
+
undefined,
|
|
150
|
+
resumeStream,
|
|
151
|
+
);
|
|
139
152
|
}
|
|
140
153
|
})
|
|
141
154
|
.catch((error) => {
|
|
@@ -193,14 +206,11 @@ export class AgUiThreadRuntimeCore {
|
|
|
193
206
|
|
|
194
207
|
async resume(config: ResumeRunConfig): Promise<void> {
|
|
195
208
|
this.assertNoPendingInterrupts();
|
|
196
|
-
if (config.stream) {
|
|
197
|
-
this.logger.debug?.(
|
|
198
|
-
"[agui] resume stream is not supported, falling back to regular run",
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
209
|
await this.startRun(
|
|
202
210
|
config.parentId,
|
|
203
211
|
config.runConfig ?? this.lastRunConfig,
|
|
212
|
+
undefined,
|
|
213
|
+
config.stream,
|
|
204
214
|
);
|
|
205
215
|
}
|
|
206
216
|
|
|
@@ -360,7 +370,6 @@ export class AgUiThreadRuntimeCore {
|
|
|
360
370
|
|
|
361
371
|
addToolResult(options: AddToolResultOptions): void {
|
|
362
372
|
let updated = false;
|
|
363
|
-
let shouldResume = false;
|
|
364
373
|
this.messages = this.messages.map((message) => {
|
|
365
374
|
if (message.id !== options.messageId || message.role !== "assistant")
|
|
366
375
|
return message;
|
|
@@ -379,47 +388,60 @@ export class AgUiThreadRuntimeCore {
|
|
|
379
388
|
});
|
|
380
389
|
if (!matchedToolCall) return message;
|
|
381
390
|
updated = true;
|
|
382
|
-
|
|
383
|
-
if (
|
|
384
|
-
assistant.status?.type === "requires-action" &&
|
|
385
|
-
assistant.status.reason === "tool-calls" &&
|
|
386
|
-
content.every(
|
|
387
|
-
(part) =>
|
|
388
|
-
part.type !== "tool-call" ||
|
|
389
|
-
("result" in part && part.result !== undefined),
|
|
390
|
-
)
|
|
391
|
-
) {
|
|
392
|
-
shouldResume = true;
|
|
393
|
-
return {
|
|
394
|
-
...assistant,
|
|
395
|
-
content,
|
|
396
|
-
status: { type: "complete" as const, reason: "unknown" as const },
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
return {
|
|
401
|
-
...assistant,
|
|
402
|
-
content,
|
|
403
|
-
};
|
|
391
|
+
return { ...assistant, content };
|
|
404
392
|
});
|
|
405
393
|
|
|
406
|
-
if (updated)
|
|
407
|
-
|
|
394
|
+
if (!updated) return;
|
|
395
|
+
this.notifyUpdate();
|
|
396
|
+
this.maybeResumeAfterToolResults(options.messageId);
|
|
397
|
+
}
|
|
408
398
|
|
|
409
|
-
|
|
410
|
-
|
|
399
|
+
// Re-evaluate whether every tool call on the assistant message now has a
|
|
400
|
+
// result and, if so, drive the follow-up run. Centralizing this lets the
|
|
401
|
+
// continuation fire whether the frontend result lands before RUN_FINISHED
|
|
402
|
+
// (status flips to requires-action only later, while a run is still draining)
|
|
403
|
+
// or after it. Idempotent: once the message is marked complete it no-ops.
|
|
404
|
+
private maybeResumeAfterToolResults(messageId: string): void {
|
|
405
|
+
const message = this.messages.find((m) => m.id === messageId);
|
|
406
|
+
if (!message || message.role !== "assistant") return;
|
|
407
|
+
const assistant = message as ThreadAssistantMessage;
|
|
408
|
+
if (
|
|
409
|
+
assistant.status?.type !== "requires-action" ||
|
|
410
|
+
assistant.status.reason !== "tool-calls"
|
|
411
|
+
) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const allResolved = assistant.content.every(
|
|
415
|
+
(part) =>
|
|
416
|
+
part.type !== "tool-call" ||
|
|
417
|
+
("result" in part && part.result !== undefined),
|
|
418
|
+
);
|
|
419
|
+
if (!allResolved) return;
|
|
420
|
+
|
|
421
|
+
this.messages = this.messages.map((m) =>
|
|
422
|
+
m.id === messageId && m.role === "assistant"
|
|
423
|
+
? {
|
|
424
|
+
...(m as ThreadAssistantMessage),
|
|
425
|
+
status: { type: "complete" as const, reason: "unknown" as const },
|
|
426
|
+
}
|
|
427
|
+
: m,
|
|
428
|
+
);
|
|
429
|
+
this.notifyUpdate();
|
|
430
|
+
this.persistAssistantHistory(messageId);
|
|
411
431
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
);
|
|
418
|
-
},
|
|
419
|
-
);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
432
|
+
if (this.isRunningFlag) {
|
|
433
|
+
// A run is still draining (RUN_FINISHED arrived but the stream has not
|
|
434
|
+
// closed). Defer until startRun's tail so we never start two runs.
|
|
435
|
+
this.pendingResumeMessageId = messageId;
|
|
436
|
+
return;
|
|
422
437
|
}
|
|
438
|
+
this.startResumeRun(messageId);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private startResumeRun(messageId: string): void {
|
|
442
|
+
void this.startRun(messageId, this.lastRunConfig).catch((error) => {
|
|
443
|
+
this.onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
444
|
+
});
|
|
423
445
|
}
|
|
424
446
|
|
|
425
447
|
applyExternalMessages(messages: readonly ThreadMessage[]): void {
|
|
@@ -441,20 +463,14 @@ export class AgUiThreadRuntimeCore {
|
|
|
441
463
|
parentId: string | null,
|
|
442
464
|
runConfig?: RunConfig,
|
|
443
465
|
resume?: AgUiResumeEntry[],
|
|
466
|
+
resumeStream?: ResumeStream,
|
|
444
467
|
): Promise<void> {
|
|
445
468
|
const normalizedRunConfig = runConfig ?? {};
|
|
446
469
|
this.lastRunConfig = normalizedRunConfig;
|
|
447
470
|
this.resetHead(parentId);
|
|
448
471
|
const historicalMessages = [...this.messages];
|
|
449
472
|
|
|
450
|
-
const runId = generateId();
|
|
451
473
|
this.pendingError = null;
|
|
452
|
-
const input = this.buildRunInput(
|
|
453
|
-
runId,
|
|
454
|
-
normalizedRunConfig,
|
|
455
|
-
historicalMessages,
|
|
456
|
-
resume,
|
|
457
|
-
);
|
|
458
474
|
const assistantParentId = parentId ?? this.messages.at(-1)?.id ?? null;
|
|
459
475
|
let assistantMessageId: string | undefined;
|
|
460
476
|
const ensureAssistant = () => {
|
|
@@ -465,15 +481,17 @@ export class AgUiThreadRuntimeCore {
|
|
|
465
481
|
return created;
|
|
466
482
|
};
|
|
467
483
|
|
|
484
|
+
const applyUpdate = (update: ChatModelRunResult) => {
|
|
485
|
+
const resolved = this.updateAssistantMessage(ensureAssistant(), update);
|
|
486
|
+
if (resolved !== assistantMessageId) {
|
|
487
|
+
assistantMessageId = resolved;
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
468
491
|
const aggregator = new RunAggregator({
|
|
469
492
|
showThinking: this.showThinking,
|
|
470
493
|
logger: this.logger,
|
|
471
|
-
emit:
|
|
472
|
-
const resolved = this.updateAssistantMessage(ensureAssistant(), update);
|
|
473
|
-
if (resolved !== assistantMessageId) {
|
|
474
|
-
assistantMessageId = resolved;
|
|
475
|
-
}
|
|
476
|
-
},
|
|
494
|
+
emit: applyUpdate,
|
|
477
495
|
onServerMessageId: (serverId) => {
|
|
478
496
|
const placeholder = ensureAssistant();
|
|
479
497
|
if (placeholder === serverId) return;
|
|
@@ -487,40 +505,63 @@ export class AgUiThreadRuntimeCore {
|
|
|
487
505
|
const abortSignal = abortController.signal;
|
|
488
506
|
this.abortController = abortController;
|
|
489
507
|
|
|
508
|
+
let cancelRun = () => dispatch({ type: "RUN_CANCELLED" });
|
|
490
509
|
abortSignal.addEventListener(
|
|
491
510
|
"abort",
|
|
492
511
|
() => {
|
|
493
|
-
|
|
512
|
+
cancelRun();
|
|
494
513
|
this.finishRun(abortController);
|
|
495
514
|
this.onCancel?.();
|
|
496
515
|
},
|
|
497
516
|
{ once: true },
|
|
498
517
|
);
|
|
499
518
|
|
|
500
|
-
const subscriber = createAgUiSubscriber({
|
|
501
|
-
dispatch,
|
|
502
|
-
runId,
|
|
503
|
-
logger: this.logger,
|
|
504
|
-
onRunFailed: (error) => {
|
|
505
|
-
this.pendingError = error;
|
|
506
|
-
this.onError?.(error);
|
|
507
|
-
},
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
aggregator.handle({ type: "RUN_STARTED", runId });
|
|
511
519
|
this.setRunning(true);
|
|
512
520
|
|
|
513
521
|
try {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
522
|
+
if (resumeStream) {
|
|
523
|
+
// Cancel flips only the status; an aggregator RUN_CANCELLED would emit an empty snapshot and wipe the replayed content.
|
|
524
|
+
cancelRun = () =>
|
|
525
|
+
applyUpdate({ status: { type: "incomplete", reason: "cancelled" } });
|
|
526
|
+
await this.consumeResumeStream(resumeStream, {
|
|
527
|
+
runConfig: normalizedRunConfig,
|
|
528
|
+
threadId: this.agent.threadId || "main",
|
|
529
|
+
parentId: assistantParentId,
|
|
530
|
+
historicalMessages,
|
|
531
|
+
abortSignal,
|
|
532
|
+
ensureAssistant,
|
|
533
|
+
applyUpdate,
|
|
534
|
+
getAssistantMessageId: () => assistantMessageId,
|
|
535
|
+
});
|
|
536
|
+
} else {
|
|
537
|
+
const runId = generateId();
|
|
538
|
+
aggregator.handle({ type: "RUN_STARTED", runId });
|
|
539
|
+
const input = this.buildRunInput(
|
|
540
|
+
runId,
|
|
541
|
+
normalizedRunConfig,
|
|
542
|
+
historicalMessages,
|
|
543
|
+
resume,
|
|
544
|
+
);
|
|
545
|
+
const subscriber = createAgUiSubscriber({
|
|
546
|
+
dispatch,
|
|
547
|
+
runId,
|
|
548
|
+
logger: this.logger,
|
|
549
|
+
onRunFailed: (error) => {
|
|
550
|
+
this.pendingError = error;
|
|
551
|
+
this.onError?.(error);
|
|
552
|
+
},
|
|
553
|
+
});
|
|
554
|
+
try {
|
|
555
|
+
(this.agent as any).messages = input.messages;
|
|
556
|
+
(this.agent as any).threadId = input.threadId;
|
|
557
|
+
(this.agent as any).state = input.state ?? null;
|
|
558
|
+
} catch {
|
|
559
|
+
// ignore
|
|
560
|
+
}
|
|
561
|
+
await (this.agent as any).runAgent(input, subscriber, {
|
|
562
|
+
signal: abortSignal,
|
|
563
|
+
});
|
|
520
564
|
}
|
|
521
|
-
await (this.agent as any).runAgent(input, subscriber, {
|
|
522
|
-
signal: abortSignal,
|
|
523
|
-
});
|
|
524
565
|
} catch (error) {
|
|
525
566
|
if (!abortSignal.aborted) {
|
|
526
567
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
@@ -535,8 +576,77 @@ export class AgUiThreadRuntimeCore {
|
|
|
535
576
|
if (this.pendingError) {
|
|
536
577
|
const err = this.pendingError;
|
|
537
578
|
this.pendingError = null;
|
|
579
|
+
this.pendingResumeMessageId = null;
|
|
538
580
|
throw err;
|
|
539
581
|
}
|
|
582
|
+
|
|
583
|
+
// A tool result that landed before the run settled deferred its
|
|
584
|
+
// continuation here so a second run never overlaps the first.
|
|
585
|
+
if (this.pendingResumeMessageId !== null) {
|
|
586
|
+
const resumeMessageId = this.pendingResumeMessageId;
|
|
587
|
+
this.pendingResumeMessageId = null;
|
|
588
|
+
if (!abortSignal.aborted) {
|
|
589
|
+
this.startResumeRun(resumeMessageId);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Replays a persisted run's snapshots into the existing assistant message, bypassing agent.runAgent so it is not re-invoked.
|
|
595
|
+
private async consumeResumeStream(
|
|
596
|
+
stream: ResumeStream,
|
|
597
|
+
ctx: {
|
|
598
|
+
runConfig: RunConfig;
|
|
599
|
+
threadId: string;
|
|
600
|
+
parentId: string | null;
|
|
601
|
+
historicalMessages: readonly ThreadMessage[];
|
|
602
|
+
abortSignal: AbortSignal;
|
|
603
|
+
ensureAssistant: () => string;
|
|
604
|
+
applyUpdate: (update: ChatModelRunResult) => void;
|
|
605
|
+
getAssistantMessageId: () => string | undefined;
|
|
606
|
+
},
|
|
607
|
+
): Promise<void> {
|
|
608
|
+
const assistantId = ctx.ensureAssistant();
|
|
609
|
+
const currentId = () => ctx.getAssistantMessageId() ?? assistantId;
|
|
610
|
+
const options: ChatModelRunOptions = {
|
|
611
|
+
messages: ctx.historicalMessages,
|
|
612
|
+
runConfig: ctx.runConfig,
|
|
613
|
+
abortSignal: ctx.abortSignal,
|
|
614
|
+
context: this.runtime?.thread.getModelContext() ?? {},
|
|
615
|
+
unstable_assistantMessageId: assistantId,
|
|
616
|
+
unstable_threadId: ctx.threadId,
|
|
617
|
+
unstable_parentId: ctx.parentId,
|
|
618
|
+
unstable_getMessage: () => {
|
|
619
|
+
const message = this.messages.find((m) => m.id === currentId());
|
|
620
|
+
if (!message) {
|
|
621
|
+
throw new Error(
|
|
622
|
+
"[agui] resume stream requested the assistant message before it existed",
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
return message;
|
|
626
|
+
},
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
try {
|
|
630
|
+
for await (const result of stream(options)) {
|
|
631
|
+
if (ctx.abortSignal.aborted) return;
|
|
632
|
+
ctx.applyUpdate(result);
|
|
633
|
+
}
|
|
634
|
+
} catch (error) {
|
|
635
|
+
if (ctx.abortSignal.aborted) return;
|
|
636
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
637
|
+
ctx.applyUpdate({
|
|
638
|
+
status: { type: "incomplete", reason: "error", error: err.message },
|
|
639
|
+
});
|
|
640
|
+
this.onError?.(err);
|
|
641
|
+
this.pendingError = this.pendingError ?? err;
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (ctx.abortSignal.aborted) return;
|
|
646
|
+
const current = this.messages.find((m) => m.id === currentId());
|
|
647
|
+
if (!current || current.status?.type === "running") {
|
|
648
|
+
ctx.applyUpdate({ status: { type: "complete", reason: "unknown" } });
|
|
649
|
+
}
|
|
540
650
|
}
|
|
541
651
|
|
|
542
652
|
private buildRunInput(
|
|
@@ -668,10 +778,16 @@ export class AgUiThreadRuntimeCore {
|
|
|
668
778
|
? this.mergeAssistantMetadata(assistant.metadata, update.metadata)
|
|
669
779
|
: assistant.metadata;
|
|
670
780
|
latestStatus = update.status ?? assistant.status;
|
|
781
|
+
const content =
|
|
782
|
+
update.content !== undefined
|
|
783
|
+
? this.preserveToolResults(
|
|
784
|
+
assistant.content,
|
|
785
|
+
update.content as ThreadAssistantMessage["content"],
|
|
786
|
+
)
|
|
787
|
+
: assistant.content;
|
|
671
788
|
return {
|
|
672
789
|
...assistant,
|
|
673
|
-
content
|
|
674
|
-
assistant.content) as ThreadAssistantMessage["content"],
|
|
790
|
+
content,
|
|
675
791
|
status: latestStatus,
|
|
676
792
|
metadata,
|
|
677
793
|
};
|
|
@@ -691,9 +807,47 @@ export class AgUiThreadRuntimeCore {
|
|
|
691
807
|
if (this.isPersistableStatus(latestStatus)) {
|
|
692
808
|
this.persistAssistantHistory(resolvedMessageId);
|
|
693
809
|
}
|
|
810
|
+
this.maybeResumeAfterToolResults(resolvedMessageId);
|
|
694
811
|
return resolvedMessageId;
|
|
695
812
|
}
|
|
696
813
|
|
|
814
|
+
// The RunAggregator rebuilds the assistant content from stream events only,
|
|
815
|
+
// so a fresh snapshot omits results injected via addToolResult (frontend tool
|
|
816
|
+
// execution). Carry those results forward so the aggregator never clobbers
|
|
817
|
+
// them. Results are only ever added in this flow, so preserving is safe.
|
|
818
|
+
private preserveToolResults(
|
|
819
|
+
previous: ThreadAssistantMessage["content"],
|
|
820
|
+
next: ThreadAssistantMessage["content"],
|
|
821
|
+
): ThreadAssistantMessage["content"] {
|
|
822
|
+
const resolved = new Map<string, ToolCallMessagePart>();
|
|
823
|
+
for (const part of previous) {
|
|
824
|
+
if (
|
|
825
|
+
part.type === "tool-call" &&
|
|
826
|
+
"result" in part &&
|
|
827
|
+
part.result !== undefined
|
|
828
|
+
) {
|
|
829
|
+
resolved.set(part.toolCallId, part);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
if (resolved.size === 0) return next;
|
|
833
|
+
|
|
834
|
+
let changed = false;
|
|
835
|
+
const merged = next.map((part) => {
|
|
836
|
+
if (part.type !== "tool-call") return part;
|
|
837
|
+
if ("result" in part && part.result !== undefined) return part;
|
|
838
|
+
const prior = resolved.get(part.toolCallId);
|
|
839
|
+
if (!prior) return part;
|
|
840
|
+
changed = true;
|
|
841
|
+
return {
|
|
842
|
+
...part,
|
|
843
|
+
result: prior.result,
|
|
844
|
+
...(prior.artifact !== undefined ? { artifact: prior.artifact } : {}),
|
|
845
|
+
...(prior.isError !== undefined ? { isError: prior.isError } : {}),
|
|
846
|
+
};
|
|
847
|
+
});
|
|
848
|
+
return changed ? (merged as ThreadAssistantMessage["content"]) : next;
|
|
849
|
+
}
|
|
850
|
+
|
|
697
851
|
private mergeAssistantMetadata(
|
|
698
852
|
current: ThreadAssistantMessage["metadata"],
|
|
699
853
|
incoming: NonNullable<ChatModelRunResult["metadata"]>,
|
|
@@ -730,7 +884,20 @@ export class AgUiThreadRuntimeCore {
|
|
|
730
884
|
return;
|
|
731
885
|
}
|
|
732
886
|
case "STATE_DELTA": {
|
|
733
|
-
|
|
887
|
+
if (event.delta.length === 0) return;
|
|
888
|
+
try {
|
|
889
|
+
const state = this.stateSnapshot ?? {};
|
|
890
|
+
const result = jsonpatch.applyPatch(
|
|
891
|
+
state,
|
|
892
|
+
event.delta as Operation[],
|
|
893
|
+
/* validateOperation */ true,
|
|
894
|
+
/* mutateDocument */ false,
|
|
895
|
+
);
|
|
896
|
+
this.stateSnapshot = result.newDocument as ReadonlyJSONValue;
|
|
897
|
+
this.notifyUpdate();
|
|
898
|
+
} catch (error) {
|
|
899
|
+
this.logger.error?.("[agui] failed to apply state delta", error);
|
|
900
|
+
}
|
|
734
901
|
return;
|
|
735
902
|
}
|
|
736
903
|
case "MESSAGES_SNAPSHOT": {
|
|
@@ -744,16 +911,14 @@ export class AgUiThreadRuntimeCore {
|
|
|
744
911
|
|
|
745
912
|
private importMessagesSnapshot(rawMessages: readonly unknown[]) {
|
|
746
913
|
try {
|
|
747
|
-
const normalized = fromAgUiMessages(rawMessages
|
|
914
|
+
const normalized = fromAgUiMessages(rawMessages, {
|
|
915
|
+
showThinking: this.showThinking,
|
|
916
|
+
});
|
|
748
917
|
const converted: ThreadMessage[] = [];
|
|
749
918
|
for (const message of normalized) {
|
|
750
919
|
try {
|
|
751
920
|
converted.push(
|
|
752
|
-
fromThreadMessageLike(
|
|
753
|
-
message as any,
|
|
754
|
-
generateId(),
|
|
755
|
-
FALLBACK_USER_STATUS,
|
|
756
|
-
),
|
|
921
|
+
fromThreadMessageLike(message, generateId(), FALLBACK_USER_STATUS),
|
|
757
922
|
);
|
|
758
923
|
} catch (error) {
|
|
759
924
|
this.logger.error?.(
|