@assistant-ui/react-ag-ui 0.0.58 → 0.0.60
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/agUiExtras.d.ts +1 -2
- package/dist/agUiExtras.d.ts.map +1 -1
- package/dist/hooks.d.ts +6 -7
- package/dist/hooks.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts +17 -6
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtime/AgUiThreadRuntimeCore.js +95 -47
- package/dist/runtime/AgUiThreadRuntimeCore.js.map +1 -1
- package/dist/runtime/adapter/conversions.d.ts +11 -11
- package/dist/runtime/adapter/conversions.d.ts.map +1 -1
- package/dist/runtime/adapter/conversions.js +114 -17
- package/dist/runtime/adapter/conversions.js.map +1 -1
- package/dist/runtime/adapter/run-aggregator.d.ts +9 -10
- package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
- package/dist/runtime/adapter/run-aggregator.js +59 -0
- package/dist/runtime/adapter/run-aggregator.js.map +1 -1
- package/dist/runtime/adapter/subscriber.d.ts +2 -3
- package/dist/runtime/adapter/subscriber.d.ts.map +1 -1
- package/dist/runtime/adapter/tool-approval.d.ts +4 -5
- package/dist/runtime/adapter/tool-approval.d.ts.map +1 -1
- package/dist/runtime/event-parser.d.ts +2 -3
- package/dist/runtime/event-parser.d.ts.map +1 -1
- package/dist/runtime/interrupt-internals.d.ts +3 -4
- package/dist/runtime/interrupt-internals.d.ts.map +1 -1
- package/dist/runtime/logger.d.ts +2 -3
- package/dist/runtime/logger.d.ts.map +1 -1
- package/dist/runtime/mcp-tool-result.d.ts +3 -4
- package/dist/runtime/mcp-tool-result.d.ts.map +1 -1
- package/dist/runtime/types.d.ts +35 -11
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/useAgUiRuntime.d.ts +2 -3
- package/dist/useAgUiRuntime.d.ts.map +1 -1
- package/dist/useAgUiRuntime.js +14 -5
- package/dist/useAgUiRuntime.js.map +1 -1
- package/package.json +12 -11
- package/src/index.ts +1 -0
- package/src/runtime/AgUiThreadRuntimeCore.test.ts +18 -0
- package/src/runtime/AgUiThreadRuntimeCore.ts +176 -63
- package/src/runtime/adapter/conversions.ts +200 -26
- package/src/runtime/adapter/run-aggregator.ts +85 -0
- package/src/runtime/types.ts +26 -0
- package/src/useAgUiRuntime.queue.test.tsx +54 -0
- package/src/useAgUiRuntime.thread-switch.test.tsx +199 -0
- package/src/useAgUiRuntime.ts +14 -3
|
@@ -35,7 +35,12 @@ import type {
|
|
|
35
35
|
import jsonpatch, { type Operation } from "fast-json-patch";
|
|
36
36
|
import type { Logger } from "./logger";
|
|
37
37
|
import { readMcpAppResourceUri } from "./mcp-tool-result";
|
|
38
|
-
import type {
|
|
38
|
+
import type {
|
|
39
|
+
AgUiEvent,
|
|
40
|
+
AgUiInterrupt,
|
|
41
|
+
AgUiResumeEntry,
|
|
42
|
+
AgUiResumeTranscript,
|
|
43
|
+
} from "./types";
|
|
39
44
|
import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
40
45
|
import {
|
|
41
46
|
AG_UI_METADATA_NAMESPACE,
|
|
@@ -87,10 +92,21 @@ type ResumeRunConfig = {
|
|
|
87
92
|
stream?: ResumeStream;
|
|
88
93
|
};
|
|
89
94
|
|
|
95
|
+
/**
|
|
96
|
+
* A resume submission, pairing the entries that answer the open interrupts with
|
|
97
|
+
* the assistant message they were opened on. That message anchors the resume
|
|
98
|
+
* transcript: under `resumeTranscript: "appended"` only what follows it is sent.
|
|
99
|
+
*/
|
|
100
|
+
type ResumeDispatch = {
|
|
101
|
+
entries: AgUiResumeEntry[];
|
|
102
|
+
interruptedMessageId: string;
|
|
103
|
+
};
|
|
104
|
+
|
|
90
105
|
type CoreOptions = {
|
|
91
106
|
agent: AbstractAgent;
|
|
92
107
|
logger: Logger;
|
|
93
108
|
showThinking: boolean;
|
|
109
|
+
resumeTranscript?: AgUiResumeTranscript | undefined;
|
|
94
110
|
autoCancelPendingToolCalls?: boolean | undefined;
|
|
95
111
|
onError?: (error: Error) => void;
|
|
96
112
|
onCancel?: () => void;
|
|
@@ -135,6 +151,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
135
151
|
private agent: AbstractAgent;
|
|
136
152
|
private logger: Logger;
|
|
137
153
|
private showThinking: boolean;
|
|
154
|
+
private resumeTranscript: AgUiResumeTranscript;
|
|
138
155
|
private autoCancelPendingToolCalls: boolean | undefined;
|
|
139
156
|
private onError: ((error: Error) => void) | undefined;
|
|
140
157
|
private onCancel: (() => void) | undefined;
|
|
@@ -164,7 +181,6 @@ export class AgUiThreadRuntimeCore {
|
|
|
164
181
|
// mid-run, and cancelling has to reach the agent holding the live request.
|
|
165
182
|
private activeRunAgent: AbstractAgent | null = null;
|
|
166
183
|
private stateSnapshot: ReadonlyJSONValue | undefined;
|
|
167
|
-
private pendingError: Error | null = null;
|
|
168
184
|
private history: ThreadHistoryAdapter | undefined;
|
|
169
185
|
private lastRunConfig: RunConfig | undefined;
|
|
170
186
|
private readonly assistantHistoryParents = new Map<string, string | null>();
|
|
@@ -174,14 +190,16 @@ export class AgUiThreadRuntimeCore {
|
|
|
174
190
|
private _isLoading = false;
|
|
175
191
|
private _loadPromise: Promise<void> | undefined;
|
|
176
192
|
private _loadRequested = false;
|
|
177
|
-
private
|
|
178
|
-
|
|
193
|
+
private pendingResume: { owner: AbortController; messageId: string } | null =
|
|
194
|
+
null;
|
|
195
|
+
private pendingA2uiResumeOwner: AbortController | null = null;
|
|
179
196
|
private pendingA2uiAction: Record<string, unknown> | undefined;
|
|
180
197
|
|
|
181
198
|
constructor(options: CoreOptions) {
|
|
182
199
|
this.agent = options.agent;
|
|
183
200
|
this.logger = options.logger;
|
|
184
201
|
this.showThinking = options.showThinking;
|
|
202
|
+
this.resumeTranscript = options.resumeTranscript ?? "full";
|
|
185
203
|
this.autoCancelPendingToolCalls = options.autoCancelPendingToolCalls;
|
|
186
204
|
this.onError = options.onError;
|
|
187
205
|
this.onCancel = options.onCancel;
|
|
@@ -193,6 +211,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
193
211
|
this.agent = options.agent;
|
|
194
212
|
this.logger = options.logger;
|
|
195
213
|
this.showThinking = options.showThinking;
|
|
214
|
+
this.resumeTranscript = options.resumeTranscript ?? "full";
|
|
196
215
|
this.autoCancelPendingToolCalls = options.autoCancelPendingToolCalls;
|
|
197
216
|
this.onError = options.onError;
|
|
198
217
|
this.onCancel = options.onCancel;
|
|
@@ -296,12 +315,17 @@ export class AgUiThreadRuntimeCore {
|
|
|
296
315
|
|
|
297
316
|
async append(message: AppendMessage): Promise<void> {
|
|
298
317
|
const startRun = message.startRun ?? message.role === "user";
|
|
318
|
+
let ownsThread = true;
|
|
299
319
|
if (startRun) {
|
|
300
320
|
this.assertNoPendingInterrupts();
|
|
301
321
|
this.maybeAutoCancelPendingToolCalls();
|
|
322
|
+
// Before the message is linked: callers parent it on the in-flight
|
|
323
|
+
// assistant, and settling that assistant reassigns its optimistic id,
|
|
324
|
+
// which would evict it and reparent this message onto a branch.
|
|
325
|
+
ownsThread = this.supersedeActiveRun();
|
|
302
326
|
}
|
|
303
327
|
const threadMessageId = this.appendEntry(message);
|
|
304
|
-
if (!startRun) return;
|
|
328
|
+
if (!startRun || !ownsThread) return;
|
|
305
329
|
await this.startRun(threadMessageId, message.runConfig);
|
|
306
330
|
}
|
|
307
331
|
|
|
@@ -344,17 +368,44 @@ export class AgUiThreadRuntimeCore {
|
|
|
344
368
|
}
|
|
345
369
|
|
|
346
370
|
async cancel(): Promise<void> {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
371
|
+
this.abortActiveRun();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// abortRun is what stops an upstream agent: AbstractAgent.runAgent declares
|
|
375
|
+
// two parameters and HttpAgent binds its request to a controller of its own,
|
|
376
|
+
// so the run options object reaches only subclasses that read it. The local
|
|
377
|
+
// abort comes last and is unconditional: its listener runs onCancel
|
|
378
|
+
// synchronously, so a callback that starts another run must keep the
|
|
379
|
+
// controller it installed, and a throw from a subclass's abortRun would
|
|
380
|
+
// otherwise strand the thread as running.
|
|
381
|
+
private abortActiveRun(): void {
|
|
382
|
+
const controller = this.abortController;
|
|
383
|
+
if (!controller) return;
|
|
353
384
|
try {
|
|
354
385
|
(this.activeRunAgent ?? this.agent).abortRun();
|
|
355
386
|
} finally {
|
|
356
|
-
|
|
387
|
+
controller.abort();
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Starting a run supersedes the active one, and reports whether the caller
|
|
392
|
+
// still owns the thread afterwards: the local abort runs onCancel
|
|
393
|
+
// synchronously, so a callback that starts its own run keeps it. abortRun is a
|
|
394
|
+
// subclass's code, and a throw there must not abandon the run being started,
|
|
395
|
+
// unlike cancel(), where the failed operation is the caller's own. Calling
|
|
396
|
+
// this twice for one run is harmless, because the second call finds no active
|
|
397
|
+
// run to supersede.
|
|
398
|
+
private supersedeActiveRun(): boolean {
|
|
399
|
+
try {
|
|
400
|
+
this.abortActiveRun();
|
|
401
|
+
} catch (error) {
|
|
402
|
+
this.logger.error?.("[agui] agent abortRun failed", error);
|
|
357
403
|
}
|
|
404
|
+
if (this.abortController === null) return true;
|
|
405
|
+
this.logger.debug?.(
|
|
406
|
+
"[agui] onCancel started a replacement run; dropping the superseding send",
|
|
407
|
+
);
|
|
408
|
+
return false;
|
|
358
409
|
}
|
|
359
410
|
|
|
360
411
|
async resume(config: ResumeRunConfig): Promise<void> {
|
|
@@ -490,7 +541,10 @@ export class AgUiThreadRuntimeCore {
|
|
|
490
541
|
resume: AgUiResumeEntry[],
|
|
491
542
|
): Promise<void> {
|
|
492
543
|
this.clearPendingInterrupts(messageId, resume);
|
|
493
|
-
await this.startRun(messageId, this.lastRunConfig,
|
|
544
|
+
await this.startRun(messageId, this.lastRunConfig, {
|
|
545
|
+
entries: resume,
|
|
546
|
+
interruptedMessageId: messageId,
|
|
547
|
+
});
|
|
494
548
|
}
|
|
495
549
|
|
|
496
550
|
/**
|
|
@@ -649,7 +703,10 @@ export class AgUiThreadRuntimeCore {
|
|
|
649
703
|
const normalized = this.toAppendMessage(message);
|
|
650
704
|
this.clearPendingInterrupts(pending.messageId, resume);
|
|
651
705
|
const threadMessageId = this.appendEntry(normalized);
|
|
652
|
-
await this.startRun(threadMessageId, normalized.runConfig,
|
|
706
|
+
await this.startRun(threadMessageId, normalized.runConfig, {
|
|
707
|
+
entries: resume,
|
|
708
|
+
interruptedMessageId: pending.messageId,
|
|
709
|
+
});
|
|
653
710
|
}
|
|
654
711
|
|
|
655
712
|
private resolveSteerAwayResume(
|
|
@@ -718,7 +775,8 @@ export class AgUiThreadRuntimeCore {
|
|
|
718
775
|
}
|
|
719
776
|
return {
|
|
720
777
|
createdAt: message.createdAt ?? new Date(),
|
|
721
|
-
parentId:
|
|
778
|
+
parentId:
|
|
779
|
+
message.parentId === undefined ? this.session.headId : message.parentId,
|
|
722
780
|
sourceId: message.sourceId ?? null,
|
|
723
781
|
role: message.role ?? "user",
|
|
724
782
|
content: message.content,
|
|
@@ -813,11 +871,17 @@ export class AgUiThreadRuntimeCore {
|
|
|
813
871
|
const { content } = mapToolCallPartsDeep(assistant.content, (part) => {
|
|
814
872
|
if (part.toolCallId !== options.toolCallId) return part;
|
|
815
873
|
matchedToolCall = true;
|
|
874
|
+
// artifact and modelContent are optional; only override when supplied
|
|
875
|
+
// so a later result that omits them keeps a stored value, and
|
|
876
|
+
// emitToolResult can forward the model-facing content to the agent.
|
|
816
877
|
return {
|
|
817
878
|
...part,
|
|
818
879
|
result: options.result,
|
|
819
|
-
artifact: options.artifact,
|
|
820
880
|
isError: options.isError,
|
|
881
|
+
...(options.artifact !== undefined && { artifact: options.artifact }),
|
|
882
|
+
...(options.modelContent !== undefined && {
|
|
883
|
+
modelContent: options.modelContent,
|
|
884
|
+
}),
|
|
821
885
|
};
|
|
822
886
|
});
|
|
823
887
|
if (!matchedToolCall) return message;
|
|
@@ -847,8 +911,9 @@ export class AgUiThreadRuntimeCore {
|
|
|
847
911
|
}
|
|
848
912
|
this.pendingA2uiAction = userAction;
|
|
849
913
|
|
|
850
|
-
|
|
851
|
-
|
|
914
|
+
const owner = this.abortController;
|
|
915
|
+
if (owner) {
|
|
916
|
+
this.pendingA2uiResumeOwner = owner;
|
|
852
917
|
return;
|
|
853
918
|
}
|
|
854
919
|
this.startResumeRun(parentId);
|
|
@@ -860,10 +925,11 @@ export class AgUiThreadRuntimeCore {
|
|
|
860
925
|
private maybeResumeAfterToolResults(messageId: string): void {
|
|
861
926
|
if (!this.maybeCompleteAfterToolResults(messageId)) return;
|
|
862
927
|
|
|
863
|
-
|
|
928
|
+
const owner = this.abortController;
|
|
929
|
+
if (owner) {
|
|
864
930
|
// A run is still draining (RUN_FINISHED arrived but the stream has not
|
|
865
931
|
// closed). Defer until startRun's tail so we never start two runs.
|
|
866
|
-
this.
|
|
932
|
+
this.pendingResume = { owner, messageId };
|
|
867
933
|
return;
|
|
868
934
|
}
|
|
869
935
|
this.startResumeRun(messageId);
|
|
@@ -913,7 +979,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
913
979
|
}
|
|
914
980
|
|
|
915
981
|
applyExternalMessages(messages: readonly ThreadMessage[]): void {
|
|
916
|
-
this.
|
|
982
|
+
this.pendingA2uiResumeOwner = null;
|
|
917
983
|
this.pendingA2uiAction = undefined;
|
|
918
984
|
this.assistantHistoryParents.clear();
|
|
919
985
|
|
|
@@ -959,6 +1025,17 @@ export class AgUiThreadRuntimeCore {
|
|
|
959
1025
|
for (const { message } of this.getMessageRepository().messages) {
|
|
960
1026
|
this.snapshotHistoryIds.add(message.id);
|
|
961
1027
|
}
|
|
1028
|
+
// MESSAGES_SNAPSHOT re-appends the active assistant, so a parked
|
|
1029
|
+
// continuation whose target survived the snapshot is still answerable.
|
|
1030
|
+
// Membership is the head branch, not the repository: a soft merge leaves
|
|
1031
|
+
// the messages it dropped behind as off-branch nodes.
|
|
1032
|
+
const resumeTarget = this.pendingResume?.messageId;
|
|
1033
|
+
if (
|
|
1034
|
+
resumeTarget !== undefined &&
|
|
1035
|
+
!this.session.getMessages().some((message) => message.id === resumeTarget)
|
|
1036
|
+
) {
|
|
1037
|
+
this.pendingResume = null;
|
|
1038
|
+
}
|
|
962
1039
|
this.notifyUpdate();
|
|
963
1040
|
}
|
|
964
1041
|
|
|
@@ -985,9 +1062,14 @@ export class AgUiThreadRuntimeCore {
|
|
|
985
1062
|
private async startRun(
|
|
986
1063
|
parentId: string | null,
|
|
987
1064
|
runConfig?: RunConfig,
|
|
988
|
-
resume?:
|
|
1065
|
+
resume?: ResumeDispatch,
|
|
989
1066
|
resumeStream?: ResumeStream,
|
|
990
1067
|
): Promise<void> {
|
|
1068
|
+
// A default AG-UI run supersedes the active run; the hook's opt-in message
|
|
1069
|
+
// queue serializes sends instead. append supersedes earlier, before it links
|
|
1070
|
+
// its message; this covers the entry points that start a run without one.
|
|
1071
|
+
if (!this.supersedeActiveRun()) return;
|
|
1072
|
+
|
|
991
1073
|
const normalizedRunConfig = runConfig ?? {};
|
|
992
1074
|
this.lastRunConfig = normalizedRunConfig;
|
|
993
1075
|
const parent =
|
|
@@ -1005,7 +1087,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1005
1087
|
this.getMessageRepository().messages.map(({ message }) => message.id),
|
|
1006
1088
|
);
|
|
1007
1089
|
|
|
1008
|
-
|
|
1090
|
+
let pendingError: Error | null = null;
|
|
1009
1091
|
const assistantParentId = parent ? parentId : this.session.headId;
|
|
1010
1092
|
let assistantMessageId: string | undefined;
|
|
1011
1093
|
// A snapshot the preserve gate declines still evicts the in-flight
|
|
@@ -1131,16 +1213,17 @@ export class AgUiThreadRuntimeCore {
|
|
|
1131
1213
|
// Cancel flips only the status; an aggregator RUN_CANCELLED would emit an empty snapshot and wipe the replayed content.
|
|
1132
1214
|
cancelRun = () =>
|
|
1133
1215
|
applyUpdate({ status: { type: "incomplete", reason: "cancelled" } });
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1216
|
+
pendingError =
|
|
1217
|
+
(await this.consumeResumeStream(resumeStream, {
|
|
1218
|
+
runConfig: normalizedRunConfig,
|
|
1219
|
+
threadId: this.agent.threadId || "main",
|
|
1220
|
+
parentId: assistantParentId,
|
|
1221
|
+
historicalMessages,
|
|
1222
|
+
abortSignal,
|
|
1223
|
+
ensureAssistant,
|
|
1224
|
+
applyUpdate,
|
|
1225
|
+
getAssistantMessageId: () => assistantMessageId,
|
|
1226
|
+
})) ?? null;
|
|
1144
1227
|
} else {
|
|
1145
1228
|
const runId = generateId();
|
|
1146
1229
|
aggregator.handle({ type: "RUN_STARTED", runId });
|
|
@@ -1156,7 +1239,7 @@ export class AgUiThreadRuntimeCore {
|
|
|
1156
1239
|
logger: this.logger,
|
|
1157
1240
|
onRunFailed: (error) => {
|
|
1158
1241
|
if (abortSignal.aborted) return;
|
|
1159
|
-
|
|
1242
|
+
pendingError = error;
|
|
1160
1243
|
invokeRuntimeCallback("onError", this.onError, error);
|
|
1161
1244
|
},
|
|
1162
1245
|
});
|
|
@@ -1176,36 +1259,31 @@ export class AgUiThreadRuntimeCore {
|
|
|
1176
1259
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
1177
1260
|
dispatch({ type: "RUN_ERROR", message: err.message });
|
|
1178
1261
|
invokeRuntimeCallback("onError", this.onError, err);
|
|
1179
|
-
|
|
1262
|
+
pendingError ??= err;
|
|
1180
1263
|
}
|
|
1181
1264
|
} finally {
|
|
1182
1265
|
this.finishRun(abortController);
|
|
1183
1266
|
}
|
|
1184
1267
|
|
|
1185
|
-
if (
|
|
1186
|
-
const err =
|
|
1268
|
+
if (pendingError) {
|
|
1269
|
+
const err = pendingError;
|
|
1187
1270
|
this.reportedErrors.add(err);
|
|
1188
|
-
this.
|
|
1189
|
-
this.pendingResumeMessageId = null;
|
|
1190
|
-
this.pendingA2uiResume = false;
|
|
1191
|
-
this.pendingA2uiAction = undefined;
|
|
1271
|
+
this.clearDeferredContinuations(abortController);
|
|
1192
1272
|
throw err;
|
|
1193
1273
|
}
|
|
1194
1274
|
|
|
1195
1275
|
// A tool result that landed before the run settled deferred its
|
|
1196
1276
|
// continuation here so a second run never overlaps the first.
|
|
1197
|
-
if (this.
|
|
1198
|
-
const
|
|
1199
|
-
this.
|
|
1277
|
+
if (this.pendingResume?.owner === abortController) {
|
|
1278
|
+
const { messageId } = this.pendingResume;
|
|
1279
|
+
this.pendingResume = null;
|
|
1200
1280
|
if (!abortSignal.aborted) {
|
|
1201
|
-
this.startResumeRun(
|
|
1202
|
-
} else {
|
|
1203
|
-
this.pendingA2uiAction = undefined;
|
|
1281
|
+
this.startResumeRun(messageId);
|
|
1204
1282
|
}
|
|
1205
1283
|
}
|
|
1206
1284
|
|
|
1207
|
-
if (this.
|
|
1208
|
-
this.
|
|
1285
|
+
if (this.pendingA2uiResumeOwner === abortController) {
|
|
1286
|
+
this.pendingA2uiResumeOwner = null;
|
|
1209
1287
|
if (!abortSignal.aborted && this.pendingA2uiAction !== undefined) {
|
|
1210
1288
|
if (this.getPendingInterrupts()) {
|
|
1211
1289
|
this.pendingA2uiAction = undefined;
|
|
@@ -1243,9 +1321,9 @@ export class AgUiThreadRuntimeCore {
|
|
|
1243
1321
|
applyUpdate: (update: ChatModelRunResult) => void;
|
|
1244
1322
|
getAssistantMessageId: () => string | undefined;
|
|
1245
1323
|
},
|
|
1246
|
-
): Promise<
|
|
1324
|
+
): Promise<Error | undefined> {
|
|
1247
1325
|
this.pendingA2uiAction = undefined;
|
|
1248
|
-
this.
|
|
1326
|
+
this.pendingA2uiResumeOwner = null;
|
|
1249
1327
|
const assistantId = ctx.ensureAssistant();
|
|
1250
1328
|
const currentId = () => ctx.getAssistantMessageId() ?? assistantId;
|
|
1251
1329
|
const options: ChatModelRunOptions = {
|
|
@@ -1269,36 +1347,39 @@ export class AgUiThreadRuntimeCore {
|
|
|
1269
1347
|
|
|
1270
1348
|
try {
|
|
1271
1349
|
for await (const result of stream(options)) {
|
|
1272
|
-
if (ctx.abortSignal.aborted) return;
|
|
1350
|
+
if (ctx.abortSignal.aborted) return undefined;
|
|
1273
1351
|
ctx.applyUpdate(result);
|
|
1274
1352
|
}
|
|
1275
1353
|
} catch (error) {
|
|
1276
|
-
if (ctx.abortSignal.aborted) return;
|
|
1354
|
+
if (ctx.abortSignal.aborted) return undefined;
|
|
1277
1355
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
1278
1356
|
ctx.applyUpdate({
|
|
1279
1357
|
status: { type: "incomplete", reason: "error", error: err.message },
|
|
1280
1358
|
});
|
|
1281
1359
|
invokeRuntimeCallback("onError", this.onError, err);
|
|
1282
|
-
|
|
1283
|
-
return;
|
|
1360
|
+
return err;
|
|
1284
1361
|
}
|
|
1285
1362
|
|
|
1286
|
-
if (ctx.abortSignal.aborted) return;
|
|
1363
|
+
if (ctx.abortSignal.aborted) return undefined;
|
|
1287
1364
|
const current = this.session.tryGetMessage(currentId())?.message;
|
|
1288
1365
|
if (!current || current.status?.type === "running") {
|
|
1289
1366
|
ctx.applyUpdate({ status: { type: "complete", reason: "unknown" } });
|
|
1290
1367
|
}
|
|
1368
|
+
return undefined;
|
|
1291
1369
|
}
|
|
1292
1370
|
|
|
1293
1371
|
private buildRunInput(
|
|
1294
1372
|
runId: string,
|
|
1295
1373
|
runConfig: RunConfig | undefined,
|
|
1296
1374
|
historyMessages: readonly ThreadMessage[] | undefined,
|
|
1297
|
-
resume?:
|
|
1375
|
+
resume?: ResumeDispatch,
|
|
1298
1376
|
) {
|
|
1299
1377
|
const threadId = this.agent.threadId || "main";
|
|
1300
1378
|
const messages = toAgUiMessages(
|
|
1301
|
-
|
|
1379
|
+
this.selectRunMessages(
|
|
1380
|
+
historyMessages ?? this.session.getMessages(),
|
|
1381
|
+
resume,
|
|
1382
|
+
),
|
|
1302
1383
|
);
|
|
1303
1384
|
const context = this.runtime?.thread.getModelContext();
|
|
1304
1385
|
const input = {
|
|
@@ -1318,22 +1399,51 @@ export class AgUiThreadRuntimeCore {
|
|
|
1318
1399
|
? { a2uiAction: { userAction: this.pendingA2uiAction } }
|
|
1319
1400
|
: {}),
|
|
1320
1401
|
},
|
|
1321
|
-
...(resume !== undefined ? { resume } : {}),
|
|
1402
|
+
...(resume !== undefined ? { resume: resume.entries } : {}),
|
|
1322
1403
|
};
|
|
1323
1404
|
this.pendingA2uiAction = undefined;
|
|
1324
1405
|
return input;
|
|
1325
1406
|
}
|
|
1326
1407
|
|
|
1408
|
+
/**
|
|
1409
|
+
* An anchor the thread no longer carries falls back to the whole transcript.
|
|
1410
|
+
* The anchor is missing exactly when a snapshot import replaced the messages
|
|
1411
|
+
* this resume was raised against, and a host that expects a delta tolerates
|
|
1412
|
+
* being told more than it knows, while one rebuilding the run from the
|
|
1413
|
+
* transcript cannot recover from being told less.
|
|
1414
|
+
*/
|
|
1415
|
+
private selectRunMessages(
|
|
1416
|
+
messages: readonly ThreadMessage[],
|
|
1417
|
+
resume: ResumeDispatch | undefined,
|
|
1418
|
+
): readonly ThreadMessage[] {
|
|
1419
|
+
if (resume === undefined || this.resumeTranscript !== "appended")
|
|
1420
|
+
return messages;
|
|
1421
|
+
const anchor = messages.findIndex(
|
|
1422
|
+
(message) => message.id === resume.interruptedMessageId,
|
|
1423
|
+
);
|
|
1424
|
+
if (anchor === -1) return messages;
|
|
1425
|
+
return messages.slice(anchor + 1);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
private clearDeferredContinuations(owner: AbortController): void {
|
|
1429
|
+
if (this.pendingResume?.owner === owner) {
|
|
1430
|
+
this.pendingResume = null;
|
|
1431
|
+
}
|
|
1432
|
+
if (this.pendingA2uiResumeOwner === owner) {
|
|
1433
|
+
this.pendingA2uiResumeOwner = null;
|
|
1434
|
+
this.pendingA2uiAction = undefined;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1327
1438
|
private setRunning(running: boolean) {
|
|
1328
1439
|
this.isRunningFlag = running;
|
|
1329
1440
|
this.notifyUpdate();
|
|
1330
1441
|
}
|
|
1331
1442
|
|
|
1332
1443
|
private finishRun(controller: AbortController | null) {
|
|
1333
|
-
if (this.abortController
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
}
|
|
1444
|
+
if (this.abortController !== controller) return;
|
|
1445
|
+
this.abortController = null;
|
|
1446
|
+
this.activeRunAgent = null;
|
|
1337
1447
|
this.setRunning(false);
|
|
1338
1448
|
}
|
|
1339
1449
|
|
|
@@ -1498,6 +1608,9 @@ export class AgUiThreadRuntimeCore {
|
|
|
1498
1608
|
result: prior.result,
|
|
1499
1609
|
...(prior.artifact !== undefined ? { artifact: prior.artifact } : {}),
|
|
1500
1610
|
...(prior.isError !== undefined ? { isError: prior.isError } : {}),
|
|
1611
|
+
...(prior.modelContent !== undefined
|
|
1612
|
+
? { modelContent: prior.modelContent }
|
|
1613
|
+
: {}),
|
|
1501
1614
|
};
|
|
1502
1615
|
});
|
|
1503
1616
|
return changed ? merged : next;
|