@tangle-network/agent-app 0.49.1 → 0.49.3

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.
@@ -7,7 +7,7 @@
7
7
  * session. This adapter joins them without guessing across turns, then returns
8
8
  * the same `DetachedTurnFinal` shape `runDetachedTurn` already consumes.
9
9
  */
10
- import type { SandboxInstance } from '@tangle-network/sandbox';
10
+ import type { SandboxInstance, SessionMessage } from '@tangle-network/sandbox';
11
11
  import type { DetachedTurnFinal } from './detached-turn';
12
12
  /** The official Sandbox methods needed for completed-turn recovery. */
13
13
  export type CompletedSandboxTurnSource = Pick<SandboxInstance, 'findCompletedTurn' | 'session'>;
@@ -17,6 +17,13 @@ export interface ReadCompletedSandboxTurnOptions {
17
17
  sessionId: string;
18
18
  log?: (message: string, meta?: Record<string, unknown>) => void;
19
19
  }
20
+ /**
21
+ * Recover the durable content a Sandbox recorder attached to one assistant
22
+ * message. The caller is responsible for proving that the message belongs to
23
+ * the exact terminal turn; this projection deliberately makes no conclusion
24
+ * about whether that terminal state was completed or interrupted.
25
+ */
26
+ export declare function recoverSandboxAssistantMessage(message: SessionMessage): DetachedTurnFinal;
20
27
  /**
21
28
  * Read the exact completed turn from its keyed cache and completed message.
22
29
  * Never borrow a session-wide aggregate from a concurrently advancing session.
@@ -18,6 +18,7 @@ export * from './model-failover-stream';
18
18
  export * from './draft-persistence';
19
19
  export * from './detached-turn';
20
20
  export * from './completed-sandbox-turn';
21
+ export * from './native-completion';
21
22
  export * from './durable-projection';
22
23
  export * from './upload';
23
24
  export * from './file-index';
@@ -61,7 +61,7 @@ import {
61
61
  rowIdOf,
62
62
  storeSupportsDraftPersistence,
63
63
  streamChatRouteAsSandboxEvents
64
- } from "../chunk-MFLUDFD3.js";
64
+ } from "../chunk-V4YGTOCS.js";
65
65
  import "../chunk-ZSSCVT6H.js";
66
66
  import "../chunk-EA4UVS4T.js";
67
67
  import {
@@ -125,18 +125,18 @@ import {
125
125
  import {
126
126
  coalesceDeltas,
127
127
  createBufferedTurnTap
128
- } from "../chunk-N4RR4WAI.js";
128
+ } from "../chunk-HXTJXODG.js";
129
129
 
130
130
  // src/chat-routes/model-failover-stream.ts
131
131
  var TERMINAL_FAILURE_TYPES = /* @__PURE__ */ new Set(["error", "session.run.failed"]);
132
132
  var NON_COMMITTING_PART_TYPES = /* @__PURE__ */ new Set(["step-start", "step-finish"]);
