@agent-native/agentkit 0.2.4 → 0.2.5-nightly-20260917210345
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/client/client.d.ts +5 -0
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +491 -195
- package/dist/client/client.js.map +1 -1
- package/dist/client/state.d.ts +9 -0
- package/dist/client/state.d.ts.map +1 -1
- package/dist/client/state.js +245 -15
- package/dist/client/state.js.map +1 -1
- package/dist/protocol/index.d.ts +2 -0
- package/dist/protocol/index.d.ts.map +1 -1
- package/dist/protocol/index.js.map +1 -1
- package/dist/protocol/validation.d.ts.map +1 -1
- package/dist/protocol/validation.js +1 -0
- package/dist/protocol/validation.js.map +1 -1
- package/dist/react/components.d.ts +3 -1
- package/dist/react/components.d.ts.map +1 -1
- package/dist/react/components.js +71 -13
- package/dist/react/components.js.map +1 -1
- package/dist/react/context.d.ts +3 -0
- package/dist/react/context.d.ts.map +1 -1
- package/dist/react/context.js +3 -0
- package/dist/react/context.js.map +1 -1
- package/dist/react/styles.css +103 -0
- package/package.json +2 -2
package/dist/client/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AgentProtocolValidationError, AgentKitProtocolError, createRequestAbortedError, createAgentKitProtocolVersionOffer, parseAgentEvent, projectAgentCapabilities, resumeEntryFromApproval, } from "../protocol/index.js";
|
|
2
|
-
import { classifyAgentEvent, createAgentThreadState, reduceAgentEvent, } from "./state.js";
|
|
2
|
+
import { classifyAgentEvent, createAgentThreadState, reduceAgentEvent, settleRunProjection, } from "./state.js";
|
|
3
3
|
/**
|
|
4
4
|
* Creates the deterministic AgentKit controller used by every UI binding.
|
|
5
5
|
* Prefer this factory at application boundaries; the class remains public for
|
|
@@ -8,6 +8,72 @@ import { classifyAgentEvent, createAgentThreadState, reduceAgentEvent, } from ".
|
|
|
8
8
|
export function createAgentKitClient(options) {
|
|
9
9
|
return new AgentKitClient(options);
|
|
10
10
|
}
|
|
11
|
+
function isTerminalRunEvent(event) {
|
|
12
|
+
return (event.type === "run.completed" ||
|
|
13
|
+
event.type === "run.failed" ||
|
|
14
|
+
event.type === "run.cancelled" ||
|
|
15
|
+
(event.type === "run.status" &&
|
|
16
|
+
(event.status === "completed" ||
|
|
17
|
+
event.status === "failed" ||
|
|
18
|
+
event.status === "cancelled")));
|
|
19
|
+
}
|
|
20
|
+
function sameQueuedMessageIds(first, second) {
|
|
21
|
+
return (first.length === second.length &&
|
|
22
|
+
first.every((message, index) => message.id === second[index]?.id));
|
|
23
|
+
}
|
|
24
|
+
function mergeQueuedMessageOverride(override, durable) {
|
|
25
|
+
const expectedIds = new Set(override.messages.map((message) => message.id));
|
|
26
|
+
const durableVisible = durable.filter((message) => !override.removedIds.has(message.id));
|
|
27
|
+
const merged = [...override.messages];
|
|
28
|
+
for (let index = 0; index < durableVisible.length; index += 1) {
|
|
29
|
+
const message = durableVisible[index];
|
|
30
|
+
if (expectedIds.has(message.id))
|
|
31
|
+
continue;
|
|
32
|
+
let nextExpectedIndex = -1;
|
|
33
|
+
for (let nextIndex = index + 1; nextIndex < durableVisible.length; nextIndex += 1) {
|
|
34
|
+
const candidateIndex = merged.findIndex((current) => current.id === durableVisible[nextIndex]?.id);
|
|
35
|
+
if (candidateIndex >= 0) {
|
|
36
|
+
nextExpectedIndex = candidateIndex;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (nextExpectedIndex >= 0) {
|
|
41
|
+
merged.splice(nextExpectedIndex, 0, message);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
let previousExpectedIndex = -1;
|
|
45
|
+
for (let previousIndex = index - 1; previousIndex >= 0; previousIndex -= 1) {
|
|
46
|
+
const candidateIndex = merged.findIndex((current) => current.id === durableVisible[previousIndex]?.id);
|
|
47
|
+
if (candidateIndex >= 0) {
|
|
48
|
+
previousExpectedIndex = candidateIndex;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (previousExpectedIndex >= 0) {
|
|
53
|
+
merged.splice(previousExpectedIndex + 1, 0, message);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
merged.push(message);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return merged;
|
|
60
|
+
}
|
|
61
|
+
function mergeRuntimeRecordMap(loaded, baseline, current) {
|
|
62
|
+
if (current === baseline)
|
|
63
|
+
return loaded;
|
|
64
|
+
const merged = { ...loaded };
|
|
65
|
+
for (const id of Object.keys(baseline)) {
|
|
66
|
+
if (!Object.prototype.hasOwnProperty.call(current, id))
|
|
67
|
+
delete merged[id];
|
|
68
|
+
}
|
|
69
|
+
for (const [id, record] of Object.entries(current)) {
|
|
70
|
+
if (!Object.prototype.hasOwnProperty.call(baseline, id) ||
|
|
71
|
+
baseline[id] !== record) {
|
|
72
|
+
merged[id] = record;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return merged;
|
|
76
|
+
}
|
|
11
77
|
function defaultCreateId(prefix) {
|
|
12
78
|
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
13
79
|
return `${prefix}-${crypto.randomUUID()}`;
|
|
@@ -152,6 +218,8 @@ export class AgentKitClient {
|
|
|
152
218
|
consumerAbortControllers = new Map();
|
|
153
219
|
threadLoads = new Map();
|
|
154
220
|
threadLeaseCounts = new Map();
|
|
221
|
+
queuedMessageOverrides = new Map();
|
|
222
|
+
queueMutationChains = new Map();
|
|
155
223
|
queuePromotions = new Set();
|
|
156
224
|
requestAbortController = new AbortController();
|
|
157
225
|
capabilitiesLoad;
|
|
@@ -273,7 +341,10 @@ export class AgentKitClient {
|
|
|
273
341
|
if (existing) {
|
|
274
342
|
return this.invokeRequest(requestContext, () => existing);
|
|
275
343
|
}
|
|
276
|
-
|
|
344
|
+
// Durable projections can lag an accepted stream. Preserve the local
|
|
345
|
+
// cursor and event boundaries so refresh cannot reorder the transcript or
|
|
346
|
+
// turn the next streamed event into an artificial sequence gap.
|
|
347
|
+
const load = this.loadThreadProjection(threadId, requestContext, true);
|
|
277
348
|
this.threadLoads.set(threadId, load);
|
|
278
349
|
try {
|
|
279
350
|
return await load;
|
|
@@ -284,6 +355,17 @@ export class AgentKitClient {
|
|
|
284
355
|
}
|
|
285
356
|
}
|
|
286
357
|
}
|
|
358
|
+
enqueueQueueMutation(threadId, mutation) {
|
|
359
|
+
const previous = this.queueMutationChains.get(threadId) ?? Promise.resolve();
|
|
360
|
+
const next = previous.then(mutation);
|
|
361
|
+
const settled = next.then(() => undefined, () => undefined);
|
|
362
|
+
this.queueMutationChains.set(threadId, settled);
|
|
363
|
+
return next.finally(() => {
|
|
364
|
+
if (this.queueMutationChains.get(threadId) === settled) {
|
|
365
|
+
this.queueMutationChains.delete(threadId);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
}
|
|
287
369
|
async loadThreadProjection(threadId, requestContext, preserveRuntimeProjection = false) {
|
|
288
370
|
const baseline = this.snapshot.threads[threadId] ?? createAgentThreadState(threadId);
|
|
289
371
|
if (!this.snapshot.threads[threadId])
|
|
@@ -301,6 +383,20 @@ export class AgentKitClient {
|
|
|
301
383
|
: undefined;
|
|
302
384
|
this.assertActive();
|
|
303
385
|
const runSnapshots = new Map((snapshot?.runs ?? []).map((run) => [run.id, run]));
|
|
386
|
+
const durableQueuedMessages = snapshot?.queuedMessages;
|
|
387
|
+
const queuedOverride = this.queuedMessageOverrides.get(threadId);
|
|
388
|
+
const queuedOverrideMessages = queuedOverride
|
|
389
|
+
? durableQueuedMessages
|
|
390
|
+
? mergeQueuedMessageOverride(queuedOverride, durableQueuedMessages)
|
|
391
|
+
: queuedOverride.messages
|
|
392
|
+
: undefined;
|
|
393
|
+
if (queuedOverride &&
|
|
394
|
+
durableQueuedMessages &&
|
|
395
|
+
queuedOverrideMessages &&
|
|
396
|
+
sameQueuedMessageIds(queuedOverrideMessages, durableQueuedMessages) &&
|
|
397
|
+
queuedOverride.removedIds.size === 0) {
|
|
398
|
+
this.queuedMessageOverrides.delete(threadId);
|
|
399
|
+
}
|
|
304
400
|
const missingRunIds = (snapshot?.activeRunIds ?? []).filter((runId) => !runSnapshots.has(runId));
|
|
305
401
|
const getRun = this.transport.getRun;
|
|
306
402
|
const activeRuns = missingRunIds.length
|
|
@@ -351,26 +447,21 @@ export class AgentKitClient {
|
|
|
351
447
|
queuedMessages,
|
|
352
448
|
};
|
|
353
449
|
}
|
|
354
|
-
if (preserveRuntimeProjection) {
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
thread: thread.thread,
|
|
359
|
-
messages: reconciliation.messages,
|
|
450
|
+
if (preserveRuntimeProjection && snapshot) {
|
|
451
|
+
const current = this.getThread(threadId);
|
|
452
|
+
thread = this.mergeLoadedThread(baseline, current, thread);
|
|
453
|
+
if (queuedOverrideMessages) {
|
|
360
454
|
// Queue mutations are already optimistic and independently durable.
|
|
361
455
|
// A completed-run snapshot can lag the accepted steer/remove write,
|
|
362
456
|
// so it must not resurrect work the client has already promoted.
|
|
363
|
-
queuedMessages:
|
|
364
|
-
}
|
|
365
|
-
thread = {
|
|
366
|
-
...this.mergeLoadedThread(createAgentThreadState(threadId), runtimeProjection, thread),
|
|
367
|
-
...authoritative,
|
|
368
|
-
};
|
|
457
|
+
thread = { ...thread, queuedMessages: queuedOverrideMessages };
|
|
458
|
+
}
|
|
369
459
|
}
|
|
370
460
|
const current = this.getThread(threadId);
|
|
371
461
|
if (current !== baseline) {
|
|
372
462
|
thread = this.mergeLoadedThread(baseline, current, thread);
|
|
373
463
|
}
|
|
464
|
+
thread = this.settleTerminalThread(thread, snapshot);
|
|
374
465
|
this.setThread(threadId, thread);
|
|
375
466
|
this.setConnection("connected");
|
|
376
467
|
for (const runId of thread.activeRunIds) {
|
|
@@ -488,9 +579,17 @@ export class AgentKitClient {
|
|
|
488
579
|
}
|
|
489
580
|
resubscribeRun(threadId, runId) {
|
|
490
581
|
this.assertActive();
|
|
491
|
-
const
|
|
492
|
-
|
|
582
|
+
const thread = this.getThread(threadId);
|
|
583
|
+
const status = thread.runs[runId]?.status;
|
|
584
|
+
// A failed stream can be explicitly reattached to recover from a
|
|
585
|
+
// transient disconnect. Completed and cancelled runs have no live work to
|
|
586
|
+
// resume.
|
|
587
|
+
if (status === "completed" ||
|
|
588
|
+
status === "cancelled" ||
|
|
589
|
+
(status === "failed" &&
|
|
590
|
+
thread.events.some((event) => event.runId === runId && isTerminalRunEvent(event)))) {
|
|
493
591
|
return Promise.resolve();
|
|
592
|
+
}
|
|
494
593
|
const key = this.runKey(threadId, runId);
|
|
495
594
|
const existing = this.consumers.get(key);
|
|
496
595
|
if (existing)
|
|
@@ -680,19 +779,29 @@ export class AgentKitClient {
|
|
|
680
779
|
if (!queueMessage) {
|
|
681
780
|
throw new AgentKitCapabilityError("messageQueue");
|
|
682
781
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
782
|
+
return this.enqueueQueueMutation(input.threadId, async () => {
|
|
783
|
+
this.assertActive();
|
|
784
|
+
const result = await this.invokeRequest(requestContext, (context) => queueMessage({
|
|
785
|
+
threadId: input.threadId,
|
|
786
|
+
text: input.text,
|
|
787
|
+
attachments: input.attachments,
|
|
788
|
+
metadata: input.metadata,
|
|
789
|
+
}, context));
|
|
790
|
+
this.assertActive();
|
|
791
|
+
const thread = this.getThread(input.threadId);
|
|
792
|
+
this.setThread(input.threadId, {
|
|
793
|
+
...thread,
|
|
794
|
+
queuedMessages: [...thread.queuedMessages, result.message],
|
|
795
|
+
});
|
|
796
|
+
const queueOverride = this.queuedMessageOverrides.get(input.threadId);
|
|
797
|
+
const removedIds = new Set(queueOverride?.removedIds);
|
|
798
|
+
removedIds.delete(result.message.id);
|
|
799
|
+
this.queuedMessageOverrides.set(input.threadId, {
|
|
800
|
+
messages: [...thread.queuedMessages, result.message],
|
|
801
|
+
removedIds,
|
|
802
|
+
});
|
|
803
|
+
return result.message;
|
|
694
804
|
});
|
|
695
|
-
return result.message;
|
|
696
805
|
}
|
|
697
806
|
async removeQueuedMessage(threadId, messageId, context) {
|
|
698
807
|
this.assertActive();
|
|
@@ -702,30 +811,52 @@ export class AgentKitClient {
|
|
|
702
811
|
if (!removeQueuedMessage) {
|
|
703
812
|
throw new AgentKitCapabilityError("messageQueue");
|
|
704
813
|
}
|
|
705
|
-
|
|
706
|
-
const removedIndex = previous.queuedMessages.findIndex((message) => message.id === messageId);
|
|
707
|
-
const removed = previous.queuedMessages[removedIndex];
|
|
708
|
-
this.setThread(threadId, {
|
|
709
|
-
...previous,
|
|
710
|
-
queuedMessages: previous.queuedMessages.filter((message) => message.id !== messageId),
|
|
711
|
-
});
|
|
712
|
-
try {
|
|
713
|
-
await this.invokeRequest(requestContext, (context) => removeQueuedMessage({ threadId, messageId }, context));
|
|
814
|
+
return this.enqueueQueueMutation(threadId, async () => {
|
|
714
815
|
this.assertActive();
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
816
|
+
const previous = this.getThread(threadId);
|
|
817
|
+
const removedIndex = previous.queuedMessages.findIndex((message) => message.id === messageId);
|
|
818
|
+
const removed = previous.queuedMessages[removedIndex];
|
|
819
|
+
const previousOverride = this.queuedMessageOverrides.get(threadId);
|
|
719
820
|
if (removed) {
|
|
720
|
-
const
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
821
|
+
const queuedMessages = previous.queuedMessages.filter((message) => message.id !== messageId);
|
|
822
|
+
this.setThread(threadId, { ...previous, queuedMessages });
|
|
823
|
+
const removedIds = new Set(previousOverride?.removedIds);
|
|
824
|
+
removedIds.add(messageId);
|
|
825
|
+
this.queuedMessageOverrides.set(threadId, {
|
|
826
|
+
messages: queuedMessages,
|
|
827
|
+
removedIds,
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
try {
|
|
831
|
+
await this.invokeRequest(requestContext, (context) => removeQueuedMessage({ threadId, messageId }, context));
|
|
832
|
+
this.assertActive();
|
|
833
|
+
}
|
|
834
|
+
catch (error) {
|
|
835
|
+
if (this.disposed)
|
|
836
|
+
throw error;
|
|
837
|
+
if (removed) {
|
|
838
|
+
const current = this.getThread(threadId);
|
|
839
|
+
if (!current.queuedMessages.some((message) => message.id === messageId)) {
|
|
840
|
+
const queuedMessages = [...current.queuedMessages];
|
|
841
|
+
queuedMessages.splice(Math.min(removedIndex, queuedMessages.length), 0, removed);
|
|
842
|
+
this.setThread(threadId, { ...current, queuedMessages });
|
|
843
|
+
const currentOverride = this.queuedMessageOverrides.get(threadId);
|
|
844
|
+
const removedIds = new Set(currentOverride?.removedIds);
|
|
845
|
+
removedIds.delete(messageId);
|
|
846
|
+
if (currentOverride || removedIds.size > 0) {
|
|
847
|
+
this.queuedMessageOverrides.set(threadId, {
|
|
848
|
+
messages: queuedMessages,
|
|
849
|
+
removedIds,
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
else {
|
|
853
|
+
this.queuedMessageOverrides.delete(threadId);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
725
856
|
}
|
|
857
|
+
throw error;
|
|
726
858
|
}
|
|
727
|
-
|
|
728
|
-
}
|
|
859
|
+
});
|
|
729
860
|
}
|
|
730
861
|
async steerQueuedMessage(threadId, messageId, context) {
|
|
731
862
|
this.assertActive();
|
|
@@ -735,68 +866,94 @@ export class AgentKitClient {
|
|
|
735
866
|
if (!steerQueuedMessage) {
|
|
736
867
|
throw new AgentKitCapabilityError("messageQueue");
|
|
737
868
|
}
|
|
738
|
-
|
|
739
|
-
const queued = previous.queuedMessages.find((message) => message.id === messageId);
|
|
740
|
-
if (!queued)
|
|
741
|
-
throw new Error(`Unknown queued message: ${messageId}`);
|
|
742
|
-
const message = {
|
|
743
|
-
id: queued.id,
|
|
744
|
-
role: "user",
|
|
745
|
-
createdAt: queued.createdAt,
|
|
746
|
-
status: "complete",
|
|
747
|
-
parts: [
|
|
748
|
-
{ type: "text", text: queued.text },
|
|
749
|
-
...(queued.attachments ?? []),
|
|
750
|
-
],
|
|
751
|
-
metadata: queued.metadata,
|
|
752
|
-
};
|
|
753
|
-
const previousConnection = this.snapshot.connection;
|
|
754
|
-
const previousError = this.snapshot.error;
|
|
755
|
-
this.setThread(threadId, {
|
|
756
|
-
...previous,
|
|
757
|
-
messages: [...previous.messages, message],
|
|
758
|
-
queuedMessages: previous.queuedMessages.filter((candidate) => candidate.id !== messageId),
|
|
759
|
-
suggestions: [],
|
|
760
|
-
});
|
|
761
|
-
this.setConnection("connecting");
|
|
762
|
-
try {
|
|
763
|
-
const result = await this.invokeRequest(requestContext, (context) => steerQueuedMessage({ threadId, messageId }, context));
|
|
869
|
+
return this.enqueueQueueMutation(threadId, async () => {
|
|
764
870
|
this.assertActive();
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
completed,
|
|
781
|
-
cancel: () => this.cancelRun(threadId, result.runId),
|
|
871
|
+
const previous = this.getThread(threadId);
|
|
872
|
+
const queued = previous.queuedMessages.find((message) => message.id === messageId);
|
|
873
|
+
if (!queued)
|
|
874
|
+
throw new Error(`Unknown queued message: ${messageId}`);
|
|
875
|
+
const previousOverride = this.queuedMessageOverrides.get(threadId);
|
|
876
|
+
const message = {
|
|
877
|
+
id: queued.id,
|
|
878
|
+
role: "user",
|
|
879
|
+
createdAt: queued.createdAt,
|
|
880
|
+
status: "complete",
|
|
881
|
+
parts: [
|
|
882
|
+
{ type: "text", text: queued.text },
|
|
883
|
+
...(queued.attachments ?? []),
|
|
884
|
+
],
|
|
885
|
+
metadata: queued.metadata,
|
|
782
886
|
};
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
if (this.disposed)
|
|
786
|
-
throw error;
|
|
787
|
-
const current = this.getThread(threadId);
|
|
788
|
-
const queuedMessages = current.queuedMessages.some((candidate) => candidate.id === queued.id)
|
|
789
|
-
? current.queuedMessages
|
|
790
|
-
: [queued, ...current.queuedMessages];
|
|
887
|
+
const previousConnection = this.snapshot.connection;
|
|
888
|
+
const previousError = this.snapshot.error;
|
|
791
889
|
this.setThread(threadId, {
|
|
792
|
-
...
|
|
793
|
-
messages:
|
|
794
|
-
queuedMessages,
|
|
890
|
+
...previous,
|
|
891
|
+
messages: [...previous.messages, message],
|
|
892
|
+
queuedMessages: previous.queuedMessages.filter((candidate) => candidate.id !== messageId),
|
|
893
|
+
suggestions: [],
|
|
795
894
|
});
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
895
|
+
const removedIds = new Set(previousOverride?.removedIds);
|
|
896
|
+
removedIds.add(messageId);
|
|
897
|
+
this.queuedMessageOverrides.set(threadId, {
|
|
898
|
+
messages: previous.queuedMessages.filter((candidate) => candidate.id !== messageId),
|
|
899
|
+
removedIds,
|
|
900
|
+
});
|
|
901
|
+
this.setConnection("connecting");
|
|
902
|
+
try {
|
|
903
|
+
const result = await this.invokeRequest(requestContext, (context) => steerQueuedMessage({ threadId, messageId }, context));
|
|
904
|
+
this.assertActive();
|
|
905
|
+
if (!result) {
|
|
906
|
+
this.setConnection("connected");
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
if (result.capabilities) {
|
|
910
|
+
this.patch({
|
|
911
|
+
capabilities: result.capabilities,
|
|
912
|
+
capabilitiesStatus: "ready",
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
this.markRunStarted(threadId, result.runId);
|
|
916
|
+
const completed = this.consume(threadId, result.runId);
|
|
917
|
+
this.trackConsumer(threadId, result.runId, completed);
|
|
918
|
+
return {
|
|
919
|
+
runId: result.runId,
|
|
920
|
+
completed,
|
|
921
|
+
cancel: () => this.cancelRun(threadId, result.runId),
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
catch (error) {
|
|
925
|
+
if (this.disposed)
|
|
926
|
+
throw error;
|
|
927
|
+
const current = this.getThread(threadId);
|
|
928
|
+
const queuedMessages = current.queuedMessages.some((candidate) => candidate.id === queued.id)
|
|
929
|
+
? current.queuedMessages
|
|
930
|
+
: (() => {
|
|
931
|
+
const restored = [...current.queuedMessages];
|
|
932
|
+
restored.splice(Math.min(previous.queuedMessages.indexOf(queued), restored.length), 0, queued);
|
|
933
|
+
return restored;
|
|
934
|
+
})();
|
|
935
|
+
this.setThread(threadId, {
|
|
936
|
+
...current,
|
|
937
|
+
messages: current.messages.filter((candidate) => candidate.id !== message.id),
|
|
938
|
+
queuedMessages,
|
|
939
|
+
});
|
|
940
|
+
const currentOverride = this.queuedMessageOverrides.get(threadId);
|
|
941
|
+
const restoredRemovedIds = new Set(currentOverride?.removedIds);
|
|
942
|
+
restoredRemovedIds.delete(messageId);
|
|
943
|
+
if (currentOverride || restoredRemovedIds.size > 0) {
|
|
944
|
+
this.queuedMessageOverrides.set(threadId, {
|
|
945
|
+
messages: queuedMessages,
|
|
946
|
+
removedIds: restoredRemovedIds,
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
else {
|
|
950
|
+
this.queuedMessageOverrides.delete(threadId);
|
|
951
|
+
}
|
|
952
|
+
this.patch({ connection: previousConnection, error: previousError });
|
|
953
|
+
this.report(error, "queue_steer_failed");
|
|
954
|
+
throw error;
|
|
955
|
+
}
|
|
956
|
+
});
|
|
800
957
|
}
|
|
801
958
|
async cancelRun(threadId, runId, context) {
|
|
802
959
|
this.assertActive();
|
|
@@ -872,6 +1029,7 @@ export class AgentKitClient {
|
|
|
872
1029
|
await this.invokeRequest(requestContext, (context) => deleteThread({ threadId }, context));
|
|
873
1030
|
this.assertActive();
|
|
874
1031
|
this.stopThreadConsumers(threadId, "deleted");
|
|
1032
|
+
this.queuedMessageOverrides.delete(threadId);
|
|
875
1033
|
const threads = { ...this.snapshot.threads };
|
|
876
1034
|
delete threads[threadId];
|
|
877
1035
|
this.patch({ threads });
|
|
@@ -886,6 +1044,8 @@ export class AgentKitClient {
|
|
|
886
1044
|
}
|
|
887
1045
|
this.consumerAbortControllers.clear();
|
|
888
1046
|
this.threadLeaseCounts.clear();
|
|
1047
|
+
this.queueMutationChains.clear();
|
|
1048
|
+
this.queuedMessageOverrides.clear();
|
|
889
1049
|
this.patch({ connection: "offline" });
|
|
890
1050
|
this.listeners.clear();
|
|
891
1051
|
this.consumers.clear();
|
|
@@ -953,9 +1113,7 @@ export class AgentKitClient {
|
|
|
953
1113
|
event.request.status === "failed")) {
|
|
954
1114
|
interruptedForContinuation = false;
|
|
955
1115
|
}
|
|
956
|
-
if (event
|
|
957
|
-
event.type === "run.failed" ||
|
|
958
|
-
event.type === "run.cancelled") {
|
|
1116
|
+
if (isTerminalRunEvent(event)) {
|
|
959
1117
|
terminalEvent = event;
|
|
960
1118
|
}
|
|
961
1119
|
}
|
|
@@ -972,7 +1130,9 @@ export class AgentKitClient {
|
|
|
972
1130
|
throw new Error(`AgentKit run ${runId} ended without a terminal event.`);
|
|
973
1131
|
}
|
|
974
1132
|
this.setConnection("connected");
|
|
975
|
-
if (terminalEvent.type === "run.completed"
|
|
1133
|
+
if (terminalEvent.type === "run.completed" ||
|
|
1134
|
+
(terminalEvent.type === "run.status" &&
|
|
1135
|
+
terminalEvent.status === "completed")) {
|
|
976
1136
|
if (!this.threadLoads.has(threadId) &&
|
|
977
1137
|
(this.transport.getThreadSnapshot || this.transport.getThread)) {
|
|
978
1138
|
await this.loadThreadProjection(threadId, this.createRequestContext(), true);
|
|
@@ -1001,6 +1161,8 @@ export class AgentKitClient {
|
|
|
1001
1161
|
if (abortController.signal.reason instanceof AgentKitConsumerStoppedError) {
|
|
1002
1162
|
return;
|
|
1003
1163
|
}
|
|
1164
|
+
const runError = toError(error, "run_stream_failed");
|
|
1165
|
+
this.markRunFailed(threadId, runId, runError);
|
|
1004
1166
|
this.fail(error, "run_stream_failed");
|
|
1005
1167
|
throw error;
|
|
1006
1168
|
}
|
|
@@ -1082,6 +1244,38 @@ export class AgentKitClient {
|
|
|
1082
1244
|
Number.isFinite(current) &&
|
|
1083
1245
|
expiration <= current);
|
|
1084
1246
|
}
|
|
1247
|
+
settleTerminalThread(thread, snapshot) {
|
|
1248
|
+
const runs = Object.values(thread.runs);
|
|
1249
|
+
const terminalRuns = runs.filter((run) => this.isTerminalStatus(run.status));
|
|
1250
|
+
const settled = terminalRuns.reduce((currentThread, run) => settleRunProjection(currentThread, run.id, run.status, run.completedAt ?? this.now(), run.activeMessageId), thread);
|
|
1251
|
+
if (thread.activeRunIds.length > 0 ||
|
|
1252
|
+
runs.some((run) => !this.isTerminalStatus(run.status))) {
|
|
1253
|
+
return settled;
|
|
1254
|
+
}
|
|
1255
|
+
const snapshotHasNoActiveRuns = snapshot?.activeRunIds?.length === 0 ||
|
|
1256
|
+
(snapshot?.activeRunIds === undefined &&
|
|
1257
|
+
snapshot?.runs !== undefined &&
|
|
1258
|
+
snapshot.runs.length > 0 &&
|
|
1259
|
+
snapshot.runs.every((run) => this.isTerminalStatus(run.status)));
|
|
1260
|
+
if (!snapshotHasNoActiveRuns)
|
|
1261
|
+
return settled;
|
|
1262
|
+
// A snapshot can outlive the event association for its assistant message.
|
|
1263
|
+
// If any known run failed or was cancelled, settle an unassociated message
|
|
1264
|
+
// as an error rather than presenting a partial response as successful.
|
|
1265
|
+
const settledMessageStatus = terminalRuns.length > 0 &&
|
|
1266
|
+
terminalRuns.every((run) => run.status === "completed")
|
|
1267
|
+
? "complete"
|
|
1268
|
+
: "error";
|
|
1269
|
+
// Lifecycle events can age out independently of the message projection.
|
|
1270
|
+
// Once the snapshot proves that no run remains active, any assistant
|
|
1271
|
+
// message still marked streaming is stale rather than in-flight work.
|
|
1272
|
+
return {
|
|
1273
|
+
...settled,
|
|
1274
|
+
messages: settled.messages.map((message) => message.role === "assistant" && message.status === "streaming"
|
|
1275
|
+
? { ...message, status: settledMessageStatus }
|
|
1276
|
+
: message),
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1085
1279
|
hydrateThread(snapshot, runs, activeRunIds) {
|
|
1086
1280
|
let hydrated = createAgentThreadState(snapshot.id);
|
|
1087
1281
|
for (const event of snapshot.events ?? []) {
|
|
@@ -1107,7 +1301,7 @@ export class AgentKitClient {
|
|
|
1107
1301
|
]));
|
|
1108
1302
|
const mergedRuns = this.mergeRuns(hydrated.runs, runs);
|
|
1109
1303
|
const currentActiveRunIds = activeRunIds.filter((runId) => !this.isTerminalStatus(mergedRuns[runId]?.status ?? "running"));
|
|
1110
|
-
|
|
1304
|
+
const projected = {
|
|
1111
1305
|
...hydrated,
|
|
1112
1306
|
thread: snapshot,
|
|
1113
1307
|
// The snapshot projection is authoritative. The event log rebuilds all
|
|
@@ -1157,6 +1351,7 @@ export class AgentKitClient {
|
|
|
1157
1351
|
artifacts: snapshot.artifacts ?? hydrated.artifacts,
|
|
1158
1352
|
suggestions: snapshot.suggestions ?? hydrated.suggestions,
|
|
1159
1353
|
};
|
|
1354
|
+
return this.settleTerminalThread(projected, snapshot);
|
|
1160
1355
|
}
|
|
1161
1356
|
async requireCapability(capability, context) {
|
|
1162
1357
|
const capabilities = await this.ensureCapabilities(context);
|
|
@@ -1180,81 +1375,86 @@ export class AgentKitClient {
|
|
|
1180
1375
|
return new AgentKitCapabilityError(descriptor.id, unavailable ? "unavailable" : "unsupported", descriptor.error?.message ?? descriptor.description, descriptor.error?.retryable ?? unavailable, descriptor.error?.correlationId, descriptor.error?.details);
|
|
1181
1376
|
}
|
|
1182
1377
|
mergeLoadedThread(baseline, current, loaded) {
|
|
1183
|
-
const
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1378
|
+
const messagesChanged = current.messages !== baseline.messages;
|
|
1379
|
+
const reconciliation = loaded.messages.length > 0
|
|
1380
|
+
? this.reconcileMessages(current.messages, loaded.messages)
|
|
1381
|
+
: undefined;
|
|
1382
|
+
const hasMessageIdRemap = (reconciliation?.idRemap.size ?? 0) > 0;
|
|
1383
|
+
// Keep the live projection as the winner only for identities accepted by
|
|
1384
|
+
// a concurrent stream. A content match still needs the durable identity
|
|
1385
|
+
// when the stream finished before this refresh established its baseline.
|
|
1386
|
+
const live = reconciliation
|
|
1387
|
+
? this.remapThreadMessageReferences(current, reconciliation.idRemap)
|
|
1388
|
+
: current;
|
|
1389
|
+
const loadedSnapshot = loaded.thread;
|
|
1390
|
+
const snapshotIncludes = (key) => loadedSnapshot !== undefined &&
|
|
1391
|
+
Object.prototype.hasOwnProperty.call(loadedSnapshot, key);
|
|
1392
|
+
const snapshotIncludesRuntimeProjection = (key) => snapshotIncludes(key) || snapshotIncludes("events");
|
|
1393
|
+
const mergeRecordProjection = (loadedProjection, baselineProjection, currentProjection, snapshotKey) => currentProjection === baselineProjection &&
|
|
1394
|
+
!snapshotIncludesRuntimeProjection(snapshotKey)
|
|
1395
|
+
? currentProjection
|
|
1396
|
+
: mergeRuntimeRecordMap(loadedProjection, baselineProjection, currentProjection);
|
|
1397
|
+
const mergeListProjection = (loadedProjection, baselineProjection, currentProjection, snapshotKey) => {
|
|
1398
|
+
if (currentProjection === baselineProjection &&
|
|
1399
|
+
!snapshotIncludesRuntimeProjection(snapshotKey)) {
|
|
1400
|
+
return currentProjection;
|
|
1401
|
+
}
|
|
1402
|
+
if (currentProjection === baselineProjection)
|
|
1403
|
+
return loadedProjection;
|
|
1404
|
+
const baselineById = new Map(baselineProjection.map((item) => [item.id, item]));
|
|
1405
|
+
const currentById = new Map(currentProjection.map((item) => [item.id, item]));
|
|
1406
|
+
const removedIds = new Set([...baselineById.keys()].filter((id) => !currentById.has(id)));
|
|
1407
|
+
const changedItems = currentProjection.filter((item) => {
|
|
1408
|
+
const baselineItem = baselineById.get(item.id);
|
|
1409
|
+
return baselineItem === undefined || baselineItem !== item;
|
|
1410
|
+
});
|
|
1411
|
+
return this.mergeItemsById(loadedProjection.filter((item) => !removedIds.has(item.id)), changedItems);
|
|
1412
|
+
};
|
|
1413
|
+
const runs = this.mergeRuns(loaded.runs, live.runs);
|
|
1414
|
+
const activeRunIds = Array.from(new Set([...loaded.activeRunIds, ...live.activeRunIds])).filter((runId) => !this.isTerminalStatus(runs[runId]?.status ?? "running"));
|
|
1187
1415
|
return {
|
|
1188
1416
|
...loaded,
|
|
1189
1417
|
thread: current.thread === baseline.thread ? loaded.thread : current.thread,
|
|
1190
|
-
messages:
|
|
1191
|
-
?
|
|
1192
|
-
:
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1418
|
+
messages: messagesChanged || hasMessageIdRemap
|
|
1419
|
+
? reconciliation.messages
|
|
1420
|
+
: loaded.messages.length > 0 || current.messages.length === 0
|
|
1421
|
+
? loaded.messages
|
|
1422
|
+
: current.messages,
|
|
1423
|
+
queuedMessages: current.queuedMessages === baseline.queuedMessages &&
|
|
1424
|
+
!snapshotIncludes("queuedMessages")
|
|
1425
|
+
? current.queuedMessages
|
|
1426
|
+
: this.mergeQueuedMessages(baseline.queuedMessages, loaded.queuedMessages, live.queuedMessages),
|
|
1196
1427
|
runs,
|
|
1197
1428
|
activeRunIds,
|
|
1198
|
-
events:
|
|
1199
|
-
? loaded.events
|
|
1200
|
-
:
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
activities:
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
: { ...loaded.connectionRequests, ...current.connectionRequests },
|
|
1225
|
-
connectionRequestRunIds: current.connectionRequestRunIds === baseline.connectionRequestRunIds
|
|
1226
|
-
? loaded.connectionRequestRunIds
|
|
1227
|
-
: {
|
|
1228
|
-
...loaded.connectionRequestRunIds,
|
|
1229
|
-
...current.connectionRequestRunIds,
|
|
1230
|
-
},
|
|
1231
|
-
artifacts: current.artifacts === baseline.artifacts
|
|
1232
|
-
? loaded.artifacts
|
|
1233
|
-
: this.mergeItemsById(loaded.artifacts, current.artifacts),
|
|
1234
|
-
widgets: current.widgets === baseline.widgets
|
|
1235
|
-
? loaded.widgets
|
|
1236
|
-
: { ...loaded.widgets, ...current.widgets },
|
|
1237
|
-
widgetMessageIds: current.widgetMessageIds === baseline.widgetMessageIds
|
|
1238
|
-
? loaded.widgetMessageIds
|
|
1239
|
-
: { ...loaded.widgetMessageIds, ...current.widgetMessageIds },
|
|
1240
|
-
annotations: current.annotations === baseline.annotations
|
|
1241
|
-
? loaded.annotations
|
|
1242
|
-
: { ...loaded.annotations, ...current.annotations },
|
|
1243
|
-
annotationMessageIds: current.annotationMessageIds === baseline.annotationMessageIds
|
|
1244
|
-
? loaded.annotationMessageIds
|
|
1245
|
-
: {
|
|
1246
|
-
...loaded.annotationMessageIds,
|
|
1247
|
-
...current.annotationMessageIds,
|
|
1248
|
-
},
|
|
1249
|
-
suggestions: current.suggestions === baseline.suggestions
|
|
1250
|
-
? loaded.suggestions
|
|
1429
|
+
events: reconciliation
|
|
1430
|
+
? this.mergeEvents(loaded.events, live.events)
|
|
1431
|
+
: current.events === baseline.events && !snapshotIncludes("events")
|
|
1432
|
+
? live.events
|
|
1433
|
+
: current.events === baseline.events
|
|
1434
|
+
? loaded.events
|
|
1435
|
+
: this.mergeEvents(loaded.events, live.events),
|
|
1436
|
+
agents: mergeRecordProjection(loaded.agents, baseline.agents, live.agents, "agents"),
|
|
1437
|
+
agentInteractions: mergeListProjection(loaded.agentInteractions, baseline.agentInteractions, live.agentInteractions, "interactions"),
|
|
1438
|
+
activities: mergeRecordProjection(loaded.activities, baseline.activities, live.activities, "activities"),
|
|
1439
|
+
tasks: mergeRecordProjection(loaded.tasks, baseline.tasks, live.tasks, "tasks"),
|
|
1440
|
+
taskGroups: mergeRecordProjection(loaded.taskGroups, baseline.taskGroups, live.taskGroups, "taskGroups"),
|
|
1441
|
+
tools: mergeRecordProjection(loaded.tools, baseline.tools, live.tools, "toolCalls"),
|
|
1442
|
+
approvals: mergeRecordProjection(loaded.approvals, baseline.approvals, live.approvals, "approvals"),
|
|
1443
|
+
approvalRunIds: mergeRecordProjection(loaded.approvalRunIds, baseline.approvalRunIds, live.approvalRunIds, "approvals"),
|
|
1444
|
+
connectionRequests: mergeRecordProjection(loaded.connectionRequests, baseline.connectionRequests, live.connectionRequests, "connectionRequests"),
|
|
1445
|
+
connectionRequestRunIds: mergeRecordProjection(loaded.connectionRequestRunIds, baseline.connectionRequestRunIds, live.connectionRequestRunIds, "connectionRequests"),
|
|
1446
|
+
artifacts: mergeListProjection(loaded.artifacts, baseline.artifacts, live.artifacts, "artifacts"),
|
|
1447
|
+
widgets: mergeRecordProjection(loaded.widgets, baseline.widgets, live.widgets, "widgets"),
|
|
1448
|
+
widgetMessageIds: mergeRecordProjection(loaded.widgetMessageIds, baseline.widgetMessageIds, live.widgetMessageIds, "widgets"),
|
|
1449
|
+
annotations: mergeRecordProjection(loaded.annotations, baseline.annotations, live.annotations, "annotations"),
|
|
1450
|
+
annotationMessageIds: mergeRecordProjection(loaded.annotationMessageIds, baseline.annotationMessageIds, live.annotationMessageIds, "annotations"),
|
|
1451
|
+
suggestions: snapshotIncludesRuntimeProjection("suggestions")
|
|
1452
|
+
? current.suggestions === baseline.suggestions
|
|
1453
|
+
? loaded.suggestions
|
|
1454
|
+
: live.suggestions
|
|
1251
1455
|
: current.suggestions,
|
|
1252
|
-
actions:
|
|
1253
|
-
|
|
1254
|
-
: { ...loaded.actions, ...current.actions },
|
|
1255
|
-
uploads: current.uploads === baseline.uploads
|
|
1256
|
-
? loaded.uploads
|
|
1257
|
-
: { ...loaded.uploads, ...current.uploads },
|
|
1456
|
+
actions: mergeRecordProjection(loaded.actions, baseline.actions, live.actions, "events"),
|
|
1457
|
+
uploads: mergeRecordProjection(loaded.uploads, baseline.uploads, live.uploads, "events"),
|
|
1258
1458
|
};
|
|
1259
1459
|
}
|
|
1260
1460
|
mergeRuns(first, second) {
|
|
@@ -1290,9 +1490,8 @@ export class AgentKitClient {
|
|
|
1290
1490
|
return merged;
|
|
1291
1491
|
}
|
|
1292
1492
|
reconcileMessages(current, durable) {
|
|
1293
|
-
if (durable.length === 0)
|
|
1493
|
+
if (durable.length === 0)
|
|
1294
1494
|
return { messages: current, idRemap: new Map() };
|
|
1295
|
-
}
|
|
1296
1495
|
const durableIds = new Set(durable.map((message) => message.id));
|
|
1297
1496
|
const currentIds = new Set(current.map((message) => message.id));
|
|
1298
1497
|
const unmatchedContent = new Map();
|
|
@@ -1307,22 +1506,68 @@ export class AgentKitClient {
|
|
|
1307
1506
|
message,
|
|
1308
1507
|
]);
|
|
1309
1508
|
}
|
|
1310
|
-
const optimistic = [];
|
|
1311
1509
|
const idRemap = new Map();
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1510
|
+
let hasMatchedMessage = false;
|
|
1511
|
+
const liveMessages = current.map((message) => {
|
|
1512
|
+
if (durableIds.has(message.id)) {
|
|
1513
|
+
hasMatchedMessage = true;
|
|
1514
|
+
return message;
|
|
1515
|
+
}
|
|
1315
1516
|
const key = this.messageContentKey(message);
|
|
1316
1517
|
const matches = unmatchedContent.get(key);
|
|
1317
1518
|
const durableMatch = matches?.shift();
|
|
1318
1519
|
if (durableMatch) {
|
|
1520
|
+
hasMatchedMessage = true;
|
|
1319
1521
|
idRemap.set(message.id, durableMatch.id);
|
|
1522
|
+
return durableMatch;
|
|
1523
|
+
}
|
|
1524
|
+
return message;
|
|
1525
|
+
});
|
|
1526
|
+
if (!hasMatchedMessage) {
|
|
1527
|
+
return { messages: [...durable, ...current], idRemap };
|
|
1528
|
+
}
|
|
1529
|
+
const presentIds = new Set(liveMessages.map((message) => message.id));
|
|
1530
|
+
const messages = [...liveMessages];
|
|
1531
|
+
for (let durableIndex = 0; durableIndex < durable.length; durableIndex++) {
|
|
1532
|
+
const message = durable[durableIndex];
|
|
1533
|
+
if (presentIds.has(message.id))
|
|
1534
|
+
continue;
|
|
1535
|
+
let insertBefore = -1;
|
|
1536
|
+
for (let nextIndex = durableIndex + 1; nextIndex < durable.length; nextIndex++) {
|
|
1537
|
+
const nextId = durable[nextIndex]?.id;
|
|
1538
|
+
if (!nextId)
|
|
1539
|
+
continue;
|
|
1540
|
+
const candidateIndex = messages.findIndex((currentMessage) => currentMessage.id === nextId);
|
|
1541
|
+
if (candidateIndex >= 0) {
|
|
1542
|
+
insertBefore = candidateIndex;
|
|
1543
|
+
break;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
if (insertBefore >= 0) {
|
|
1547
|
+
messages.splice(insertBefore, 0, message);
|
|
1320
1548
|
}
|
|
1321
1549
|
else {
|
|
1322
|
-
|
|
1550
|
+
let previousDurableIndex = -1;
|
|
1551
|
+
for (let previousIndex = durableIndex - 1; previousIndex >= 0; previousIndex -= 1) {
|
|
1552
|
+
const previousId = durable[previousIndex]?.id;
|
|
1553
|
+
if (!previousId)
|
|
1554
|
+
continue;
|
|
1555
|
+
const candidateIndex = messages.findIndex((currentMessage) => currentMessage.id === previousId);
|
|
1556
|
+
if (candidateIndex >= 0) {
|
|
1557
|
+
previousDurableIndex = candidateIndex;
|
|
1558
|
+
break;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
if (previousDurableIndex >= 0) {
|
|
1562
|
+
messages.splice(previousDurableIndex + 1, 0, message);
|
|
1563
|
+
}
|
|
1564
|
+
else {
|
|
1565
|
+
messages.unshift(message);
|
|
1566
|
+
}
|
|
1323
1567
|
}
|
|
1568
|
+
presentIds.add(message.id);
|
|
1324
1569
|
}
|
|
1325
|
-
return { messages
|
|
1570
|
+
return { messages, idRemap };
|
|
1326
1571
|
}
|
|
1327
1572
|
remapThreadMessageReferences(thread, idRemap) {
|
|
1328
1573
|
if (idRemap.size === 0)
|
|
@@ -1412,13 +1657,38 @@ export class AgentKitClient {
|
|
|
1412
1657
|
}
|
|
1413
1658
|
mergeEvents(first, second) {
|
|
1414
1659
|
const merged = [...first];
|
|
1415
|
-
const
|
|
1660
|
+
const indexByKey = new Map(first.map((event, index) => [
|
|
1661
|
+
`${event.threadId}\u0000${event.runId}\u0000${event.sequence}`,
|
|
1662
|
+
index,
|
|
1663
|
+
]));
|
|
1416
1664
|
for (const event of second) {
|
|
1417
1665
|
const key = `${event.threadId}\u0000${event.runId}\u0000${event.sequence}`;
|
|
1418
|
-
|
|
1419
|
-
|
|
1666
|
+
const existingIndex = indexByKey.get(key);
|
|
1667
|
+
if (existingIndex === undefined) {
|
|
1668
|
+
indexByKey.set(key, merged.length);
|
|
1420
1669
|
merged.push(event);
|
|
1421
1670
|
}
|
|
1671
|
+
else {
|
|
1672
|
+
// The second projection is the live stream. It may contain richer
|
|
1673
|
+
// payloads than a snapshot captured before that event was persisted.
|
|
1674
|
+
merged[existingIndex] = event;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
// Sequences are scoped to a run, so there is no valid global numeric sort.
|
|
1678
|
+
// Keep cross-run interleaving stable while repairing each run's local
|
|
1679
|
+
// order in the slots it already occupies.
|
|
1680
|
+
const positionsByRun = new Map();
|
|
1681
|
+
merged.forEach((event, index) => {
|
|
1682
|
+
const positions = positionsByRun.get(event.runId) ?? [];
|
|
1683
|
+
positions.push(index);
|
|
1684
|
+
positionsByRun.set(event.runId, positions);
|
|
1685
|
+
});
|
|
1686
|
+
for (const positions of positionsByRun.values()) {
|
|
1687
|
+
const events = positions.map((index) => merged[index]);
|
|
1688
|
+
events.sort((left, right) => left.sequence - right.sequence);
|
|
1689
|
+
positions.forEach((index, position) => {
|
|
1690
|
+
merged[index] = events[position];
|
|
1691
|
+
});
|
|
1422
1692
|
}
|
|
1423
1693
|
return merged;
|
|
1424
1694
|
}
|
|
@@ -1449,18 +1719,37 @@ export class AgentKitClient {
|
|
|
1449
1719
|
const thread = this.getThread(threadId);
|
|
1450
1720
|
const run = thread.runs[runId] ?? this.runState(runId);
|
|
1451
1721
|
const activeRunIds = thread.activeRunIds.filter((id) => id !== runId);
|
|
1452
|
-
this.
|
|
1722
|
+
const completedAt = this.now();
|
|
1723
|
+
this.setThread(threadId, settleRunProjection({
|
|
1453
1724
|
...thread,
|
|
1454
1725
|
runs: {
|
|
1455
1726
|
...thread.runs,
|
|
1456
1727
|
[runId]: {
|
|
1457
1728
|
...run,
|
|
1458
1729
|
status: "cancelled",
|
|
1459
|
-
completedAt
|
|
1730
|
+
completedAt,
|
|
1460
1731
|
},
|
|
1461
1732
|
},
|
|
1462
1733
|
activeRunIds,
|
|
1463
|
-
});
|
|
1734
|
+
}, runId, "cancelled", completedAt, run.activeMessageId));
|
|
1735
|
+
}
|
|
1736
|
+
markRunFailed(threadId, runId, error) {
|
|
1737
|
+
const thread = this.getThread(threadId);
|
|
1738
|
+
const run = thread.runs[runId] ?? this.runState(runId);
|
|
1739
|
+
const completedAt = this.now();
|
|
1740
|
+
this.setThread(threadId, settleRunProjection({
|
|
1741
|
+
...thread,
|
|
1742
|
+
runs: {
|
|
1743
|
+
...thread.runs,
|
|
1744
|
+
[runId]: {
|
|
1745
|
+
...run,
|
|
1746
|
+
status: "failed",
|
|
1747
|
+
completedAt,
|
|
1748
|
+
error,
|
|
1749
|
+
},
|
|
1750
|
+
},
|
|
1751
|
+
activeRunIds: thread.activeRunIds.filter((id) => id !== runId),
|
|
1752
|
+
}, runId, "failed", completedAt, run.activeMessageId));
|
|
1464
1753
|
}
|
|
1465
1754
|
markRunStarted(threadId, runId) {
|
|
1466
1755
|
const thread = this.getThread(threadId);
|
|
@@ -1494,6 +1783,7 @@ export class AgentKitClient {
|
|
|
1494
1783
|
completedAt: run?.completedAt,
|
|
1495
1784
|
usage: run?.usage,
|
|
1496
1785
|
error: run?.error,
|
|
1786
|
+
activeMessageId: run?.activeMessageId,
|
|
1497
1787
|
};
|
|
1498
1788
|
}
|
|
1499
1789
|
runSnapshot(threadId, runId) {
|
|
@@ -1565,7 +1855,13 @@ export class AgentKitClient {
|
|
|
1565
1855
|
receivedSequence: admission.receivedSequence,
|
|
1566
1856
|
});
|
|
1567
1857
|
}
|
|
1568
|
-
|
|
1858
|
+
const next = reduceAgentEvent(thread, event);
|
|
1859
|
+
if (event.type === "queue.updated") {
|
|
1860
|
+
// A replayed queue snapshot is the ordered server view. It supersedes
|
|
1861
|
+
// the temporary protection used while a queue mutation catches up.
|
|
1862
|
+
this.queuedMessageOverrides.delete(event.threadId);
|
|
1863
|
+
}
|
|
1864
|
+
this.setThread(event.threadId, next);
|
|
1569
1865
|
}
|
|
1570
1866
|
setThread(threadId, thread) {
|
|
1571
1867
|
this.patch({
|