@tangle-network/agent-app 0.44.53 → 0.44.55
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.
|
@@ -9,13 +9,13 @@ import { T as TurnEventStore } from '../turn-buffer-COMQUwO2.js';
|
|
|
9
9
|
import { M as ModelFailoverAttempt } from '../failover-D-3UXXTb.js';
|
|
10
10
|
export { D as DEFAULT_STALE_TURN_LOCK_GRACE_MS, a as DEFAULT_TERMINAL_TURN_LOCK_GRACE_MS, R as ReconcileStaleTurnLockOptions, b as ReconcileStaleTurnLockResult, S as StaleTurnLockSandboxProbeResult, c as StaleTurnLockSessionProbeResult, r as reconcileStaleTurnLock } from '../stale-turn-lock-DucQzvXu.js';
|
|
11
11
|
import { J as JsonRecord } from '../stream-normalizer-CnPnMaTp.js';
|
|
12
|
+
import { SandboxInstance } from '@tangle-network/sandbox';
|
|
12
13
|
import { SandboxExecChannel, PromptInputPart } from '../sandbox/index.js';
|
|
13
14
|
import '@tangle-network/agent-interface';
|
|
14
15
|
import '../contract-CQNvv5th.js';
|
|
15
16
|
import '../types-CCeYywdS.js';
|
|
16
17
|
import '../plans/index.js';
|
|
17
18
|
import '@tangle-network/sandbox/core';
|
|
18
|
-
import '@tangle-network/sandbox';
|
|
19
19
|
import '../auth-DJs6lfAs.js';
|
|
20
20
|
import '../types-DbU-oO5h.js';
|
|
21
21
|
import '../harness/index.js';
|
|
@@ -1086,9 +1086,10 @@ type DetachedTurnParts = Array<Record<string, unknown>>;
|
|
|
1086
1086
|
interface DetachedTurnFinal {
|
|
1087
1087
|
text?: string;
|
|
1088
1088
|
usage?: ChatTurnUsage;
|
|
1089
|
-
/** The structured parts to persist when this receipt
|
|
1090
|
-
* cached
|
|
1091
|
-
*
|
|
1089
|
+
/** The structured parts to persist when this receipt is more complete than
|
|
1090
|
+
* the live stream (cached, finished server-side, or a fast stream that
|
|
1091
|
+
* delivered scalar text before its message-part events). Omitted when the
|
|
1092
|
+
* record only carries a usage receipt. */
|
|
1092
1093
|
parts?: DetachedTurnParts;
|
|
1093
1094
|
}
|
|
1094
1095
|
/** Define options for managing and projecting a detached turn event stream in a session */
|
|
@@ -1142,8 +1143,10 @@ interface DetachedTurnOptions {
|
|
|
1142
1143
|
/** Authoritative final receipt, consulted whenever a re-invoke finds a prior
|
|
1143
1144
|
* buffer: (a) an already-`complete` turn returns it as the cached result,
|
|
1144
1145
|
* (b) a `running` turn (crash mid-run) uses it to detect a run that finished
|
|
1145
|
-
* server-side, and (c) a clean run whose stream carried no usage
|
|
1146
|
-
* back to it.
|
|
1146
|
+
* server-side, and (c) a clean run whose stream carried no usage or only
|
|
1147
|
+
* scalar text falls back to it. For Sandbox runs, use
|
|
1148
|
+
* `readCompletedSandboxTurn` so the exact completed session message
|
|
1149
|
+
* restores tool/file parts as well as text. */
|
|
1147
1150
|
completedResult?: () => Promise<DetachedTurnFinal | null | undefined>;
|
|
1148
1151
|
/** Clear the prior partial buffer for `turnId` before a genuine re-stream.
|
|
1149
1152
|
* A crash mid-run leaves buffered rows at seqs 1..N with status `running`;
|
|
@@ -1236,6 +1239,31 @@ interface DetachedTurnResult {
|
|
|
1236
1239
|
*/
|
|
1237
1240
|
declare function runDetachedTurn(opts: DetachedTurnOptions): Promise<DetachedTurnResult>;
|
|
1238
1241
|
|
|
1242
|
+
/**
|
|
1243
|
+
* Recover a detached Sandbox turn from its durable completed records.
|
|
1244
|
+
*
|
|
1245
|
+
* A fast detached run can finish before the live event subscriber receives
|
|
1246
|
+
* every message-part event. The Sandbox still retains two exact records: a
|
|
1247
|
+
* turn-id keyed result cache and the completed assistant message on the
|
|
1248
|
+
* session. This adapter joins them without guessing across turns, then returns
|
|
1249
|
+
* the same `DetachedTurnFinal` shape `runDetachedTurn` already consumes.
|
|
1250
|
+
*/
|
|
1251
|
+
|
|
1252
|
+
/** The official Sandbox methods needed for completed-turn recovery. */
|
|
1253
|
+
type CompletedSandboxTurnSource = Pick<SandboxInstance, 'findCompletedTurn' | 'session'>;
|
|
1254
|
+
/** Options for resolving one exact detached turn. */
|
|
1255
|
+
interface ReadCompletedSandboxTurnOptions {
|
|
1256
|
+
turnId: string;
|
|
1257
|
+
sessionId: string;
|
|
1258
|
+
log?: (message: string, meta?: Record<string, unknown>) => void;
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Read the exact completed turn. The session-level result is consulted only
|
|
1262
|
+
* when the matching completed assistant message is still the latest message;
|
|
1263
|
+
* otherwise that result could belong to a newer turn on the same session.
|
|
1264
|
+
*/
|
|
1265
|
+
declare function readCompletedSandboxTurn(box: CompletedSandboxTurnSource, options: ReadCompletedSandboxTurnOptions): Promise<DetachedTurnFinal | null>;
|
|
1266
|
+
|
|
1239
1267
|
/** Resolve chat route events and materialize their durable state records */
|
|
1240
1268
|
interface ChatRouteDurableProjection {
|
|
1241
1269
|
observe(event: unknown): void | Promise<void>;
|
|
@@ -1755,4 +1783,4 @@ interface PromoteAgentFilePartOptions {
|
|
|
1755
1783
|
/** Promote a part of an agent file with optional byte limits and MIME type detection */
|
|
1756
1784
|
declare function promoteAgentFilePart(options: PromoteAgentFilePartOptions): Promise<PromoteFilePartResult>;
|
|
1757
1785
|
|
|
1758
|
-
export { type AssistantDraftSnapshot, type AssistantDraftStore, type AssistantDraftWriter, type AssistantDraftWriterOptions, type AssistantRowValues, type AttachmentPathArgs, type AttachmentPathCheck, type AttachmentReadResult, type AttachmentUploadAuthorization, type AttachmentWriteResult, type BuildDispatchPartsInput, ChatAttachmentKind, type ChatRouteDurableProjection, type ChatRouteDurableProjectionLogger, type ChatRouteEvent, type ChatTurnAuthorization, type ChatTurnAuthorizeArgs, type ChatTurnCompleteInput, ChatTurnFilePartInput, type ChatTurnGateResult, type ChatTurnHeartbeat, type ChatTurnInputPatch, type ChatTurnLifecycle, type ChatTurnLifecycleComplete, type ChatTurnLifecycleError, type ChatTurnLifecycleStart, type ChatTurnLock, type ChatTurnLockResult, type ChatTurnMessageStore, type ChatTurnModelAttribution, type ChatTurnModelFailover, ChatTurnPartInput, type ChatTurnProduceArgs, ChatTurnRequestPayload, type ChatTurnRouteProducer, type ChatTurnRoutes, type ChatTurnUsage, type CreateAttachmentUploadRouteOptions, type CreateChatTurnRoutesOptions, type CreateUploadRouteOptions, type DetachedTurnFinal, type DetachedTurnOptions, type DetachedTurnParts, type DetachedTurnResult, type DispatchPartsOutcome, type DraftPersistenceTuning, type DraftStoredMessage, type EmptyTurnRetryInfo, type FilePartPromotionOutcome, MAX_EMPTY_TURN_RETRIES, type ModelFailoverStreamHandle, type ModelFailoverStreamOptions, type ModelFallbackInfo, type OpenModelStream, PROMOTE_MAX_FILE_BYTES, type PromoteAgentFilePartOptions, type PromoteFilePartResult, PromptInputPart, type RawAgentFilePart, type ReadAttachmentFn, type ReadSandboxMentionFn, type ResolveChatAttachmentsOptions, type ResolveChatAttachmentsResult, type SandboxChatProducerOptions, type SandboxUploadSink, UPLOAD_INLINE_MAX_BYTES, UPLOAD_MAX_FILE_BYTES, type UploadAuthorization, type UploadedChatFile, type WriteAttachmentFn, assistantRowIdForTurn, buildDispatchParts, bytesToBase64, classifyTerminalFailure, createAssistantDraftWriter, createAttachmentUploadRoute, createChatTurnRoutes, createSandboxChatProducer, createUploadRoute, defaultValidateAttachmentPath, isCommittingSandboxEvent, isDraftContentEvent, normalizeChatPromptForSandbox, promoteAgentFilePart, resolveChatAttachments, resolveEmptyTurnRetries, rowIdOf, runDetachedTurn, sanitizeUploadFilename, sniffMimeFromName, storeSupportsDraftPersistence, streamWithModelFailover, summarizeFailoverReason, withDurableChatProjection };
|
|
1786
|
+
export { type AssistantDraftSnapshot, type AssistantDraftStore, type AssistantDraftWriter, type AssistantDraftWriterOptions, type AssistantRowValues, type AttachmentPathArgs, type AttachmentPathCheck, type AttachmentReadResult, type AttachmentUploadAuthorization, type AttachmentWriteResult, type BuildDispatchPartsInput, ChatAttachmentKind, type ChatRouteDurableProjection, type ChatRouteDurableProjectionLogger, type ChatRouteEvent, type ChatTurnAuthorization, type ChatTurnAuthorizeArgs, type ChatTurnCompleteInput, ChatTurnFilePartInput, type ChatTurnGateResult, type ChatTurnHeartbeat, type ChatTurnInputPatch, type ChatTurnLifecycle, type ChatTurnLifecycleComplete, type ChatTurnLifecycleError, type ChatTurnLifecycleStart, type ChatTurnLock, type ChatTurnLockResult, type ChatTurnMessageStore, type ChatTurnModelAttribution, type ChatTurnModelFailover, ChatTurnPartInput, type ChatTurnProduceArgs, ChatTurnRequestPayload, type ChatTurnRouteProducer, type ChatTurnRoutes, type ChatTurnUsage, type CompletedSandboxTurnSource, type CreateAttachmentUploadRouteOptions, type CreateChatTurnRoutesOptions, type CreateUploadRouteOptions, type DetachedTurnFinal, type DetachedTurnOptions, type DetachedTurnParts, type DetachedTurnResult, type DispatchPartsOutcome, type DraftPersistenceTuning, type DraftStoredMessage, type EmptyTurnRetryInfo, type FilePartPromotionOutcome, MAX_EMPTY_TURN_RETRIES, type ModelFailoverStreamHandle, type ModelFailoverStreamOptions, type ModelFallbackInfo, type OpenModelStream, PROMOTE_MAX_FILE_BYTES, type PromoteAgentFilePartOptions, type PromoteFilePartResult, PromptInputPart, type RawAgentFilePart, type ReadAttachmentFn, type ReadCompletedSandboxTurnOptions, type ReadSandboxMentionFn, type ResolveChatAttachmentsOptions, type ResolveChatAttachmentsResult, type SandboxChatProducerOptions, type SandboxUploadSink, UPLOAD_INLINE_MAX_BYTES, UPLOAD_MAX_FILE_BYTES, type UploadAuthorization, type UploadedChatFile, type WriteAttachmentFn, assistantRowIdForTurn, buildDispatchParts, bytesToBase64, classifyTerminalFailure, createAssistantDraftWriter, createAttachmentUploadRoute, createChatTurnRoutes, createSandboxChatProducer, createUploadRoute, defaultValidateAttachmentPath, isCommittingSandboxEvent, isDraftContentEvent, normalizeChatPromptForSandbox, promoteAgentFilePart, readCompletedSandboxTurn, resolveChatAttachments, resolveEmptyTurnRetries, rowIdOf, runDetachedTurn, sanitizeUploadFilename, sniffMimeFromName, storeSupportsDraftPersistence, streamWithModelFailover, summarizeFailoverReason, withDurableChatProjection };
|
|
@@ -66,6 +66,7 @@ import {
|
|
|
66
66
|
import {
|
|
67
67
|
asRecord,
|
|
68
68
|
asString,
|
|
69
|
+
collapseRedundantTextParts,
|
|
69
70
|
draftAssistantParts,
|
|
70
71
|
finalizeAssistantParts,
|
|
71
72
|
finalizePendingInteractionParts,
|
|
@@ -1027,6 +1028,28 @@ function streamWithModelFailover(options) {
|
|
|
1027
1028
|
};
|
|
1028
1029
|
}
|
|
1029
1030
|
|
|
1031
|
+
// src/chat-routes/sandbox-turn-usage.ts
|
|
1032
|
+
function addStepFinishUsage(part, usage) {
|
|
1033
|
+
const tokens = asRecord(part.tokens);
|
|
1034
|
+
if (tokens) {
|
|
1035
|
+
const cache = asRecord(tokens.cache);
|
|
1036
|
+
const add = (current, value) => {
|
|
1037
|
+
const n = Number(value);
|
|
1038
|
+
if (!Number.isFinite(n)) return current;
|
|
1039
|
+
return (current ?? 0) + n;
|
|
1040
|
+
};
|
|
1041
|
+
usage.inputTokens = add(usage.inputTokens, tokens.input);
|
|
1042
|
+
usage.outputTokens = add(usage.outputTokens, tokens.output);
|
|
1043
|
+
usage.reasoningTokens = add(usage.reasoningTokens, tokens.reasoning);
|
|
1044
|
+
if (cache) {
|
|
1045
|
+
usage.cacheReadTokens = add(usage.cacheReadTokens, cache.read);
|
|
1046
|
+
usage.cacheWriteTokens = add(usage.cacheWriteTokens, cache.write);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
const cost = Number(part.cost);
|
|
1050
|
+
if (Number.isFinite(cost)) usage.costUsd = (usage.costUsd ?? 0) + cost;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1030
1053
|
// src/chat-routes/sandbox-producer.ts
|
|
1031
1054
|
function textDelta(tracker, key, part, rawDelta) {
|
|
1032
1055
|
const explicit = typeof rawDelta === "string" ? rawDelta : void 0;
|
|
@@ -1058,26 +1081,6 @@ function parseEffectiveBackend(data) {
|
|
|
1058
1081
|
...source ? { servedSource: source } : {}
|
|
1059
1082
|
};
|
|
1060
1083
|
}
|
|
1061
|
-
function usageFromStepFinish(part, usage) {
|
|
1062
|
-
const tokens = asRecord(part.tokens);
|
|
1063
|
-
if (tokens) {
|
|
1064
|
-
const cache = asRecord(tokens.cache);
|
|
1065
|
-
const add = (current, value) => {
|
|
1066
|
-
const n = Number(value);
|
|
1067
|
-
if (!Number.isFinite(n)) return current;
|
|
1068
|
-
return (current ?? 0) + n;
|
|
1069
|
-
};
|
|
1070
|
-
usage.inputTokens = add(usage.inputTokens, tokens.input);
|
|
1071
|
-
usage.outputTokens = add(usage.outputTokens, tokens.output);
|
|
1072
|
-
usage.reasoningTokens = add(usage.reasoningTokens, tokens.reasoning);
|
|
1073
|
-
if (cache) {
|
|
1074
|
-
usage.cacheReadTokens = add(usage.cacheReadTokens, cache.read);
|
|
1075
|
-
usage.cacheWriteTokens = add(usage.cacheWriteTokens, cache.write);
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
const cost = Number(part.cost);
|
|
1079
|
-
if (Number.isFinite(cost)) usage.costUsd = (usage.costUsd ?? 0) + cost;
|
|
1080
|
-
}
|
|
1081
1084
|
function toFiniteNumber(value) {
|
|
1082
1085
|
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
1083
1086
|
if (typeof value === "string" && value.trim() !== "") {
|
|
@@ -1316,7 +1319,7 @@ function createSandboxChatProducer(options) {
|
|
|
1316
1319
|
continue;
|
|
1317
1320
|
}
|
|
1318
1321
|
if (partType === "step-finish") {
|
|
1319
|
-
|
|
1322
|
+
addStepFinishUsage(part, usage);
|
|
1320
1323
|
recordPersistedPart(part, void 0, `step-finish:#${stepCounter++}`);
|
|
1321
1324
|
const promptTokens = usage.inputTokens ?? 0;
|
|
1322
1325
|
const completionTokens = usage.outputTokens ?? 0;
|
|
@@ -1558,11 +1561,11 @@ function errorMessageOf(ev) {
|
|
|
1558
1561
|
function hasUsage(usage) {
|
|
1559
1562
|
return typeof usage.inputTokens === "number" && usage.inputTokens > 0;
|
|
1560
1563
|
}
|
|
1561
|
-
function cachedResultFrom(final) {
|
|
1564
|
+
function cachedResultFrom(final, persisted) {
|
|
1562
1565
|
return {
|
|
1563
1566
|
state: "completed",
|
|
1564
|
-
text: final?.text ?? "",
|
|
1565
|
-
parts: final?.parts ?? [],
|
|
1567
|
+
text: final?.text ?? persisted?.content ?? "",
|
|
1568
|
+
parts: final?.parts ?? persisted?.parts ?? [],
|
|
1566
1569
|
usage: final?.usage ?? {},
|
|
1567
1570
|
cached: true
|
|
1568
1571
|
};
|
|
@@ -1571,6 +1574,12 @@ async function runDetachedTurn(opts) {
|
|
|
1571
1574
|
const { store, turnId, scopeId } = opts;
|
|
1572
1575
|
let producer;
|
|
1573
1576
|
const servingModel = () => producer?.model ?? opts.model;
|
|
1577
|
+
const persistedRow = async () => {
|
|
1578
|
+
if (!opts.persist) return void 0;
|
|
1579
|
+
return (await opts.persist.store.listMessages(opts.persist.threadId)).find(
|
|
1580
|
+
(message) => message.id === (opts.persist?.messageId ?? assistantRowIdForTurn(turnId))
|
|
1581
|
+
);
|
|
1582
|
+
};
|
|
1574
1583
|
let draft;
|
|
1575
1584
|
if (opts.persist) {
|
|
1576
1585
|
const { store: persistStore, threadId, messageId, transformText, ...tuning } = opts.persist;
|
|
@@ -1594,10 +1603,8 @@ async function runDetachedTurn(opts) {
|
|
|
1594
1603
|
...opts.log ? { log: opts.log } : {}
|
|
1595
1604
|
});
|
|
1596
1605
|
}
|
|
1597
|
-
const settleRow = async (base) => {
|
|
1598
|
-
const persisted = !producer
|
|
1599
|
-
(message) => message.id === (opts.persist?.messageId ?? assistantRowIdForTurn(turnId))
|
|
1600
|
-
) : void 0;
|
|
1606
|
+
const settleRow = async (base, cachedPersisted) => {
|
|
1607
|
+
const persisted = cachedPersisted ?? (!producer ? await persistedRow() : void 0);
|
|
1601
1608
|
const info = producer?.modelFailover?.();
|
|
1602
1609
|
const attribution = producer?.modelAttribution?.();
|
|
1603
1610
|
const model = producer?.model ?? persisted?.model ?? opts.model;
|
|
@@ -1660,14 +1667,21 @@ async function runDetachedTurn(opts) {
|
|
|
1660
1667
|
} catch (err) {
|
|
1661
1668
|
opts.log?.("[chat-routes] runDetachedTurn getStatus failed; treating as no prior", { turnId, err: String(err) });
|
|
1662
1669
|
}
|
|
1663
|
-
if (prior === "complete")
|
|
1670
|
+
if (prior === "complete") {
|
|
1671
|
+
const persisted = await persistedRow();
|
|
1672
|
+
return await settleRow(
|
|
1673
|
+
cachedResultFrom(await completed(), persisted),
|
|
1674
|
+
persisted
|
|
1675
|
+
);
|
|
1676
|
+
}
|
|
1664
1677
|
if (prior === "running") {
|
|
1665
1678
|
const final = await completed();
|
|
1666
1679
|
if (final) {
|
|
1667
1680
|
await store.setStatus(turnId, "complete", scopeId).catch((err) => {
|
|
1668
1681
|
opts.log?.("[chat-routes] runDetachedTurn failed to settle a completed running turn", { turnId, err: String(err) });
|
|
1669
1682
|
});
|
|
1670
|
-
|
|
1683
|
+
const persisted = await persistedRow();
|
|
1684
|
+
return await settleRow(cachedResultFrom(final, persisted), persisted);
|
|
1671
1685
|
}
|
|
1672
1686
|
if (opts.resetBuffer) {
|
|
1673
1687
|
await opts.resetBuffer(turnId).catch((err) => {
|
|
@@ -1714,17 +1728,171 @@ async function runDetachedTurn(opts) {
|
|
|
1714
1728
|
const text = producer.finalText?.() ?? "";
|
|
1715
1729
|
const parts = producer.assistantParts?.() ?? [];
|
|
1716
1730
|
let usage = producer.usage?.() ?? {};
|
|
1717
|
-
|
|
1731
|
+
const onlyTextParts = parts.every(
|
|
1732
|
+
(part) => String(part.type ?? "") === "text"
|
|
1733
|
+
);
|
|
1734
|
+
if (!runError && (!hasUsage(usage) || !text || onlyTextParts)) {
|
|
1718
1735
|
const final = await completed();
|
|
1719
1736
|
if (final?.usage) usage = { ...usage, ...final.usage };
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1737
|
+
return await settleRow({
|
|
1738
|
+
state: "completed",
|
|
1739
|
+
text: final?.text ?? text,
|
|
1740
|
+
parts: final?.parts?.length ? final.parts : parts,
|
|
1741
|
+
usage,
|
|
1742
|
+
cached: false
|
|
1743
|
+
});
|
|
1723
1744
|
}
|
|
1724
1745
|
if (runError) return await settleRow({ state: "failed", text, parts, usage, error: runError, cached: false });
|
|
1725
1746
|
return await settleRow({ state: "completed", text, parts, usage, cached: false });
|
|
1726
1747
|
}
|
|
1727
1748
|
|
|
1749
|
+
// src/chat-routes/completed-sandbox-turn.ts
|
|
1750
|
+
function finiteNumber(value) {
|
|
1751
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
1752
|
+
if (typeof value !== "string" || !value.trim()) return void 0;
|
|
1753
|
+
const parsed = Number(value);
|
|
1754
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
1755
|
+
}
|
|
1756
|
+
function firstNumber(record, keys) {
|
|
1757
|
+
for (const key of keys) {
|
|
1758
|
+
const value = finiteNumber(record?.[key]);
|
|
1759
|
+
if (value !== void 0) return value;
|
|
1760
|
+
}
|
|
1761
|
+
return void 0;
|
|
1762
|
+
}
|
|
1763
|
+
function usageFromResult(result) {
|
|
1764
|
+
const raw = asRecord(result?.usage) ?? asRecord(result?.tokenUsage);
|
|
1765
|
+
const inputTokens = firstNumber(raw, ["inputTokens", "promptTokens", "input"]);
|
|
1766
|
+
const outputTokens = firstNumber(raw, ["outputTokens", "completionTokens", "output"]);
|
|
1767
|
+
const reasoningTokens = firstNumber(raw, ["reasoningTokens", "reasoning"]);
|
|
1768
|
+
const cacheReadTokens = firstNumber(raw, [
|
|
1769
|
+
"cacheReadTokens",
|
|
1770
|
+
"cacheReadInputTokens",
|
|
1771
|
+
"cacheRead"
|
|
1772
|
+
]);
|
|
1773
|
+
const cacheWriteTokens = firstNumber(raw, [
|
|
1774
|
+
"cacheWriteTokens",
|
|
1775
|
+
"cacheCreationInputTokens",
|
|
1776
|
+
"cacheWrite"
|
|
1777
|
+
]);
|
|
1778
|
+
const costUsd = firstNumber(result, ["costUsd", "totalCostUsd", "cost"]) ?? firstNumber(raw, ["costUsd", "cost"]);
|
|
1779
|
+
return {
|
|
1780
|
+
...inputTokens !== void 0 ? { inputTokens } : {},
|
|
1781
|
+
...outputTokens !== void 0 ? { outputTokens } : {},
|
|
1782
|
+
...reasoningTokens !== void 0 ? { reasoningTokens } : {},
|
|
1783
|
+
...cacheReadTokens !== void 0 ? { cacheReadTokens } : {},
|
|
1784
|
+
...cacheWriteTokens !== void 0 ? { cacheWriteTokens } : {},
|
|
1785
|
+
...costUsd !== void 0 ? { costUsd } : {}
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
function textFromResult(result) {
|
|
1789
|
+
return asString(result?.response) ?? asString(result?.finalText) ?? asString(result?.text) ?? asString(result?.output);
|
|
1790
|
+
}
|
|
1791
|
+
function completedMessagesForTurn(messages, turnId) {
|
|
1792
|
+
return messages.filter(
|
|
1793
|
+
(message) => message.role === "assistant" && message.metadata?.turnId === turnId && message.metadata?.interrupted !== true && (message.metadata?.completed === true || message.metadata?.status === "completed")
|
|
1794
|
+
);
|
|
1795
|
+
}
|
|
1796
|
+
function normalizedMessageParts(message) {
|
|
1797
|
+
const normalized = [];
|
|
1798
|
+
const usage = {};
|
|
1799
|
+
for (const raw of message.parts) {
|
|
1800
|
+
const record = asRecord(raw);
|
|
1801
|
+
if (!record) continue;
|
|
1802
|
+
const part = normalizePersistedPart(record);
|
|
1803
|
+
if (!part) continue;
|
|
1804
|
+
normalized.push(part);
|
|
1805
|
+
if (part.type === "step-finish") addStepFinishUsage(part, usage);
|
|
1806
|
+
}
|
|
1807
|
+
const collapsed = collapseRedundantTextParts(normalized);
|
|
1808
|
+
const derivedText = collapsed.filter((part) => part.type === "text").map((part) => String(part.text ?? "")).join("");
|
|
1809
|
+
return { parts: normalized, usage, derivedText };
|
|
1810
|
+
}
|
|
1811
|
+
function projectMessageParts(parts, finalText) {
|
|
1812
|
+
const order = [];
|
|
1813
|
+
const map = /* @__PURE__ */ new Map();
|
|
1814
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
1815
|
+
const part = parts[index];
|
|
1816
|
+
const type = String(part.type ?? "");
|
|
1817
|
+
const key = type === "step-start" || type === "step-finish" ? `${type}:#${index}` : getPartKey(part);
|
|
1818
|
+
if (!map.has(key)) order.push(key);
|
|
1819
|
+
map.set(key, mergePersistedPart(map.get(key), part));
|
|
1820
|
+
}
|
|
1821
|
+
return finalizeAssistantParts(order, map, finalText);
|
|
1822
|
+
}
|
|
1823
|
+
function hasUsage2(usage) {
|
|
1824
|
+
return Object.values(usage).some(
|
|
1825
|
+
(value) => typeof value === "number" && Number.isFinite(value)
|
|
1826
|
+
);
|
|
1827
|
+
}
|
|
1828
|
+
async function readCompletedSandboxTurn(box, options) {
|
|
1829
|
+
const { turnId, sessionId, log } = options;
|
|
1830
|
+
const session = box.session(sessionId);
|
|
1831
|
+
const [cacheOutcome, messagesOutcome] = await Promise.allSettled([
|
|
1832
|
+
box.findCompletedTurn(turnId, { sessionId }),
|
|
1833
|
+
session.messages({ limit: 1e3 })
|
|
1834
|
+
]);
|
|
1835
|
+
if (cacheOutcome.status === "rejected") {
|
|
1836
|
+
log?.("[chat-routes] completed Sandbox turn cache lookup failed", {
|
|
1837
|
+
turnId,
|
|
1838
|
+
sessionId,
|
|
1839
|
+
error: String(cacheOutcome.reason)
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
if (messagesOutcome.status === "rejected") {
|
|
1843
|
+
log?.("[chat-routes] completed Sandbox session message lookup failed", {
|
|
1844
|
+
turnId,
|
|
1845
|
+
sessionId,
|
|
1846
|
+
error: String(messagesOutcome.reason)
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1849
|
+
const rawCached = cacheOutcome.status === "fulfilled" ? cacheOutcome.value : null;
|
|
1850
|
+
const cached = rawCached && rawCached.turnId === turnId && rawCached.sessionId === sessionId ? rawCached : null;
|
|
1851
|
+
if (rawCached && !cached) {
|
|
1852
|
+
log?.("[chat-routes] ignored mismatched completed Sandbox turn cache record", {
|
|
1853
|
+
turnId,
|
|
1854
|
+
sessionId
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
const messages = messagesOutcome.status === "fulfilled" ? messagesOutcome.value : [];
|
|
1858
|
+
const matchingMessages = completedMessagesForTurn(messages, turnId);
|
|
1859
|
+
const matchingCount = matchingMessages.length;
|
|
1860
|
+
const message = matchingCount === 1 ? matchingMessages[0] : null;
|
|
1861
|
+
if (matchingCount > 1) {
|
|
1862
|
+
log?.("[chat-routes] ignored ambiguous completed Sandbox session messages", {
|
|
1863
|
+
turnId,
|
|
1864
|
+
sessionId,
|
|
1865
|
+
matchingCount
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
if (!cached && !message) return null;
|
|
1869
|
+
let result = cached ? asRecord(cached.result) : void 0;
|
|
1870
|
+
const latestConversationMessage = messages.filter((candidate) => candidate.role === "user" || candidate.role === "assistant").at(-1);
|
|
1871
|
+
if (!result && message && latestConversationMessage === message) {
|
|
1872
|
+
try {
|
|
1873
|
+
result = asRecord(await session.result());
|
|
1874
|
+
} catch (error) {
|
|
1875
|
+
log?.("[chat-routes] completed Sandbox session result lookup failed", {
|
|
1876
|
+
turnId,
|
|
1877
|
+
sessionId,
|
|
1878
|
+
error: String(error)
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
const recovered = message ? normalizedMessageParts(message) : null;
|
|
1883
|
+
const text = textFromResult(result) ?? recovered?.derivedText;
|
|
1884
|
+
const resultUsage = usageFromResult(result);
|
|
1885
|
+
const usage = { ...recovered?.usage ?? {}, ...resultUsage };
|
|
1886
|
+
const resultParts = Array.isArray(result?.parts) ? result.parts.map((part) => asRecord(part)).filter((part) => Boolean(part)).map((part) => normalizePersistedPart(part)).filter((part) => Boolean(part)) : null;
|
|
1887
|
+
const sourceParts = recovered?.parts ?? resultParts;
|
|
1888
|
+
const parts = sourceParts ? projectMessageParts(sourceParts, text ?? recovered?.derivedText ?? "") : void 0;
|
|
1889
|
+
return {
|
|
1890
|
+
...text !== void 0 ? { text } : {},
|
|
1891
|
+
...hasUsage2(usage) ? { usage } : {},
|
|
1892
|
+
...parts ? { parts } : {}
|
|
1893
|
+
};
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1728
1896
|
// src/chat-routes/durable-projection.ts
|
|
1729
1897
|
function errorMessage(error) {
|
|
1730
1898
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -2537,6 +2705,7 @@ export {
|
|
|
2537
2705
|
parseFileMentions,
|
|
2538
2706
|
promoteAgentFilePart,
|
|
2539
2707
|
promptPartsByteSize,
|
|
2708
|
+
readCompletedSandboxTurn,
|
|
2540
2709
|
reconcileStaleTurnLock,
|
|
2541
2710
|
resolveChatAttachments,
|
|
2542
2711
|
resolveEmptyTurnRetries,
|