@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.
- package/dist/chat-routes/completed-sandbox-turn.d.ts +8 -1
- package/dist/chat-routes/index.d.ts +1 -0
- package/dist/chat-routes/index.js +255 -57
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-routes/native-completion.d.ts +76 -0
- package/dist/chat-routes/turn-routes.d.ts +11 -0
- package/dist/{chunk-N4RR4WAI.js → chunk-HXTJXODG.js} +29 -6
- package/dist/chunk-HXTJXODG.js.map +1 -0
- package/dist/{chunk-MFLUDFD3.js → chunk-V4YGTOCS.js} +41 -9
- package/dist/chunk-V4YGTOCS.js.map +1 -0
- package/dist/preset-cloudflare/detached-turn-workflow.d.ts +11 -6
- package/dist/preset-cloudflare/index.d.ts +1 -0
- package/dist/preset-cloudflare/index.js +36 -1
- package/dist/preset-cloudflare/index.js.map +1 -1
- package/dist/preset-cloudflare/native-completion-workflow.d.ts +42 -0
- package/dist/public-consultation/index.js +2 -2
- package/dist/stream/index.js +1 -1
- package/dist/stream/turn-buffer.d.ts +17 -0
- package/dist/turn-stream/index.js +3 -1
- package/dist/turn-stream/index.js.map +1 -1
- package/package.json +7 -7
- package/dist/chunk-MFLUDFD3.js.map +0 -1
- package/dist/chunk-N4RR4WAI.js.map +0 -1
|
@@ -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-
|
|
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-
|
|
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
|
|
135
|
-
if (!
|
|
136
|
-
const type = asString(
|
|
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(
|
|
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
|
|
172
|
-
if (!
|
|
173
|
-
const type = asString(
|
|
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(
|
|
176
|
-
const outage = isUpstreamUnavailable(data) || isUpstreamUnavailable(
|
|
177
|
-
const reason = asString(data?.message) ?? asString(data?.error) ?? asString(data?.reason) ?? asString(
|
|
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
|
|
221
|
-
if (!
|
|
222
|
-
const type = asString(
|
|
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(
|
|
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
|
|
644
|
-
const message = asString(
|
|
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
|
|
809
|
-
captureEffectiveBackend(asRecord(
|
|
808
|
+
const record2 = asRecord(raw);
|
|
809
|
+
captureEffectiveBackend(asRecord(record2?.data));
|
|
810
810
|
yield* drainModelNotices();
|
|
811
|
-
if (!
|
|
812
|
-
const normalized = normalizeToolEvent({ type:
|
|
813
|
-
const event = normalized.type === "message.part.updated" ? normalized : { type:
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
967
|
+
yield toProducerWireEvent(record2);
|
|
968
968
|
continue;
|
|
969
969
|
}
|
|
970
970
|
if (event.type === "plan.submitted") {
|
|
971
|
-
const parsed = parsePlanSubmittedEvent(
|
|
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(
|
|
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(
|
|
990
|
+
yield toProducerWireEvent(record2);
|
|
991
991
|
continue;
|
|
992
992
|
}
|
|
993
993
|
if (event.type === "usage") {
|
|
994
|
-
const reported = extractUsageSnapshot(asRecord(
|
|
994
|
+
const reported = extractUsageSnapshot(asRecord(record2.usage));
|
|
995
995
|
if (reported) applyTerminalUsage(reported, usage);
|
|
996
|
-
yield toProducerWireEvent(
|
|
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(
|
|
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(
|
|
1043
|
+
yield toProducerWireEvent(record2);
|
|
1044
1044
|
continue;
|
|
1045
1045
|
}
|
|
1046
|
-
yield toProducerWireEvent(
|
|
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((
|
|
1163
|
-
outputTokens: settlement.calls.reduce((
|
|
1164
|
-
reasoningTokens: settlement.calls.reduce((
|
|
1165
|
-
costUsd: settlement.calls.reduce((
|
|
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
|
|
1369
|
+
const attribution2 = producer?.modelAttribution?.();
|
|
1370
1370
|
const model = producer?.model ?? persisted?.model ?? opts.model;
|
|
1371
|
-
const requestedModel =
|
|
1372
|
-
const servedModel =
|
|
1373
|
-
const servedProvider =
|
|
1374
|
-
const servedSource =
|
|
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(
|
|
1503
|
+
function firstNumber(record2, keys) {
|
|
1504
1504
|
for (const key of keys) {
|
|
1505
|
-
const value = finiteNumber(
|
|
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
|
|
1548
|
-
if (!
|
|
1549
|
-
const part = normalizePersistedPart(
|
|
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
|
|
1833
|
-
const 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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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((
|
|
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((
|
|
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,
|