@agent-native/core 0.203.0-nightly-20261007064822 → 0.203.0

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.
Files changed (63) hide show
  1. package/dist/agent/engine/builder-engine.d.ts +1 -1
  2. package/dist/agent/model-config.d.ts +2 -2
  3. package/dist/agent/model-config.js +0 -2
  4. package/dist/agent/thread-data-builder.d.ts +6 -0
  5. package/dist/agent/thread-data-builder.js +747 -25
  6. package/dist/agent/thread-message-projection.d.ts +20 -0
  7. package/dist/agent/thread-message-projection.js +300 -0
  8. package/dist/automation/index.d.ts +1 -1
  9. package/dist/chat-threads/store.d.ts +4 -1
  10. package/dist/chat-threads/store.js +92 -17
  11. package/dist/cli/skills-content/turn-into-app-skill.d.ts +10 -4
  12. package/dist/cli/skills-content/turn-into-app-skill.js +2454 -705
  13. package/dist/cli/skills.d.ts +6 -0
  14. package/dist/cli/skills.js +8 -2
  15. package/dist/client/chat/agentkit-agent-native.js +839 -89
  16. package/dist/client/chat/agentkit-protocol.js +67 -17
  17. package/dist/collab/routes.d.ts +2 -2
  18. package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
  19. package/dist/mcp/build-server.d.ts +5 -1
  20. package/dist/mcp/build-server.js +56 -13
  21. package/dist/mcp/embed-app.js +6 -1
  22. package/dist/notifications/routes.d.ts +1 -1
  23. package/dist/observability/routes.d.ts +3 -3
  24. package/dist/org/index.d.ts +2 -2
  25. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  26. package/dist/resources/handlers.d.ts +1 -1
  27. package/dist/server/agent-chat/mcp-options.d.ts +4 -0
  28. package/dist/server/agent-chat-plugin.js +97 -22
  29. package/dist/server/agent-engine-default-model-route.d.ts +2 -2
  30. package/dist/server/onboarding-html.js +1 -1
  31. package/dist/server/realtime-token.d.ts +1 -1
  32. package/dist/server/transcribe-voice.d.ts +1 -1
  33. package/dist/shared/embed-auth.js +5 -4
  34. package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
  35. package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +26 -24
  36. package/dist/templates/factory/.agents/skills/review-latest-feedback/references/deployment-recovery.md +53 -0
  37. package/dist/templates/factory/.agents/skills/review-latest-feedback/references/documentation-proof.md +7 -0
  38. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +284 -461
  39. package/dist/templates/workspace-core/.agents/skills/turn-into-app/agents/openai.yaml +2 -2
  40. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +65 -90
  41. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/local-run-and-deploy.md +161 -0
  42. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/review-loop.md +384 -0
  43. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/source-brief.md +116 -0
  44. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/spreadsheet-source.md +206 -190
  45. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/ui-archetypes.md +381 -0
  46. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/ui-direction.md +323 -0
  47. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/ui-palettes.md +564 -0
  48. package/dist/triggers/actions/manage-automation.d.ts +7 -7
  49. package/docs/content/observability.mdx +136 -0
  50. package/package.json +4 -4
  51. package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +26 -24
  52. package/src/templates/factory/.agents/skills/review-latest-feedback/references/deployment-recovery.md +53 -0
  53. package/src/templates/factory/.agents/skills/review-latest-feedback/references/documentation-proof.md +7 -0
  54. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +284 -461
  55. package/src/templates/workspace-core/.agents/skills/turn-into-app/agents/openai.yaml +2 -2
  56. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +65 -90
  57. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/local-run-and-deploy.md +161 -0
  58. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/review-loop.md +384 -0
  59. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/source-brief.md +116 -0
  60. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/spreadsheet-source.md +206 -190
  61. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/ui-archetypes.md +381 -0
  62. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/ui-direction.md +323 -0
  63. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/ui-palettes.md +564 -0
@@ -1,5 +1,6 @@
1
1
  import { AgentKitRunSlotBusyError } from "@agent-native/agentkit/client";
2
2
  import { isAgentKitProtocolVersion, parseAgentRunOptions, parseAgentThreadSnapshot, } from "@agent-native/agentkit/protocol";
3
+ import { projectRootAssistantMessages } from "../../agent/thread-message-projection.js";
3
4
  import { RUN_NOT_STARTED_METADATA_KEY, retryContextFromRequest, } from "../../shared/agent-chat-run-not-started.js";
4
5
  import { agentNativePath } from "../api-path.js";
5
6
  import { CHAT_REQUEST_TOO_LARGE_MESSAGE } from "../error-format.js";
@@ -11,11 +12,48 @@ import { runOutcomeForCode } from "./run-outcome.js";
11
12
  import { createAgentNativeChatRuntime, isAgentNativeChatRuntime, } from "./runtime.js";
12
13
  const RUN_SLOT_POLL_INTERVAL_MS = 150;
13
14
  const RUN_SLOT_STABLE_POLLS = 2;
15
+ const MAX_THREAD_SNAPSHOT_REQUEST_BYTES = 4 * 1024 * 1024;
16
+ const MAX_THREAD_SNAPSHOT_RETRIES = 2;
17
+ const MAX_THREAD_SNAPSHOT_ANNOTATION_REPLACEMENT_BYTES = 64 * 1024;
14
18
  function asRecord(value) {
15
19
  return value && typeof value === "object" && !Array.isArray(value)
16
20
  ? value
17
21
  : null;
18
22
  }
