@assistant-ui/react-ag-ui 0.0.56 → 0.0.57
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/runtime/AgUiThreadRuntimeCore.d.ts +2 -13
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtime/AgUiThreadRuntimeCore.js +106 -193
- package/dist/runtime/AgUiThreadRuntimeCore.js.map +1 -1
- package/dist/runtime/adapter/conversions.d.ts.map +1 -1
- package/dist/runtime/adapter/conversions.js +20 -14
- package/dist/runtime/adapter/conversions.js.map +1 -1
- package/dist/runtime/adapter/run-aggregator.d.ts +18 -1
- package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
- package/dist/runtime/adapter/run-aggregator.js +130 -41
- package/dist/runtime/adapter/run-aggregator.js.map +1 -1
- package/dist/useAgUiRuntime.js +11 -9
- package/dist/useAgUiRuntime.js.map +1 -1
- package/package.json +8 -8
- package/src/runtime/AgUiThreadRuntimeCore.test.ts +105 -0
- package/src/runtime/AgUiThreadRuntimeCore.ts +152 -261
- package/src/runtime/adapter/conversions.ts +41 -19
- package/src/runtime/adapter/run-aggregator.ts +198 -47
- package/src/useAgUiRuntime.history.test.tsx +76 -0
- package/src/useAgUiRuntime.ts +11 -9
|
@@ -20,7 +20,10 @@ import type {
|
|
|
20
20
|
ThreadMessage,
|
|
21
21
|
ToolCallMessagePart,
|
|
22
22
|
} from "@assistant-ui/core";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
createMessageRepositorySession,
|
|
25
|
+
invokeUserCallback,
|
|
26
|
+
} from "@assistant-ui/core/internal";
|
|
24
27
|
import type {
|
|
25
28
|
AbstractAgent,
|
|
26
29
|
AgentSubscriber,
|
|
@@ -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;
|
|
@@ -198,8 +197,19 @@ export class AgUiThreadRuntimeCore {
|
|
|
198
197
|
this.autoCancelPendingToolCalls = options.autoCancelPendingToolCalls;
|
|
199
198
|
this.onError = options.onError;
|
|
200
199
|
this.onCancel = options.onCancel;
|
|
200
|
+
const previousHistory = this.history;
|
|
201
201
|
this.history = options.history;
|
|
202
202
|
this.installResumeShim();
|
|
203
|
+
|
|
204
|
+
if (
|
|
205
|
+
this._loadRequested &&
|
|
206
|
+
!this._loadPromise &&
|
|
207
|
+
!previousHistory &&
|
|
208
|
+
options.history &&
|
|
209
|
+
this.session.getMessages().length === 0
|
|
210
|
+
) {
|
|
211
|
+
void this.__internal_load();
|
|
212
|
+
}
|
|
203
213
|
}
|
|
204
214
|
|
|
205
215
|
attachRuntime(runtime: AssistantRuntime) {
|
|
@@ -214,97 +224,11 @@ export class AgUiThreadRuntimeCore {
|
|
|
214
224
|
}
|
|
215
225
|
|
|
216
226
|
getMessages(): readonly ThreadMessage[] {
|
|
217
|
-
return this.
|
|
227
|
+
return this.session.getMessages();
|
|
218
228
|
}
|
|
219
229
|
|
|
220
230
|
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;
|
|
231
|
+
return this.session.export();
|
|
308
232
|
}
|
|
309
233
|
|
|
310
234
|
getState(): ReadonlyJSONValue | undefined {
|
|
@@ -320,9 +244,11 @@ export class AgUiThreadRuntimeCore {
|
|
|
320
244
|
}
|
|
321
245
|
|
|
322
246
|
__internal_load(): Promise<void> {
|
|
247
|
+
this._loadRequested = true;
|
|
323
248
|
if (this._loadPromise) return this._loadPromise;
|
|
249
|
+
if (!this.history) return Promise.resolve();
|
|
324
250
|
|
|
325
|
-
const promise = this.history
|
|
251
|
+
const promise = this.history.load();
|
|
326
252
|
|
|
327
253
|
this._isLoading = true;
|
|
328
254
|
|
|
@@ -330,14 +256,21 @@ export class AgUiThreadRuntimeCore {
|
|
|
330
256
|
.then(async (repo) => {
|
|
331
257
|
if (!repo) return;
|
|
332
258
|
|
|
333
|
-
this.applyExternalMessageRepository(repo);
|
|
259
|
+
this.session.applyExternalMessageRepository(repo);
|
|
260
|
+
this.assistantHistoryParents.clear();
|
|
261
|
+
this.snapshotHistoryIds.clear();
|
|
262
|
+
this.persistedHistoryIds.clear();
|
|
263
|
+
for (const { message } of repo.messages) {
|
|
264
|
+
this.persistedHistoryIds.add(message.id);
|
|
265
|
+
}
|
|
266
|
+
this.notifyUpdate();
|
|
334
267
|
|
|
335
268
|
if (repo.state !== undefined) {
|
|
336
269
|
this.loadExternalState(repo.state);
|
|
337
270
|
}
|
|
338
271
|
|
|
339
272
|
if (repo.unstable_resume) {
|
|
340
|
-
const parentId = repo.headId ?? this.
|
|
273
|
+
const parentId = repo.headId ?? this.session.headId;
|
|
341
274
|
const resumeStream = this.history?.resume?.bind(this.history);
|
|
342
275
|
await this.startRun(
|
|
343
276
|
parentId,
|
|
@@ -384,17 +317,17 @@ export class AgUiThreadRuntimeCore {
|
|
|
384
317
|
}
|
|
385
318
|
|
|
386
319
|
private appendEntry(message: AppendMessage): string {
|
|
387
|
-
if (message.sourceId) this.tryDeleteMessage(message.sourceId);
|
|
320
|
+
if (message.sourceId) this.session.tryDeleteMessage(message.sourceId);
|
|
388
321
|
|
|
389
322
|
const threadMessage = this.toThreadMessage(message);
|
|
390
323
|
const parentId =
|
|
391
324
|
message.parentId === null
|
|
392
325
|
? null
|
|
393
|
-
: message.parentId && this.hasMessage(message.parentId)
|
|
326
|
+
: message.parentId && this.session.hasMessage(message.parentId)
|
|
394
327
|
? message.parentId
|
|
395
|
-
: this.
|
|
396
|
-
this.addOrUpdateMessage(parentId, threadMessage);
|
|
397
|
-
this.switchToBranch(threadMessage.id);
|
|
328
|
+
: this.session.headId;
|
|
329
|
+
this.session.addOrUpdateMessage(parentId, threadMessage);
|
|
330
|
+
this.session.switchToBranch(threadMessage.id);
|
|
398
331
|
this.notifyUpdate();
|
|
399
332
|
this.recordHistoryEntry(parentId, threadMessage);
|
|
400
333
|
return threadMessage.id;
|
|
@@ -619,9 +552,8 @@ export class AgUiThreadRuntimeCore {
|
|
|
619
552
|
|
|
620
553
|
// Bound against the message the gates landed on, so this check claims a
|
|
621
554
|
// batch only where the projection did.
|
|
622
|
-
const gatedMessage = this.tryGetMessage(pending.messageId)
|
|
623
|
-
|
|
|
624
|
-
| undefined;
|
|
555
|
+
const gatedMessage = this.session.tryGetMessage(pending.messageId)
|
|
556
|
+
?.message as ThreadAssistantMessage | undefined;
|
|
625
557
|
const gated = projectAgUiToolApprovals(
|
|
626
558
|
pending.interrupts,
|
|
627
559
|
new Set(
|
|
@@ -647,13 +579,16 @@ export class AgUiThreadRuntimeCore {
|
|
|
647
579
|
pending.interrupts,
|
|
648
580
|
);
|
|
649
581
|
|
|
650
|
-
const recorded = this.updateMessage(
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
582
|
+
const recorded = this.session.updateMessage(
|
|
583
|
+
pending.messageId,
|
|
584
|
+
(message) => {
|
|
585
|
+
if (message.role !== "assistant") return message;
|
|
586
|
+
const assistant = message as ThreadAssistantMessage;
|
|
587
|
+
const content = withToolApprovalDecision(assistant.content, options);
|
|
588
|
+
if (content === assistant.content) return assistant;
|
|
589
|
+
return { ...assistant, content };
|
|
590
|
+
},
|
|
591
|
+
);
|
|
657
592
|
if (!recorded) {
|
|
658
593
|
throw new Error(
|
|
659
594
|
`[agui] respondToToolApproval: approval "${options.approvalId}" is already decided`,
|
|
@@ -661,7 +596,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
661
596
|
}
|
|
662
597
|
this.notifyUpdate();
|
|
663
598
|
|
|
664
|
-
const assistant = this.tryGetMessage(pending.messageId)?.message as
|
|
599
|
+
const assistant = this.session.tryGetMessage(pending.messageId)?.message as
|
|
665
600
|
| ThreadAssistantMessage
|
|
666
601
|
| undefined;
|
|
667
602
|
if (!assistant) return;
|
|
@@ -776,7 +711,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
776
711
|
if (typeof message === "string") {
|
|
777
712
|
return {
|
|
778
713
|
createdAt: new Date(),
|
|
779
|
-
parentId: this.
|
|
714
|
+
parentId: this.session.headId,
|
|
780
715
|
sourceId: null,
|
|
781
716
|
runConfig: {},
|
|
782
717
|
role: "user",
|
|
@@ -787,7 +722,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
787
722
|
}
|
|
788
723
|
return {
|
|
789
724
|
createdAt: message.createdAt ?? new Date(),
|
|
790
|
-
parentId: message.parentId ?? this.
|
|
725
|
+
parentId: message.parentId ?? this.session.headId,
|
|
791
726
|
sourceId: message.sourceId ?? null,
|
|
792
727
|
role: message.role ?? "user",
|
|
793
728
|
content: message.content,
|
|
@@ -802,7 +737,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
802
737
|
messageId: string,
|
|
803
738
|
resume: readonly AgUiResumeEntry[],
|
|
804
739
|
): void {
|
|
805
|
-
const touched = this.updateMessage(messageId, (message) => {
|
|
740
|
+
const touched = this.session.updateMessage(messageId, (message) => {
|
|
806
741
|
if (message.role !== "assistant") return message;
|
|
807
742
|
const assistant = message as ThreadAssistantMessage;
|
|
808
743
|
if (
|
|
@@ -852,7 +787,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
852
787
|
}
|
|
853
788
|
|
|
854
789
|
private cancelUnresolvedToolCalls(messageId: string): void {
|
|
855
|
-
const updated = this.updateMessage(messageId, (message) => {
|
|
790
|
+
const updated = this.session.updateMessage(messageId, (message) => {
|
|
856
791
|
if (message.role !== "assistant") return message;
|
|
857
792
|
const assistant = message as ThreadAssistantMessage;
|
|
858
793
|
const content = assistant.content.map((part) => {
|
|
@@ -869,7 +804,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
869
804
|
}
|
|
870
805
|
|
|
871
806
|
addToolResult(options: AddToolResultOptions): void {
|
|
872
|
-
const updated = this.updateMessage(options.messageId, (message) => {
|
|
807
|
+
const updated = this.session.updateMessage(options.messageId, (message) => {
|
|
873
808
|
if (message.role !== "assistant") return message;
|
|
874
809
|
const assistant = message as ThreadAssistantMessage;
|
|
875
810
|
let matchedToolCall = false;
|
|
@@ -896,7 +831,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
896
831
|
sendA2uiAction(action: Record<string, unknown>): void {
|
|
897
832
|
this.assertNoPendingInterrupts();
|
|
898
833
|
this.maybeAutoCancelPendingToolCalls();
|
|
899
|
-
const parentId = this.
|
|
834
|
+
const parentId = this.session.headId;
|
|
900
835
|
if (parentId === null) {
|
|
901
836
|
this.logger.debug(
|
|
902
837
|
"[agui] sendA2uiAction: no messages to resume, dropping action",
|
|
@@ -934,7 +869,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
934
869
|
}
|
|
935
870
|
|
|
936
871
|
private maybeCompleteAfterToolResults(messageId: string): boolean {
|
|
937
|
-
const message = this.tryGetMessage(messageId)?.message;
|
|
872
|
+
const message = this.session.tryGetMessage(messageId)?.message;
|
|
938
873
|
if (!message || message.role !== "assistant") return false;
|
|
939
874
|
const assistant = message as ThreadAssistantMessage;
|
|
940
875
|
if (
|
|
@@ -948,7 +883,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
948
883
|
);
|
|
949
884
|
if (!allResolved) return false;
|
|
950
885
|
|
|
951
|
-
const updated = this.updateMessage(messageId, (current) =>
|
|
886
|
+
const updated = this.session.updateMessage(messageId, (current) =>
|
|
952
887
|
current.role === "assistant"
|
|
953
888
|
? {
|
|
954
889
|
...(current as ThreadAssistantMessage),
|
|
@@ -978,7 +913,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
978
913
|
this.assistantHistoryParents.clear();
|
|
979
914
|
|
|
980
915
|
if (messages.length === 0) {
|
|
981
|
-
this.
|
|
916
|
+
this.session.clear();
|
|
982
917
|
} else {
|
|
983
918
|
let expectedParentId: string | null = null;
|
|
984
919
|
let lastAppliedId: string | null = null;
|
|
@@ -988,31 +923,31 @@ export class AgUiThreadRuntimeCore {
|
|
|
988
923
|
for (const message of messages) {
|
|
989
924
|
if (seen.has(message.id)) continue;
|
|
990
925
|
seen.add(message.id);
|
|
991
|
-
const existing = this.tryGetMessage(message.id);
|
|
926
|
+
const existing = this.session.tryGetMessage(message.id);
|
|
992
927
|
if (existing && existing.parentId !== expectedParentId) {
|
|
993
928
|
hardReplace = true;
|
|
994
929
|
break;
|
|
995
930
|
}
|
|
996
|
-
this.addOrUpdateMessage(expectedParentId, message);
|
|
931
|
+
this.session.addOrUpdateMessage(expectedParentId, message);
|
|
997
932
|
expectedParentId = message.id;
|
|
998
933
|
lastAppliedId = message.id;
|
|
999
934
|
}
|
|
1000
935
|
|
|
1001
936
|
if (hardReplace) {
|
|
1002
|
-
this.
|
|
937
|
+
this.session.clear();
|
|
1003
938
|
expectedParentId = null;
|
|
1004
939
|
lastAppliedId = null;
|
|
1005
940
|
seen.clear();
|
|
1006
941
|
for (const message of messages) {
|
|
1007
942
|
if (seen.has(message.id)) continue;
|
|
1008
943
|
seen.add(message.id);
|
|
1009
|
-
this.addOrUpdateMessage(expectedParentId, message);
|
|
944
|
+
this.session.addOrUpdateMessage(expectedParentId, message);
|
|
1010
945
|
expectedParentId = message.id;
|
|
1011
946
|
lastAppliedId = message.id;
|
|
1012
947
|
}
|
|
1013
948
|
}
|
|
1014
949
|
|
|
1015
|
-
this.
|
|
950
|
+
this.session.resetHead(lastAppliedId);
|
|
1016
951
|
}
|
|
1017
952
|
|
|
1018
953
|
this.snapshotHistoryIds.clear();
|
|
@@ -1022,71 +957,6 @@ export class AgUiThreadRuntimeCore {
|
|
|
1022
957
|
this.notifyUpdate();
|
|
1023
958
|
}
|
|
1024
959
|
|
|
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
960
|
loadExternalState(state: ReadonlyJSONValue): void {
|
|
1091
961
|
this.stateSnapshot = state;
|
|
1092
962
|
this.notifyUpdate();
|
|
@@ -1115,22 +985,23 @@ export class AgUiThreadRuntimeCore {
|
|
|
1115
985
|
): Promise<void> {
|
|
1116
986
|
const normalizedRunConfig = runConfig ?? {};
|
|
1117
987
|
this.lastRunConfig = normalizedRunConfig;
|
|
1118
|
-
const parent =
|
|
988
|
+
const parent =
|
|
989
|
+
parentId === null ? undefined : this.session.tryGetMessage(parentId);
|
|
1119
990
|
const shouldEagerlyInsertAssistant =
|
|
1120
991
|
parentId !== null &&
|
|
1121
992
|
parent !== undefined &&
|
|
1122
|
-
parentId !== this.
|
|
993
|
+
parentId !== this.session.headId;
|
|
1123
994
|
const historicalMessages = [
|
|
1124
995
|
...(shouldEagerlyInsertAssistant
|
|
1125
|
-
? (this.tryGetMessages(parentId) ?? this.
|
|
1126
|
-
: this.
|
|
996
|
+
? (this.session.tryGetMessages(parentId) ?? this.session.getMessages())
|
|
997
|
+
: this.session.getMessages()),
|
|
1127
998
|
];
|
|
1128
999
|
const runStartMessageIds = new Set(
|
|
1129
1000
|
this.getMessageRepository().messages.map(({ message }) => message.id),
|
|
1130
1001
|
);
|
|
1131
1002
|
|
|
1132
1003
|
this.pendingError = null;
|
|
1133
|
-
const assistantParentId = parent ? parentId : this.
|
|
1004
|
+
const assistantParentId = parent ? parentId : this.session.headId;
|
|
1134
1005
|
let assistantMessageId: string | undefined;
|
|
1135
1006
|
// A snapshot the preserve gate declines still evicts the in-flight
|
|
1136
1007
|
// assistant; recreating under the cached id on the next content-bearing
|
|
@@ -1142,11 +1013,12 @@ export class AgUiThreadRuntimeCore {
|
|
|
1142
1013
|
let assistantCollided = false;
|
|
1143
1014
|
const ensureAssistant = (allowRecreate = false): string => {
|
|
1144
1015
|
const cached = assistantMessageId;
|
|
1145
|
-
if (cached !== undefined && this.tryGetMessage(cached))
|
|
1016
|
+
if (cached !== undefined && this.session.tryGetMessage(cached))
|
|
1017
|
+
return cached;
|
|
1146
1018
|
if (cached !== undefined && (assistantCollided || !allowRecreate)) {
|
|
1147
1019
|
return cached;
|
|
1148
1020
|
}
|
|
1149
|
-
const repositoryHeadId = this.
|
|
1021
|
+
const repositoryHeadId = this.session.headId;
|
|
1150
1022
|
const shouldUseSelectedParent =
|
|
1151
1023
|
cached === undefined ||
|
|
1152
1024
|
(shouldEagerlyInsertAssistant &&
|
|
@@ -1157,7 +1029,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1157
1029
|
const parentId =
|
|
1158
1030
|
shouldUseSelectedParent &&
|
|
1159
1031
|
assistantParentId &&
|
|
1160
|
-
this.hasMessage(assistantParentId)
|
|
1032
|
+
this.session.hasMessage(assistantParentId)
|
|
1161
1033
|
? assistantParentId
|
|
1162
1034
|
: repositoryHeadId;
|
|
1163
1035
|
const created = this.insertAssistantPlaceholder(parentId, cached);
|
|
@@ -1181,32 +1053,51 @@ export class AgUiThreadRuntimeCore {
|
|
|
1181
1053
|
}
|
|
1182
1054
|
};
|
|
1183
1055
|
|
|
1056
|
+
const adoptServerMessageId = (
|
|
1057
|
+
serverId: string,
|
|
1058
|
+
startNewMessage: boolean,
|
|
1059
|
+
) => {
|
|
1060
|
+
if (startNewMessage && assistantMessageId !== undefined) {
|
|
1061
|
+
applyUpdate({ status: { type: "complete", reason: "unknown" } });
|
|
1062
|
+
const previousId = assistantMessageId;
|
|
1063
|
+
assistantMessageId = undefined;
|
|
1064
|
+
if (this.session.tryGetMessage(previousId)) {
|
|
1065
|
+
const created = this.insertAssistantPlaceholder(previousId);
|
|
1066
|
+
assistantMessageId = created;
|
|
1067
|
+
this.markPendingAssistantHistory(created, previousId);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
const placeholder = ensureAssistant(true);
|
|
1071
|
+
if (placeholder === serverId) return;
|
|
1072
|
+
const reassigned = this.reassignAssistantId(placeholder, serverId);
|
|
1073
|
+
// A collision drops the placeholder before revealing the existing
|
|
1074
|
+
// server message as the current head. Only messages introduced during
|
|
1075
|
+
// this run can replace the placeholder; regeneration must not rewrite
|
|
1076
|
+
// a previous branch when the server incorrectly reuses its id.
|
|
1077
|
+
const adoptsVisibleCollision =
|
|
1078
|
+
!reassigned &&
|
|
1079
|
+
!runStartMessageIds.has(serverId) &&
|
|
1080
|
+
this.session.headId === serverId;
|
|
1081
|
+
if (reassigned || adoptsVisibleCollision) {
|
|
1082
|
+
assistantMessageId = serverId;
|
|
1083
|
+
if (adoptsVisibleCollision) {
|
|
1084
|
+
const parentId =
|
|
1085
|
+
this.session.tryGetMessage(serverId)?.parentId ?? null;
|
|
1086
|
+
this.markPendingAssistantHistory(serverId, parentId);
|
|
1087
|
+
}
|
|
1088
|
+
} else {
|
|
1089
|
+
assistantCollided = true;
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
|
|
1184
1093
|
const aggregator = new RunAggregator({
|
|
1185
1094
|
showThinking: this.showThinking,
|
|
1186
1095
|
logger: this.logger,
|
|
1187
1096
|
emit: applyUpdate,
|
|
1188
1097
|
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
|
-
}
|
|
1098
|
+
adoptServerMessageId(serverId, false);
|
|
1209
1099
|
},
|
|
1100
|
+
onTextMessageStart: (serverId) => adoptServerMessageId(serverId, true),
|
|
1210
1101
|
});
|
|
1211
1102
|
const dispatch = (event: AgUiEvent) =>
|
|
1212
1103
|
this.handleEvent(aggregator, event, assistantMessageId);
|
|
@@ -1319,7 +1210,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1319
1210
|
return;
|
|
1320
1211
|
}
|
|
1321
1212
|
this.maybeAutoCancelPendingToolCalls();
|
|
1322
|
-
const parentId = this.
|
|
1213
|
+
const parentId = this.session.headId;
|
|
1323
1214
|
if (parentId !== null) {
|
|
1324
1215
|
this.startResumeRun(parentId);
|
|
1325
1216
|
} else {
|
|
@@ -1361,7 +1252,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1361
1252
|
unstable_threadId: ctx.threadId,
|
|
1362
1253
|
unstable_parentId: ctx.parentId,
|
|
1363
1254
|
unstable_getMessage: () => {
|
|
1364
|
-
const message = this.tryGetMessage(currentId())?.message;
|
|
1255
|
+
const message = this.session.tryGetMessage(currentId())?.message;
|
|
1365
1256
|
if (!message) {
|
|
1366
1257
|
throw new Error(
|
|
1367
1258
|
"[agui] resume stream requested the assistant message before it existed",
|
|
@@ -1388,7 +1279,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1388
1279
|
}
|
|
1389
1280
|
|
|
1390
1281
|
if (ctx.abortSignal.aborted) return;
|
|
1391
|
-
const current = this.tryGetMessage(currentId())?.message;
|
|
1282
|
+
const current = this.session.tryGetMessage(currentId())?.message;
|
|
1392
1283
|
if (!current || current.status?.type === "running") {
|
|
1393
1284
|
ctx.applyUpdate({ status: { type: "complete", reason: "unknown" } });
|
|
1394
1285
|
}
|
|
@@ -1402,7 +1293,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1402
1293
|
) {
|
|
1403
1294
|
const threadId = this.agent.threadId || "main";
|
|
1404
1295
|
const messages = toAgUiMessages(
|
|
1405
|
-
historyMessages ?? this.
|
|
1296
|
+
historyMessages ?? this.session.getMessages(),
|
|
1406
1297
|
);
|
|
1407
1298
|
const context = this.runtime?.thread.getModelContext();
|
|
1408
1299
|
const input = {
|
|
@@ -1481,34 +1372,34 @@ export class AgUiThreadRuntimeCore {
|
|
|
1481
1372
|
custom: {},
|
|
1482
1373
|
},
|
|
1483
1374
|
};
|
|
1484
|
-
this.addOrUpdateMessage(parentId ?? this.
|
|
1485
|
-
this.switchToBranch(id);
|
|
1375
|
+
this.session.addOrUpdateMessage(parentId ?? this.session.headId, assistant);
|
|
1376
|
+
this.session.switchToBranch(id);
|
|
1486
1377
|
this.notifyUpdate();
|
|
1487
1378
|
return id;
|
|
1488
1379
|
}
|
|
1489
1380
|
|
|
1490
1381
|
private reassignAssistantId(oldId: string, newId: string): boolean {
|
|
1491
1382
|
if (oldId === newId) return true;
|
|
1492
|
-
const oldItem = this.tryGetMessage(oldId);
|
|
1383
|
+
const oldItem = this.session.tryGetMessage(oldId);
|
|
1493
1384
|
if (!oldItem) return false;
|
|
1494
1385
|
|
|
1495
|
-
const collidesWithExisting = this.hasMessage(newId);
|
|
1386
|
+
const collidesWithExisting = this.session.hasMessage(newId);
|
|
1496
1387
|
|
|
1497
1388
|
if (collidesWithExisting) {
|
|
1498
1389
|
this.logger.debug?.(
|
|
1499
1390
|
"[agui] reassignAssistantId: server id already present in messages or repository, dropping placeholder",
|
|
1500
1391
|
{ oldId, newId },
|
|
1501
1392
|
);
|
|
1502
|
-
this.deleteMessage(oldId, oldItem.parentId ?? null);
|
|
1393
|
+
this.session.deleteMessage(oldId, oldItem.parentId ?? null);
|
|
1503
1394
|
} else {
|
|
1504
1395
|
const { isOptimistic: _, ...metadata } = oldItem.message.metadata;
|
|
1505
|
-
this.addOrUpdateMessage(oldItem.parentId, {
|
|
1396
|
+
this.session.addOrUpdateMessage(oldItem.parentId, {
|
|
1506
1397
|
...oldItem.message,
|
|
1507
1398
|
id: newId,
|
|
1508
1399
|
metadata,
|
|
1509
1400
|
} as ThreadMessage);
|
|
1510
|
-
this.switchToBranch(newId);
|
|
1511
|
-
this.tryDeleteMessage(oldId);
|
|
1401
|
+
this.session.switchToBranch(newId);
|
|
1402
|
+
this.session.tryDeleteMessage(oldId);
|
|
1512
1403
|
}
|
|
1513
1404
|
|
|
1514
1405
|
const pendingParent = this.assistantHistoryParents.get(oldId);
|
|
@@ -1558,7 +1449,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1558
1449
|
update: ChatModelRunResult,
|
|
1559
1450
|
): string {
|
|
1560
1451
|
let latestStatus: MessageStatus | undefined;
|
|
1561
|
-
const touched = this.updateMessage(messageId, (message) => {
|
|
1452
|
+
const touched = this.session.updateMessage(messageId, (message) => {
|
|
1562
1453
|
if (message.role !== "assistant") return message;
|
|
1563
1454
|
const assistant = message as ThreadAssistantMessage;
|
|
1564
1455
|
const metadata = update.metadata
|
|
@@ -1727,7 +1618,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1727
1618
|
messageId: string,
|
|
1728
1619
|
event: Extract<AgUiEvent, { type: "TOOL_CALL_RESULT" }>,
|
|
1729
1620
|
): void {
|
|
1730
|
-
const updated = this.updateMessage(messageId, (message) => {
|
|
1621
|
+
const updated = this.session.updateMessage(messageId, (message) => {
|
|
1731
1622
|
if (message.role !== "assistant") return message;
|
|
1732
1623
|
const assistant = message as ThreadAssistantMessage;
|
|
1733
1624
|
const mcpAppUri = readMcpAppResourceUri(event.mcpResult?._meta);
|
|
@@ -1814,7 +1705,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1814
1705
|
? serverHash
|
|
1815
1706
|
: undefined;
|
|
1816
1707
|
const result = event.content["result"];
|
|
1817
|
-
const updated = this.updateMessage(messageId, (message) => {
|
|
1708
|
+
const updated = this.session.updateMessage(messageId, (message) => {
|
|
1818
1709
|
if (message.role !== "assistant") return message;
|
|
1819
1710
|
const assistant = message as ThreadAssistantMessage;
|
|
1820
1711
|
let matchedToolCall = false;
|
|
@@ -1866,7 +1757,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1866
1757
|
) {
|
|
1867
1758
|
try {
|
|
1868
1759
|
const activeMessage = activeAssistantId
|
|
1869
|
-
? this.tryGetMessage(activeAssistantId)?.message
|
|
1760
|
+
? this.session.tryGetMessage(activeAssistantId)?.message
|
|
1870
1761
|
: undefined;
|
|
1871
1762
|
const activeAssistant =
|
|
1872
1763
|
activeMessage?.role === "assistant" ? activeMessage : undefined;
|
|
@@ -1899,7 +1790,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1899
1790
|
}
|
|
1900
1791
|
this.applyExternalMessages(converted);
|
|
1901
1792
|
if (activeAssistant !== undefined) {
|
|
1902
|
-
const activeItem = this.tryGetMessage(activeAssistant.id);
|
|
1793
|
+
const activeItem = this.session.tryGetMessage(activeAssistant.id);
|
|
1903
1794
|
if (activeItem) {
|
|
1904
1795
|
if (preservesActiveAssistant) {
|
|
1905
1796
|
this.snapshotHistoryIds.delete(activeAssistant.id);
|
|
@@ -1951,7 +1842,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1951
1842
|
if (!history) return;
|
|
1952
1843
|
const parentId = this.assistantHistoryParents.get(messageId);
|
|
1953
1844
|
if (parentId === undefined) return;
|
|
1954
|
-
const message = this.tryGetMessage(messageId)?.message;
|
|
1845
|
+
const message = this.session.tryGetMessage(messageId)?.message;
|
|
1955
1846
|
if (!message || message.role !== "assistant") return;
|
|
1956
1847
|
if (!this.isPersistableStatus(message.status)) return;
|
|
1957
1848
|
const wasPersisted = this.persistedHistoryIds.has(messageId);
|