133
133
  function isCommittingSandboxEvent(event) {
134
- const record = asRecord(event);
135
- if (!record) return false;
136
- const type = asString(record.type) ?? "";
134
+ const record2 = asRecord(event);
135
+ if (!record2) return false;
136
+ const type = asString(record2.type) ?? "";
137
137
  if (!type) return false;
138
138
  if (type === "message.part.updated") {
139
- const part = asRecord(asRecord(record.data)?.part);
139
+ const part = asRecord(asRecord(record2.data)?.part);
140
140
  const partType = asString(part?.type) ?? "";
141
141
  if (NON_COMMITTING_PART_TYPES.has(partType)) return false;
142
142
  if (partType === "text" || partType === "reasoning") {
@@ -168,13 +168,13 @@ function isLiveLifecycleEvent(event) {
168
168
  return type === "start" || type === "execution.started" || type === "status" || type === "model-processing" || type === "model.processing" || type === "session.created" || type === "session.updated" || type === "session.idle";
169
169
  }
170
170
  function classifyTerminalFailure(event) {
171
- const record = asRecord(event);
172
- if (!record) return null;
173
- const type = asString(record.type) ?? "";
171
+ const record2 = asRecord(event);
172
+ if (!record2) return null;
173
+ const type = asString(record2.type) ?? "";
174
174
  if (!TERMINAL_FAILURE_TYPES.has(type)) return null;
175
- const data = asRecord(record.data);
176
- const outage = isUpstreamUnavailable(data) || isUpstreamUnavailable(record);
177
- const reason = asString(data?.message) ?? asString(data?.error) ?? asString(data?.reason) ?? asString(record.message) ?? `sandbox stream reported ${type}`;
175
+ const data = asRecord(record2.data);
176
+ const outage = isUpstreamUnavailable(data) || isUpstreamUnavailable(record2);
177
+ const reason = asString(data?.message) ?? asString(data?.error) ?? asString(data?.reason) ?? asString(record2.message) ?? `sandbox stream reported ${type}`;
178
178
  const code = asString(data?.errorCode) ?? asString(data?.code);
179
179
  return { outage, reason, ...code ? { code } : {} };
180
180
  }
@@ -217,11 +217,11 @@ function deadlineAfter(timeoutMs) {
217
217
  };
218
218
  }
219
219
  function isEmptyTerminalReceipt(event) {
220
- const record = asRecord(event);
221
- if (!record) return false;
222
- const type = asString(record.type) ?? "";
220
+ const record2 = asRecord(event);
221
+ if (!record2) return false;
222
+ const type = asString(record2.type) ?? "";
223
223
  if (type !== "result" && type !== "done") return false;
224
- const data = asRecord(record.data);
224
+ const data = asRecord(record2.data);
225
225
  const text = asString(data?.finalText) ?? asString(data?.text) ?? "";
226
226
  return text.trim().length === 0;
227
227
  }
@@ -640,8 +640,8 @@ function applyTerminalUsage(reported, usage) {
640
640
  if (reported.costUsd !== void 0) usage.costUsd = reported.costUsd;
641
641
  }
642
642
  function sandboxStreamErrorMessage(data) {
643
- const record = asRecord(data);
644
- const message = asString(record?.message) ?? asString(record?.error);
643
+ const record2 = asRecord(data);
644
+ const message = asString(record2?.message) ?? asString(record2?.error);
645
645
  if (message) return message;
646
646
  try {
647
647
  return JSON.stringify(data) || "Sandbox stream returned an error event without details.";
@@ -805,12 +805,12 @@ function createSandboxChatProducer(options) {
805
805
  async function* stream() {
806
806
  try {
807
807
  for await (const raw of source) {
808
- const record = asRecord(raw);
809
- captureEffectiveBackend(asRecord(record?.data));
808
+ const record2 = asRecord(raw);
809
+ captureEffectiveBackend(asRecord(record2?.data));
810
810
  yield* drainModelNotices();
811
- if (!record || typeof record.type !== "string") continue;
812
- const normalized = normalizeToolEvent({ type: record.type, data: asRecord(record.data) });
813
- const event = normalized.type === "message.part.updated" ? normalized : { type: record.type, data: asRecord(record.data) };
811
+ if (!record2 || typeof record2.type !== "string") continue;
812
+ const normalized = normalizeToolEvent({ type: record2.type, data: asRecord(record2.data) });
813
+ const event = normalized.type === "message.part.updated" ? normalized : { type: record2.type, data: asRecord(record2.data) };
814
814
  if (event.type === "message.part.updated") {
815
815
  const part = asRecord(event.data?.part);
816
816
  if (!part) continue;
@@ -911,7 +911,7 @@ function createSandboxChatProducer(options) {
911
911
  continue;
912
912
  }
913
913
  if (event.type === "interaction") {
914
- const parsed = parseInteractionRequest(asRecord(record.data));
914
+ const parsed = parseInteractionRequest(asRecord(record2.data));
915
915
  if (!parsed.succeeded) {
916
916
  log("[chat-routes] dropping malformed interaction event", { error: parsed.error });
917
917
  continue;
@@ -922,7 +922,7 @@ function createSandboxChatProducer(options) {
922
922
  void 0,
923
923
  interactionPartKey(parsed.value.id)
924
924
  );
925
- yield toProducerWireEvent(record);
925
+ yield toProducerWireEvent(record2);
926
926
  continue;
927
927
  }
928
928
  let declineFailed = false;
@@ -950,7 +950,7 @@ function createSandboxChatProducer(options) {
950
950
  continue;
951
951
  }
952
952
  if (event.type === "interaction.cancel") {
953
- const parsed = parseInteractionCancel(asRecord(record.data));
953
+ const parsed = parseInteractionCancel(asRecord(record2.data));
954
954
  if (!parsed.succeeded) {
955
955
  log("[chat-routes] dropping malformed interaction.cancel event", { error: parsed.error });
956
956
  continue;
@@ -964,17 +964,17 @@ function createSandboxChatProducer(options) {
964
964
  ...parsed.value.reason ? { cancelReason: parsed.value.reason } : {}
965
965
  }, void 0, key);
966
966
  }
967
- yield toProducerWireEvent(record);
967
+ yield toProducerWireEvent(record2);
968
968
  continue;
969
969
  }
970
970
  if (event.type === "plan.submitted") {
971
- const parsed = parsePlanSubmittedEvent(record);
971
+ const parsed = parsePlanSubmittedEvent(record2);
972
972
  if (!parsed.succeeded) {
973
973
  log("[chat-routes] dropping malformed plan.submitted event", { error: parsed.error });
974
974
  continue;
975
975
  }
976
976
  recordPersistedPart(planToPersistedPart(parsed.value), void 0);
977
- yield toProducerWireEvent(record);
977
+ yield toProducerWireEvent(record2);
978
978
  continue;
979
979
  }
980
980
  if (event.type === "warning") {
@@ -987,13 +987,13 @@ function createSandboxChatProducer(options) {
987
987
  recordPersistedPart(notice, void 0, noticePartKey(notice.id));
988
988
  yield { type: "notice", id: notice.id, noticeKind: "warning", text };
989
989
  }
990
- yield toProducerWireEvent(record);
990
+ yield toProducerWireEvent(record2);
991
991
  continue;
992
992
  }
993
993
  if (event.type === "usage") {
994
- const reported = extractUsageSnapshot(asRecord(record.usage));
994
+ const reported = extractUsageSnapshot(asRecord(record2.usage));
995
995
  if (reported) applyTerminalUsage(reported, usage);
996
- yield toProducerWireEvent(record);
996
+ yield toProducerWireEvent(record2);
997
997
  continue;
998
998
  }
999
999
  if (event.type === "result") {
@@ -1022,7 +1022,7 @@ function createSandboxChatProducer(options) {
1022
1022
  yield* emitFinalUsage();
1023
1023
  }
1024
1024
  yield* emitTerminalizedTools();
1025
- yield toProducerWireEvent(record);
1025
+ yield toProducerWireEvent(record2);
1026
1026
  continue;
1027
1027
  }
1028
1028
  if (event.type === "error") {
@@ -1040,10 +1040,10 @@ ${errorContent}` : errorContent;
1040
1040
  interactionOutcome = "expired";
1041
1041
  yield { type: "text", text: errorDelta };
1042
1042
  yield* emitTerminalizedTools();
1043
- yield toProducerWireEvent(record);
1043
+ yield toProducerWireEvent(record2);
1044
1044
  continue;
1045
1045
  }
1046
- yield toProducerWireEvent(record);
1046
+ yield toProducerWireEvent(record2);
1047
1047
  }
1048
1048
  yield* drainModelNotices();
1049
1049
  } catch (streamErr) {
@@ -1159,10 +1159,10 @@ function createProtectedRuntimeChatProducer(options) {
1159
1159
  return error.settlement;
1160
1160
  });
1161
1161
  usage = {
1162
- inputTokens: settlement.calls.reduce((sum, call) => sum + call.accountedInputTokens, 0),
1163
- outputTokens: settlement.calls.reduce((sum, call) => sum + call.outputTokens, 0),
1164
- reasoningTokens: settlement.calls.reduce((sum, call) => sum + call.reasoningTokens, 0),
1165
- costUsd: settlement.calls.reduce((sum, call) => sum + call.costUsdNanos, 0) / 1e9
1162
+ inputTokens: settlement.calls.reduce((sum2, call) => sum2 + call.accountedInputTokens, 0),
1163
+ outputTokens: settlement.calls.reduce((sum2, call) => sum2 + call.outputTokens, 0),
1164
+ reasoningTokens: settlement.calls.reduce((sum2, call) => sum2 + call.reasoningTokens, 0),
1165
+ costUsd: settlement.calls.reduce((sum2, call) => sum2 + call.costUsdNanos, 0) / 1e9
1166
1166
  };
1167
1167
  budgetEnforced = settlement.usageWithinLimits;
1168
1168
  if (!budgetEnforced) throw new Error("Provider settlement exceeded execution limits");
@@ -1366,12 +1366,12 @@ async function runDetachedTurn(opts) {
1366
1366
  const settleRow = async (base, cachedPersisted) => {
1367
1367
  const persisted = cachedPersisted ?? (!producer ? await persistedRow() : void 0);
1368
1368
  const info = producer?.modelFailover?.();
1369
- const attribution = producer?.modelAttribution?.();
1369
+ const attribution2 = producer?.modelAttribution?.();
1370
1370
  const model = producer?.model ?? persisted?.model ?? opts.model;
1371
- const requestedModel = attribution?.requestedModel ?? persisted?.requestedModel ?? opts.model;
1372
- const servedModel = attribution?.servedModel ?? persisted?.servedModel;
1373
- const servedProvider = attribution?.servedProvider ?? persisted?.servedProvider;
1374
- const servedSource = attribution?.servedSource ?? (persisted?.servedSource === "request" || persisted?.servedSource === "environment" || persisted?.servedSource === "profile" ? persisted.servedSource : void 0);
1371
+ const requestedModel = attribution2?.requestedModel ?? persisted?.requestedModel ?? opts.model;
1372
+ const servedModel = attribution2?.servedModel ?? persisted?.servedModel;
1373
+ const servedProvider = attribution2?.servedProvider ?? persisted?.servedProvider;
1374
+ const servedSource = attribution2?.servedSource ?? (persisted?.servedSource === "request" || persisted?.servedSource === "environment" || persisted?.servedSource === "profile" ? persisted.servedSource : void 0);
1375
1375
  const result = {
1376
1376
  ...base,
1377
1377
  ...model ? { model } : {},
@@ -1500,9 +1500,9 @@ function finiteNumber(value) {
1500
1500
  const parsed = Number(value);
1501
1501
  return Number.isFinite(parsed) ? parsed : void 0;
1502
1502
  }
1503
- function firstNumber(record, keys) {
1503
+ function firstNumber(record2, keys) {
1504
1504
  for (const key of keys) {
1505
- const value = finiteNumber(record?.[key]);
1505
+ const value = finiteNumber(record2?.[key]);
1506
1506
  if (value !== void 0) return value;
1507
1507
  }
1508
1508
  return void 0;
@@ -1544,9 +1544,9 @@ function normalizedMessageParts(message) {
1544
1544
  const normalized = [];
1545
1545
  const usage = {};
1546
1546
  for (const raw of message.parts) {
1547
- const record = asRecord(raw);
1548
- if (!record) continue;
1549
- const part = normalizePersistedPart(record);
1547
+ const record2 = asRecord(raw);
1548
+ if (!record2) continue;
1549
+ const part = normalizePersistedPart(record2);
1550
1550
  if (!part) continue;
1551
1551
  normalized.push(part);
1552
1552
  if (part.type === "step-finish") addStepFinishUsage(part, usage);
@@ -1567,6 +1567,14 @@ function projectMessageParts(parts, finalText) {
1567
1567
  }
1568
1568
  return finalizeAssistantParts(order, map, finalText);
1569
1569
  }
1570
+ function recoverSandboxAssistantMessage(message) {
1571
+ const recovered = normalizedMessageParts(message);
1572
+ return {
1573
+ ...recovered.derivedText ? { text: recovered.derivedText } : {},
1574
+ ...hasUsage2(recovered.usage) ? { usage: recovered.usage } : {},
1575
+ parts: projectMessageParts(recovered.parts, recovered.derivedText)
1576
+ };
1577
+ }
1570
1578
  function hasUsage2(usage) {
1571
1579
  return Object.values(usage).some(
1572
1580
  (value) => typeof value === "number" && Number.isFinite(value)
@@ -1636,6 +1644,193 @@ async function readCompletedSandboxTurn(box, options) {
1636
1644
  };
1637
1645
  }
1638
1646
 
1647
+ // src/chat-routes/native-completion.ts
1648
+ var DEFAULT_ABSENT_DISPATCH_DEADLINE_MS = 10 * 6e4;
1649
+ var DEFAULT_RECEIPT_DEADLINE_MS = 10 * 6e4;
1650
+ function timestamp(value) {
1651
+ if (value instanceof Date) return value.getTime();
1652
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1653
+ }
1654
+ function nonEmptyString(value) {
1655
+ return typeof value === "string" && value.trim() ? value : void 0;
1656
+ }
1657
+ function record(value) {
1658
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1659
+ }
1660
+ function isSandboxNotFoundError(value) {
1661
+ const error = record(value);
1662
+ return error?.name === "NotFoundError" || error?.code === "NOT_FOUND" || error?.status === 404;
1663
+ }
1664
+ function attribution(result) {
1665
+ const metadata = record(result?.metadata);
1666
+ const modelAttribution = record(result?.modelAttribution) ?? record(metadata?.modelAttribution);
1667
+ const backend = record(result?.effectiveBackend) ?? record(metadata?.effectiveBackend);
1668
+ const source = nonEmptyString(result?.servedSource) ?? nonEmptyString(modelAttribution?.servedSource) ?? nonEmptyString(backend?.source);
1669
+ const servedSource = source === "request" || source === "environment" || source === "profile" ? source : void 0;
1670
+ const servedModel = nonEmptyString(result?.servedModel) ?? nonEmptyString(modelAttribution?.servedModel) ?? nonEmptyString(backend?.model);
1671
+ const servedProvider = nonEmptyString(result?.servedProvider) ?? nonEmptyString(modelAttribution?.servedProvider) ?? nonEmptyString(backend?.provider);
1672
+ return {
1673
+ ...servedModel ? { servedModel } : {},
1674
+ ...servedProvider ? { servedProvider } : {},
1675
+ ...servedSource ? { servedSource } : {}
1676
+ };
1677
+ }
1678
+ function usageFromResult2(result) {
1679
+ const raw = record(result?.usage) ?? record(result?.tokenUsage);
1680
+ const number = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
1681
+ return {
1682
+ ...number(raw?.inputTokens) !== void 0 ? { inputTokens: number(raw?.inputTokens) } : {},
1683
+ ...number(raw?.outputTokens) !== void 0 ? { outputTokens: number(raw?.outputTokens) } : {},
1684
+ ...number(raw?.cacheReadTokens) !== void 0 ? { cacheReadTokens: number(raw?.cacheReadTokens) } : {},
1685
+ ...number(raw?.cacheWriteTokens) !== void 0 ? { cacheWriteTokens: number(raw?.cacheWriteTokens) } : {},
1686
+ ...number(result?.costUsd) !== void 0 ? { costUsd: number(result?.costUsd) } : {}
1687
+ };
1688
+ }
1689
+ function interruptedMessages(messages, turnId) {
1690
+ return messages.filter(
1691
+ (message) => message.role === "assistant" && message.metadata?.turnId === turnId && (message.metadata.interrupted === true || message.metadata.status === "interrupted")
1692
+ );
1693
+ }
1694
+ function mergeUsage(messageUsage, resultUsage) {
1695
+ return { ...messageUsage, ...resultUsage };
1696
+ }
1697
+ function consistent(values) {
1698
+ if (values.length === 0 || values.some((value) => value === void 0)) return void 0;
1699
+ const first = values[0];
1700
+ return values.every((value) => value === first) ? first : void 0;
1701
+ }
1702
+ function sum(values) {
1703
+ if (values.some((value) => value === void 0)) return void 0;
1704
+ return values.reduce((total, value) => total + value, 0);
1705
+ }
1706
+ function aggregateNativeCompletionReceipts(turns) {
1707
+ const failed = turns.some((turn) => turn.state === "failed");
1708
+ const usage = {};
1709
+ for (const key of ["inputTokens", "outputTokens", "reasoningTokens", "cacheReadTokens", "cacheWriteTokens", "costUsd"]) {
1710
+ const total = sum(turns.map((turn) => turn.usage[key]));
1711
+ if (total !== void 0) usage[key] = total;
1712
+ }
1713
+ const error = turns.find(
1714
+ (turn) => turn.error?.startsWith("Admitted native execution did not produce") || turn.error?.startsWith("Native Sandbox execution ledger is missing")
1715
+ )?.error ?? turns.find((turn) => turn.error)?.error;
1716
+ return {
1717
+ state: failed ? "failed" : "completed",
1718
+ text: turns.map((turn) => turn.text).join(""),
1719
+ parts: turns.flatMap((turn) => turn.parts),
1720
+ usage,
1721
+ ...consistent(turns.map((turn) => turn.servedModel)) ? { servedModel: consistent(turns.map((turn) => turn.servedModel)) } : {},
1722
+ ...consistent(turns.map((turn) => turn.servedProvider)) ? { servedProvider: consistent(turns.map((turn) => turn.servedProvider)) } : {},
1723
+ ...consistent(turns.map((turn) => turn.servedSource)) ? { servedSource: consistent(turns.map((turn) => turn.servedSource)) } : {},
1724
+ ...error ? { error } : {},
1725
+ completedTurnIds: turns.map((turn) => turn.turnId)
1726
+ };
1727
+ }
1728
+ function missingTurnReceipt(turnId, error) {
1729
+ return { turnId, state: "failed", text: "", parts: [], usage: {}, error };
1730
+ }
1731
+ async function observeNativeCompletion(options) {
1732
+ const now = options.now ?? Date.now();
1733
+ let admission = await options.admissionStore.read(options.executionId);
1734
+ if (!admission) throw new Error("Native completion admission is missing");
1735
+ if (admission.executionId !== options.executionId) throw new Error("Native completion admission identity conflict");
1736
+ const leaseUntil = timestamp(admission.ownerLeaseUntil) ?? 0;
1737
+ const source = options.source;
1738
+ const session = source?.session(options.sessionId);
1739
+ let status;
1740
+ if (!session) {
1741
+ status = null;
1742
+ } else {
1743
+ try {
1744
+ status = await session.status();
1745
+ } catch (error) {
1746
+ if (!isSandboxNotFoundError(error)) throw error;
1747
+ status = null;
1748
+ }
1749
+ }
1750
+ if (!status || !session || !source) {
1751
+ if (admission.state === "open" && leaseUntil <= now) {
1752
+ admission = await options.admissionStore.closeExpired(options.executionId, new Date(now)) ?? admission;
1753
+ }
1754
+ if (admission.state === "open" || now - options.registeredAt < (options.absentDispatchDeadlineMs ?? DEFAULT_ABSENT_DISPATCH_DEADLINE_MS)) {
1755
+ return { state: "running" };
1756
+ }
1757
+ return {
1758
+ state: "failed",
1759
+ receipt: aggregateNativeCompletionReceipts([
1760
+ missingTurnReceipt(options.turnId, "Native Sandbox dispatch was not observed before the admission deadline")
1761
+ ])
1762
+ };
1763
+ }
1764
+ const exactSession = session;
1765
+ if (admission.state === "open") {
1766
+ if (status.status === "queued" || status.status === "running") {
1767
+ admission = await options.admissionStore.renew(options.executionId, new Date(now)) ?? admission;
1768
+ } else if (leaseUntil <= now) {
1769
+ admission = await options.admissionStore.closeExpired(options.executionId, new Date(now)) ?? admission;
1770
+ }
1771
+ if (admission.state === "open") return { state: "running" };
1772
+ }
1773
+ const admittedTurnIds = [...admission.admittedTurnIds];
1774
+ if (admittedTurnIds.length === 0 || admittedTurnIds[0] !== options.turnId) {
1775
+ throw new Error("Native completion admission has an invalid turn sequence");
1776
+ }
1777
+ const runs = await exactSession.runs();
1778
+ const runsByTurnId = new Map(runs.map((run) => [run.executionId, run]));
1779
+ const missingExecution = admittedTurnIds.find((turnId) => !runsByTurnId.has(turnId));
1780
+ if (missingExecution && now - options.registeredAt < (options.absentDispatchDeadlineMs ?? DEFAULT_ABSENT_DISPATCH_DEADLINE_MS)) {
1781
+ return { state: "running" };
1782
+ }
1783
+ if (admittedTurnIds.some((turnId) => runsByTurnId.get(turnId)?.status === "active")) return { state: "running" };
1784
+ const messages = await exactSession.messages({ limit: 1e3 });
1785
+ const recovered = [];
1786
+ for (const turnId of admittedTurnIds) {
1787
+ const run = runsByTurnId.get(turnId);
1788
+ if (!run) {
1789
+ recovered.push(missingTurnReceipt(turnId, "Native Sandbox execution ledger is missing the admitted turn"));
1790
+ continue;
1791
+ }
1792
+ const completed = run.status === "completed" ? await readCompletedSandboxTurn(source, { turnId, sessionId: options.sessionId }) : null;
1793
+ const cached = run.status === "completed" ? await source.findCompletedTurn(turnId, { sessionId: options.sessionId }) : null;
1794
+ if (run.status === "completed" && completed) {
1795
+ recovered.push({
1796
+ turnId,
1797
+ state: "completed",
1798
+ text: completed.text ?? "",
1799
+ parts: completed.parts ?? [],
1800
+ usage: completed.usage ?? {},
1801
+ ...attribution(cached?.result)
1802
+ });
1803
+ continue;
1804
+ }
1805
+ const interrupted = interruptedMessages(messages, turnId);
1806
+ if ((run.status === "failed" || run.status === "cancelled") && interrupted.length === 1) {
1807
+ const message = recoverSandboxAssistantMessage(interrupted[0]);
1808
+ const result = await exactSession.result({ executionId: turnId });
1809
+ recovered.push({
1810
+ turnId,
1811
+ state: "failed",
1812
+ text: message.text ?? result.response ?? "",
1813
+ parts: message.parts ?? [],
1814
+ usage: mergeUsage(message.usage ?? {}, {
1815
+ ...usageFromResult2(result),
1816
+ ...result.costUsd !== void 0 ? { costUsd: result.costUsd } : {}
1817
+ }),
1818
+ ...attribution(result),
1819
+ error: result.error ?? interrupted[0].metadata?.interruptReason ?? status.failureReason?.message
1820
+ });
1821
+ continue;
1822
+ }
1823
+ const closedAt = timestamp(admission.closedAt) ?? timestamp(admission.updatedAt) ?? options.registeredAt;
1824
+ if (now - closedAt < (options.receiptDeadlineMs ?? DEFAULT_RECEIPT_DEADLINE_MS)) return { state: "running" };
1825
+ recovered.push(missingTurnReceipt(
1826
+ turnId,
1827
+ `Admitted native execution did not produce an exact completion: ${turnId}`
1828
+ ));
1829
+ }
1830
+ const receipt = aggregateNativeCompletionReceipts(recovered);
1831
+ return receipt.state === "completed" ? { state: "completed", receipt } : { state: "failed", receipt };
1832
+ }
1833
+
1639
1834
  // src/chat-routes/durable-projection.ts
1640
1835
  function errorMessage(error) {
1641
1836
  return error instanceof Error ? error.message : String(error);
@@ -1829,12 +2024,12 @@ function parseAttachmentInput(value, index, validatePath) {
1829
2024
  if (!value || typeof value !== "object" || Array.isArray(value)) {
1830
2025
  return { succeeded: false, error: `attachments[${index}] must be an object` };
1831
2026
  }
1832
- const record = value;
1833
- const path = record.path;
2027
+ const record2 = value;
2028
+ const path = record2.path;
1834
2029
  if (typeof path !== "string" || !path) {
1835
2030
  return { succeeded: false, error: `attachments[${index}].path must be a non-empty string` };
1836
2031
  }
1837
- const name = record.name;
2032
+ const name = record2.name;
1838
2033
  if (typeof name !== "string" || !name.trim()) {
1839
2034
  return { succeeded: false, error: `attachments[${index}].name must be a non-empty string` };
1840
2035
  }
@@ -1844,18 +2039,18 @@ function parseAttachmentInput(value, index, validatePath) {
1844
2039
  if (CONTROL_CHARS.test(name)) {
1845
2040
  return { succeeded: false, error: `attachments[${index}].name must not contain control characters` };
1846
2041
  }
1847
- const size = record.size;
2042
+ const size = record2.size;
1848
2043
  if (typeof size !== "number" || !Number.isFinite(size)) {
1849
2044
  return { succeeded: false, error: `attachments[${index}].size must be a finite number` };
1850
2045
  }
1851
2046
  if (size < 0) {
1852
2047
  return { succeeded: false, error: `attachments[${index}].size must not be negative` };
1853
2048
  }
1854
- const mediaType = record.mediaType;
2049
+ const mediaType = record2.mediaType;
1855
2050
  if (typeof mediaType !== "string") {
1856
2051
  return { succeeded: false, error: `attachments[${index}].mediaType must be a string` };
1857
2052
  }
1858
- const kind = record.kind;
2053
+ const kind = record2.kind;
1859
2054
  if (!isAttachmentKind(kind)) {
1860
2055
  return { succeeded: false, error: `attachments[${index}].kind must be "image" or "file"` };
1861
2056
  }
@@ -1883,7 +2078,7 @@ async function resolveChatAttachments(value, options) {
1883
2078
  seenPaths.add(parsed.value.path);
1884
2079
  inputs.push(parsed.value);
1885
2080
  }
1886
- const advisoryTotal = inputs.reduce((sum, input) => sum + input.size, 0);
2081
+ const advisoryTotal = inputs.reduce((sum2, input) => sum2 + input.size, 0);
1887
2082
  if (advisoryTotal > maxTotalBytes) {
1888
2083
  return { succeeded: false, error: attachmentTotalSizeErrorMessage(advisoryTotal, maxTotalBytes) };
1889
2084
  }
@@ -2369,7 +2564,7 @@ function createAttachmentUploadRoute(options) {
2369
2564
  `Too many files \u2014 the ${maxCount}-file limit was exceeded`
2370
2565
  );
2371
2566
  }
2372
- const advisoryTotal = files.reduce((sum, file) => sum + file.size, 0);
2567
+ const advisoryTotal = files.reduce((sum2, file) => sum2 + file.size, 0);
2373
2568
  if (advisoryTotal > maxTotalBytes) {
2374
2569
  return attachmentUploadError(
2375
2570
  413,
@@ -2826,6 +3021,7 @@ export {
2826
3021
  PROMOTE_MAX_FILE_BYTES,
2827
3022
  UPLOAD_INLINE_MAX_BYTES,
2828
3023
  UPLOAD_MAX_FILE_BYTES,
3024
+ aggregateNativeCompletionReceipts,
2829
3025
  assertPromptPartsWithinCap,
2830
3026
  assistantRowIdForTurn,
2831
3027
  attachmentSizeErrorMessage,
@@ -2855,12 +3051,14 @@ export {
2855
3051
  mediaTypeForMentionPath,
2856
3052
  mentionKindForPath,
2857
3053
  normalizeChatPromptForSandbox,
3054
+ observeNativeCompletion,
2858
3055
  parseChatTurnParts,
2859
3056
  parseFileMentions,
2860
3057
  promoteAgentFilePart,
2861
3058
  promptPartsByteSize,
2862
3059
  readCompletedSandboxTurn,
2863
3060
  reconcileStaleTurnLock,
3061
+ recoverSandboxAssistantMessage,
2864
3062
  resolveChatAttachments,
2865
3063
  resolveEmptyTurnRetries,
2866
3064
  rowIdOf,