23
+ function mergeStoredReasoningParts(messageParts, storedParts) {
24
+ const storedReasoning = storedParts.filter((part) => part.type === "reasoning");
25
+ if (storedReasoning.length === 0)
26
+ return messageParts;
27
+ const reasoningText = (parts) => parts
28
+ .filter((part) => part.type === "reasoning")
29
+ .map((part) => part.text)
30
+ .join("");
31
+ if (reasoningText(messageParts) === reasoningText(storedReasoning)) {
32
+ return messageParts;
33
+ }
34
+ const withoutReasoning = messageParts.filter((part) => part.type !== "reasoning");
35
+ const firstTextIndex = withoutReasoning.findIndex((part) => part.type === "text");
36
+ const insertionIndex = firstTextIndex < 0 ? withoutReasoning.length : firstTextIndex;
37
+ return [
38
+ ...withoutReasoning.slice(0, insertionIndex),
39
+ ...storedReasoning,
40
+ ...withoutReasoning.slice(insertionIndex),
41
+ ];
42
+ }
43
+ function latestSnapshotRunRecord(runs) {
44
+ if (!Array.isArray(runs))
45
+ return undefined;
46
+ return runs.reduce((latest, run) => {
47
+ const record = asRecord(run);
48
+ if (typeof record?.id !== "string")
49
+ return latest;
50
+ if (!latest)
51
+ return record;
52
+ const latestTime = latest.startedAt ?? latest.completedAt;
53
+ const runTime = record.startedAt ?? record.completedAt;
54
+ return latestTime && runTime && runTime < latestTime ? latest : record;
55
+ }, undefined);
56
+ }
19
57
  function protocolTurnId(metadata) {
20
58
  const native = asRecord(asRecord(metadata)?.[AGENT_NATIVE_PROTOCOL_METADATA_KEY]);
21
59
  const observability = asRecord(native?.observability);
@@ -23,6 +61,13 @@ function protocolTurnId(metadata) {
23
61
  ? observability.turnId
24
62
  : undefined;
25
63
  }
64
+ function protocolInterruptedRunId(metadata) {
65
+ const native = asRecord(asRecord(metadata)?.[AGENT_NATIVE_PROTOCOL_METADATA_KEY]);
66
+ const observability = asRecord(native?.observability);
67
+ return typeof observability?.interruptedRunId === "string"
68
+ ? observability.interruptedRunId
69
+ : undefined;
70
+ }
26
71
  function scopeObject(scope) {
27
72
  const value = asRecord(scope);
28
73
  if (typeof value?.id !== "string" || typeof value.type !== "string") {
@@ -367,7 +412,7 @@ function withRefusedTurnMetadata(message, stored) {
367
412
  },
368
413
  };
369
414
  }
370
- function reconcileDurableMessages(messages, durable, events, runs) {
415
+ function reconcileDurableMessages(messages, durable, events, runs, toolCalls) {
371
416
  const submittedRunId = (message) => {
372
417
  const value = asRecord(asRecord(message.metadata)?.custom)?.submittedRunId;
373
418
  return typeof value === "string" ? value : undefined;
@@ -442,13 +487,22 @@ function reconcileDurableMessages(messages, durable, events, runs) {
442
487
  durableByFoldedRun.set(runId, durableByFoldedRun.has(runId) ? null : message);
443
488
  }
444
489
  }
490
+ const rootAssistantProjection = projectRootAssistantMessages({
491
+ rootMessages: durable,
492
+ snapshotMessages: messages,
493
+ events,
494
+ runs,
495
+ toolCalls,
496
+ });
497
+ const representedDurableAssistantIds = rootAssistantProjection.representedRootMessageIds;
498
+ const rootMessageIdsBySnapshotMessageId = new Map([...rootAssistantProjection.snapshotMessageIdsByRootMessageId].map(([rootId, snapshotId]) => [snapshotId, rootId]));
499
+ const deduplicatedMessages = messages.filter((message) => !representedDurableAssistantIds.has(message.id));
445
500
  const representedSubmittedUserIds = new Set();
446
501
  const storedUserBySnapshotId = new Map();
447
502
  const unmatchedSnapshotUsers = [];
448
- const representedAssistantIds = new Set(messages.flatMap((message) => message.role === "assistant" ? [message.id] : []));
449
- const representedAssistantRunIds = new Set();
503
+ const representedAssistantIds = new Set(deduplicatedMessages.flatMap((message) => message.role === "assistant" ? [message.id] : []));
450
504
  const snapshotAssistantRunIds = new Set();
451
- for (const message of messages) {
505
+ for (const message of deduplicatedMessages) {
452
506
  if (message.role === "user" &&
453
507
  asRecord(message.metadata)?.hideUserMessage !== true) {
454
508
  const runId = submittedRunId(message);
@@ -473,9 +527,6 @@ function reconcileDurableMessages(messages, durable, events, runs) {
473
527
  const runId = typeof metadataRunId === "string"
474
528
  ? metadataRunId
475
529
  : runByAssistantId.get(message.id);
476
- const stored = runId ? durableByRun.get(runId) : undefined;
477
- if (runId && stored)
478
- representedAssistantRunIds.add(runId);
479
530
  if (runId)
480
531
  snapshotAssistantRunIds.add(runId);
481
532
  }
@@ -538,6 +589,7 @@ function reconcileDurableMessages(messages, durable, events, runs) {
538
589
  ]);
539
590
  for (const message of durable) {
540
591
  if (message.role !== "assistant" ||
592
+ representedDurableAssistantIds.has(message.id) ||
541
593
  representedAssistantIds.has(message.id)) {
542
594
  continue;
543
595
  }
@@ -545,18 +597,11 @@ function reconcileDurableMessages(messages, durable, events, runs) {
545
597
  if (typeof runId !== "string" || !recoverableAssistantRunIds.has(runId)) {
546
598
  continue;
547
599
  }
548
- if (representedAssistantRunIds.has(runId))
549
- continue;
550
- // The snapshot holds an earlier run of this folded reply; the final pass
551
- // completes that message instead of adding a second copy.
552
- if (durableRunIds(message).some((id) => id !== runId && snapshotAssistantRunIds.has(id))) {
553
- continue;
554
- }
555
600
  missingMessages.push(message);
556
601
  representedAssistantIds.add(message.id);
557
602
  }
558
603
  const projectedMessages = [
559
- ...messages,
604
+ ...deduplicatedMessages,
560
605
  ...missingMessages.sort((left, right) => (durableIndexById.get(left.id) ?? 0) -
561
606
  (durableIndexById.get(right.id) ?? 0)),
562
607
  ]
@@ -591,6 +636,24 @@ function reconcileDurableMessages(messages, durable, events, runs) {
591
636
  }
592
637
  if (message.role !== "assistant")
593
638
  return message;
639
+ const representedRootId = rootMessageIdsBySnapshotMessageId.get(message.id);
640
+ const representedRoot = representedRootId
641
+ ? durableById.get(representedRootId)
642
+ : undefined;
643
+ if (representedRoot?.role === "assistant") {
644
+ const parts = mergeStoredReasoningParts(message.parts, representedRoot.parts);
645
+ const terminalStatus = representedRoot.status === "complete" ||
646
+ representedRoot.status === "error"
647
+ ? representedRoot.status
648
+ : undefined;
649
+ if (parts !== message.parts || terminalStatus !== undefined) {
650
+ message = {
651
+ ...message,
652
+ ...(parts !== message.parts ? { parts } : {}),
653
+ ...(terminalStatus ? { status: terminalStatus } : {}),
654
+ };
655
+ }
656
+ }
594
657
  const runId = snapshotRunId(message);
595
658
  const matched = durableById.get(message.id) ??
596
659
  (runId ? durableByRun.get(runId) : undefined);
@@ -606,6 +669,13 @@ function reconcileDurableMessages(messages, durable, events, runs) {
606
669
  : folded);
607
670
  if (stored?.role !== "assistant")
608
671
  return message;
672
+ const foldedRunIds = durableRunIds(stored);
673
+ const spansRuns = foldedRunIds.length > 1;
674
+ const foldedGroup = spansRuns
675
+ ? projectedMessages.filter((candidate) => candidate.role === "assistant" &&
676
+ foldedRunIds.includes(snapshotRunId(candidate) ?? ""))
677
+ : [message];
678
+ const isFoldedTerminalProjection = !spansRuns || foldedGroup.at(-1) === message;
609
679
  const messageMetadata = asRecord(message.metadata);
610
680
  const storedMetadata = asRecord(stored.metadata);
611
681
  const messageCustom = asRecord(messageMetadata?.custom);
@@ -619,30 +689,62 @@ function reconcileDurableMessages(messages, durable, events, runs) {
619
689
  : {}),
620
690
  }
621
691
  : message.metadata;
692
+ const reconciledParts = isFoldedTerminalProjection
693
+ ? mergeStoredReasoningParts(message.parts, stored.parts)
694
+ : message.parts;
622
695
  const reconciled = stored === matched
623
696
  ? {
624
697
  ...message,
698
+ ...(reconciledParts !== message.parts
699
+ ? { parts: reconciledParts }
700
+ : {}),
625
701
  ...(stored.status === "complete" || stored.status === "error"
626
702
  ? { status: stored.status }
627
703
  : {}),
628
704
  ...(metadata ? { metadata } : {}),
629
705
  }
630
- : message;
706
+ : reconciledParts !== message.parts
707
+ ? { ...message, parts: reconciledParts }
708
+ : message;
631
709
  const lastPart = reconciled.parts.at(-1);
632
- const foldedRunIds = durableRunIds(stored);
633
- const spansRuns = foldedRunIds.length > 1;
634
710
  if (lastPart && lastPart.type !== "text" && !spansRuns)
635
711
  return reconciled;
636
712
  // Only the last message the page saved for a folded reply takes the
637
713
  // continuation, and it is measured against everything the page saved.
638
- const foldedGroup = spansRuns
639
- ? projectedMessages.filter((candidate) => candidate.role === "assistant" &&
640
- foldedRunIds.includes(snapshotRunId(candidate) ?? ""))
641
- : [reconciled];
642
714
  if (spansRuns && foldedGroup.at(-1) !== message)
643
715
  return reconciled;
644
716
  const currentText = textOf(foldedGroup.flatMap((entry) => entry.parts));
645
717
  const storedText = textOf(stored.parts);
718
+ const orderedFoldedRunIds = [
719
+ ...new Set([
720
+ ...(Array.isArray(storedCustom?.foldedRunIds)
721
+ ? storedCustom.foldedRunIds
722
+ : []),
723
+ asRecord(stored.metadata)?.runId,
724
+ ].filter((id) => typeof id === "string")),
725
+ ];
726
+ const hasEarlierFoldedRun = orderedFoldedRunIds
727
+ .slice(0, -1)
728
+ .some((id) => snapshotAssistantRunIds.has(id));
729
+ if (spansRuns &&
730
+ runId === asRecord(stored.metadata)?.runId &&
731
+ !hasEarlierFoldedRun &&
732
+ currentText.length > 0 &&
733
+ storedText.endsWith(currentText) &&
734
+ storedText.length > currentText.length) {
735
+ let replacedText = false;
736
+ const parts = reconciled.parts.map((part) => {
737
+ if (part.type !== "text")
738
+ return part;
739
+ if (replacedText)
740
+ return { ...part, text: "" };
741
+ replacedText = true;
742
+ return { ...part, text: storedText };
743
+ });
744
+ if (!replacedText)
745
+ parts.push({ type: "text", text: storedText });
746
+ return { ...reconciled, parts };
747
+ }
646
748
  if (!storedText.startsWith(currentText) ||
647
749
  storedText.length <= currentText.length) {
648
750
  return reconciled;
@@ -777,6 +879,26 @@ function persistedAnnotations(annotations = [], messageIds) {
777
879
  ? [{ messageId, annotation: persistedAnnotation(annotation) }]
778
880
  : []);
779
881
  }
882
+ function assistantMessageIdsByRun(events) {
883
+ const messageIdsByRun = new Map();
884
+ for (const event of events ?? []) {
885
+ if ((event.type !== "message.created" &&
886
+ event.type !== "message.completed") ||
887
+ event.message.role !== "assistant") {
888
+ continue;
889
+ }
890
+ const messageIds = messageIdsByRun.get(event.runId) ?? new Set();
891
+ messageIds.add(event.message.id);
892
+ messageIdsByRun.set(event.runId, messageIds);
893
+ }
894
+ const uniqueMessageIdsByRun = new Map();
895
+ for (const [runId, messageIds] of messageIdsByRun) {
896
+ if (messageIds.size === 1) {
897
+ uniqueMessageIdsByRun.set(runId, [...messageIds][0]);
898
+ }
899
+ }
900
+ return uniqueMessageIdsByRun;
901
+ }
780
902
  function persistedFileUrl(url) {
781
903
  return url && !/^\s*data:/i.test(url) ? url : undefined;
782
904
  }
@@ -940,6 +1062,23 @@ function mergeStoredAndIncomingMessages(stored, incoming) {
940
1062
  }
941
1063
  return [...messages.values()];
942
1064
  }
1065
+ function mergeLegacyRootMessages(snapshotMessages, legacyMessages) {
1066
+ const merged = [...snapshotMessages];
1067
+ for (const message of legacyMessages) {
1068
+ const createdAt = Date.parse(message.createdAt ?? "");
1069
+ const index = merged.findIndex((candidate) => {
1070
+ const candidateCreatedAt = Date.parse(candidate.createdAt ?? "");
1071
+ return (Number.isFinite(createdAt) &&
1072
+ Number.isFinite(candidateCreatedAt) &&
1073
+ candidateCreatedAt > createdAt);
1074
+ });
1075
+ if (index === -1)
1076
+ merged.push(message);
1077
+ else
1078
+ merged.splice(index, 0, message);
1079
+ }
1080
+ return merged;
1081
+ }
943
1082
  function mergeStoredAndIncomingToolCalls(stored, incoming) {
944
1083
  const toolCalls = new Map(stored.map((toolCall) => [toolCall.id, toolCall]));
945
1084
  for (const toolCall of incoming) {
@@ -948,12 +1087,9 @@ function mergeStoredAndIncomingToolCalls(stored, incoming) {
948
1087
  return [...toolCalls.values()];
949
1088
  }
950
1089
  function mergeStoredAndIncomingWidgets(stored, incoming) {
951
- const widgets = new Map(stored.map((snapshot) => [
952
- JSON.stringify([snapshot.messageId, snapshot.widget.id]),
953
- snapshot,
954
- ]));
1090
+ const widgets = new Map(stored.map((snapshot) => [snapshot.widget.id, snapshot]));
955
1091
  for (const snapshot of incoming) {
956
- widgets.set(JSON.stringify([snapshot.messageId, snapshot.widget.id]), snapshot);
1092
+ widgets.set(snapshot.widget.id, snapshot);
957
1093
  }
958
1094
  return [...widgets.values()];
959
1095
  }
@@ -1007,6 +1143,52 @@ function persistedToolCalls(toolCalls = []) {
1007
1143
  ];
1008
1144
  });
1009
1145
  }
1146
+ function changedSnapshotEntries(incoming, previous, getId) {
1147
+ const previousById = new Map();
1148
+ for (const entry of Array.isArray(previous) ? previous : []) {
1149
+ const id = getId(entry);
1150
+ if (id)
1151
+ previousById.set(id, entry);
1152
+ }
1153
+ return incoming.filter((entry) => {
1154
+ const id = getId(entry);
1155
+ if (!id || !previousById.has(id))
1156
+ return true;
1157
+ return JSON.stringify(previousById.get(id)) !== JSON.stringify(entry);
1158
+ });
1159
+ }
1160
+ function samePersistedMessageContent(left, right) {
1161
+ return (left.role === right.role &&
1162
+ JSON.stringify(left.parts) === JSON.stringify(right.parts) &&
1163
+ JSON.stringify(left.status) === JSON.stringify(right.status) &&
1164
+ JSON.stringify(left.metadata) === JSON.stringify(right.metadata));
1165
+ }
1166
+ function snapshotEntryId(value) {
1167
+ const entry = asRecord(value);
1168
+ return typeof entry?.id === "string" ? entry.id : undefined;
1169
+ }
1170
+ function snapshotWidgetId(value) {
1171
+ const entry = asRecord(value);
1172
+ const widget = asRecord(entry?.widget);
1173
+ return typeof widget?.id === "string" ? widget.id : undefined;
1174
+ }
1175
+ function snapshotAnnotationKey(value) {
1176
+ const entry = asRecord(value);
1177
+ const annotation = asRecord(entry?.annotation);
1178
+ return typeof annotation?.id === "string"
1179
+ ? JSON.stringify(["id", annotation.id])
1180
+ : JSON.stringify(["value", value]);
1181
+ }
1182
+ function threadSnapshotBody(input) {
1183
+ const repository = { messages: [], agentKit: input.agentKit };
1184
+ return JSON.stringify({
1185
+ threadData: JSON.stringify(repository),
1186
+ title: input.title ??
1187
+ (typeof input.stored.title === "string" ? input.stored.title : ""),
1188
+ preview: typeof input.stored.preview === "string" ? input.stored.preview : "",
1189
+ messageCount: input.snapshotMessages.length,
1190
+ });
1191
+ }
1010
1192
  function storedMessageId(value) {
1011
1193
  const outer = asRecord(value);
1012
1194
  const message = asRecord(outer?.message ?? outer);
@@ -1148,6 +1330,33 @@ function httpErrorCode(status) {
1148
1330
  function isRetryableHttpStatus(status) {
1149
1331
  return status === 408 || status === 429 || status >= 500;
1150
1332
  }
1333
+ function snapshotAnnotationConflicts(value) {
1334
+ const response = asRecord(value);
1335
+ if (!response || response.ok !== true) {
1336
+ throw new TypeError("Agent chat thread save response must be an object.");
1337
+ }
1338
+ if (response.annotationConflicts === undefined)
1339
+ return [];
1340
+ if (!Array.isArray(response.annotationConflicts)) {
1341
+ throw new TypeError("Agent chat annotation conflicts must be an array.");
1342
+ }
1343
+ return response.annotationConflicts.map((value) => {
1344
+ const conflict = asRecord(value);
1345
+ if (typeof conflict?.messageId !== "string" ||
1346
+ (conflict.operation !== "upsert" && conflict.operation !== "remove") ||
1347
+ (conflict.annotationId !== undefined &&
1348
+ typeof conflict.annotationId !== "string")) {
1349
+ throw new TypeError("Agent chat annotation conflict is invalid.");
1350
+ }
1351
+ return {
1352
+ messageId: conflict.messageId,
1353
+ ...(typeof conflict.annotationId === "string"
1354
+ ? { annotationId: conflict.annotationId }
1355
+ : {}),
1356
+ operation: conflict.operation,
1357
+ };
1358
+ });
1359
+ }
1151
1360
  function scopedThreadEndpoint(endpoint, options) {
1152
1361
  if (!options.isolateHistoryByScope)
1153
1362
  return endpoint;
@@ -1168,6 +1377,8 @@ export function createAgentNativeAgentKitTransport(options = {}) {
1168
1377
  const fetcher = options.fetch ?? fetch;
1169
1378
  const now = options.adapter?.now ?? (() => new Date().toISOString());
1170
1379
  const promotionClaimIds = new Map();
1380
+ const durableAssistantMessageIdsByRun = new Map();
1381
+ const assistantHistoryMessageIdsByRun = new Map();
1171
1382
  let transport;
1172
1383
  function promotionClaimId(threadId, messageId) {
1173
1384
  const key = JSON.stringify([threadId, messageId]);
@@ -1218,6 +1429,33 @@ export function createAgentNativeAgentKitTransport(options = {}) {
1218
1429
  }));
1219
1430
  const storedMessageProjection = storedMessages(repository.messages, now, options.adapter?.textFormat).filter((message) => !isRenderedRunNotStartedNotice(message, failedRunIds));
1220
1431
  const queuedMessages = storedQueue(repository.queuedMessages, threadId, updatedAt);
1432
+ const agentKitMessages = Array.isArray(agentKit?.messages)
1433
+ ? agentKit.messages
1434
+ : undefined;
1435
+ const agentKitMessageIds = new Set(agentKitMessages?.map((message) => message.id) ?? []);
1436
+ const legacyRootMessages = storedMessageProjection.filter((message) => {
1437
+ if (agentKitMessageIds.has(message.id))
1438
+ return false;
1439
+ const metadata = asRecord(message.metadata);
1440
+ const custom = asRecord(metadata?.custom);
1441
+ if (typeof metadata?.runId === "string" ||
1442
+ typeof custom?.submittedRunId === "string" ||
1443
+ typeof custom?.agentKitMessageId === "string") {
1444
+ return false;
1445
+ }
1446
+ return !message.parts.some((part) => {
1447
+ if (part.type !== "data")
1448
+ return false;
1449
+ const repositoryPart = asRecord(part.data);
1450
+ return repositoryPart?.type === "tool-call";
1451
+ });
1452
+ });
1453
+ const mergeAllStoredMessages = agentKit?._mergeRootMessages === true || agentKitMessages?.length === 0;
1454
+ const messagesWithLegacyRoot = agentKitMessages &&
1455
+ agentKit?._mergeRootMessages !== true &&
1456
+ agentKitMessages.length > 0
1457
+ ? mergeLegacyRootMessages(agentKitMessages, legacyRootMessages)
1458
+ : undefined;
1221
1459
  const protocolSnapshot = agentKit
1222
1460
  ? parseAgentThreadSnapshot({
1223
1461
  id: threadId,
@@ -1225,8 +1463,10 @@ export function createAgentNativeAgentKitTransport(options = {}) {
1225
1463
  createdAt,
1226
1464
  updatedAt,
1227
1465
  metadata: asRecord(stored.metadata) ?? undefined,
1228
- messages: Array.isArray(agentKit.messages)
1229
- ? agentKit.messages
1466
+ messages: agentKitMessages
1467
+ ? mergeAllStoredMessages
1468
+ ? mergeStoredAndIncomingMessages(storedMessageProjection, agentKitMessages)
1469
+ : (messagesWithLegacyRoot ?? agentKitMessages)
1230
1470
  : storedMessageProjection,
1231
1471
  events: agentKit.events,
1232
1472
  runs: agentKit.runs,
@@ -1240,7 +1480,7 @@ export function createAgentNativeAgentKitTransport(options = {}) {
1240
1480
  : undefined;
1241
1481
  const durableMessages = storedMessageProjection;
1242
1482
  const messages = protocolSnapshot?.messages
1243
- ? reconcileDurableMessages(protocolSnapshot.messages, durableMessages, protocolSnapshot.events, protocolSnapshot.runs)
1483
+ ? reconcileDurableMessages(protocolSnapshot.messages, durableMessages, protocolSnapshot.events, protocolSnapshot.runs, protocolSnapshot.toolCalls ?? [])
1244
1484
  : durableMessages;
1245
1485
  const actionWidgets = storedActionWidgets(repository.messages);
1246
1486
  const canonicalToolCallMessageIds = new Map((protocolSnapshot?.toolCalls ?? []).flatMap((toolCall) => toolCall.messageId ? [[toolCall.id, toolCall.messageId]] : []));
@@ -1407,8 +1647,12 @@ export function createAgentNativeAgentKitTransport(options = {}) {
1407
1647
  else {
1408
1648
  throw new TypeError("Agent chat active-run response has an invalid status.");
1409
1649
  }
1650
+ const localRun = await protocolTransport.getRun?.({
1651
+ threadId,
1652
+ runId: value.runId,
1653
+ });
1410
1654
  return {
1411
- id: value.runId,
1655
+ id: localRun?.id ?? value.runId,
1412
1656
  threadId,
1413
1657
  status: runStatus,
1414
1658
  // The durable SSE endpoint replays from its first event when a browser
@@ -1422,10 +1666,32 @@ export function createAgentNativeAgentKitTransport(options = {}) {
1422
1666
  const thread = stored ? projectThread(threadId, stored) : null;
1423
1667
  if (!stored || !thread)
1424
1668
  return thread;
1669
+ const repository = storedRepository(stored);
1670
+ const durableMessages = storedMessages(repository.messages, now, options.adapter?.textFormat);
1671
+ for (const message of durableMessages) {
1672
+ if (message.role !== "assistant")
1673
+ continue;
1674
+ const foldedRunIds = durableRunIds(message);
1675
+ if (foldedRunIds.length < 2)
1676
+ continue;
1677
+ for (const runId of foldedRunIds) {
1678
+ const previous = durableAssistantMessageIdsByRun.get(runId);
1679
+ durableAssistantMessageIdsByRun.set(runId, previous === undefined || previous === message.id ? message.id : null);
1680
+ }
1681
+ }
1682
+ for (const [runId, messageId] of assistantMessageIdsByRun(thread.events)) {
1683
+ assistantHistoryMessageIdsByRun.set(runId, messageId);
1684
+ }
1685
+ const assistantMessageIds = new Set(thread.messages.flatMap((message) => message.role === "assistant" ? [message.id] : []));
1686
+ for (const run of thread.runs ?? []) {
1687
+ if (typeof run.activeMessageId === "string" &&
1688
+ assistantMessageIds.has(run.activeMessageId)) {
1689
+ assistantHistoryMessageIdsByRun.set(run.id, run.activeMessageId);
1690
+ }
1691
+ }
1425
1692
  if (options.runtime && !isAgentNativeChatRuntime(options.runtime)) {
1426
1693
  return thread;
1427
1694
  }
1428
- const durableMessages = storedMessages(storedRepository(stored).messages, now, options.adapter?.textFormat);
1429
1695
  const completedRunIds = completedDurableRunIds(durableMessages);
1430
1696
  const userStoppedRunIds = userStoppedDurableRunIds(durableMessages);
1431
1697
  const durableFailures = durableRunFailures(durableMessages);
@@ -1493,7 +1759,7 @@ export function createAgentNativeAgentKitTransport(options = {}) {
1493
1759
  .map((run) => run.id)
1494
1760
  : [];
1495
1761
  const failedRunIds = new Set(runs.flatMap((run) => run.status === "failed" && run.error ? [run.id] : []));
1496
- const messages = reconcileDurableMessages(thread.messages, durableMessages.filter((message) => !isRenderedRunNotStartedNotice(message, failedRunIds)), thread.events, runs);
1762
+ const messages = reconcileDurableMessages(thread.messages, durableMessages.filter((message) => !isRenderedRunNotStartedNotice(message, failedRunIds)), thread.events, runs, thread.toolCalls);
1497
1763
  const replayFromStart = [
1498
1764
  "running",
1499
1765
  "awaiting_approval",
@@ -1512,10 +1778,10 @@ export function createAgentNativeAgentKitTransport(options = {}) {
1512
1778
  ? messages.filter((message) => {
1513
1779
  if (message.role !== "assistant")
1514
1780
  return true;
1515
- const runId = asRecord(message.metadata)?.runId;
1516
- return !(runId === discoveredRun?.id ||
1781
+ return !(durableRunIds(message).includes(discoveredRun?.id ?? "") ||
1517
1782
  replayedMessageIds.has(message.id) ||
1518
- (message.status === "streaming" && typeof runId !== "string"));
1783
+ (message.status === "streaming" &&
1784
+ typeof asRecord(message.metadata)?.runId !== "string"));
1519
1785
  })
1520
1786
  : messages;
1521
1787
  return {
@@ -1575,22 +1841,6 @@ export function createAgentNativeAgentKitTransport(options = {}) {
1575
1841
  : [], input.snapshot.widgets ?? [])
1576
1842
  : input.snapshot.widgets;
1577
1843
  const compactEvents = persistedHistoryEvents(input.snapshot.events);
1578
- const compactRunIds = new Set(compactEvents.map((event) => event.runId));
1579
- const eventsById = new Map();
1580
- const previousEvents = Array.isArray(previousAgentKit.events)
1581
- ? previousAgentKit.events
1582
- : [];
1583
- for (const event of previousEvents) {
1584
- const record = asRecord(event);
1585
- if (typeof record?.id === "string" &&
1586
- typeof record.runId === "string" &&
1587
- !compactRunIds.has(record.runId)) {
1588
- eventsById.set(record.id, event);
1589
- }
1590
- }
1591
- for (const event of compactEvents) {
1592
- eventsById.set(event.id, event);
1593
- }
1594
1844
  const runsById = new Map();
1595
1845
  const previousRuns = Array.isArray(previousAgentKit.runs)
1596
1846
  ? previousAgentKit.runs
@@ -1601,43 +1851,445 @@ export function createAgentNativeAgentKitTransport(options = {}) {
1601
1851
  runsById.set(record.id, run);
1602
1852
  }
1603
1853
  const snapshotMessageIds = new Set(snapshotMessages.map((message) => message.id));
1854
+ const previousAgentKitMessageIds = new Set((Array.isArray(previousAgentKit.messages)
1855
+ ? previousAgentKit.messages
1856
+ : []).flatMap((message) => {
1857
+ const id = snapshotEntryId(message);
1858
+ return id ? [id] : [];
1859
+ }));
1860
+ const topLevelMessageProjections = new Map(persistedMessages(Array.isArray(repository.messages)
1861
+ ? storedMessages(repository.messages, now, options.adapter?.textFormat)
1862
+ : []).map((message) => [message.id, message]));
1863
+ const persistedSnapshotMessages = persistedMessages(snapshotMessages);
1864
+ const changedMessages = changedSnapshotEntries(persistedSnapshotMessages, previousAgentKit.messages, snapshotEntryId).filter((message) => {
1865
+ if (previousAgentKitMessageIds.has(message.id))
1866
+ return true;
1867
+ const topLevelMessage = topLevelMessageProjections.get(message.id);
1868
+ return (!topLevelMessage ||
1869
+ !samePersistedMessageContent(topLevelMessage, message));
1870
+ });
1871
+ const mergeRootMessages = previousAgentKit._mergeRootMessages === true ||
1872
+ persistedSnapshotMessages.some((message) => {
1873
+ const topLevelMessage = topLevelMessageProjections.get(message.id);
1874
+ return (!previousAgentKitMessageIds.has(message.id) &&
1875
+ topLevelMessage !== undefined &&
1876
+ samePersistedMessageContent(topLevelMessage, message));
1877
+ });
1878
+ const changedWidgets = changedSnapshotEntries(persistedActionWidgets(snapshotWidgets, snapshotMessageIds), previousAgentKit.widgets, snapshotWidgetId);
1879
+ const changedToolCalls = changedSnapshotEntries(persistedToolCalls(snapshotToolCalls), previousAgentKit.toolCalls, snapshotEntryId);
1880
+ const previousEventsByRun = new Map();
1881
+ for (const event of Array.isArray(previousAgentKit.events)
1882
+ ? previousAgentKit.events
1883
+ : []) {
1884
+ const runId = asRecord(event)?.runId;
1885
+ if (typeof runId !== "string")
1886
+ continue;
1887
+ const events = previousEventsByRun.get(runId);
1888
+ if (events)
1889
+ events.push(event);
1890
+ else
1891
+ previousEventsByRun.set(runId, [event]);
1892
+ }
1893
+ const incomingEventsByRun = new Map();
1894
+ for (const event of compactEvents) {
1895
+ const events = incomingEventsByRun.get(event.runId);
1896
+ if (events)
1897
+ events.push(event);
1898
+ else
1899
+ incomingEventsByRun.set(event.runId, [event]);
1900
+ }
1901
+ const changedEventRunIds = new Set();
1902
+ for (const [runId, events] of incomingEventsByRun) {
1903
+ if (JSON.stringify(previousEventsByRun.get(runId) ?? []) ===
1904
+ JSON.stringify(events)) {
1905
+ continue;
1906
+ }
1907
+ changedEventRunIds.add(runId);
1908
+ }
1909
+ const changedEvents = compactEvents.filter((event) => changedEventRunIds.has(event.runId));
1910
+ const eventRunLastSequenceById = new Map();
1911
+ for (const run of input.snapshot.runs ?? []) {
1912
+ if (typeof run.id === "string" &&
1913
+ Number.isSafeInteger(run.lastSequence) &&
1914
+ run.lastSequence >= 0) {
1915
+ eventRunLastSequenceById.set(run.id, run.lastSequence);
1916
+ }
1917
+ }
1918
+ for (const event of input.snapshot.events ?? []) {
1919
+ const previousSequence = eventRunLastSequenceById.get(event.runId) ?? 0;
1920
+ if (Number.isSafeInteger(event.sequence)) {
1921
+ eventRunLastSequenceById.set(event.runId, Math.max(previousSequence, event.sequence));
1922
+ }
1923
+ }
1924
+ const changedEventsByRun = new Map();
1925
+ for (const event of changedEvents) {
1926
+ const runEvents = changedEventsByRun.get(event.runId);
1927
+ if (runEvents)
1928
+ runEvents.push(event);
1929
+ else
1930
+ changedEventsByRun.set(event.runId, [event]);
1931
+ }
1932
+ const eventRunSnapshotBatches = new Map();
1933
+ for (const [runId, events] of changedEventsByRun) {
1934
+ const lastSequence = eventRunLastSequenceById.get(runId) ?? events.length;
1935
+ const snapshotId = typeof crypto !== "undefined" && crypto.randomUUID
1936
+ ? crypto.randomUUID()
1937
+ : `${Date.now()}-${Math.random().toString(36).slice(2)}`;
1938
+ eventRunSnapshotBatches.set(runId, {
1939
+ runId,
1940
+ snapshotId,
1941
+ lastSequence,
1942
+ expectedEventCount: new Set(events.map((event) => event.id)).size,
1943
+ });
1944
+ }
1945
+ const lastChangedEventIndexByRun = new Map();
1946
+ changedEvents.forEach((event, index) => {
1947
+ lastChangedEventIndexByRun.set(event.runId, index);
1948
+ });
1949
+ const changedEventUpdates = changedEvents.map((event, index) => {
1950
+ return {
1951
+ key: "events",
1952
+ entry: event,
1953
+ eventRunSnapshotBatch: eventRunSnapshotBatches.get(event.runId),
1954
+ completesEventRunSnapshot: lastChangedEventIndexByRun.get(event.runId) === index,
1955
+ };
1956
+ });
1957
+ const persistedRuns = [...runsById.values()].map((run) => {
1958
+ const record = asRecord(run);
1959
+ return record && record.error !== undefined
1960
+ ? { ...record, error: persistedError(record.error) }
1961
+ : run;
1962
+ });
1963
+ const changedRuns = changedSnapshotEntries(persistedRuns, previousAgentKit.runs, snapshotEntryId);
1964
+ const latestRun = latestSnapshotRunRecord(persistedRuns);
1965
+ const latestRunId = latestRun?.id;
1966
+ const previousLatestRun = latestSnapshotRunRecord(previousAgentKit.runs);
1967
+ const latestRunSequence = typeof latestRun?.lastSequence === "number" &&
1968
+ Number.isSafeInteger(latestRun.lastSequence) &&
1969
+ latestRun.lastSequence >= 0
1970
+ ? latestRun.lastSequence
1971
+ : undefined;
1972
+ const previousLatestRunSequence = typeof previousLatestRun?.lastSequence === "number" &&
1973
+ Number.isSafeInteger(previousLatestRun.lastSequence) &&
1974
+ previousLatestRun.lastSequence >= 0
1975
+ ? previousLatestRun.lastSequence
1976
+ : undefined;
1977
+ const latestRunIsNew = latestRun?.id !== previousLatestRun?.id;
1978
+ const latestRunJustCompleted = latestRun?.id === previousLatestRun?.id &&
1979
+ previousLatestRun?.status !== "completed" &&
1980
+ latestRunSequence !== undefined &&
1981
+ previousLatestRunSequence !== undefined &&
1982
+ latestRunSequence >= previousLatestRunSequence;
1983
+ const latestRunSuggestionsChanged = input.snapshot.suggestions !== undefined &&
1984
+ JSON.stringify(input.snapshot.suggestions) !==
1985
+ JSON.stringify(previousAgentKit.suggestions);
1986
+ const shouldPersistLatestRunSuggestions = latestRun?.status === "completed" &&
1987
+ (latestRunIsNew ||
1988
+ latestRunJustCompleted ||
1989
+ (latestRunSuggestionsChanged &&
1990
+ latestRunSequence !== undefined &&
1991
+ previousLatestRunSequence !== undefined &&
1992
+ latestRunSequence > previousLatestRunSequence));
1604
1993
  const annotations = input.snapshot.annotations ??
1605
1994
  (Array.isArray(previousAgentKit.annotations)
1606
1995
  ? previousAgentKit.annotations
1607
1996
  : []);
1608
- const agentKit = {
1609
- ...previousAgentKit,
1610
- messages: persistedMessages(snapshotMessages),
1611
- widgets: persistedActionWidgets(snapshotWidgets, snapshotMessageIds),
1612
- toolCalls: persistedToolCalls(snapshotToolCalls),
1613
- events: [...eventsById.values()],
1614
- runs: [...runsById.values()].map((run) => {
1615
- const record = asRecord(run);
1616
- return record && record.error !== undefined
1617
- ? { ...record, error: persistedError(record.error) }
1618
- : run;
1619
- }),
1620
- activeRunIds: input.snapshot.activeRunIds ?? [],
1621
- suggestions: input.snapshot.suggestions ?? previousAgentKit.suggestions,
1622
- annotations: persistedAnnotations(annotations, snapshotMessageIds),
1997
+ const persistedSnapshotAnnotations = persistedAnnotations(annotations, snapshotMessageIds);
1998
+ const previousAnnotationsByMessage = new Map();
1999
+ for (const annotation of Array.isArray(previousAgentKit.annotations)
2000
+ ? previousAgentKit.annotations
2001
+ : []) {
2002
+ const messageId = asRecord(annotation)?.messageId;
2003
+ if (typeof messageId !== "string")
2004
+ continue;
2005
+ const messageAnnotations = previousAnnotationsByMessage.get(messageId);
2006
+ if (messageAnnotations)
2007
+ messageAnnotations.push(annotation);
2008
+ else
2009
+ previousAnnotationsByMessage.set(messageId, [annotation]);
2010
+ }
2011
+ const incomingAnnotationsByMessage = new Map();
2012
+ for (const annotation of persistedSnapshotAnnotations) {
2013
+ const messageAnnotations = incomingAnnotationsByMessage.get(annotation.messageId);
2014
+ if (messageAnnotations)
2015
+ messageAnnotations.push(annotation);
2016
+ else
2017
+ incomingAnnotationsByMessage.set(annotation.messageId, [annotation]);
2018
+ }
2019
+ const annotationMessageIdsToReplace = [];
2020
+ const changedAnnotations = [];
2021
+ const annotationMessageIds = new Set([
2022
+ ...previousAnnotationsByMessage.keys(),
2023
+ ...incomingAnnotationsByMessage.keys(),
2024
+ ]);
2025
+ for (const messageId of annotationMessageIds) {
2026
+ const previous = previousAnnotationsByMessage.get(messageId) ?? [];
2027
+ const incoming = incomingAnnotationsByMessage.get(messageId) ?? [];
2028
+ const previousByKey = new Map(previous.map((annotation) => [
2029
+ snapshotAnnotationKey(annotation),
2030
+ annotation,
2031
+ ]));
2032
+ const incomingKeys = new Set(incoming.map(snapshotAnnotationKey));
2033
+ const annotationsToRemove = previous.flatMap((annotation) => {
2034
+ const key = snapshotAnnotationKey(annotation);
2035
+ return incomingKeys.has(key) ? [] : [{ key, baseline: annotation }];
2036
+ });
2037
+ const changedIncoming = incoming.flatMap((annotation) => {
2038
+ const previousAnnotation = previousByKey.get(snapshotAnnotationKey(annotation));
2039
+ if (previousAnnotation !== undefined &&
2040
+ JSON.stringify(previousAnnotation) === JSON.stringify(annotation)) {
2041
+ return [];
2042
+ }
2043
+ return [
2044
+ {
2045
+ entry: annotation,
2046
+ baseline: previousAnnotation ?? null,
2047
+ },
2048
+ ];
2049
+ });
2050
+ if (annotationsToRemove.length === 0 && changedIncoming.length === 0) {
2051
+ continue;
2052
+ }
2053
+ if (annotationsToRemove.length > 0) {
2054
+ let removals = [];
2055
+ for (const removal of annotationsToRemove) {
2056
+ const candidate = {
2057
+ messageId,
2058
+ annotationsToRemove: [...removals, removal],
2059
+ };
2060
+ if (new TextEncoder().encode(JSON.stringify(candidate)).byteLength <=
2061
+ MAX_THREAD_SNAPSHOT_ANNOTATION_REPLACEMENT_BYTES) {
2062
+ removals.push(removal);
2063
+ continue;
2064
+ }
2065
+ if (removals.length === 0) {
2066
+ throw new RangeError("An annotation removal exceeds the persistence request size limit.");
2067
+ }
2068
+ annotationMessageIdsToReplace.push({
2069
+ messageId,
2070
+ annotationsToRemove: removals,
2071
+ });
2072
+ removals = [removal];
2073
+ const singleRemoval = { messageId, annotationsToRemove: removals };
2074
+ if (new TextEncoder().encode(JSON.stringify(singleRemoval)).byteLength >
2075
+ MAX_THREAD_SNAPSHOT_ANNOTATION_REPLACEMENT_BYTES) {
2076
+ throw new RangeError("An annotation removal exceeds the persistence request size limit.");
2077
+ }
2078
+ }
2079
+ if (removals.length > 0) {
2080
+ annotationMessageIdsToReplace.push({
2081
+ messageId,
2082
+ annotationsToRemove: removals,
2083
+ });
2084
+ }
2085
+ }
2086
+ changedAnnotations.push(...changedIncoming);
2087
+ }
2088
+ const updates = [
2089
+ ...changedMessages.map((entry) => ({ key: "messages", entry })),
2090
+ ...changedWidgets.map((entry) => ({ key: "widgets", entry })),
2091
+ ...changedToolCalls.map((entry) => ({ key: "toolCalls", entry })),
2092
+ ...changedEventUpdates,
2093
+ ...changedAnnotations.map((entry) => ({
2094
+ key: "annotationUpserts",
2095
+ entry,
2096
+ })),
2097
+ ...annotationMessageIdsToReplace.map((entry) => ({
2098
+ key: "annotationMessageIdsToReplace",
2099
+ entry,
2100
+ })),
2101
+ ...changedRuns.map((entry) => ({
2102
+ key: "runs",
2103
+ entry,
2104
+ ...(shouldPersistLatestRunSuggestions &&
2105
+ asRecord(entry)?.id === latestRunId
2106
+ ? { includeSuggestions: true }
2107
+ : {}),
2108
+ })),
2109
+ ];
2110
+ const fixedAgentKit = {
2111
+ _snapshotDelta: true,
2112
+ ...(mergeRootMessages ? { _mergeRootMessages: true } : {}),
2113
+ messages: [],
2114
+ widgets: [],
2115
+ toolCalls: [],
2116
+ events: [],
2117
+ eventRunSnapshotBatches: [],
2118
+ annotationMessageIdsToReplace: [],
2119
+ annotations: [],
2120
+ annotationUpserts: [],
2121
+ runs: [],
1623
2122
  };
1624
2123
  const requestHeaders = await headers({ sessionId: input.threadId });
1625
2124
  requestHeaders.set("content-type", "application/json");
1626
- const snapshotRepository = { ...repository };
1627
- delete snapshotRepository.queuedMessages;
1628
- const response = await fetcher(scopedThreadEndpoint(`${apiUrl}/threads/${encodeURIComponent(input.threadId)}`, options), {
1629
- method: "PUT",
1630
- headers: requestHeaders,
1631
- body: JSON.stringify({
1632
- threadData: JSON.stringify({ ...snapshotRepository, agentKit }),
1633
- title: input.snapshot.title ??
1634
- (typeof stored.title === "string" ? stored.title : ""),
1635
- preview: typeof stored.preview === "string" ? stored.preview : "",
1636
- messageCount: snapshotMessages.length,
1637
- }),
1638
- });
1639
- if (!response.ok)
1640
- throw await responseError(response);
2125
+ const bodyFor = (entries) => {
2126
+ const agentKit = { ...fixedAgentKit };
2127
+ for (const key of [
2128
+ "messages",
2129
+ "widgets",
2130
+ "toolCalls",
2131
+ "events",
2132
+ "eventRunSnapshotBatches",
2133
+ "annotationMessageIdsToReplace",
2134
+ "annotations",
2135
+ "annotationUpserts",
2136
+ "runs",
2137
+ ]) {
2138
+ agentKit[key] = [];
2139
+ }
2140
+ const eventSnapshotBatchRunIds = new Set();
2141
+ const completedEventRunIds = new Set(entries.flatMap((update) => update.key === "events" &&
2142
+ update.completesEventRunSnapshot &&
2143
+ update.eventRunSnapshotBatch
2144
+ ? [update.eventRunSnapshotBatch.runId]
2145
+ : []));
2146
+ for (const { key, entry, eventRunSnapshotBatch, includeSuggestions, } of entries) {
2147
+ if (key === "events") {
2148
+ agentKit.events.push(entry);
2149
+ if (eventRunSnapshotBatch &&
2150
+ !eventSnapshotBatchRunIds.has(eventRunSnapshotBatch.runId)) {
2151
+ eventSnapshotBatchRunIds.add(eventRunSnapshotBatch.runId);
2152
+ agentKit.eventRunSnapshotBatches.push({
2153
+ ...eventRunSnapshotBatch,
2154
+ complete: completedEventRunIds.has(eventRunSnapshotBatch.runId),
2155
+ });
2156
+ }
2157
+ continue;
2158
+ }
2159
+ if (key === "runs") {
2160
+ agentKit.runs.push(entry);
2161
+ if (includeSuggestions && input.snapshot.suggestions !== undefined) {
2162
+ agentKit.suggestions = input.snapshot.suggestions;
2163
+ }
2164
+ }
2165
+ else {
2166
+ agentKit[key].push(entry);
2167
+ }
2168
+ }
2169
+ return threadSnapshotBody({
2170
+ stored,
2171
+ title: input.snapshot.title,
2172
+ snapshotMessages,
2173
+ agentKit,
2174
+ });
2175
+ };
2176
+ const chunks = [];
2177
+ let chunk = [];
2178
+ const encoder = new TextEncoder();
2179
+ const baseBodyBytes = encoder.encode(bodyFor([])).byteLength;
2180
+ let chunkBytes = baseBodyBytes;
2181
+ const chunkKeyCounts = new Map();
2182
+ for (const update of updates) {
2183
+ const duplicateAnnotationRemoval = update.key === "annotationMessageIdsToReplace" &&
2184
+ chunk.some((entry) => entry.key === "annotationMessageIdsToReplace" &&
2185
+ asRecord(entry.entry)?.messageId ===
2186
+ asRecord(update.entry)?.messageId);
2187
+ if (duplicateAnnotationRemoval) {
2188
+ chunks.push(chunk);
2189
+ chunk = [];
2190
+ chunkBytes = baseBodyBytes;
2191
+ chunkKeyCounts.clear();
2192
+ }
2193
+ const entryBytes = encoder.encode(bodyFor([update])).byteLength - baseBodyBytes;
2194
+ const addition = entryBytes + (chunkKeyCounts.has(update.key) ? 1 : 0);
2195
+ if (chunkBytes + addition > MAX_THREAD_SNAPSHOT_REQUEST_BYTES) {
2196
+ if (chunk.length === 0) {
2197
+ throw new RangeError("An Agent chat snapshot entry exceeds the persistence request size limit.");
2198
+ }
2199
+ chunks.push(chunk);
2200
+ chunk = [];
2201
+ chunkBytes = baseBodyBytes;
2202
+ chunkKeyCounts.clear();
2203
+ }
2204
+ const firstEntryBytes = entryBytes + (chunkKeyCounts.has(update.key) ? 1 : 0);
2205
+ if (chunkBytes + firstEntryBytes > MAX_THREAD_SNAPSHOT_REQUEST_BYTES) {
2206
+ throw new RangeError("An Agent chat snapshot entry exceeds the persistence request size limit.");
2207
+ }
2208
+ chunk.push(update);
2209
+ chunkBytes += firstEntryBytes;
2210
+ chunkKeyCounts.set(update.key, (chunkKeyCounts.get(update.key) ?? 0) + 1);
2211
+ }
2212
+ if (chunk.length > 0 || chunks.length === 0)
2213
+ chunks.push(chunk);
2214
+ const threadUrl = scopedThreadEndpoint(apiUrl + "/threads/" + encodeURIComponent(input.threadId), options);
2215
+ const persistChunk = async (entries) => {
2216
+ const body = bodyFor(entries);
2217
+ if (encoder.encode(body).byteLength > MAX_THREAD_SNAPSHOT_REQUEST_BYTES) {
2218
+ if (entries.length > 1) {
2219
+ const middle = Math.floor(entries.length / 2);
2220
+ return [
2221
+ ...(await persistChunk(entries.slice(0, middle))),
2222
+ ...(await persistChunk(entries.slice(middle))),
2223
+ ];
2224
+ }
2225
+ throw new RangeError("An Agent chat snapshot entry exceeds the persistence request size limit.");
2226
+ }
2227
+ for (let attempt = 0;; attempt += 1) {
2228
+ const retryAfterReadFailure = async (error) => {
2229
+ if (asRecord(error)?.name === "AbortError" ||
2230
+ attempt >= MAX_THREAD_SNAPSHOT_RETRIES) {
2231
+ throw error;
2232
+ }
2233
+ await new Promise((resolve) => setTimeout(resolve, 100 * 2 ** attempt));
2234
+ };
2235
+ let response;
2236
+ try {
2237
+ response = await fetcher(threadUrl, {
2238
+ method: "PUT",
2239
+ headers: requestHeaders,
2240
+ body,
2241
+ });
2242
+ }
2243
+ catch (error) {
2244
+ await retryAfterReadFailure(error);
2245
+ continue;
2246
+ }
2247
+ if (response.status === 413 && entries.length > 1) {
2248
+ const middle = Math.floor(entries.length / 2);
2249
+ return [
2250
+ ...(await persistChunk(entries.slice(0, middle))),
2251
+ ...(await persistChunk(entries.slice(middle))),
2252
+ ];
2253
+ }
2254
+ if (!response.ok) {
2255
+ let error;
2256
+ try {
2257
+ error = await responseError(response);
2258
+ }
2259
+ catch (readError) {
2260
+ await retryAfterReadFailure(readError);
2261
+ continue;
2262
+ }
2263
+ if (asRecord(error)?.retryable !== true ||
2264
+ attempt >= MAX_THREAD_SNAPSHOT_RETRIES) {
2265
+ throw error;
2266
+ }
2267
+ await new Promise((resolve) => setTimeout(resolve, 100 * 2 ** attempt));
2268
+ continue;
2269
+ }
2270
+ try {
2271
+ return snapshotAnnotationConflicts(await response.json());
2272
+ }
2273
+ catch (readError) {
2274
+ await retryAfterReadFailure(readError);
2275
+ }
2276
+ }
2277
+ };
2278
+ const annotationConflicts = new Map();
2279
+ for (const entries of chunks) {
2280
+ for (const conflict of await persistChunk(entries)) {
2281
+ annotationConflicts.set(JSON.stringify([
2282
+ conflict.messageId,
2283
+ conflict.annotationId ?? null,
2284
+ conflict.operation,
2285
+ ]), conflict);
2286
+ }
2287
+ }
2288
+ if (annotationConflicts.size > 0) {
2289
+ const error = new Error("An annotation changed in another save. The latest saved value was preserved; reload the thread before retrying.");
2290
+ error.name = "AgentAnnotationSaveConflictError";
2291
+ throw error;
2292
+ }
1641
2293
  }
1642
2294
  async function persistQueueMutation(threadId, mutation) {
1643
2295
  const requestHeaders = await headers({ sessionId: threadId });
@@ -1707,7 +2359,11 @@ export function createAgentNativeAgentKitTransport(options = {}) {
1707
2359
  }
1708
2360
  if (lastError)
1709
2361
  throw lastError;
1710
- throw new AgentKitRunSlotBusyError(typeof status?.runId === "string" ? status.runId : undefined);
2362
+ const activeRunId = typeof status?.runId === "string" ? status.runId : undefined;
2363
+ const localRun = activeRunId
2364
+ ? await protocolTransport.getRun?.({ threadId, runId: activeRunId })
2365
+ : null;
2366
+ throw new AgentKitRunSlotBusyError(localRun?.id ?? activeRunId);
1711
2367
  }
1712
2368
  async function startRunTrackingRunningState(input, context) {
1713
2369
  dispatchAgentChatRunning({
@@ -1777,8 +2433,6 @@ export function createAgentNativeAgentKitTransport(options = {}) {
1777
2433
  connectionRequests: true,
1778
2434
  },
1779
2435
  operations: {
1780
- ...options.operations,
1781
- persistThreadSnapshot: options.operations?.persistThreadSnapshot ?? persistThreadSnapshot,
1782
2436
  getThread: async ({ threadId }) => {
1783
2437
  const thread = await snapshot(threadId);
1784
2438
  if (!thread)
@@ -1933,8 +2587,8 @@ export function createAgentNativeAgentKitTransport(options = {}) {
1933
2587
  if (fromMessageId) {
1934
2588
  const repository = storedRepository(source);
1935
2589
  const agentKit = asRecord(repository.agentKit);
1936
- const sourceMessages = Array.isArray(agentKit?.messages)
1937
- ? agentKit.messages
2590
+ const sourceMessages = agentKit
2591
+ ? projectThread(threadId, source).messages
1938
2592
  : repository.messages;
1939
2593
  if (!Array.isArray(sourceMessages)) {
1940
2594
  throw new Error("The Agent-Native thread cannot be forked from a message without durable history.");
@@ -2026,6 +2680,7 @@ export function createAgentNativeAgentKitTransport(options = {}) {
2026
2680
  trackRunFeedback({ runId, threadId, positive: value === "positive" });
2027
2681
  },
2028
2682
  ...options.operations,
2683
+ persistThreadSnapshot: options.operations?.persistThreadSnapshot ?? persistThreadSnapshot,
2029
2684
  },
2030
2685
  });
2031
2686
  const protocolStartRun = protocolTransport.startRun.bind(protocolTransport);
@@ -2066,7 +2721,16 @@ export function createAgentNativeAgentKitTransport(options = {}) {
2066
2721
  }
2067
2722
  catch (error) {
2068
2723
  const record = asRecord(error);
2069
- const activeRunId = record?.activeRunId;
2724
+ const serverActiveRunId = typeof record?.activeRunId === "string"
2725
+ ? record.activeRunId
2726
+ : undefined;
2727
+ const localRun = serverActiveRunId
2728
+ ? await protocolTransport.getRun?.({
2729
+ threadId: input.threadId,
2730
+ runId: serverActiveRunId,
2731
+ })
2732
+ : null;
2733
+ const activeRunId = localRun?.id ?? serverActiveRunId;
2070
2734
  const explicitNonSlotCode = typeof record?.code === "string" &&
2071
2735
  record.code !== "run_slot_busy" &&
2072
2736
  record.code !== "http_409";
@@ -2095,9 +2759,95 @@ export function createAgentNativeAgentKitTransport(options = {}) {
2095
2759
  runId: input.runId,
2096
2760
  });
2097
2761
  const assistantMessageIds = new Set();
2762
+ // Continuation runs keep the durable identity of their folded reply.
2763
+ let durableAssistantMessageId = durableAssistantMessageIdsByRun.get(input.runId) ?? undefined;
2764
+ let foldedAssistantSourceMessageId;
2765
+ const foldedMessageIdFor = (sourceMessageId) => {
2766
+ if (!durableAssistantMessageId || !sourceMessageId)
2767
+ return undefined;
2768
+ foldedAssistantSourceMessageId ??= sourceMessageId;
2769
+ return foldedAssistantSourceMessageId === sourceMessageId
2770
+ ? durableAssistantMessageId
2771
+ : undefined;
2772
+ };
2098
2773
  let responseStarted = false;
2099
2774
  let turnId;
2100
- for await (const event of subscribeToRun(input)) {
2775
+ for await (const sourceEvent of subscribeToRun(input)) {
2776
+ const interruptedRunId = protocolInterruptedRunId(sourceEvent.metadata);
2777
+ const inheritedMessageId = interruptedRunId
2778
+ ? (durableAssistantMessageIdsByRun.get(interruptedRunId) ??
2779
+ assistantHistoryMessageIdsByRun.get(interruptedRunId))
2780
+ : undefined;
2781
+ if (inheritedMessageId) {
2782
+ if (durableAssistantMessageId !== inheritedMessageId) {
2783
+ foldedAssistantSourceMessageId = undefined;
2784
+ }
2785
+ durableAssistantMessageId = inheritedMessageId;
2786
+ durableAssistantMessageIdsByRun.set(input.runId, inheritedMessageId);
2787
+ }
2788
+ let event = sourceEvent;
2789
+ if (isAgentNativeChatRuntime(runtime)) {
2790
+ switch (sourceEvent.type) {
2791
+ case "message.created":
2792
+ case "message.completed":
2793
+ if (sourceEvent.message.role === "assistant") {
2794
+ const messageId = foldedMessageIdFor(sourceEvent.message.id);
2795
+ if (messageId && messageId !== sourceEvent.message.id) {
2796
+ event = {
2797
+ ...sourceEvent,
2798
+ message: { ...sourceEvent.message, id: messageId },
2799
+ };
2800
+ }
2801
+ }
2802
+ break;
2803
+ case "message.delta":
2804
+ case "reasoning.delta": {
2805
+ const messageId = foldedMessageIdFor(sourceEvent.messageId);
2806
+ if (messageId && messageId !== sourceEvent.messageId) {
2807
+ event = { ...sourceEvent, messageId };
2808
+ }
2809
+ break;
2810
+ }
2811
+ case "tool.started":
2812
+ case "tool.updated": {
2813
+ if (sourceEvent.toolCall.messageId) {
2814
+ const messageId = foldedMessageIdFor(sourceEvent.toolCall.messageId);
2815
+ if (messageId && messageId !== sourceEvent.toolCall.messageId) {
2816
+ event = {
2817
+ ...sourceEvent,
2818
+ toolCall: { ...sourceEvent.toolCall, messageId },
2819
+ };
2820
+ }
2821
+ }
2822
+ break;
2823
+ }
2824
+ case "widget.created":
2825
+ case "widget.updated":
2826
+ case "annotation.created":
2827
+ case "annotation.updated": {
2828
+ if (sourceEvent.messageId) {
2829
+ const messageId = foldedMessageIdFor(sourceEvent.messageId);
2830
+ if (messageId && messageId !== sourceEvent.messageId) {
2831
+ event = { ...sourceEvent, messageId };
2832
+ }
2833
+ }
2834
+ break;
2835
+ }
2836
+ case "action.started": {
2837
+ if (sourceEvent.invocation.messageId) {
2838
+ const messageId = foldedMessageIdFor(sourceEvent.invocation.messageId);
2839
+ if (messageId &&
2840
+ messageId !== sourceEvent.invocation.messageId) {
2841
+ event = {
2842
+ ...sourceEvent,
2843
+ invocation: { ...sourceEvent.invocation, messageId },
2844
+ };
2845
+ }
2846
+ }
2847
+ break;
2848
+ }
2849
+ }
2850
+ }
2101
2851
  turnId ??= protocolTurnId(event.metadata);
2102
2852
  if (event.type === "run.started" && turnId) {
2103
2853
  dispatchAgentChatRunning({