@assistant-ui/react-ag-ui 0.0.56 → 0.0.58
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/runtime/AgUiThreadRuntimeCore.d.ts +5 -17
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtime/AgUiThreadRuntimeCore.js +133 -234
- package/dist/runtime/AgUiThreadRuntimeCore.js.map +1 -1
- package/dist/runtime/adapter/conversions.d.ts.map +1 -1
- package/dist/runtime/adapter/conversions.js +54 -25
- package/dist/runtime/adapter/conversions.js.map +1 -1
- package/dist/runtime/adapter/run-aggregator.d.ts +31 -4
- package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
- package/dist/runtime/adapter/run-aggregator.js +419 -123
- 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 +42 -1
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/useAgUiRuntime.js +12 -9
- package/dist/useAgUiRuntime.js.map +1 -1
- package/package.json +10 -10
- package/src/runtime/AgUiThreadRuntimeCore.test.ts +105 -0
- package/src/runtime/AgUiThreadRuntimeCore.ts +190 -319
- package/src/runtime/adapter/conversions.ts +128 -36
- package/src/runtime/adapter/run-aggregator.approval.test.ts +128 -0
- package/src/runtime/adapter/run-aggregator.ts +670 -148
- 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 +70 -12
- package/src/useAgUiRuntime.approval.test.tsx +34 -24
- package/src/useAgUiRuntime.history.test.tsx +76 -0
- package/src/useAgUiRuntime.toolDeferral.test.tsx +186 -0
- package/src/useAgUiRuntime.ts +12 -9
|
@@ -20,7 +20,12 @@ import type {
|
|
|
20
20
|
ThreadMessage,
|
|
21
21
|
ToolCallMessagePart,
|
|
22
22
|
} from "@assistant-ui/core";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
createMessageRepositorySession,
|
|
25
|
+
invokeUserCallback,
|
|
26
|
+
iterateToolCallParts,
|
|
27
|
+
mapToolCallPartsDeep,
|
|
28
|
+
} from "@assistant-ui/core/internal";
|
|
24
29
|
import type {
|
|
25
30
|
AbstractAgent,
|
|
26
31
|
AgentSubscriber,
|
|
@@ -71,8 +76,6 @@ const isResolvedToolCall = (
|
|
|
71
76
|
): boolean =>
|
|
72
77
|
part.type === "tool-call" && "result" in part && part.result !== undefined;
|
|
73
78
|
|
|
74
|
-
const symbolResumeShim = Symbol("agui-resume-shim");
|
|
75
|
-
|
|
76
79
|
type RunConfig = NonNullable<AppendMessage["runConfig"]>;
|
|
77
80
|
type ResumeStream = (
|
|
78
81
|
options: ChatModelRunOptions,
|
|
@@ -99,36 +102,17 @@ const FALLBACK_USER_STATUS = { type: "complete", reason: "unknown" } as const;
|
|
|
99
102
|
|
|
100
103
|
type AgUiRuntimeCallbackName = "onError" | "onCancel";
|
|
101
104
|
|
|
102
|
-
const
|
|
103
|
-
console.error(`[react-ag-ui] ${name} callback threw an error`, error);
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const invokeRuntimeCallback = <TArgs extends unknown[]>(
|
|
105
|
+
const invokeRuntimeCallback = <TArgs extends readonly unknown[]>(
|
|
107
106
|
name: AgUiRuntimeCallbackName,
|
|
108
|
-
callback: ((...args: TArgs) =>
|
|
107
|
+
callback: ((...args: TArgs) => unknown) | undefined,
|
|
109
108
|
...args: TArgs
|
|
110
|
-
) => {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
const result = callback(...args) as unknown;
|
|
115
|
-
if (
|
|
116
|
-
result !== null &&
|
|
117
|
-
(typeof result === "object" || typeof result === "function") &&
|
|
118
|
-
"then" in result &&
|
|
119
|
-
typeof result.then === "function"
|
|
120
|
-
) {
|
|
121
|
-
void Promise.resolve(result).catch((error) => {
|
|
122
|
-
reportCallbackError(name, error);
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
} catch (error) {
|
|
126
|
-
reportCallbackError(name, error);
|
|
127
|
-
}
|
|
109
|
+
): void => {
|
|
110
|
+
void invokeUserCallback("react-ag-ui", name, callback, ...args);
|
|
128
111
|
};
|
|
129
112
|
|
|
130
|
-
// The aggregator only
|
|
131
|
-
//
|
|
113
|
+
// The aggregator sends only the agui keys it owns (interrupts and
|
|
114
|
+
// opaqueReasoning, each as a full replacement), so a shallow spread at the
|
|
115
|
+
// custom level would drop any sibling agui state a snapshot import attached.
|
|
132
116
|
const mergeAgUiNamespace = (
|
|
133
117
|
current: Record<string, unknown> | undefined,
|
|
134
118
|
incoming: Record<string, unknown>,
|
|
@@ -158,8 +142,22 @@ export class AgUiThreadRuntimeCore {
|
|
|
158
142
|
private readonly reportedErrors = new WeakSet<object>();
|
|
159
143
|
|
|
160
144
|
private runtime: AssistantRuntime | undefined;
|
|
161
|
-
private readonly
|
|
162
|
-
|
|
145
|
+
private readonly session = createMessageRepositorySession({
|
|
146
|
+
decorateExport: (exported, repository) => {
|
|
147
|
+
let parentId: string | null = null;
|
|
148
|
+
for (const message of repository.getMessages()) {
|
|
149
|
+
if (message.metadata.isOptimistic) {
|
|
150
|
+
exported.messages.push({ parentId, message });
|
|
151
|
+
}
|
|
152
|
+
parentId = message.id;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
...exported,
|
|
157
|
+
headId: repository.headId,
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
});
|
|
163
161
|
private isRunningFlag = false;
|
|
164
162
|
private abortController: AbortController | null = null;
|
|
165
163
|
// The agent that started the active run. updateOptions can swap this.agent
|
|
@@ -175,6 +173,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
175
173
|
private readonly historyWrites = new Map<string, Promise<void>>();
|
|
176
174
|
private _isLoading = false;
|
|
177
175
|
private _loadPromise: Promise<void> | undefined;
|
|
176
|
+
private _loadRequested = false;
|
|
178
177
|
private pendingResumeMessageId: string | null = null;
|
|
179
178
|
private pendingA2uiResume = false;
|
|
180
179
|
private pendingA2uiAction: Record<string, unknown> | undefined;
|
|
@@ -188,7 +187,6 @@ export class AgUiThreadRuntimeCore {
|
|
|
188
187
|
this.onCancel = options.onCancel;
|
|
189
188
|
this.history = options.history;
|
|
190
189
|
this.notifyUpdate = options.notifyUpdate;
|
|
191
|
-
this.installResumeShim();
|
|
192
190
|
}
|
|
193
191
|
|
|
194
192
|
updateOptions(options: Omit<CoreOptions, "notifyUpdate">) {
|
|
@@ -198,8 +196,17 @@ export class AgUiThreadRuntimeCore {
|
|
|
198
196
|
this.autoCancelPendingToolCalls = options.autoCancelPendingToolCalls;
|
|
199
197
|
this.onError = options.onError;
|
|
200
198
|
this.onCancel = options.onCancel;
|
|
199
|
+
const previousHistory = this.history;
|
|
201
200
|
this.history = options.history;
|
|
202
|
-
|
|
201
|
+
if (
|
|
202
|
+
this._loadRequested &&
|
|
203
|
+
!this._loadPromise &&
|
|
204
|
+
!previousHistory &&
|
|
205
|
+
options.history &&
|
|
206
|
+
this.session.getMessages().length === 0
|
|
207
|
+
) {
|
|
208
|
+
void this.__internal_load();
|
|
209
|
+
}
|
|
203
210
|
}
|
|
204
211
|
|
|
205
212
|
attachRuntime(runtime: AssistantRuntime) {
|
|
@@ -214,97 +221,11 @@ export class AgUiThreadRuntimeCore {
|
|
|
214
221
|
}
|
|
215
222
|
|
|
216
223
|
getMessages(): readonly ThreadMessage[] {
|
|
217
|
-
return this.
|
|
224
|
+
return this.session.getMessages();
|
|
218
225
|
}
|
|
219
226
|
|
|
220
227
|
getMessageRepository(): ExportedMessageRepository {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const exportedRepository = this.repository.export();
|
|
224
|
-
let parentId: string | null = null;
|
|
225
|
-
for (const message of this.repository.getMessages()) {
|
|
226
|
-
if (message.metadata.isOptimistic) {
|
|
227
|
-
exportedRepository.messages.push({ parentId, message });
|
|
228
|
-
}
|
|
229
|
-
parentId = message.id;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
this.exportedRepository = {
|
|
233
|
-
...exportedRepository,
|
|
234
|
-
headId: this.repository.headId,
|
|
235
|
-
};
|
|
236
|
-
return this.exportedRepository;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
private tryGetMessage(messageId: string) {
|
|
240
|
-
try {
|
|
241
|
-
return this.repository.getMessage(messageId);
|
|
242
|
-
} catch {
|
|
243
|
-
return undefined;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
private tryGetMessages(
|
|
248
|
-
messageId: string,
|
|
249
|
-
): readonly ThreadMessage[] | undefined {
|
|
250
|
-
try {
|
|
251
|
-
return this.repository.getMessages(messageId);
|
|
252
|
-
} catch {
|
|
253
|
-
return undefined;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
private hasMessage(messageId: string): boolean {
|
|
258
|
-
return this.tryGetMessage(messageId) !== undefined;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
private addOrUpdateMessage(
|
|
262
|
-
parentId: string | null,
|
|
263
|
-
message: ThreadMessage,
|
|
264
|
-
): void {
|
|
265
|
-
this.repository.addOrUpdateMessage(parentId, message);
|
|
266
|
-
this.exportedRepository = undefined;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
private deleteMessage(
|
|
270
|
-
messageId: string,
|
|
271
|
-
replacementId?: string | null,
|
|
272
|
-
): void {
|
|
273
|
-
this.repository.deleteMessage(messageId, replacementId);
|
|
274
|
-
this.exportedRepository = undefined;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
private tryDeleteMessage(messageId: string): boolean {
|
|
278
|
-
if (!this.hasMessage(messageId)) return false;
|
|
279
|
-
this.deleteMessage(messageId);
|
|
280
|
-
return true;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
private switchToBranch(messageId: string): void {
|
|
284
|
-
this.repository.switchToBranch(messageId);
|
|
285
|
-
this.exportedRepository = undefined;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
private resetRepositoryHead(messageId: string | null): void {
|
|
289
|
-
this.repository.resetHead(messageId);
|
|
290
|
-
this.exportedRepository = undefined;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
private clearRepository(): void {
|
|
294
|
-
this.repository.clear();
|
|
295
|
-
this.exportedRepository = undefined;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
private updateMessage(
|
|
299
|
-
messageId: string,
|
|
300
|
-
updater: (message: ThreadMessage) => ThreadMessage,
|
|
301
|
-
): boolean {
|
|
302
|
-
const item = this.tryGetMessage(messageId);
|
|
303
|
-
if (!item) return false;
|
|
304
|
-
const message = updater(item.message);
|
|
305
|
-
if (message === item.message) return false;
|
|
306
|
-
this.addOrUpdateMessage(item.parentId, message);
|
|
307
|
-
return true;
|
|
228
|
+
return this.session.export();
|
|
308
229
|
}
|
|
309
230
|
|
|
310
231
|
getState(): ReadonlyJSONValue | undefined {
|
|
@@ -320,9 +241,11 @@ export class AgUiThreadRuntimeCore {
|
|
|
320
241
|
}
|
|
321
242
|
|
|
322
243
|
__internal_load(): Promise<void> {
|
|
244
|
+
this._loadRequested = true;
|
|
323
245
|
if (this._loadPromise) return this._loadPromise;
|
|
246
|
+
if (!this.history) return Promise.resolve();
|
|
324
247
|
|
|
325
|
-
const promise = this.history
|
|
248
|
+
const promise = this.history.load();
|
|
326
249
|
|
|
327
250
|
this._isLoading = true;
|
|
328
251
|
|
|
@@ -330,14 +253,21 @@ export class AgUiThreadRuntimeCore {
|
|
|
330
253
|
.then(async (repo) => {
|
|
331
254
|
if (!repo) return;
|
|
332
255
|
|
|
333
|
-
this.applyExternalMessageRepository(repo);
|
|
256
|
+
this.session.applyExternalMessageRepository(repo);
|
|
257
|
+
this.assistantHistoryParents.clear();
|
|
258
|
+
this.snapshotHistoryIds.clear();
|
|
259
|
+
this.persistedHistoryIds.clear();
|
|
260
|
+
for (const { message } of repo.messages) {
|
|
261
|
+
this.persistedHistoryIds.add(message.id);
|
|
262
|
+
}
|
|
263
|
+
this.notifyUpdate();
|
|
334
264
|
|
|
335
265
|
if (repo.state !== undefined) {
|
|
336
266
|
this.loadExternalState(repo.state);
|
|
337
267
|
}
|
|
338
268
|
|
|
339
269
|
if (repo.unstable_resume) {
|
|
340
|
-
const parentId = repo.headId ?? this.
|
|
270
|
+
const parentId = repo.headId ?? this.session.headId;
|
|
341
271
|
const resumeStream = this.history?.resume?.bind(this.history);
|
|
342
272
|
await this.startRun(
|
|
343
273
|
parentId,
|
|
@@ -384,17 +314,17 @@ export class AgUiThreadRuntimeCore {
|
|
|
384
314
|
}
|
|
385
315
|
|
|
386
316
|
private appendEntry(message: AppendMessage): string {
|
|
387
|
-
if (message.sourceId) this.tryDeleteMessage(message.sourceId);
|
|
317
|
+
if (message.sourceId) this.session.tryDeleteMessage(message.sourceId);
|
|
388
318
|
|
|
389
319
|
const threadMessage = this.toThreadMessage(message);
|
|
390
320
|
const parentId =
|
|
391
321
|
message.parentId === null
|
|
392
322
|
? null
|
|
393
|
-
: message.parentId && this.hasMessage(message.parentId)
|
|
323
|
+
: message.parentId && this.session.hasMessage(message.parentId)
|
|
394
324
|
? message.parentId
|
|
395
|
-
: this.
|
|
396
|
-
this.addOrUpdateMessage(parentId, threadMessage);
|
|
397
|
-
this.switchToBranch(threadMessage.id);
|
|
325
|
+
: this.session.headId;
|
|
326
|
+
this.session.addOrUpdateMessage(parentId, threadMessage);
|
|
327
|
+
this.session.switchToBranch(threadMessage.id);
|
|
398
328
|
this.notifyUpdate();
|
|
399
329
|
this.recordHistoryEntry(parentId, threadMessage);
|
|
400
330
|
return threadMessage.id;
|
|
@@ -506,8 +436,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
506
436
|
const assistant = this.findRequiresActionAssistant("tool-calls");
|
|
507
437
|
if (!assistant) return null;
|
|
508
438
|
const toolCallIds: string[] = [];
|
|
509
|
-
for (const part of assistant.content) {
|
|
510
|
-
if (part.type !== "tool-call") continue;
|
|
439
|
+
for (const part of iterateToolCallParts(assistant.content)) {
|
|
511
440
|
if (isResolvedToolCall(part)) continue;
|
|
512
441
|
toolCallIds.push(part.toolCallId);
|
|
513
442
|
}
|
|
@@ -565,9 +494,9 @@ export class AgUiThreadRuntimeCore {
|
|
|
565
494
|
}
|
|
566
495
|
|
|
567
496
|
/**
|
|
568
|
-
*
|
|
569
|
-
*
|
|
570
|
-
*
|
|
497
|
+
* A rejected decision reaches the caller through the approval seam, and
|
|
498
|
+
* `onError` in addition, because a consumer watching only the runtime would
|
|
499
|
+
* otherwise miss it. A failure raised by the resumed run itself was already
|
|
571
500
|
* reported by `startRun` before it rethrew, so reporting it here again would
|
|
572
501
|
* give the consumer two notifications for one failure.
|
|
573
502
|
*/
|
|
@@ -619,15 +548,14 @@ export class AgUiThreadRuntimeCore {
|
|
|
619
548
|
|
|
620
549
|
// Bound against the message the gates landed on, so this check claims a
|
|
621
550
|
// batch only where the projection did.
|
|
622
|
-
const gatedMessage = this.tryGetMessage(pending.messageId)
|
|
623
|
-
|
|
|
624
|
-
| undefined;
|
|
551
|
+
const gatedMessage = this.session.tryGetMessage(pending.messageId)
|
|
552
|
+
?.message as ThreadAssistantMessage | undefined;
|
|
625
553
|
const gated = projectAgUiToolApprovals(
|
|
626
554
|
pending.interrupts,
|
|
627
555
|
new Set(
|
|
628
|
-
(gatedMessage?.content ?? [])
|
|
629
|
-
|
|
630
|
-
|
|
556
|
+
Array.from(iterateToolCallParts(gatedMessage?.content ?? [])).map(
|
|
557
|
+
(part) => part.toolCallId,
|
|
558
|
+
),
|
|
631
559
|
),
|
|
632
560
|
);
|
|
633
561
|
const isGated = [...gated.values()].some(
|
|
@@ -647,13 +575,16 @@ export class AgUiThreadRuntimeCore {
|
|
|
647
575
|
pending.interrupts,
|
|
648
576
|
);
|
|
649
577
|
|
|
650
|
-
const recorded = this.updateMessage(
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
578
|
+
const recorded = this.session.updateMessage(
|
|
579
|
+
pending.messageId,
|
|
580
|
+
(message) => {
|
|
581
|
+
if (message.role !== "assistant") return message;
|
|
582
|
+
const assistant = message as ThreadAssistantMessage;
|
|
583
|
+
const content = withToolApprovalDecision(assistant.content, options);
|
|
584
|
+
if (content === assistant.content) return assistant;
|
|
585
|
+
return { ...assistant, content };
|
|
586
|
+
},
|
|
587
|
+
);
|
|
657
588
|
if (!recorded) {
|
|
658
589
|
throw new Error(
|
|
659
590
|
`[agui] respondToToolApproval: approval "${options.approvalId}" is already decided`,
|
|
@@ -661,7 +592,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
661
592
|
}
|
|
662
593
|
this.notifyUpdate();
|
|
663
594
|
|
|
664
|
-
const assistant = this.tryGetMessage(pending.messageId)?.message as
|
|
595
|
+
const assistant = this.session.tryGetMessage(pending.messageId)?.message as
|
|
665
596
|
| ThreadAssistantMessage
|
|
666
597
|
| undefined;
|
|
667
598
|
if (!assistant) return;
|
|
@@ -776,7 +707,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
776
707
|
if (typeof message === "string") {
|
|
777
708
|
return {
|
|
778
709
|
createdAt: new Date(),
|
|
779
|
-
parentId: this.
|
|
710
|
+
parentId: this.session.headId,
|
|
780
711
|
sourceId: null,
|
|
781
712
|
runConfig: {},
|
|
782
713
|
role: "user",
|
|
@@ -787,7 +718,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
787
718
|
}
|
|
788
719
|
return {
|
|
789
720
|
createdAt: message.createdAt ?? new Date(),
|
|
790
|
-
parentId: message.parentId ?? this.
|
|
721
|
+
parentId: message.parentId ?? this.session.headId,
|
|
791
722
|
sourceId: message.sourceId ?? null,
|
|
792
723
|
role: message.role ?? "user",
|
|
793
724
|
content: message.content,
|
|
@@ -802,7 +733,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
802
733
|
messageId: string,
|
|
803
734
|
resume: readonly AgUiResumeEntry[],
|
|
804
735
|
): void {
|
|
805
|
-
const touched = this.updateMessage(messageId, (message) => {
|
|
736
|
+
const touched = this.session.updateMessage(messageId, (message) => {
|
|
806
737
|
if (message.role !== "assistant") return message;
|
|
807
738
|
const assistant = message as ThreadAssistantMessage;
|
|
808
739
|
if (
|
|
@@ -839,9 +770,8 @@ export class AgUiThreadRuntimeCore {
|
|
|
839
770
|
for (let index = messages.length - 1; index >= 0; index--) {
|
|
840
771
|
const message = messages[index];
|
|
841
772
|
if (!message || message.role !== "assistant") continue;
|
|
842
|
-
for (const part of message.content) {
|
|
843
|
-
if (part.
|
|
844
|
-
continue;
|
|
773
|
+
for (const part of iterateToolCallParts(message.content)) {
|
|
774
|
+
if (part.toolCallId !== toolCallId) continue;
|
|
845
775
|
if (!isResolvedToolCall(part)) {
|
|
846
776
|
return message.id;
|
|
847
777
|
}
|
|
@@ -852,11 +782,11 @@ export class AgUiThreadRuntimeCore {
|
|
|
852
782
|
}
|
|
853
783
|
|
|
854
784
|
private cancelUnresolvedToolCalls(messageId: string): void {
|
|
855
|
-
const updated = this.updateMessage(messageId, (message) => {
|
|
785
|
+
const updated = this.session.updateMessage(messageId, (message) => {
|
|
856
786
|
if (message.role !== "assistant") return message;
|
|
857
787
|
const assistant = message as ThreadAssistantMessage;
|
|
858
|
-
const content = assistant.content
|
|
859
|
-
if (
|
|
788
|
+
const { content } = mapToolCallPartsDeep(assistant.content, (part) => {
|
|
789
|
+
if (isResolvedToolCall(part)) return part;
|
|
860
790
|
return {
|
|
861
791
|
...part,
|
|
862
792
|
result: { error: "Tool call cancelled by user" },
|
|
@@ -869,13 +799,19 @@ export class AgUiThreadRuntimeCore {
|
|
|
869
799
|
}
|
|
870
800
|
|
|
871
801
|
addToolResult(options: AddToolResultOptions): void {
|
|
872
|
-
|
|
802
|
+
// Core's ToolInvocationTracker resolves a nested call to the nested
|
|
803
|
+
// subagent message's id, which is not a session message; re-anchor on the
|
|
804
|
+
// top-level message that owns the tree so the update can land.
|
|
805
|
+
const sessionMessageId = this.session.tryGetMessage(options.messageId)
|
|
806
|
+
? options.messageId
|
|
807
|
+
: this.findMessageIdForToolCall(options.toolCallId);
|
|
808
|
+
if (sessionMessageId === undefined) return;
|
|
809
|
+
const updated = this.session.updateMessage(sessionMessageId, (message) => {
|
|
873
810
|
if (message.role !== "assistant") return message;
|
|
874
811
|
const assistant = message as ThreadAssistantMessage;
|
|
875
812
|
let matchedToolCall = false;
|
|
876
|
-
const content = assistant.content
|
|
877
|
-
if (part.
|
|
878
|
-
return part;
|
|
813
|
+
const { content } = mapToolCallPartsDeep(assistant.content, (part) => {
|
|
814
|
+
if (part.toolCallId !== options.toolCallId) return part;
|
|
879
815
|
matchedToolCall = true;
|
|
880
816
|
return {
|
|
881
817
|
...part,
|
|
@@ -890,13 +826,13 @@ export class AgUiThreadRuntimeCore {
|
|
|
890
826
|
|
|
891
827
|
if (!updated) return;
|
|
892
828
|
this.notifyUpdate();
|
|
893
|
-
this.maybeResumeAfterToolResults(
|
|
829
|
+
this.maybeResumeAfterToolResults(sessionMessageId);
|
|
894
830
|
}
|
|
895
831
|
|
|
896
832
|
sendA2uiAction(action: Record<string, unknown>): void {
|
|
897
833
|
this.assertNoPendingInterrupts();
|
|
898
834
|
this.maybeAutoCancelPendingToolCalls();
|
|
899
|
-
const parentId = this.
|
|
835
|
+
const parentId = this.session.headId;
|
|
900
836
|
if (parentId === null) {
|
|
901
837
|
this.logger.debug(
|
|
902
838
|
"[agui] sendA2uiAction: no messages to resume, dropping action",
|
|
@@ -934,7 +870,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
934
870
|
}
|
|
935
871
|
|
|
936
872
|
private maybeCompleteAfterToolResults(messageId: string): boolean {
|
|
937
|
-
const message = this.tryGetMessage(messageId)?.message;
|
|
873
|
+
const message = this.session.tryGetMessage(messageId)?.message;
|
|
938
874
|
if (!message || message.role !== "assistant") return false;
|
|
939
875
|
const assistant = message as ThreadAssistantMessage;
|
|
940
876
|
if (
|
|
@@ -943,12 +879,16 @@ export class AgUiThreadRuntimeCore {
|
|
|
943
879
|
) {
|
|
944
880
|
return false;
|
|
945
881
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
882
|
+
let allResolved = true;
|
|
883
|
+
for (const part of iterateToolCallParts(assistant.content)) {
|
|
884
|
+
if (!isResolvedToolCall(part)) {
|
|
885
|
+
allResolved = false;
|
|
886
|
+
break;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
949
889
|
if (!allResolved) return false;
|
|
950
890
|
|
|
951
|
-
const updated = this.updateMessage(messageId, (current) =>
|
|
891
|
+
const updated = this.session.updateMessage(messageId, (current) =>
|
|
952
892
|
current.role === "assistant"
|
|
953
893
|
? {
|
|
954
894
|
...(current as ThreadAssistantMessage),
|
|
@@ -978,7 +918,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
978
918
|
this.assistantHistoryParents.clear();
|
|
979
919
|
|
|
980
920
|
if (messages.length === 0) {
|
|
981
|
-
this.
|
|
921
|
+
this.session.clear();
|
|
982
922
|
} else {
|
|
983
923
|
let expectedParentId: string | null = null;
|
|
984
924
|
let lastAppliedId: string | null = null;
|
|
@@ -988,31 +928,31 @@ export class AgUiThreadRuntimeCore {
|
|
|
988
928
|
for (const message of messages) {
|
|
989
929
|
if (seen.has(message.id)) continue;
|
|
990
930
|
seen.add(message.id);
|
|
991
|
-
const existing = this.tryGetMessage(message.id);
|
|
931
|
+
const existing = this.session.tryGetMessage(message.id);
|
|
992
932
|
if (existing && existing.parentId !== expectedParentId) {
|
|
993
933
|
hardReplace = true;
|
|
994
934
|
break;
|
|
995
935
|
}
|
|
996
|
-
this.addOrUpdateMessage(expectedParentId, message);
|
|
936
|
+
this.session.addOrUpdateMessage(expectedParentId, message);
|
|
997
937
|
expectedParentId = message.id;
|
|
998
938
|
lastAppliedId = message.id;
|
|
999
939
|
}
|
|
1000
940
|
|
|
1001
941
|
if (hardReplace) {
|
|
1002
|
-
this.
|
|
942
|
+
this.session.clear();
|
|
1003
943
|
expectedParentId = null;
|
|
1004
944
|
lastAppliedId = null;
|
|
1005
945
|
seen.clear();
|
|
1006
946
|
for (const message of messages) {
|
|
1007
947
|
if (seen.has(message.id)) continue;
|
|
1008
948
|
seen.add(message.id);
|
|
1009
|
-
this.addOrUpdateMessage(expectedParentId, message);
|
|
949
|
+
this.session.addOrUpdateMessage(expectedParentId, message);
|
|
1010
950
|
expectedParentId = message.id;
|
|
1011
951
|
lastAppliedId = message.id;
|
|
1012
952
|
}
|
|
1013
953
|
}
|
|
1014
954
|
|
|
1015
|
-
this.
|
|
955
|
+
this.session.resetHead(lastAppliedId);
|
|
1016
956
|
}
|
|
1017
957
|
|
|
1018
958
|
this.snapshotHistoryIds.clear();
|
|
@@ -1022,71 +962,6 @@ export class AgUiThreadRuntimeCore {
|
|
|
1022
962
|
this.notifyUpdate();
|
|
1023
963
|
}
|
|
1024
964
|
|
|
1025
|
-
private applyExternalMessageRepository(
|
|
1026
|
-
loaded: ExportedMessageRepository,
|
|
1027
|
-
): void {
|
|
1028
|
-
const headId = loaded.headId ?? loaded.messages.at(-1)?.message.id ?? null;
|
|
1029
|
-
const ids = new Set<string>();
|
|
1030
|
-
let degenerate = false;
|
|
1031
|
-
for (const { message } of loaded.messages) {
|
|
1032
|
-
if (ids.has(message.id)) {
|
|
1033
|
-
degenerate = true;
|
|
1034
|
-
break;
|
|
1035
|
-
}
|
|
1036
|
-
ids.add(message.id);
|
|
1037
|
-
}
|
|
1038
|
-
if (headId !== null && !ids.has(headId)) degenerate = true;
|
|
1039
|
-
|
|
1040
|
-
if (!degenerate) {
|
|
1041
|
-
this.clearRepository();
|
|
1042
|
-
let pending = [...loaded.messages];
|
|
1043
|
-
const importedIds = new Set<string>();
|
|
1044
|
-
|
|
1045
|
-
while (pending.length > 0) {
|
|
1046
|
-
const unresolved: typeof pending = [];
|
|
1047
|
-
let progressed = false;
|
|
1048
|
-
for (const item of pending) {
|
|
1049
|
-
if (item.parentId !== null && !importedIds.has(item.parentId)) {
|
|
1050
|
-
unresolved.push(item);
|
|
1051
|
-
continue;
|
|
1052
|
-
}
|
|
1053
|
-
this.addOrUpdateMessage(item.parentId, item.message);
|
|
1054
|
-
importedIds.add(item.message.id);
|
|
1055
|
-
progressed = true;
|
|
1056
|
-
}
|
|
1057
|
-
if (!progressed) {
|
|
1058
|
-
degenerate = true;
|
|
1059
|
-
break;
|
|
1060
|
-
}
|
|
1061
|
-
pending = unresolved;
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
if (degenerate) {
|
|
1066
|
-
this.clearRepository();
|
|
1067
|
-
let previousId: string | null = null;
|
|
1068
|
-
for (const { message } of loaded.messages) {
|
|
1069
|
-
const existing = this.tryGetMessage(message.id);
|
|
1070
|
-
this.addOrUpdateMessage(
|
|
1071
|
-
existing ? existing.parentId : previousId,
|
|
1072
|
-
message,
|
|
1073
|
-
);
|
|
1074
|
-
previousId = message.id;
|
|
1075
|
-
}
|
|
1076
|
-
this.resetRepositoryHead(previousId);
|
|
1077
|
-
} else {
|
|
1078
|
-
this.resetRepositoryHead(headId);
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
this.assistantHistoryParents.clear();
|
|
1082
|
-
this.snapshotHistoryIds.clear();
|
|
1083
|
-
this.persistedHistoryIds.clear();
|
|
1084
|
-
for (const { message } of loaded.messages) {
|
|
1085
|
-
this.persistedHistoryIds.add(message.id);
|
|
1086
|
-
}
|
|
1087
|
-
this.notifyUpdate();
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
965
|
loadExternalState(state: ReadonlyJSONValue): void {
|
|
1091
966
|
this.stateSnapshot = state;
|
|
1092
967
|
this.notifyUpdate();
|
|
@@ -1115,22 +990,23 @@ export class AgUiThreadRuntimeCore {
|
|
|
1115
990
|
): Promise<void> {
|
|
1116
991
|
const normalizedRunConfig = runConfig ?? {};
|
|
1117
992
|
this.lastRunConfig = normalizedRunConfig;
|
|
1118
|
-
const parent =
|
|
993
|
+
const parent =
|
|
994
|
+
parentId === null ? undefined : this.session.tryGetMessage(parentId);
|
|
1119
995
|
const shouldEagerlyInsertAssistant =
|
|
1120
996
|
parentId !== null &&
|
|
1121
997
|
parent !== undefined &&
|
|
1122
|
-
parentId !== this.
|
|
998
|
+
parentId !== this.session.headId;
|
|
1123
999
|
const historicalMessages = [
|
|
1124
1000
|
...(shouldEagerlyInsertAssistant
|
|
1125
|
-
? (this.tryGetMessages(parentId) ?? this.
|
|
1126
|
-
: this.
|
|
1001
|
+
? (this.session.tryGetMessages(parentId) ?? this.session.getMessages())
|
|
1002
|
+
: this.session.getMessages()),
|
|
1127
1003
|
];
|
|
1128
1004
|
const runStartMessageIds = new Set(
|
|
1129
1005
|
this.getMessageRepository().messages.map(({ message }) => message.id),
|
|
1130
1006
|
);
|
|
1131
1007
|
|
|
1132
1008
|
this.pendingError = null;
|
|
1133
|
-
const assistantParentId = parent ? parentId : this.
|
|
1009
|
+
const assistantParentId = parent ? parentId : this.session.headId;
|
|
1134
1010
|
let assistantMessageId: string | undefined;
|
|
1135
1011
|
// A snapshot the preserve gate declines still evicts the in-flight
|
|
1136
1012
|
// assistant; recreating under the cached id on the next content-bearing
|
|
@@ -1142,11 +1018,12 @@ export class AgUiThreadRuntimeCore {
|
|
|
1142
1018
|
let assistantCollided = false;
|
|
1143
1019
|
const ensureAssistant = (allowRecreate = false): string => {
|
|
1144
1020
|
const cached = assistantMessageId;
|
|
1145
|
-
if (cached !== undefined && this.tryGetMessage(cached))
|
|
1021
|
+
if (cached !== undefined && this.session.tryGetMessage(cached))
|
|
1022
|
+
return cached;
|
|
1146
1023
|
if (cached !== undefined && (assistantCollided || !allowRecreate)) {
|
|
1147
1024
|
return cached;
|
|
1148
1025
|
}
|
|
1149
|
-
const repositoryHeadId = this.
|
|
1026
|
+
const repositoryHeadId = this.session.headId;
|
|
1150
1027
|
const shouldUseSelectedParent =
|
|
1151
1028
|
cached === undefined ||
|
|
1152
1029
|
(shouldEagerlyInsertAssistant &&
|
|
@@ -1157,7 +1034,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1157
1034
|
const parentId =
|
|
1158
1035
|
shouldUseSelectedParent &&
|
|
1159
1036
|
assistantParentId &&
|
|
1160
|
-
this.hasMessage(assistantParentId)
|
|
1037
|
+
this.session.hasMessage(assistantParentId)
|
|
1161
1038
|
? assistantParentId
|
|
1162
1039
|
: repositoryHeadId;
|
|
1163
1040
|
const created = this.insertAssistantPlaceholder(parentId, cached);
|
|
@@ -1181,32 +1058,51 @@ export class AgUiThreadRuntimeCore {
|
|
|
1181
1058
|
}
|
|
1182
1059
|
};
|
|
1183
1060
|
|
|
1061
|
+
const adoptServerMessageId = (
|
|
1062
|
+
serverId: string,
|
|
1063
|
+
startNewMessage: boolean,
|
|
1064
|
+
) => {
|
|
1065
|
+
if (startNewMessage && assistantMessageId !== undefined) {
|
|
1066
|
+
applyUpdate({ status: { type: "complete", reason: "unknown" } });
|
|
1067
|
+
const previousId = assistantMessageId;
|
|
1068
|
+
assistantMessageId = undefined;
|
|
1069
|
+
if (this.session.tryGetMessage(previousId)) {
|
|
1070
|
+
const created = this.insertAssistantPlaceholder(previousId);
|
|
1071
|
+
assistantMessageId = created;
|
|
1072
|
+
this.markPendingAssistantHistory(created, previousId);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
const placeholder = ensureAssistant(true);
|
|
1076
|
+
if (placeholder === serverId) return;
|
|
1077
|
+
const reassigned = this.reassignAssistantId(placeholder, serverId);
|
|
1078
|
+
// A collision drops the placeholder before revealing the existing
|
|
1079
|
+
// server message as the current head. Only messages introduced during
|
|
1080
|
+
// this run can replace the placeholder; regeneration must not rewrite
|
|
1081
|
+
// a previous branch when the server incorrectly reuses its id.
|
|
1082
|
+
const adoptsVisibleCollision =
|
|
1083
|
+
!reassigned &&
|
|
1084
|
+
!runStartMessageIds.has(serverId) &&
|
|
1085
|
+
this.session.headId === serverId;
|
|
1086
|
+
if (reassigned || adoptsVisibleCollision) {
|
|
1087
|
+
assistantMessageId = serverId;
|
|
1088
|
+
if (adoptsVisibleCollision) {
|
|
1089
|
+
const parentId =
|
|
1090
|
+
this.session.tryGetMessage(serverId)?.parentId ?? null;
|
|
1091
|
+
this.markPendingAssistantHistory(serverId, parentId);
|
|
1092
|
+
}
|
|
1093
|
+
} else {
|
|
1094
|
+
assistantCollided = true;
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1184
1098
|
const aggregator = new RunAggregator({
|
|
1185
1099
|
showThinking: this.showThinking,
|
|
1186
1100
|
logger: this.logger,
|
|
1187
1101
|
emit: applyUpdate,
|
|
1188
1102
|
onServerMessageId: (serverId) => {
|
|
1189
|
-
|
|
1190
|
-
if (placeholder === serverId) return;
|
|
1191
|
-
const reassigned = this.reassignAssistantId(placeholder, serverId);
|
|
1192
|
-
// A collision drops the placeholder before revealing the existing
|
|
1193
|
-
// server message as the current head. Only messages introduced during
|
|
1194
|
-
// this run can replace the placeholder; regeneration must not rewrite
|
|
1195
|
-
// a previous branch when the server incorrectly reuses its id.
|
|
1196
|
-
const adoptsVisibleCollision =
|
|
1197
|
-
!reassigned &&
|
|
1198
|
-
!runStartMessageIds.has(serverId) &&
|
|
1199
|
-
this.repository.headId === serverId;
|
|
1200
|
-
if (reassigned || adoptsVisibleCollision) {
|
|
1201
|
-
assistantMessageId = serverId;
|
|
1202
|
-
if (adoptsVisibleCollision) {
|
|
1203
|
-
const parentId = this.tryGetMessage(serverId)?.parentId ?? null;
|
|
1204
|
-
this.markPendingAssistantHistory(serverId, parentId);
|
|
1205
|
-
}
|
|
1206
|
-
} else {
|
|
1207
|
-
assistantCollided = true;
|
|
1208
|
-
}
|
|
1103
|
+
adoptServerMessageId(serverId, false);
|
|
1209
1104
|
},
|
|
1105
|
+
onTextMessageStart: (serverId) => adoptServerMessageId(serverId, true),
|
|
1210
1106
|
});
|
|
1211
1107
|
const dispatch = (event: AgUiEvent) =>
|
|
1212
1108
|
this.handleEvent(aggregator, event, assistantMessageId);
|
|
@@ -1319,7 +1215,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1319
1215
|
return;
|
|
1320
1216
|
}
|
|
1321
1217
|
this.maybeAutoCancelPendingToolCalls();
|
|
1322
|
-
const parentId = this.
|
|
1218
|
+
const parentId = this.session.headId;
|
|
1323
1219
|
if (parentId !== null) {
|
|
1324
1220
|
this.startResumeRun(parentId);
|
|
1325
1221
|
} else {
|
|
@@ -1361,7 +1257,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1361
1257
|
unstable_threadId: ctx.threadId,
|
|
1362
1258
|
unstable_parentId: ctx.parentId,
|
|
1363
1259
|
unstable_getMessage: () => {
|
|
1364
|
-
const message = this.tryGetMessage(currentId())?.message;
|
|
1260
|
+
const message = this.session.tryGetMessage(currentId())?.message;
|
|
1365
1261
|
if (!message) {
|
|
1366
1262
|
throw new Error(
|
|
1367
1263
|
"[agui] resume stream requested the assistant message before it existed",
|
|
@@ -1388,7 +1284,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1388
1284
|
}
|
|
1389
1285
|
|
|
1390
1286
|
if (ctx.abortSignal.aborted) return;
|
|
1391
|
-
const current = this.tryGetMessage(currentId())?.message;
|
|
1287
|
+
const current = this.session.tryGetMessage(currentId())?.message;
|
|
1392
1288
|
if (!current || current.status?.type === "running") {
|
|
1393
1289
|
ctx.applyUpdate({ status: { type: "complete", reason: "unknown" } });
|
|
1394
1290
|
}
|
|
@@ -1402,7 +1298,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1402
1298
|
) {
|
|
1403
1299
|
const threadId = this.agent.threadId || "main";
|
|
1404
1300
|
const messages = toAgUiMessages(
|
|
1405
|
-
historyMessages ?? this.
|
|
1301
|
+
historyMessages ?? this.session.getMessages(),
|
|
1406
1302
|
);
|
|
1407
1303
|
const context = this.runtime?.thread.getModelContext();
|
|
1408
1304
|
const input = {
|
|
@@ -1428,27 +1324,6 @@ export class AgUiThreadRuntimeCore {
|
|
|
1428
1324
|
return input;
|
|
1429
1325
|
}
|
|
1430
1326
|
|
|
1431
|
-
private installResumeShim(): void {
|
|
1432
|
-
const agent = this.agent as any;
|
|
1433
|
-
if (agent[symbolResumeShim]) return;
|
|
1434
|
-
agent[symbolResumeShim] = true;
|
|
1435
|
-
const onInstance = Object.hasOwn(agent, "prepareRunAgentInput");
|
|
1436
|
-
const original = onInstance
|
|
1437
|
-
? agent.prepareRunAgentInput
|
|
1438
|
-
: Object.getPrototypeOf(agent)?.prepareRunAgentInput;
|
|
1439
|
-
if (typeof original !== "function") return;
|
|
1440
|
-
agent.prepareRunAgentInput = function (
|
|
1441
|
-
this: unknown,
|
|
1442
|
-
params: { resume?: unknown } | undefined,
|
|
1443
|
-
) {
|
|
1444
|
-
const input = original.call(this, params);
|
|
1445
|
-
if (params?.resume !== undefined && input && typeof input === "object") {
|
|
1446
|
-
return { ...(input as object), resume: params.resume };
|
|
1447
|
-
}
|
|
1448
|
-
return input;
|
|
1449
|
-
};
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
1327
|
private setRunning(running: boolean) {
|
|
1453
1328
|
this.isRunningFlag = running;
|
|
1454
1329
|
this.notifyUpdate();
|
|
@@ -1481,34 +1356,34 @@ export class AgUiThreadRuntimeCore {
|
|
|
1481
1356
|
custom: {},
|
|
1482
1357
|
},
|
|
1483
1358
|
};
|
|
1484
|
-
this.addOrUpdateMessage(parentId ?? this.
|
|
1485
|
-
this.switchToBranch(id);
|
|
1359
|
+
this.session.addOrUpdateMessage(parentId ?? this.session.headId, assistant);
|
|
1360
|
+
this.session.switchToBranch(id);
|
|
1486
1361
|
this.notifyUpdate();
|
|
1487
1362
|
return id;
|
|
1488
1363
|
}
|
|
1489
1364
|
|
|
1490
1365
|
private reassignAssistantId(oldId: string, newId: string): boolean {
|
|
1491
1366
|
if (oldId === newId) return true;
|
|
1492
|
-
const oldItem = this.tryGetMessage(oldId);
|
|
1367
|
+
const oldItem = this.session.tryGetMessage(oldId);
|
|
1493
1368
|
if (!oldItem) return false;
|
|
1494
1369
|
|
|
1495
|
-
const collidesWithExisting = this.hasMessage(newId);
|
|
1370
|
+
const collidesWithExisting = this.session.hasMessage(newId);
|
|
1496
1371
|
|
|
1497
1372
|
if (collidesWithExisting) {
|
|
1498
1373
|
this.logger.debug?.(
|
|
1499
1374
|
"[agui] reassignAssistantId: server id already present in messages or repository, dropping placeholder",
|
|
1500
1375
|
{ oldId, newId },
|
|
1501
1376
|
);
|
|
1502
|
-
this.deleteMessage(oldId, oldItem.parentId ?? null);
|
|
1377
|
+
this.session.deleteMessage(oldId, oldItem.parentId ?? null);
|
|
1503
1378
|
} else {
|
|
1504
1379
|
const { isOptimistic: _, ...metadata } = oldItem.message.metadata;
|
|
1505
|
-
this.addOrUpdateMessage(oldItem.parentId, {
|
|
1380
|
+
this.session.addOrUpdateMessage(oldItem.parentId, {
|
|
1506
1381
|
...oldItem.message,
|
|
1507
1382
|
id: newId,
|
|
1508
1383
|
metadata,
|
|
1509
1384
|
} as ThreadMessage);
|
|
1510
|
-
this.switchToBranch(newId);
|
|
1511
|
-
this.tryDeleteMessage(oldId);
|
|
1385
|
+
this.session.switchToBranch(newId);
|
|
1386
|
+
this.session.tryDeleteMessage(oldId);
|
|
1512
1387
|
}
|
|
1513
1388
|
|
|
1514
1389
|
const pendingParent = this.assistantHistoryParents.get(oldId);
|
|
@@ -1558,7 +1433,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1558
1433
|
update: ChatModelRunResult,
|
|
1559
1434
|
): string {
|
|
1560
1435
|
let latestStatus: MessageStatus | undefined;
|
|
1561
|
-
const touched = this.updateMessage(messageId, (message) => {
|
|
1436
|
+
const touched = this.session.updateMessage(messageId, (message) => {
|
|
1562
1437
|
if (message.role !== "assistant") return message;
|
|
1563
1438
|
const assistant = message as ThreadAssistantMessage;
|
|
1564
1439
|
const metadata = update.metadata
|
|
@@ -1607,19 +1482,17 @@ export class AgUiThreadRuntimeCore {
|
|
|
1607
1482
|
next: ThreadAssistantMessage["content"],
|
|
1608
1483
|
): ThreadAssistantMessage["content"] {
|
|
1609
1484
|
const resolved = new Map<string, ToolCallMessagePart>();
|
|
1610
|
-
for (const part of previous) {
|
|
1611
|
-
if (
|
|
1485
|
+
for (const part of iterateToolCallParts(previous)) {
|
|
1486
|
+
if (isResolvedToolCall(part)) {
|
|
1612
1487
|
resolved.set(part.toolCallId, part);
|
|
1613
1488
|
}
|
|
1614
1489
|
}
|
|
1615
1490
|
if (resolved.size === 0) return next;
|
|
1616
1491
|
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
if (part.type !== "tool-call" || isResolvedToolCall(part)) return part;
|
|
1492
|
+
const { content: merged, changed } = mapToolCallPartsDeep(next, (part) => {
|
|
1493
|
+
if (isResolvedToolCall(part)) return part;
|
|
1620
1494
|
const prior = resolved.get(part.toolCallId);
|
|
1621
1495
|
if (!prior) return part;
|
|
1622
|
-
changed = true;
|
|
1623
1496
|
return {
|
|
1624
1497
|
...part,
|
|
1625
1498
|
result: prior.result,
|
|
@@ -1627,7 +1500,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1627
1500
|
...(prior.isError !== undefined ? { isError: prior.isError } : {}),
|
|
1628
1501
|
};
|
|
1629
1502
|
});
|
|
1630
|
-
return changed ?
|
|
1503
|
+
return changed ? merged : next;
|
|
1631
1504
|
}
|
|
1632
1505
|
|
|
1633
1506
|
private mergeAssistantMetadata(
|
|
@@ -1727,14 +1600,13 @@ export class AgUiThreadRuntimeCore {
|
|
|
1727
1600
|
messageId: string,
|
|
1728
1601
|
event: Extract<AgUiEvent, { type: "TOOL_CALL_RESULT" }>,
|
|
1729
1602
|
): void {
|
|
1730
|
-
const updated = this.updateMessage(messageId, (message) => {
|
|
1603
|
+
const updated = this.session.updateMessage(messageId, (message) => {
|
|
1731
1604
|
if (message.role !== "assistant") return message;
|
|
1732
1605
|
const assistant = message as ThreadAssistantMessage;
|
|
1733
1606
|
const mcpAppUri = readMcpAppResourceUri(event.mcpResult?._meta);
|
|
1734
1607
|
let matchedToolCall = false;
|
|
1735
|
-
const content = assistant.content
|
|
1736
|
-
if (part.
|
|
1737
|
-
return part;
|
|
1608
|
+
const { content } = mapToolCallPartsDeep(assistant.content, (part) => {
|
|
1609
|
+
if (part.toolCallId !== event.toolCallId) return part;
|
|
1738
1610
|
matchedToolCall = true;
|
|
1739
1611
|
// An applied activity snapshot owns part.result; a later result only
|
|
1740
1612
|
// fills what is missing, mirroring the aggregator's finishToolCall.
|
|
@@ -1814,13 +1686,12 @@ export class AgUiThreadRuntimeCore {
|
|
|
1814
1686
|
? serverHash
|
|
1815
1687
|
: undefined;
|
|
1816
1688
|
const result = event.content["result"];
|
|
1817
|
-
const updated = this.updateMessage(messageId, (message) => {
|
|
1689
|
+
const updated = this.session.updateMessage(messageId, (message) => {
|
|
1818
1690
|
if (message.role !== "assistant") return message;
|
|
1819
1691
|
const assistant = message as ThreadAssistantMessage;
|
|
1820
1692
|
let matchedToolCall = false;
|
|
1821
|
-
const content = assistant.content
|
|
1822
|
-
if (part.
|
|
1823
|
-
return part;
|
|
1693
|
+
const { content } = mapToolCallPartsDeep(assistant.content, (part) => {
|
|
1694
|
+
if (part.toolCallId !== toolCallId) return part;
|
|
1824
1695
|
matchedToolCall = true;
|
|
1825
1696
|
return {
|
|
1826
1697
|
...part,
|
|
@@ -1866,7 +1737,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1866
1737
|
) {
|
|
1867
1738
|
try {
|
|
1868
1739
|
const activeMessage = activeAssistantId
|
|
1869
|
-
? this.tryGetMessage(activeAssistantId)?.message
|
|
1740
|
+
? this.session.tryGetMessage(activeAssistantId)?.message
|
|
1870
1741
|
: undefined;
|
|
1871
1742
|
const activeAssistant =
|
|
1872
1743
|
activeMessage?.role === "assistant" ? activeMessage : undefined;
|
|
@@ -1899,7 +1770,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1899
1770
|
}
|
|
1900
1771
|
this.applyExternalMessages(converted);
|
|
1901
1772
|
if (activeAssistant !== undefined) {
|
|
1902
|
-
const activeItem = this.tryGetMessage(activeAssistant.id);
|
|
1773
|
+
const activeItem = this.session.tryGetMessage(activeAssistant.id);
|
|
1903
1774
|
if (activeItem) {
|
|
1904
1775
|
if (preservesActiveAssistant) {
|
|
1905
1776
|
this.snapshotHistoryIds.delete(activeAssistant.id);
|
|
@@ -1951,7 +1822,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1951
1822
|
if (!history) return;
|
|
1952
1823
|
const parentId = this.assistantHistoryParents.get(messageId);
|
|
1953
1824
|
if (parentId === undefined) return;
|
|
1954
|
-
const message = this.tryGetMessage(messageId)?.message;
|
|
1825
|
+
const message = this.session.tryGetMessage(messageId)?.message;
|
|
1955
1826
|
if (!message || message.role !== "assistant") return;
|
|
1956
1827
|
if (!this.isPersistableStatus(message.status)) return;
|
|
1957
1828
|
const wasPersisted = this.persistedHistoryIds.has(messageId);
|