@tangle-network/agent-app 0.49.1 → 0.49.2
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 +1 -1
- package/dist/chunk-MFLUDFD3.js.map +0 -1
- package/dist/chunk-N4RR4WAI.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/chat-routes/model-failover-stream.ts","../../src/chat-routes/sandbox-turn-usage.ts","../../src/chat-routes/sandbox-producer.ts","../../src/chat-routes/protected-runtime-producer.ts","../../src/chat-routes/detached-turn.ts","../../src/chat-routes/completed-sandbox-turn.ts","../../src/chat-routes/durable-projection.ts","../../src/chat-routes/upload.ts","../../src/chat-routes/attachment-store.ts","../../src/chat-routes/resolve-attachments.ts","../../src/chat-routes/attachment-write-safety.ts","../../src/chat-routes/promote-file-part.ts","../../src/chat-routes/attachment-upload.ts","../../src/chat-routes/dispatch-parts.ts"],"sourcesContent":["/**\n * Model failover for a STREAMING turn.\n *\n * `/model-resolution`'s `runWithModelFailover` already owns the policy: walk a\n * chain, classify a resolved-or-thrown signal, re-throw a non-outage failure\n * immediately, and carry the attempt trail. This module does NOT re-implement\n * any of that — it composes it. What it adds is the one thing a whole-call\n * primitive cannot express, because a stream fails PARTWAY:\n *\n * **A turn may only fail over before its first client-visible byte.**\n *\n * Once a text delta, tool call, or ask has reached the browser (and the\n * persisted transcript), restarting on another model would duplicate the\n * answer. So each attempt is probed: open the stream, pull events into a small\n * buffer, and decide at the first meaningful event whether this model is\n * serving. Committing replays the buffer and hands the live iterator through;\n * abandoning discards the buffer (those events describe the dead model's\n * session — including its `step-finish` usage, which must never be billed) and\n * lets `runWithModelFailover` walk to the next model.\n *\n * `liveLifecycleEvents` relaxes the buffer for progress only. `start`,\n * `status`, `model-processing`, and session lifecycle carry no answer and no\n * receipt, so a product may take them the moment they arrive and show what\n * the turn is waiting on. Measured 2026-09-11 on a warm production box:\n * OpenCode reports whole parts, so the buffer held every event for 16 s and\n * the product could show nothing through provisioning and agent startup. Off\n * by default: with it on, an abandoned model's progress stays in the stream.\n *\n * The classification itself is `isUpstreamUnavailable` verbatim, so this path\n * inherits the measured facts from the 2026-07-25 outage — above all that an\n * outage is NOT always a thrown error: the sandbox RESOLVES a terminal `error`\n * event carrying `{ errorCode: 'provider_inference_unavailable' }`, which a\n * classifier inspecting only `catch` misses entirely. That resolved shape is\n * the whole reason the breakage went unnoticed, so it is classified here first.\n *\n * Conservative by construction:\n * - A terminal failure that is NOT an outage (400, bad schema, content filter)\n * COMMITS rather than failing over — it surfaces to the user exactly as it\n * does today. Those fail identically on every model; walking the chain would\n * only multiply latency and spend to reach the same error.\n * - A clean stream that produced nothing NEVER walks the chain. An empty answer\n * is not evidence of a dead upstream, and a silent re-roll on another model is\n * precisely the unattributable downgrade this work exists to prevent. Opt-in\n * `emptyTurnRetries` re-runs the SAME model instead, which leaves attribution\n * untouched — measured on production 2026-07-27, an empty turn is a transient\n * platform flake that a same-model re-run recovers (8 hard cases: 7/8\n * delivered on the first pass, 8/8 with one re-run, at a cost of 1 extra turn\n * in 9). Default `0`, so the behavior is unchanged unless a product asks.\n * - A responsive chain of length 1 costs no extra call or latency and remains\n * byte-identical to no failover. A silent chain is now deliberately bounded.\n */\n\nimport {\n isUpstreamUnavailable,\n ModelFailoverExhaustedError,\n readHttpStatusHint,\n runWithModelFailover,\n type ModelFailoverAttempt,\n} from '../model-resolution/failover'\nimport { asRecord, asString } from '../stream/index'\n\n/** Terminal event types that end an attempt with a failure verdict. */\nconst TERMINAL_FAILURE_TYPES = new Set(['error', 'session.run.failed'])\n\n/**\n * Part types that carry no transcript content. They are buffered like anything\n * else — replayed verbatim when the attempt commits — but their presence alone\n * never commits a model, so an outage arriving right after a `step-start` is\n * still recoverable. `step-finish` matters most: it carries the abandoned\n * attempt's token/cost receipt, which must be discarded rather than billed.\n */\nconst NON_COMMITTING_PART_TYPES = new Set(['step-start', 'step-finish'])\n\n/**\n * True when `event` puts content in front of the user (or in the persisted\n * transcript), making a restart on another model unsafe.\n *\n * Deliberately an allow-list of KNOWN-INERT types rather than a deny-list: an\n * unrecognized event commits. Getting this wrong in the safe direction costs a\n * missed failover; getting it wrong the other way duplicates a user's answer.\n */\nexport function isCommittingSandboxEvent(event: unknown): boolean {\n const record = asRecord(event)\n if (!record) return false\n const type = asString(record.type) ?? ''\n if (!type) return false\n\n if (type === 'message.part.updated') {\n const part = asRecord(asRecord(record.data)?.part)\n const partType = asString(part?.type) ?? ''\n if (NON_COMMITTING_PART_TYPES.has(partType)) return false\n // A text/reasoning part with NO content yet (the platform opens the part\n // with `text: \"\"` before the first token — verbatim in the 2026-07-26\n // capture) puts nothing in front of the user; discarding it cannot\n // duplicate an answer. The first real token commits.\n if (partType === 'text' || partType === 'reasoning') {\n const text = asString(part?.text) ?? asString(part?.content) ?? ''\n return text.length > 0\n }\n return true\n }\n\n // Session/turn lifecycle, progress status, and warnings belong to the\n // attempt, not the transcript — discardable with an abandoned model. Every\n // entry here was OBSERVED on a real box (2026-07-26 capture: a dead model\n // emits `start`, `execution.started`, `status`×3, `session.updated`×2,\n // `warning`×2 BEFORE its terminal `error` — any one of them committing\n // would pin the turn to the dead model and kill the failover).\n if (\n type === 'start' ||\n type === 'execution.started' ||\n type === 'status' ||\n type === 'model-processing' ||\n type === 'model.processing' ||\n type === 'session.created' ||\n type === 'session.updated' ||\n type === 'session.idle' ||\n type === 'step-start' ||\n type === 'step-finish' ||\n type === 'turn' ||\n type === 'warning'\n ) {\n return false\n }\n\n // Everything else — text/tool shapes the producer folds, asks, plans,\n // `result`/`done`, and any type this shell does not recognize — commits.\n return true\n}\n\n/**\n * Condense an abandoned attempt's raw failure text into something safe to show\n * a customer in the transcript.\n *\n * Measured on the live router 2026-07-27 19:40 UTC: an edge 5xx arrives as\n * Cloudflare's full HTML error PAGE, so the verbatim reason began\n * `<!DOCTYPE html>\\n<!--[if lt IE 7]> <html class=\"no-js ie6 oldie\"…` and the\n * fallback notice pasted 200 bytes of that markup into the answer the customer\n * reads. The cause is worth stating; the markup is not.\n *\n * Only the DISPLAY string is condensed. The full text stays on\n * `modelFailover.attempts[].reason` for the operator, so nothing is lost —\n * this narrows what the customer sees, never what telemetry records.\n */\nexport function summarizeFailoverReason(reason: string): string {\n const collapsed = reason.replace(/\\s+/g, ' ').trim()\n if (!collapsed) return 'upstream unavailable'\n\n // An HTML error page carries no message worth quoting — the status is the\n // whole content. Detected on the ORIGINAL text, before collapsing, because\n // that is the form the producer hands over.\n if (/<!doctype html|<html[\\s>]/i.test(collapsed)) {\n const status = readHttpStatusHint(collapsed)\n return status ? `HTTP ${status}` : 'upstream returned an error page'\n }\n\n // Anything else is a real message. Cap it so a verbose upstream cannot push\n // the answer off the screen, and cut on a word boundary when one is near.\n const LIMIT = 160\n if (collapsed.length <= LIMIT) return collapsed\n const clipped = collapsed.slice(0, LIMIT)\n const lastSpace = clipped.lastIndexOf(' ')\n return `${(lastSpace > LIMIT - 30 ? clipped.slice(0, lastSpace) : clipped).trimEnd()}…`\n}\n\n/** A terminal failure event, classified. `outage` decides failover vs surface. */\ninterface TerminalFailure {\n outage: boolean\n reason: string\n code?: string\n}\n\n/**\n * Classify a terminal failure event. Returns `null` for any non-terminal event.\n *\n * The RESOLVED shape is checked first and deliberately: the sandbox reports an\n * upstream outage by resolving `{ success: false, errorCode:\n * 'provider_inference_unavailable' }` inside a terminal `error` event's `data`,\n * never by throwing. Both `data` and the whole record are offered to\n * `isUpstreamUnavailable` so a payload nested either way is caught.\n */\n/**\n * Progress the consumer may see before the commit point: it names no model\n * output and carries no billing receipt, so streaming it live cannot duplicate\n * an answer or bill an abandoned attempt. `warning` and every\n * `message.part.updated` (including `step-start`/`step-finish`) stay buffered.\n */\nexport function isLiveLifecycleEvent(event: unknown): boolean {\n const type = asString(asRecord(event)?.type) ?? ''\n return (\n type === 'start' ||\n type === 'execution.started' ||\n type === 'status' ||\n type === 'model-processing' ||\n type === 'model.processing' ||\n type === 'session.created' ||\n type === 'session.updated' ||\n type === 'session.idle'\n )\n}\n\nexport function classifyTerminalFailure(event: unknown): TerminalFailure | null {\n const record = asRecord(event)\n if (!record) return null\n const type = asString(record.type) ?? ''\n if (!TERMINAL_FAILURE_TYPES.has(type)) return null\n\n const data = asRecord(record.data)\n const outage = isUpstreamUnavailable(data) || isUpstreamUnavailable(record)\n const reason =\n asString(data?.message) ??\n asString(data?.error) ??\n asString(data?.reason) ??\n asString(record.message) ??\n `sandbox stream reported ${type}`\n const code = asString(data?.errorCode) ?? asString(data?.code)\n return { outage, reason, ...(code ? { code } : {}) }\n}\n\n/** A model that served: its buffered preamble plus the still-live iterator. */\ninterface AttemptCommit {\n committed: true\n buffered: unknown[]\n /** `null` when the stream already ended during the probe. */\n iterator: AsyncIterator<unknown> | null\n /** Cancel transport work when this attempt is drained or abandoned. */\n abort: () => void\n}\n\n/**\n * A model whose upstream is down. `error`/`errorCode` are read by\n * `runWithModelFailover`'s attempt-trail describe(), so the trail names the\n * real upstream cause rather than a wrapper's own words.\n */\ninterface AttemptOutage {\n committed: false\n error: string\n errorCode?: string\n timeoutCode?: ModelAttemptTimeoutCode\n}\n\ntype AttemptOutcome = AttemptCommit | AttemptOutage\n\n/** Open the raw turn stream for one specific model. */\nexport type OpenModelStream = (args: {\n model: string\n /** 1 for the preferred model, 2 for the first fallback, and so on. */\n attempt: number\n /** Aborted when this attempt times out, is abandoned, or finishes. Forward\n * this to `streamSandboxPrompt(..., { signal })` so a blocked transport is\n * cancelled immediately rather than only receiving iterator.return(). */\n signal: AbortSignal\n}) => AsyncIterable<unknown> | Promise<AsyncIterable<unknown>>\n\n/** Fired when a model is abandoned and the next one is about to be tried. */\nexport interface ModelFallbackInfo {\n from: string\n to: string\n reason: string\n}\n\n/** Define inputs for streaming a turn across a model failover chain */\nexport interface ModelFailoverStreamOptions {\n /** Preferred model first, then fallbacks in descending preference. */\n models: readonly string[]\n open: OpenModelStream\n /**\n * Maximum time to open/start one model's event source, including its first\n * iterator pull. Default 120 seconds, matching the sandbox provisioning\n * ceiling so cold infrastructure startup is bounded independently from\n * provider inference.\n */\n openTimeoutMs?: number\n /**\n * Hard deadline from the source's first lifecycle event to its first\n * answer-bearing event. Default 60 seconds. Processing/lifecycle events do\n * not commit the attempt and do not extend this deadline.\n */\n firstResponseTimeoutMs?: number\n /** Override the commit-point rule. Default {@link isCommittingSandboxEvent}. */\n isCommitting?: (event: unknown) => boolean\n /**\n * Stream {@link isLiveLifecycleEvent} progress to the consumer as it arrives\n * instead of holding it until the attempt commits. Default `false`, which\n * keeps an abandoned model's events entirely off the wire. With it on, such\n * an event is never replayed from the buffer, and one from an abandoned\n * attempt stays in the stream — it describes the sandbox, not the answer.\n */\n liveLifecycleEvents?: boolean\n onFallback?: (info: ModelFallbackInfo) => void\n /**\n * How many times to RE-RUN THE SAME MODEL when a turn completes having\n * produced no assistant text at all. Default `0` — byte-identical to no\n * retry.\n *\n * This is deliberately not a chain walk. Falling over to a different model\n * on an empty answer is the unattributable downgrade this module refuses to\n * do; re-running the SAME model changes nothing about attribution, because\n * the model that serves is the model that was asked for.\n *\n * Bounded by the same commit rule as failover: only a turn whose ONLY\n * committing event is a terminal receipt with no text is retried, so nothing\n * that reached the user can ever be produced twice.\n */\n emptyTurnRetries?: number\n /** Fired when an empty turn is discarded and the same model re-run. */\n onEmptyTurnRetry?: (info: EmptyTurnRetryInfo) => void\n log?: (message: string, meta?: Record<string, unknown>) => void\n}\n\n/** One same-model re-run of a turn that completed with no assistant text. */\nexport interface EmptyTurnRetryInfo {\n model: string\n /** 1 for the first re-run. */\n retry: number\n /** How many re-runs remain after this one. */\n remaining: number\n}\n\n/** The failover-wrapped stream plus the attribution every consumer needs. */\nexport interface ModelFailoverStreamHandle {\n events: AsyncGenerator<unknown, void, unknown>\n /** The model that actually served. `undefined` until the first pull resolves it. */\n servingModel(): string | undefined\n /** Every model tried, in order, with the reason each was abandoned. */\n attempts(): ModelFailoverAttempt[]\n /** True when the preferred model did not serve — the attributability signal. */\n usedFallback(): boolean\n}\n\n/** Default ceiling for opening/starting one source through its first event. */\nexport const DEFAULT_MODEL_STREAM_OPEN_TIMEOUT_MS = 120_000\n\n/** Default ceiling for the first answer-bearing event after the source's first event. */\nexport const DEFAULT_MODEL_FIRST_RESPONSE_TIMEOUT_MS = 60_000\n\n/** Structured timeout codes surfaced by the producer on final exhaustion. */\nexport type ModelAttemptTimeoutCode =\n | 'model_stream_open_timeout'\n | 'provider_first_response_timeout'\n\n/** Every configured model was exhausted and the final one timed out. */\nexport class ModelFailoverTimeoutError extends ModelFailoverExhaustedError {\n readonly code: ModelAttemptTimeoutCode\n readonly model: string\n readonly timeoutMs: number\n\n constructor(input: {\n attempts: ModelFailoverAttempt[]\n code: ModelAttemptTimeoutCode\n model: string\n timeoutMs: number\n }) {\n super(input.attempts)\n this.name = 'ModelFailoverTimeoutError'\n this.code = input.code\n this.model = input.model\n this.timeoutMs = input.timeoutMs\n this.message = input.code === 'model_stream_open_timeout'\n ? `Opening the model event source for ${input.model} exceeded ${input.timeoutMs}ms.`\n : `Model ${input.model} produced no first answer-bearing event within ${input.timeoutMs}ms.`\n }\n}\n\nfunction resolveTimeoutMs(value: number | undefined, fallback: number, label: string): number {\n if (value === undefined) return fallback\n if (!Number.isFinite(value) || value <= 0) {\n throw new RangeError(`${label} must be a finite number greater than 0`)\n }\n return Math.max(1, Math.trunc(value))\n}\n\nconst TIMED_OUT = Symbol('model-attempt-timed-out')\n\nfunction deadlineAfter(timeoutMs: number): {\n promise: Promise<typeof TIMED_OUT>\n clear: () => void\n} {\n let timer: ReturnType<typeof setTimeout> | undefined\n const promise = new Promise<typeof TIMED_OUT>((resolve) => {\n timer = setTimeout(() => resolve(TIMED_OUT), timeoutMs)\n })\n return {\n promise,\n clear: () => {\n if (timer !== undefined) {\n clearTimeout(timer)\n timer = undefined\n }\n },\n }\n}\n\n/**\n * True when `event` is a terminal receipt (`result` / `done`) carrying no\n * assistant text.\n *\n * Measured on production `sandbox.tangle.tools` (2026-07-27, 28 turns through\n * the gtm-agent profile): a turn can end `{ outcome: { type: 'completed' } }`\n * with `finalText: ''` and zero token events — the platform reports success and\n * the customer's message is blank. It is not an outage, nothing throws, and\n * `isUpstreamUnavailable` correctly declines to classify it, so before this the\n * blank answer committed and shipped.\n */\nfunction isEmptyTerminalReceipt(event: unknown): boolean {\n const record = asRecord(event)\n if (!record) return false\n const type = asString(record.type) ?? ''\n if (type !== 'result' && type !== 'done') return false\n const data = asRecord(record.data)\n const text = asString(data?.finalText) ?? asString(data?.text) ?? ''\n return text.trim().length === 0\n}\n\n/**\n * Hard ceiling on same-model re-runs. A turn that comes back blank three times\n * running is not a flake this can retry away, and each pass costs a full\n * sandbox turn — so the budget is capped rather than trusted.\n */\nexport const MAX_EMPTY_TURN_RETRIES = 3\n\n/**\n * Coerce the caller's budget to a finite, bounded, non-negative integer.\n *\n * `Math.trunc(NaN)` is `NaN` and `retry >= NaN` is false for every `retry`, so\n * a naive clamp turns a bad config value into a loop that opens sandbox streams\n * until the worker dies. `Infinity` has the same shape. Both resolve to `0` —\n * an unusable budget disables the retry rather than running unbounded.\n */\nexport function resolveEmptyTurnRetries(value: number | undefined): number {\n if (typeof value !== 'number' || !Number.isFinite(value)) return 0\n const truncated = Math.trunc(value)\n if (truncated <= 0) return 0\n return Math.min(truncated, MAX_EMPTY_TURN_RETRIES)\n}\n\n/** Abandon a dead attempt's iterator. Never allowed to mask the outage. */\nasync function closeIterator(iterator: AsyncIterator<unknown>, log?: ModelFailoverStreamOptions['log']): Promise<void> {\n try {\n await iterator.return?.()\n } catch (err) {\n log?.('[chat-routes] abandoning a failed model stream threw', {\n error: err instanceof Error ? err.message : String(err),\n })\n }\n}\n\n/**\n * Wrap `open` in reactive model failover, streaming from the first model in\n * `models` that reaches its commit point.\n *\n * Zero added latency on the happy path: the preferred model is opened first and,\n * the moment it emits anything meaningful, its events flow straight through.\n *\n * @throws ModelFailoverTimeoutError when the final model times out,\n * ModelFailoverExhaustedError when every model's upstream is down, and\n * re-throws a non-outage error from the FIRST model without walking the\n * chain (the latter behaviors are inherited from\n * `runWithModelFailover`).\n */\nexport function streamWithModelFailover(\n options: ModelFailoverStreamOptions,\n): ModelFailoverStreamHandle {\n const committing = options.isCommitting ?? isCommittingSandboxEvent\n const emptyTurnRetries = resolveEmptyTurnRetries(options.emptyTurnRetries)\n const openTimeoutMs = resolveTimeoutMs(\n options.openTimeoutMs,\n DEFAULT_MODEL_STREAM_OPEN_TIMEOUT_MS,\n 'openTimeoutMs',\n )\n const firstResponseTimeoutMs = resolveTimeoutMs(\n options.firstResponseTimeoutMs,\n DEFAULT_MODEL_FIRST_RESPONSE_TIMEOUT_MS,\n 'firstResponseTimeoutMs',\n )\n const liveLifecycle = options.liveLifecycleEvents === true\n const progress = createProgressQueue()\n let serving: string | undefined\n let trail: ModelFailoverAttempt[] = []\n let fellBack = false\n let attemptIndex = 0\n let finalTimeout: {\n code: ModelAttemptTimeoutCode\n model: string\n timeoutMs: number\n } | undefined\n\n /**\n * One open-and-drain pass. `empty` marks the pass as an EMPTY TURN — the\n * only committing event was a terminal receipt with no assistant text, so\n * nothing reached the user and the pass can be discarded without any risk of\n * producing an answer twice. It still carries the outcome it would otherwise\n * have returned, so exhausting the retry budget is byte-identical to no\n * retry at all.\n */\n const drainOnce = async (model: string): Promise<{ outcome: AttemptOutcome; empty: boolean }> => {\n attemptIndex += 1\n const controller = new AbortController()\n const abort = (): void => {\n if (!controller.signal.aborted) controller.abort()\n }\n const opening = Promise.resolve().then(() => options.open({\n model,\n attempt: attemptIndex,\n signal: controller.signal,\n }))\n const openDeadline = deadlineAfter(openTimeoutMs)\n let opened: AsyncIterable<unknown> | typeof TIMED_OUT\n try {\n opened = await Promise.race([opening, openDeadline.promise])\n } catch (err) {\n openDeadline.clear()\n abort()\n throw err\n }\n if (opened === TIMED_OUT) {\n openDeadline.clear()\n abort()\n // A Promise cannot be forcibly cancelled. If the abandoned open settles\n // later, close its iterator immediately so it cannot leak a connection.\n void opening.then(\n (lateSource) => {\n try {\n void closeIterator(lateSource[Symbol.asyncIterator](), options.log)\n } catch (err) {\n options.log?.('[chat-routes] closing a late model stream threw', {\n error: err instanceof Error ? err.message : String(err),\n })\n }\n },\n (err) => {\n options.log?.('[chat-routes] abandoned model stream open rejected after timeout', {\n error: err instanceof Error ? err.message : String(err),\n })\n },\n )\n return {\n outcome: {\n committed: false,\n error: `Opening the model event source for ${model} exceeded ${openTimeoutMs}ms`,\n errorCode: 'model_stream_open_timeout',\n timeoutCode: 'model_stream_open_timeout',\n },\n empty: false,\n }\n }\n\n const source = opened\n let iterator: AsyncIterator<unknown>\n try {\n iterator = source[Symbol.asyncIterator]()\n } catch (err) {\n openDeadline.clear()\n abort()\n throw err\n }\n const buffered: unknown[] = []\n // Starting the source includes its first pull. `streamSandboxPrompt` is an\n // async generator, so its cold-start work does not execute until `next()`;\n // counting that time as provider inference would collapse the two phases.\n let next: IteratorResult<unknown> | typeof TIMED_OUT\n try {\n next = await Promise.race([\n Promise.resolve(iterator.next()),\n openDeadline.promise,\n ])\n } catch (err) {\n openDeadline.clear()\n abort()\n void closeIterator(iterator, options.log)\n throw err\n }\n openDeadline.clear()\n if (next === TIMED_OUT) {\n abort()\n void closeIterator(iterator, options.log)\n return {\n outcome: {\n committed: false,\n error: `Starting the model event source for ${model} exceeded ${openTimeoutMs}ms`,\n errorCode: 'model_stream_open_timeout',\n timeoutCode: 'model_stream_open_timeout',\n },\n empty: false,\n }\n }\n if (next.done) {\n abort()\n return { outcome: { committed: true, buffered, iterator: null, abort }, empty: true }\n }\n\n // One deadline for the WHOLE provider pre-response phase. Pulling a\n // lifecycle or processing event races the same promise; liveness cannot\n // reset the clock.\n const firstResponseDeadline = deadlineAfter(firstResponseTimeoutMs)\n\n for (;;) {\n const event = next.value\n let failure: TerminalFailure | null\n let commits = false\n try {\n failure = classifyTerminalFailure(event)\n commits = !failure && committing(event)\n } catch (err) {\n firstResponseDeadline.clear()\n abort()\n void closeIterator(iterator, options.log)\n throw err\n }\n if (failure?.outage) {\n firstResponseDeadline.clear()\n abort()\n void closeIterator(iterator, options.log)\n return {\n outcome: {\n committed: false,\n error: failure.reason,\n ...(failure.code ? { errorCode: failure.code } : {}),\n },\n empty: false,\n }\n }\n\n if (liveLifecycle && !failure && !commits && isLiveLifecycleEvent(event)) {\n progress.push(event)\n } else {\n buffered.push(event)\n }\n // A terminal NON-outage failure surfaces exactly as it does today.\n if (failure) {\n firstResponseDeadline.clear()\n return { outcome: { committed: true, buffered, iterator, abort }, empty: false }\n }\n if (commits) {\n firstResponseDeadline.clear()\n return { outcome: { committed: true, buffered, iterator, abort }, empty: isEmptyTerminalReceipt(event) }\n }\n\n // A THROWN failure propagates untouched: `runWithModelFailover` classifies\n // it (outage → next model, anything else → re-thrown to the caller).\n try {\n next = await Promise.race([\n Promise.resolve(iterator.next()),\n firstResponseDeadline.promise,\n ])\n } catch (err) {\n firstResponseDeadline.clear()\n abort()\n void closeIterator(iterator, options.log)\n throw err\n }\n if (next === TIMED_OUT) {\n firstResponseDeadline.clear()\n // Do not await `return()`: an async generator blocked inside `next()`\n // may not settle its return until that pull completes. Invoking it is\n // the cancellation signal; waiting for it would recreate the hang.\n abort()\n void closeIterator(iterator, options.log)\n return {\n outcome: {\n committed: false,\n error: `Model ${model} produced no first answer-bearing event within ${firstResponseTimeoutMs}ms`,\n errorCode: 'provider_first_response_timeout',\n timeoutCode: 'provider_first_response_timeout',\n },\n empty: false,\n }\n }\n if (next.done) {\n firstResponseDeadline.clear()\n abort()\n // Clean end with nothing committing. Not an outage — but also not an\n // answer, so it is re-runnable on the same model.\n return { outcome: { committed: true, buffered, iterator: null, abort }, empty: true }\n }\n }\n }\n\n const probe = async (model: string): Promise<AttemptOutcome> => {\n // Only the FINAL attempted model decides the exhaustion code. Clear a\n // prior model's timeout before this model can throw a different outage.\n finalTimeout = undefined\n for (let retry = 0; ; retry += 1) {\n const pass = await drainOnce(model)\n if (!pass.empty || retry >= emptyTurnRetries) {\n if (!pass.outcome.committed && pass.outcome.timeoutCode) {\n finalTimeout = {\n code: pass.outcome.timeoutCode,\n model,\n timeoutMs: pass.outcome.timeoutCode === 'model_stream_open_timeout'\n ? openTimeoutMs\n : firstResponseTimeoutMs,\n }\n } else {\n finalTimeout = undefined\n }\n return pass.outcome\n }\n // Discard the empty pass entirely — including its `step-finish` token\n // receipt, which must not be billed — and re-run the same model.\n const iterator = pass.outcome.committed ? pass.outcome.iterator : null\n if (pass.outcome.committed) pass.outcome.abort()\n if (iterator) void closeIterator(iterator, options.log)\n const info: EmptyTurnRetryInfo = { model, retry: retry + 1, remaining: emptyTurnRetries - retry - 1 }\n options.log?.('[chat-routes] turn completed with no assistant text; re-running the same model', {\n ...info,\n })\n options.onEmptyTurnRetry?.(info)\n }\n }\n\n const events = (async function* (): AsyncGenerator<unknown, void, unknown> {\n let handle: AttemptCommit\n // Progress arrives while the probe is still running. The queue closes only\n // after the verdict settles, so every event pushed before it is delivered\n // in order and ahead of the replayed buffer.\n const settled = runWithModelFailover<AttemptOutcome>({\n models: options.models,\n run: probe,\n // The probe has already classified the raw payload with\n // `isUpstreamUnavailable`; this reads its verdict rather than\n // re-classifying a wrapper object, so the two can never disagree.\n isUnavailableResult: (result) => result.committed === false,\n onFallback: (attempt, nextModel) => {\n const info: ModelFallbackInfo = {\n from: attempt.model,\n to: nextModel,\n reason: attempt.reason ?? 'upstream unavailable',\n }\n options.log?.('[chat-routes] model upstream unavailable; falling over', { ...info })\n options.onFallback?.(info)\n },\n }).finally(() => progress.close())\n // The verdict is read after the drain; mark the rejection handled until then.\n settled.catch(() => undefined)\n for (;;) {\n const item = await progress.pull()\n if (item === PROGRESS_CLOSED) break\n yield item\n }\n try {\n const outcome = await settled\n serving = outcome.model\n trail = outcome.attempts\n fellBack = outcome.usedFallback\n handle = outcome.value as AttemptCommit\n } catch (err) {\n // Exhaustion carries the full trail; keep it so the receipt still names\n // every model tried even though the turn produced nothing.\n const attempts = (err as { attempts?: ModelFailoverAttempt[] })?.attempts\n if (Array.isArray(attempts)) trail = attempts\n if (err instanceof ModelFailoverExhaustedError && finalTimeout) {\n throw new ModelFailoverTimeoutError({\n attempts: trail,\n ...finalTimeout,\n })\n }\n throw err\n }\n\n for (const event of handle.buffered) yield event\n\n const live = handle.iterator\n if (!live) {\n handle.abort()\n return\n }\n try {\n for (;;) {\n const next = await live.next()\n if (next.done) return\n yield next.value\n }\n } finally {\n // The consumer may abandon this generator mid-turn (client drop); close\n // the underlying stream rather than leaking it.\n handle.abort()\n await closeIterator(live, options.log)\n }\n })()\n\n return {\n events,\n servingModel: () => serving,\n attempts: () => trail,\n usedFallback: () => fellBack,\n }\n}\n\nconst PROGRESS_CLOSED: unique symbol = Symbol('progress-closed')\n\n/**\n * FIFO handoff from the probe (a plain promise chain) to the event generator.\n * `close` is idempotent and queues one sentinel behind everything pushed\n * before it, so a consumer never loses a progress event to the verdict.\n */\nfunction createProgressQueue(): {\n push(event: unknown): void\n close(): void\n pull(): Promise<unknown>\n} {\n const items: unknown[] = []\n const waiters: Array<(value: unknown) => void> = []\n let closed = false\n const deliver = (value: unknown): void => {\n const waiter = waiters.shift()\n if (waiter) waiter(value)\n else items.push(value)\n }\n return {\n push(event) {\n if (closed) return\n deliver(event)\n },\n close() {\n if (closed) return\n closed = true\n deliver(PROGRESS_CLOSED)\n },\n pull() {\n if (items.length > 0) return Promise.resolve(items.shift())\n if (closed) return Promise.resolve(PROGRESS_CLOSED)\n return new Promise((resolve) => waiters.push(resolve))\n },\n }\n}\n","import { asRecord, type JsonRecord } from '../stream/index'\nimport type { ChatTurnUsage } from './turn-routes'\n\n/** Add one canonical Sandbox `step-finish` receipt to a turn total. */\nexport function addStepFinishUsage(part: JsonRecord, usage: ChatTurnUsage): void {\n const tokens = asRecord(part.tokens)\n if (tokens) {\n const cache = asRecord(tokens.cache)\n const add = (current: number | undefined, value: unknown): number | undefined => {\n const n = Number(value)\n if (!Number.isFinite(n)) return current\n return (current ?? 0) + n\n }\n usage.inputTokens = add(usage.inputTokens, tokens.input)\n usage.outputTokens = add(usage.outputTokens, tokens.output)\n usage.reasoningTokens = add(usage.reasoningTokens, tokens.reasoning)\n if (cache) {\n usage.cacheReadTokens = add(usage.cacheReadTokens, cache.read)\n usage.cacheWriteTokens = add(usage.cacheWriteTokens, cache.write)\n }\n }\n const cost = Number(part.cost)\n if (Number.isFinite(cost)) usage.costUsd = (usage.costUsd ?? 0) + cost\n}\n","/**\n * Sandbox lane: bridge a raw sandbox event stream (`streamSandboxPrompt`) into\n * the `ChatTurnProducer` shape agent-runtime's `handleChatTurn` consumes AND\n * the client vocabulary `/web-react`'s `dispatchChatStreamLine` already parses\n * (`text` / `reasoning` / `tool_call` / `tool_result` / `usage` /\n * `notice` / structured `error` / `interaction`). Legal and tax each hand-rolled\n * this mapping differently;\n * this is that middle, composed from `/stream`'s normalizers — no new loop\n * logic, no SDK import (the event source is an injected `AsyncIterable`).\n *\n * Alongside the live mapping it accumulates the PERSISTED projection — the\n * `message.parts` rows `/chat-store` stores — via `normalizePersistedPart` /\n * `mergePersistedPart` / `finalizeAssistantParts`, plus the usage receipt from\n * `step-finish` parts. `createChatTurnRoutes` reads both after drain.\n */\n\nimport {\n cancelStatusFor,\n interactionPartKey,\n interactionToPersistedPart,\n isRenderableInteractionKind,\n noticePart,\n noticePartKey,\n parseInteractionCancel,\n parseInteractionRequest,\n} from '../interactions/contract'\nimport {\n parsePlanSubmittedEvent,\n planToPersistedPart,\n} from '../plans/index'\nimport {\n asRecord,\n asString,\n draftAssistantParts,\n finalizeAssistantParts,\n finalizePendingInteractionParts,\n getPartKey,\n mergePersistedPart,\n normalizePersistedPart,\n normalizeToolEvent,\n terminalizeDanglingAssistantToolUpdates,\n type JsonRecord,\n type StreamEvent,\n} from '../stream/index'\nimport { buildModelChain, type ModelFailoverAttempt } from '../model-resolution/failover'\nimport {\n ModelFailoverTimeoutError,\n resolveEmptyTurnRetries,\n streamWithModelFailover,\n summarizeFailoverReason,\n type EmptyTurnRetryInfo,\n type ModelFallbackInfo,\n type ModelFailoverStreamHandle,\n type OpenModelStream,\n} from './model-failover-stream'\nimport type {\n ChatTurnModelAttribution,\n ChatTurnRouteProducer,\n ChatTurnUsage,\n} from './turn-routes'\nimport { addStepFinishUsage } from './sandbox-turn-usage'\nimport type { ProducerWireEvent } from './wire'\n\n/** Outcome of a `promoteFilePart` attempt. `key`, when given, becomes the\n * persisted part's row key (e.g. `attachment:<path>`) so repeat promotions\n * of the same underlying file fold into one segment instead of appending;\n * omitted, the default `getPartKey` keying applies.\n *\n * On failure, `part` is an OPTIONAL substitute part to persist in place of\n * the raw url-bearing one — this is how a product swaps in a transcript\n * notice (gtm persists a `warning` notice part, never the transient url) for\n * a failed promotion instead of baking a `data:`/sandbox-path url into the\n * durable row. When `part` is present it is persisted (via the same\n * `recordPersistedPart` path as a success, honoring the optional `key`);\n * when absent, the existing raw-part fallback applies unchanged — so a\n * caller that only returns `{ succeeded: false, reason }` keeps today's\n * behavior verbatim. */\nexport type FilePartPromotionOutcome =\n | { succeeded: true; part: Record<string, unknown>; key?: string }\n | { succeeded: false; reason: string; part?: Record<string, unknown>; key?: string }\n\n/** Define options for producing sandbox chat events with rendering and interaction controls */\nexport interface SandboxChatProducerOptions {\n /** The raw sandbox event stream (e.g. `streamSandboxPrompt(...)`).\n *\n * An ALREADY-OPEN stream is bound to one model and cannot be reopened, so\n * this form can never fail over. Prefer {@link openEvents}; exactly one of\n * the two is required. */\n events?: AsyncIterable<unknown>\n /** Open the raw sandbox stream FOR A GIVEN MODEL — the failover-capable form\n * of {@link events}. The callback receives the model to run and returns the\n * same `AsyncIterable` `events` would have been (typically\n * `streamSandboxPrompt(shell, box, prompt, { ...opts, model })`).\n *\n * Wiring this turns failover ON with no further flag: whenever the resolved\n * chain (`model` + {@link fallbackModels}) holds more than one entry, a model\n * whose upstream is dead is abandoned BEFORE its first client-visible byte\n * and the next one is tried. A one-entry chain opens exactly once — no extra\n * call, no added latency, byte-identical to today.\n *\n * Requires {@link model}: failover has to know which model it is running. */\n openEvents?: OpenModelStream\n /** Recorded on the persisted assistant message. When failover moves the turn\n * to another model, the producer's `model` reports the model that ACTUALLY\n * served, not this preferred one — see {@link modelFailover}. */\n model?: string\n /** Models to try, in order, when `model`'s upstream is dead. Product config,\n * never a shell default: agent-app cannot know which ids are live, and a\n * baked list would rot into exactly the stale-liveness bug this guards\n * against. Empty/omitted → one attempt, today's behavior.\n *\n * Pick these deliberately. A same-family fallback is NOT automatically safe:\n * `gemini-2.5-flash` produced zero persisted deliverables in 2 of 3 measured\n * runs on a med-legal filing flow where `gemini-2.5-pro` succeeded. That is\n * why every fallback is surfaced (persisted `model`, a transcript notice, and\n * `modelFailover()`) instead of being applied silently. */\n fallbackModels?: readonly string[]\n /** Opt out of failover while still using {@link openEvents}. `false` collapses\n * the chain to `model` alone. */\n modelFailover?: false\n /** Maximum time to open/start one model event source through its first event.\n * Default 120 seconds. This is separate from provider first-output latency\n * so cold sandbox startup is not mislabeled as an inference failure. */\n openTimeoutMs?: number\n /** Hard deadline after the source's first lifecycle event for the first\n * answer-bearing event. Default 60 seconds. Processing/lifecycle events do\n * not extend it. */\n firstResponseTimeoutMs?: number\n /** Forward `start`/`status`/`model-processing`/session lifecycle to the\n * client as it arrives instead of after the commit point, so a product can\n * show what a turn is waiting on. Default `false`. An abandoned model's\n * progress then stays on the wire; its answer and receipt never do. */\n liveLifecycleEvents?: boolean\n /** Fired when a model is abandoned mid-chain (telemetry/alerting). The user\n * already sees a transcript notice; this is for the operator. */\n onModelFallback?: (info: ModelFallbackInfo) => void\n /** Re-run the SAME model this many times when a turn completes with no\n * assistant text at all. Default `0` (unchanged behavior). Requires\n * {@link openEvents} — there is nothing to re-open on a fixed stream.\n *\n * Distinct from {@link fallbackModels} on purpose: this never changes which\n * model answers, so it carries none of the attribution risk a downgrade\n * does. Measured on production 2026-07-27 through gtm-agent's profile, a\n * completed-but-blank turn is a transient platform flake — 8 hard cases went\n * 7/8 delivered to 8/8 with one re-run, costing 1 extra turn in 9. */\n emptyTurnRetries?: number\n /** Fired when a blank turn is discarded and the same model re-run. */\n onEmptyTurnRetry?: (info: EmptyTurnRetryInfo) => void\n /** Which ask kinds the product renders a card for. Anything else is\n * auto-declined (see `declineInteraction`) so the run never hangs in the\n * broker waiting on a card no client will show. Default: question/plan.\n * Products with per-turn plan mode can close over it without another option:\n * `(kind) => kind === 'question' || (kind === 'plan' && planEnabled)`. */\n isRenderableInteraction?: (kind: string) => boolean\n /** Resolve a non-renderable ask (wire `respondToSessionInteraction` with the\n * session's sidecar connection). Without it, a failure notice is emitted and\n * the run stays blocked until the broker times out. */\n declineInteraction?: (id: string) => Promise<void>\n /** Opt-in eager promotion of harness-emitted `file` parts. Unset, a `file`\n * part persists exactly as the harness sent it — a transient `url` (a\n * `data:` URI or in-sandbox path) baked into the transcript, which is\n * today's behavior and stays byte-identical if this is never wired. Set,\n * EVERY `file` part (never `image`, never any other kind) is routed\n * through this callback instead of `recordPersistedPart`'s default\n * fallback — including a part with NEITHER `id` NOR `url` (gtm always\n * attempts promotion; such a part simply fails \"carries no url\" and\n * resolves through the same failure path as any other rejection, rather\n * than being persisted raw and unpromoted) — so the product can durably\n * write the bytes and swap in a path-bearing part before the raw url ever\n * reaches the persisted transcript. Keyed per source-prefixed `id:<id>` /\n * `url:<url>` (an `id` and a `url` sharing the same text must never collide\n * onto one memo entry) and memoized by PROMISE (not result), so re-emitted\n * snapshot events for the same part —\n * the harness resends the whole part on every update, not just deltas —\n * fold onto the one in-flight or settled attempt rather than promoting\n * twice or racing two concurrent writes; a raw part with neither `id` nor\n * `url` cannot be keyed, so it is invoked UN-memoized (once per event) —\n * each occurrence is its own attempt. A rejecting promise is caught,\n * logged via `log`, and treated as `succeeded: false`. On `succeeded:\n * false` the outcome's optional `part` (a substitute — e.g. a warning\n * notice — see {@link FilePartPromotionOutcome}) persists in its place when\n * given; otherwise the raw part persists exactly as it does today — this\n * seam only decides whether to call the promoter and what to do with its\n * outcomes; the promotion mechanics (vault write, key derivation, notice\n * construction) live in the caller's callback, not here. */\n promoteFilePart?: (raw: JsonRecord) => Promise<FilePartPromotionOutcome>\n log?: (message: string, meta?: Record<string, unknown>) => void\n}\n\ninterface TextTracker {\n /** Full accumulated text per part key, to derive suffix deltas from\n * snapshot-only harness events. */\n seen: Map<string, string>\n}\n\n/** Delta to emit for one text/reasoning part update: prefer the harness's\n * explicit delta; otherwise diff the snapshot against what was already\n * emitted for that part (snapshot-only harnesses re-send the whole text). */\nfunction textDelta(tracker: TextTracker, key: string, part: JsonRecord, rawDelta: unknown): string {\n const explicit = typeof rawDelta === 'string' ? rawDelta : undefined\n const previous = tracker.seen.get(key) ?? ''\n if (explicit !== undefined) {\n tracker.seen.set(key, previous + explicit)\n return explicit\n }\n const snapshot = asString(part.text) ?? asString(part.content) ?? ''\n if (!snapshot) return ''\n if (snapshot.startsWith(previous)) {\n tracker.seen.set(key, snapshot)\n return snapshot.slice(previous.length)\n }\n // The snapshot replaced the text outright — emit it whole; the persisted\n // projection stays correct because finalText is authoritative at finalize.\n tracker.seen.set(key, snapshot)\n return snapshot\n}\n\nfunction parseEffectiveBackend(data: JsonRecord): Omit<\n ChatTurnModelAttribution,\n 'requestedModel' | 'echoReceived'\n> | undefined {\n const backend = asRecord(data.effectiveBackend)\n if (!backend) return undefined\n const provider = asString(backend.provider)\n const model = asString(backend.model)\n const rawSource = asString(backend.source)\n const source =\n rawSource === 'request' || rawSource === 'environment' || rawSource === 'profile'\n ? rawSource\n : undefined\n if (!provider && !model && !source) return undefined\n return {\n ...(model ? { servedModel: model } : {}),\n ...(provider ? { servedProvider: provider } : {}),\n ...(source ? { servedSource: source } : {}),\n }\n}\n\nfunction toFiniteNumber(value: unknown): number | null {\n if (typeof value === 'number') return Number.isFinite(value) ? value : null\n if (typeof value === 'string' && value.trim() !== '') {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : null\n }\n return null\n}\n\n/** Parse authoritative usage from a terminal sandbox `result` or `done`. */\nfunction extractReportedTurnUsage(data: JsonRecord | null | undefined): ChatTurnUsage | null {\n const tokenUsage = asRecord(data?.tokenUsage)\n if (!tokenUsage) return null\n\n const inputTokens = toFiniteNumber(tokenUsage.inputTokens)\n const outputTokens = toFiniteNumber(tokenUsage.outputTokens)\n if (inputTokens === null || outputTokens === null) return null\n\n const reported: ChatTurnUsage = { inputTokens, outputTokens }\n const reasoningTokens = toFiniteNumber(tokenUsage.reasoningTokens)\n if (reasoningTokens !== null) reported.reasoningTokens = reasoningTokens\n const cacheReadTokens = toFiniteNumber(tokenUsage.cacheReadInputTokens)\n if (cacheReadTokens !== null) reported.cacheReadTokens = cacheReadTokens\n const cacheWriteTokens = toFiniteNumber(tokenUsage.cacheCreationInputTokens)\n if (cacheWriteTokens !== null) reported.cacheWriteTokens = cacheWriteTokens\n const costUsd = toFiniteNumber(data?.totalCostUsd) ?? toFiniteNumber(tokenUsage.cost)\n if (costUsd !== null) reported.costUsd = costUsd\n return reported\n}\n\n/** Native runtime usage events contain cumulative totals, including repeated terminal snapshots. */\nfunction extractUsageSnapshot(value: JsonRecord | null | undefined): ChatTurnUsage | null {\n const inputTokens = toFiniteNumber(value?.promptTokens)\n const outputTokens = toFiniteNumber(value?.completionTokens)\n if (inputTokens === null || outputTokens === null || inputTokens < 0 || outputTokens < 0) return null\n const reported: ChatTurnUsage = { inputTokens, outputTokens }\n const reasoningTokens = toFiniteNumber(value?.reasoningTokens)\n if (reasoningTokens !== null && reasoningTokens >= 0) reported.reasoningTokens = reasoningTokens\n const costUsd = toFiniteNumber(value?.providerCostUsd)\n if (costUsd !== null && costUsd >= 0) reported.costUsd = costUsd\n return reported\n}\n\nfunction applyTerminalUsage(reported: ChatTurnUsage, usage: ChatTurnUsage): void {\n usage.inputTokens = reported.inputTokens\n usage.outputTokens = reported.outputTokens\n if (reported.reasoningTokens !== undefined) usage.reasoningTokens = reported.reasoningTokens\n if (reported.cacheReadTokens !== undefined) usage.cacheReadTokens = reported.cacheReadTokens\n if (reported.cacheWriteTokens !== undefined) usage.cacheWriteTokens = reported.cacheWriteTokens\n if (reported.costUsd !== undefined) usage.costUsd = reported.costUsd\n}\n\nfunction sandboxStreamErrorMessage(data: unknown): string {\n const record = asRecord(data)\n const message = asString(record?.message) ?? asString(record?.error)\n if (message) return message\n try {\n return JSON.stringify(data) || 'Sandbox stream returned an error event without details.'\n } catch {\n return 'Sandbox stream returned an error event without details.'\n }\n}\n\nfunction toProducerWireEvent(event: JsonRecord): ProducerWireEvent {\n return event as unknown as ProducerWireEvent\n}\n\nfunction sandboxStreamFailureDiagnostic(error: unknown): {\n userMessage: string\n failureNote: string\n code: string\n} {\n const streamMessage = (error as { streamMessage?: unknown })?.streamMessage\n const message = typeof streamMessage === 'string'\n ? streamMessage\n : error instanceof Error ? error.message : 'Sandbox stream failed'\n const diagnostics = asRecord((error as { diagnostics?: unknown })?.diagnostics)\n let diagnosticText: string | undefined\n if (diagnostics) {\n try {\n diagnosticText = JSON.stringify(diagnostics)\n } catch {\n diagnosticText = '[unserializable diagnostics]'\n }\n }\n const userMessage = [\n 'The sandbox model stream stopped before a clean completion.',\n `Error: ${message}`,\n 'Please retry. If it repeats, send these support details to the team.',\n ].join('\\n\\n')\n const failureNote = diagnosticText\n ? `sandbox-stream: ${message}; ${diagnosticText}`\n : `sandbox-stream: ${message}`\n const code = error instanceof ModelFailoverTimeoutError\n ? error.code\n : 'sandbox.stream_failed'\n return { userMessage, failureNote, code }\n}\n\n/** Create a sandbox chat producer that manages chat turn routing with logging and interaction rendering options */\nexport function createSandboxChatProducer(options: SandboxChatProducerOptions): ChatTurnRouteProducer {\n const log = options.log ?? ((message, meta) => console.error(message, meta ?? ''))\n const renderable = options.isRenderableInteraction ?? isRenderableInteractionKind\n\n // ── model failover ────────────────────────────────────────────────────────\n // ON by default whenever the caller can reopen the stream (`openEvents`) and\n // named more than one model. There is no `enabled` flag on purpose: the class\n // of failure this fixes was a capability that shipped switched off, and a\n // second opt-in on top of supplying the chain would reproduce it exactly.\n if (options.openEvents && !options.model) {\n throw new Error(\n 'createSandboxChatProducer: `openEvents` requires `model` — failover must know which model it is running',\n )\n }\n if (!options.openEvents && !options.events) {\n throw new Error('createSandboxChatProducer: pass `openEvents` (failover-capable) or `events`')\n }\n // A fixed `events` stream cannot be re-opened, so the retry silently does\n // nothing. A config that asks for recovery and receives none is the exact\n // silent-no-op class this seam exists to remove — refuse it at construction\n // rather than let a product believe blank turns are being retried.\n if (!options.openEvents && resolveEmptyTurnRetries(options.emptyTurnRetries) > 0) {\n throw new Error(\n 'createSandboxChatProducer: `emptyTurnRetries` requires `openEvents` — a fixed `events` stream cannot be re-opened',\n )\n }\n const chain = options.model\n ? buildModelChain(options.model, options.modelFailover === false ? [] : (options.fallbackModels ?? []))\n : []\n\n /** Fallback notices waiting to be emitted. `onFallback` fires from inside the\n * first pull of the failover stream, where a generator cannot yield, so the\n * notice is queued and drained at the top of the next loop iteration —\n * landing immediately before the serving model's first event. */\n const pendingModelNotices: Array<{ id: string; text: string }> = []\n let modelNoticeCount = 0\n\n let failover: ModelFailoverStreamHandle | undefined\n let source: AsyncIterable<unknown>\n if (options.openEvents) {\n failover = streamWithModelFailover({\n models: chain,\n open: options.openEvents,\n log,\n ...(options.openTimeoutMs !== undefined ? { openTimeoutMs: options.openTimeoutMs } : {}),\n ...(options.firstResponseTimeoutMs !== undefined\n ? { firstResponseTimeoutMs: options.firstResponseTimeoutMs }\n : {}),\n ...(options.emptyTurnRetries !== undefined ? { emptyTurnRetries: options.emptyTurnRetries } : {}),\n ...(options.onEmptyTurnRetry ? { onEmptyTurnRetry: options.onEmptyTurnRetry } : {}),\n ...(options.liveLifecycleEvents !== undefined ? { liveLifecycleEvents: options.liveLifecycleEvents } : {}),\n onFallback: (info) => {\n modelNoticeCount += 1\n pendingModelNotices.push({\n id: `model-fallback-${modelNoticeCount}`,\n // Named models on both sides: a quality regression after a downgrade\n // must be attributable to the model that actually answered. The\n // REASON is condensed — a real edge 5xx arrives as Cloudflare's HTML\n // error page, and the raw text put `<!DOCTYPE html><!--[if lt IE 7]>…`\n // into the customer's transcript. The verbatim text is still on\n // `modelFailover.attempts[].reason` for the operator.\n text: `${info.from} was unavailable (${summarizeFailoverReason(info.reason)}) — answered with ${info.to} instead.`,\n })\n options.onModelFallback?.(info)\n },\n })\n source = failover.events\n } else {\n source = options.events!\n }\n\n /** The model that ACTUALLY served this turn. Read lazily everywhere (the\n * `model` getter, the draft snapshot, the persisted row) so a fallback that\n * happens after the row was drafted still lands on the final write. */\n const servingModel = (): string | undefined => failover?.servingModel() ?? options.model\n let effectiveBackend: ReturnType<typeof parseEffectiveBackend>\n let substitutionNoticeQueued = false\n const servedModel = (): string | undefined => effectiveBackend?.servedModel ?? servingModel()\n\n const captureEffectiveBackend = (data: JsonRecord | undefined): void => {\n if (!data) return\n const parsed = parseEffectiveBackend(data)\n if (!parsed) return\n effectiveBackend = parsed\n const sentModel = servingModel()\n if (\n substitutionNoticeQueued ||\n !options.model ||\n !sentModel ||\n !parsed.servedModel ||\n parsed.servedModel === sentModel\n ) return\n substitutionNoticeQueued = true\n pendingModelNotices.push({\n id: 'model-substitution-1',\n text: `Requested ${sentModel} — the sandbox answered with ${parsed.servedModel} instead.`,\n })\n }\n\n let fullText = ''\n const partOrder: string[] = []\n const partMap = new Map<string, JsonRecord>()\n const tracker: TextTracker = { seen: new Map() }\n const usage: ChatTurnUsage = {}\n /** Whether each announced tool_call carried populated arguments. */\n const announcedToolArgs = new Map<string, boolean>()\n const settledTools = new Set<string>()\n /** Id-less step boundaries: one occurrence per key, never merged. */\n let stepCounter = 0\n let danglingToolsTerminalized = false\n let interactionOutcome: 'answered' | 'expired' = 'answered'\n let warningCount = 0\n /** `promoteFilePart` memo, keyed by `id ?? url` of the raw part. Holds the\n * PROMISE (never the settled result) so concurrent duplicate events for\n * the same part await the one in-flight attempt instead of each starting\n * their own; the promise never rejects (see below), so a later duplicate\n * reading a settled entry reuses that first outcome — success or\n * failure — rather than retrying. */\n const promotedFileParts = new Map<string, Promise<FilePartPromotionOutcome>>()\n\n function recordPersistedPart(part: JsonRecord, delta: string | undefined, keyOverride?: string): void {\n const persisted = normalizePersistedPart(part)\n if (!persisted) return\n const key = keyOverride ?? getPartKey(persisted)\n if (!partMap.has(key)) partOrder.push(key)\n partMap.set(key, mergePersistedPart(partMap.get(key), persisted, delta))\n }\n\n function* emitTerminalizedTools(): Generator<ProducerWireEvent, void, unknown> {\n if (danglingToolsTerminalized) return\n danglingToolsTerminalized = true\n const updates = terminalizeDanglingAssistantToolUpdates(partOrder, partMap, fullText)\n for (const part of updates) {\n const toolId = asString(part.id)\n if (!toolId || settledTools.has(toolId)) continue\n settledTools.add(toolId)\n const state = asRecord(part.state)\n yield {\n type: 'tool_result',\n toolCallId: toolId,\n toolName: asString(part.tool) ?? 'tool',\n outcome: {\n ok: false,\n ...(asString(state?.error) ? { message: asString(state?.error) } : {}),\n },\n }\n }\n }\n\n function* emitFinalUsage(): Generator<ProducerWireEvent, void, unknown> {\n const promptTokens = usage.inputTokens ?? 0\n const completionTokens = usage.outputTokens ?? 0\n if (promptTokens || completionTokens) {\n yield {\n type: 'usage',\n usage: {\n promptTokens,\n completionTokens,\n ...(usage.reasoningTokens !== undefined ? { reasoningTokens: usage.reasoningTokens } : {}),\n ...(usage.costUsd !== undefined ? { providerCostUsd: usage.costUsd } : {}),\n },\n }\n }\n }\n\n /** Emit any queued fallback notices, persisting each so the downgrade is in\n * the durable transcript and not only in the live stream. Uses the existing\n * `warning` notice kind deliberately: every shipped client already renders\n * it, whereas a bespoke kind would be dropped by `dispatchChatStreamLine`'s\n * filter — an attribution nobody sees is not attribution. */\n function* drainModelNotices(): Generator<ProducerWireEvent, void, unknown> {\n while (pendingModelNotices.length > 0) {\n const queued = pendingModelNotices.shift()!\n const notice = noticePart('warning', queued.id, queued.text)\n recordPersistedPart(notice, undefined, noticePartKey(notice.id))\n yield { type: 'notice', id: notice.id, noticeKind: 'warning', text: queued.text }\n }\n }\n\n async function* stream(): AsyncGenerator<ProducerWireEvent, void, unknown> {\n try {\n for await (const raw of source) {\n const record = asRecord(raw)\n captureEffectiveBackend(asRecord(record?.data))\n yield* drainModelNotices()\n if (!record || typeof record.type !== 'string') continue\n // Fold bare tool_call/tool_result shapes into the canonical part event;\n // everything else keeps its original record (verbatim forwarding must\n // not strip fields outside `data`).\n const normalized = normalizeToolEvent({ type: record.type, data: asRecord(record.data) })\n const event: StreamEvent = normalized.type === 'message.part.updated'\n ? normalized\n : { type: record.type, data: asRecord(record.data) }\n\n if (event.type === 'message.part.updated') {\n const part = asRecord(event.data?.part)\n if (!part) continue\n const rawDelta = event.data?.delta\n const partType = String(part.type ?? '')\n\n if (partType === 'text' || partType === 'reasoning') {\n const key = getPartKey(part)\n const delta = textDelta(tracker, key, part, rawDelta)\n recordPersistedPart(part, delta || undefined)\n if (delta) {\n if (partType === 'text') fullText += delta\n yield { type: partType, text: delta }\n }\n continue\n }\n\n if (partType === 'tool') {\n recordPersistedPart(part, undefined)\n const persisted = partMap.get(getPartKey(part))\n const state = asRecord(persisted?.state)\n const toolId = String(persisted?.id ?? '')\n const toolName = String(persisted?.tool ?? 'tool')\n const args = asRecord(state?.input) ?? {}\n const hasPopulatedArgs = Object.keys(args).length > 0\n const announcedWithPopulatedArgs = announcedToolArgs.get(toolId)\n if (\n toolId &&\n (announcedWithPopulatedArgs === undefined ||\n (!announcedWithPopulatedArgs && hasPopulatedArgs && !settledTools.has(toolId)))\n ) {\n announcedToolArgs.set(toolId, hasPopulatedArgs)\n yield {\n type: 'tool_call',\n call: { toolCallId: toolId, toolName, args },\n }\n }\n const status = String(state?.status ?? '')\n if (toolId && (status === 'completed' || status === 'error') && !settledTools.has(toolId)) {\n settledTools.add(toolId)\n yield {\n type: 'tool_result',\n toolCallId: toolId,\n toolName,\n outcome: {\n ok: status === 'completed',\n ...(state?.output !== undefined ? { result: state.output } : {}),\n ...(asString(state?.error) ? { message: asString(state?.error) } : {}),\n },\n }\n }\n continue\n }\n\n if (partType === 'step-finish') {\n addStepFinishUsage(part, usage)\n // Persist the per-step receipt too (unique key per occurrence: the\n // parts have no id and two receipts must never merge into one).\n recordPersistedPart(part, undefined, `step-finish:#${stepCounter++}`)\n const promptTokens = usage.inputTokens ?? 0\n const completionTokens = usage.outputTokens ?? 0\n if (promptTokens || completionTokens) {\n yield {\n type: 'usage',\n usage: {\n promptTokens,\n completionTokens,\n ...(usage.reasoningTokens !== undefined\n ? { reasoningTokens: usage.reasoningTokens }\n : {}),\n ...(usage.costUsd !== undefined ? { providerCostUsd: usage.costUsd } : {}),\n },\n }\n }\n continue\n }\n\n if (partType === 'step-start') {\n recordPersistedPart(part, undefined, `step-start:#${stepCounter}`)\n continue\n }\n\n if (partType === 'file' && options.promoteFilePart) {\n const promote = options.promoteFilePart\n // Prefixed by source (`id:`/`url:`) rather than the bare raw string —\n // an `id` and a `url` that happen to share the same text (e.g. both\n // `\"abc\"`) would otherwise collide onto one memo entry and fold two\n // unrelated parts' promotions together. Internal Map key only; never\n // observable outside this module.\n const rawId = asString(part.id)\n const rawUrl = asString(part.url)\n const memoKey = rawId ? `id:${rawId}` : rawUrl ? `url:${rawUrl}` : undefined\n // Always invoke the callback for a `file` part when one is wired —\n // gtm never skips promotion outright, even for a part with neither\n // `id` nor `url` (it simply fails \"carries no url\" and resolves\n // through the ordinary failure path). Memoization by PROMISE only\n // applies when there is something to key it on; keyless parts are\n // invoked un-memoized, once per occurrence.\n //\n // `Promise.resolve().then(...)` (rather than calling `promote(part)`\n // directly) so a SYNC throw from a non-async `promoteFilePart` is\n // captured into the promise chain instead of escaping this function\n // call outright — `.catch` below only ever sees a rejection, never a\n // thrown exception that unwinds past `attempt()`.\n const attempt = (): Promise<FilePartPromotionOutcome> =>\n Promise.resolve()\n .then(() => promote(part))\n .catch((err) => {\n const reason = err instanceof Error ? err.message : String(err)\n log('[chat-routes] file part promotion threw', { key: memoKey ?? '(keyless)', error: reason })\n return { succeeded: false as const, reason }\n })\n\n let pending: Promise<FilePartPromotionOutcome>\n if (memoKey) {\n pending = promotedFileParts.get(memoKey) ?? attempt()\n promotedFileParts.set(memoKey, pending)\n } else {\n pending = attempt()\n }\n\n const outcome = await pending\n if (outcome.succeeded) {\n recordPersistedPart(outcome.part, undefined, outcome.key)\n } else if (outcome.part) {\n // Substitute part (e.g. a warning notice) takes the raw part's\n // place in the transcript — the transient url never lands.\n recordPersistedPart(outcome.part, undefined, outcome.key)\n } else {\n recordPersistedPart(part, undefined)\n }\n continue\n }\n\n // Remaining storable kinds (file/image/subtask) have no live\n // vocabulary line; they persist so the transcript keeps them.\n recordPersistedPart(part, undefined)\n continue\n }\n\n if (event.type === 'interaction') {\n const parsed = parseInteractionRequest(asRecord(record.data))\n if (!parsed.succeeded) {\n log('[chat-routes] dropping malformed interaction event', { error: parsed.error })\n continue\n }\n if (renderable(parsed.value.kind)) {\n recordPersistedPart(\n interactionToPersistedPart(parsed.value, 'pending'),\n undefined,\n interactionPartKey(parsed.value.id),\n )\n yield toProducerWireEvent(record)\n continue\n }\n // Non-renderable ask: the run is blocked in the broker until someone\n // answers. Decline it so the turn proceeds instead of hanging.\n let declineFailed = false\n if (options.declineInteraction) {\n try {\n await options.declineInteraction(parsed.value.id)\n } catch (err) {\n declineFailed = true\n log('[chat-routes] failed to auto-decline interaction', {\n id: parsed.value.id,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n } else {\n declineFailed = true\n log('[chat-routes] non-renderable interaction with no declineInteraction wired', {\n id: parsed.value.id,\n kind: parsed.value.kind,\n })\n }\n const text = declineFailed\n ? `The agent requested ${parsed.value.kind} approval; declining it failed — it will expire on its own.`\n : `The agent requested ${parsed.value.kind} approval — auto-declined by policy.`\n const notice = noticePart('auto-declined', `auto-declined-${parsed.value.id}`, text)\n recordPersistedPart(notice, undefined, noticePartKey(notice.id))\n yield { type: 'notice', id: notice.id, noticeKind: 'auto-declined', text }\n continue\n }\n\n if (event.type === 'interaction.cancel') {\n const parsed = parseInteractionCancel(asRecord(record.data))\n if (!parsed.succeeded) {\n log('[chat-routes] dropping malformed interaction.cancel event', { error: parsed.error })\n continue\n }\n const key = interactionPartKey(parsed.value.id)\n const existing = partMap.get(key)\n if (existing?.type === 'interaction' && existing.status === 'pending') {\n recordPersistedPart({\n ...existing,\n status: cancelStatusFor(parsed.value.reason),\n ...(parsed.value.reason ? { cancelReason: parsed.value.reason } : {}),\n }, undefined, key)\n }\n yield toProducerWireEvent(record)\n continue\n }\n\n if (event.type === 'plan.submitted') {\n const parsed = parsePlanSubmittedEvent(record)\n if (!parsed.succeeded) {\n log('[chat-routes] dropping malformed plan.submitted event', { error: parsed.error })\n continue\n }\n recordPersistedPart(planToPersistedPart(parsed.value), undefined)\n yield toProducerWireEvent(record)\n continue\n }\n\n if (event.type === 'warning') {\n const message = asString(event.data?.message)\n if (message) {\n warningCount += 1\n const code = asString(event.data?.code)\n const text = code ? `${code}: ${message}` : message\n const notice = noticePart('warning', `warning-${warningCount}`, text)\n recordPersistedPart(notice, undefined, noticePartKey(notice.id))\n yield { type: 'notice', id: notice.id, noticeKind: 'warning', text }\n }\n // The flattened notice is additive; existing raw-warning consumers keep\n // receiving the original event too.\n yield toProducerWireEvent(record)\n continue\n }\n\n if (event.type === 'usage') {\n const reported = extractUsageSnapshot(asRecord(record.usage))\n if (reported) applyTerminalUsage(reported, usage)\n yield toProducerWireEvent(record)\n continue\n }\n\n if (event.type === 'result') {\n const finalText = asString(event.data?.finalText)\n if (finalText) fullText = finalText\n const reported = extractReportedTurnUsage(asRecord(event.data))\n if (reported) {\n applyTerminalUsage(reported, usage)\n yield* emitFinalUsage()\n } else {\n // Legacy result shape kept as a secondary fallback.\n const resultUsage = asRecord(event.data?.usage)\n if (resultUsage) {\n const input = Number(resultUsage.inputTokens)\n const output = Number(resultUsage.outputTokens)\n if (Number.isFinite(input)) usage.inputTokens = input\n if (Number.isFinite(output)) usage.outputTokens = output\n }\n }\n yield* emitTerminalizedTools()\n continue\n }\n\n if (event.type === 'done') {\n const reported = extractReportedTurnUsage(asRecord(event.data))\n if (reported) {\n applyTerminalUsage(reported, usage)\n yield* emitFinalUsage()\n }\n yield* emitTerminalizedTools()\n yield toProducerWireEvent(record)\n continue\n }\n\n if (event.type === 'error') {\n const message = sandboxStreamErrorMessage(event.data)\n const errorContent = fullText.trim()\n ? `The sandbox model stream stopped before a clean completion.\\n\\nError: ${message}`\n : `The sandbox agent returned an error before producing a visible answer.\\n\\nError: ${message}`\n const errorDelta = fullText ? `\\n\\n---\\n${errorContent}` : errorContent\n fullText += errorDelta\n interactionOutcome = 'expired'\n yield { type: 'text', text: errorDelta }\n yield* emitTerminalizedTools()\n yield toProducerWireEvent(record)\n continue\n }\n\n // Remaining lifecycle events forward verbatim; the client parser ignores\n // unknown types.\n yield toProducerWireEvent(record)\n }\n // A model that fell over and then produced nothing never entered the loop\n // body; the downgrade must still be recorded.\n yield* drainModelNotices()\n } catch (streamErr) {\n const diagnostic = sandboxStreamFailureDiagnostic(streamErr)\n log('[chat-routes] sandbox stream failed', {\n failureNote: diagnostic.failureNote,\n error: streamErr instanceof Error ? streamErr.message : String(streamErr),\n })\n const errorDelta = fullText\n ? `\\n\\n---\\n${diagnostic.userMessage}`\n : diagnostic.userMessage\n fullText += errorDelta\n interactionOutcome = 'expired'\n yield { type: 'text', text: errorDelta }\n yield* emitTerminalizedTools()\n yield {\n type: 'error',\n data: {\n message: diagnostic.userMessage,\n code: diagnostic.code,\n details: { failureNote: diagnostic.failureNote },\n },\n }\n }\n }\n\n return {\n stream: stream(),\n finalText: () => fullText,\n assistantParts: () => finalizePendingInteractionParts(\n finalizeAssistantParts(partOrder, partMap, fullText),\n interactionOutcome,\n ),\n // Mid-stream snapshot for incremental persistence: the same accumulators,\n // WITHOUT the two completion-time settlements (`terminalizeDanglingTool*`\n // and `finalizePendingInteractionParts`). A running tool and an unanswered\n // ask are the correct live state; settling them early would persist\n // phantom failures the final write then reverses.\n draftParts: () => draftAssistantParts(partOrder, partMap, fullText),\n usage: () => usage,\n // A GETTER, not a captured value: `turn-routes` reads `producer.model` at\n // draft-snapshot and persist time, both of which happen after the stream\n // resolved which model serves. A plain property would freeze the PREFERRED\n // model into the row and make a downgrade unattributable — the exact\n // failure mode this work exists to prevent.\n get model(): string | undefined {\n return servedModel()\n },\n modelFailover: () => ({\n model: servingModel(),\n attempts: failover?.attempts() ?? [],\n usedFallback: failover?.usedFallback() ?? false,\n }),\n modelAttribution: () => ({\n ...(options.model ? { requestedModel: options.model } : {}),\n ...(effectiveBackend ?? {}),\n echoReceived: effectiveBackend !== undefined,\n }),\n }\n}\n","import type { AgentProfile } from '@tangle-network/agent-interface'\nimport { createExecutor, streamAgentTurn } from '@tangle-network/agent-runtime/kernel'\nimport {\n runProtectedAgentCandidateModelGrant,\n type RunProtectedAgentCandidateModelGrantOptions,\n} from '@tangle-network/agent-runtime/candidate-execution'\nimport { ProtectedModelSettlementError } from '../runtime/protected-model'\nimport type { McpToolDefinition } from '../tools/mcp-rpc'\nimport { createSandboxChatProducer } from './sandbox-producer'\nimport type { ChatRouteEvent, ChatTurnRouteProducer, ChatTurnUsage } from './turn-routes'\n\nexport interface ProtectedRuntimeChatOptions {\n profile: AgentProfile\n prompt: string\n priorMessages?: ReadonlyArray<{ role: string; content: string }>\n /** Runtime owns reservation, activation, revocation and authoritative settlement. */\n grant: Omit<RunProtectedAgentCandidateModelGrantOptions<void>, 'execute'>\n /** Only tools authorized for this execution. Each tool validates its arguments and effects. */\n tools: readonly McpToolDefinition<{ signal: AbortSignal }>[]\n maxToolCalls: number\n signal?: AbortSignal\n}\n\n/** Run a profile through the protected Router tool executor and the shared chat projection. */\nexport function createProtectedRuntimeChatProducer(options: ProtectedRuntimeChatOptions): ChatTurnRouteProducer {\n if (!Number.isSafeInteger(options.maxToolCalls) || options.maxToolCalls < 0) {\n throw new Error('maxToolCalls must be a non-negative integer')\n }\n options = { ...options,\n grant: { ...options.grant, resolve: structuredClone(options.grant.resolve), reserve: structuredClone(options.grant.reserve) },\n tools: options.tools.map(tool => ({ ...tool, inputSchema: structuredClone(tool.inputSchema) })),\n priorMessages: options.priorMessages ? structuredClone(options.priorMessages) : undefined,\n }\n const tools = new Map(options.tools.map(tool => [tool.name, tool]))\n if (tools.size !== options.tools.length) throw new Error('Duplicate protected tool name')\n const profile = structuredClone(options.profile)\n let toolCalls = 0\n let usage: ChatTurnUsage = {}\n let started = false\n let model: string | undefined\n let budgetEnforced = false\n let toolTokens: number | undefined\n\n async function* events(): AsyncGenerator<unknown> {\n if (started) throw new Error('A protected chat producer can only execute once')\n started = true\n const controller = new AbortController()\n options.signal?.throwIfAborted()\n if (Date.now() >= options.grant.deadlineAtMs) throw new Error('Protected execution deadline expired')\n const signal = AbortSignal.any([\n controller.signal, AbortSignal.timeout(Math.max(1, options.grant.deadlineAtMs - Date.now())),\n ...(options.signal ? [options.signal] : []),\n ])\n const channel = new TransformStream<unknown, unknown>()\n const writer = channel.writable.getWriter()\n const reader = channel.readable.getReader()\n const emit = (event: unknown) => writer.write(event)\n const execution = runProtectedAgentCandidateModelGrant({\n ...options.grant,\n port: {\n resolve: input => options.grant.port.resolve(input),\n reserveGrant: input => options.grant.port.reserveGrant(input),\n activateGrant: input => options.grant.port.activateGrant(input),\n // Runtime settles failed executions before rethrowing. Retain that receipt too.\n settleGrant: async input => {\n let auditError: ProtectedModelSettlementError | undefined\n const settlement = await options.grant.port.settleGrant(input).catch(error => {\n if (!(error instanceof ProtectedModelSettlementError)) throw error\n auditError = error\n return error.settlement\n })\n usage = {\n inputTokens: settlement.calls.reduce((sum, call) => sum + call.accountedInputTokens, 0),\n outputTokens: settlement.calls.reduce((sum, call) => sum + call.outputTokens, 0),\n reasoningTokens: settlement.calls.reduce((sum, call) => sum + call.reasoningTokens, 0),\n costUsd: settlement.calls.reduce((sum, call) => sum + call.costUsdNanos, 0) / 1e9,\n }\n budgetEnforced = settlement.usageWithinLimits\n if (!budgetEnforced) throw new Error('Provider settlement exceeded execution limits')\n if (auditError) throw auditError\n return settlement\n },\n },\n execute: async ({ activation, resolved }) => {\n signal.throwIfAborted()\n if (Date.now() >= options.grant.deadlineAtMs) throw new Error('Protected execution deadline expired')\n model = resolved.model\n // Protected Anthropic requests permit only client tools. Their result\n // text is included in input tokens, with no separately billed hosted tools.\n if (resolved.provider !== 'anthropic' || resolved.reasoningEffort !== 'none') {\n throw new Error('Protected chat currently requires Anthropic without reasoning')\n }\n toolTokens = 0\n const apiKey = activation.env.OPENAI_API_KEY\n const baseUrl = activation.env.OPENAI_BASE_URL\n if (!apiKey || !baseUrl) throw new Error('Protected Router activation is missing')\n const factory = createExecutor({\n backend: 'router-tools', routerBaseUrl: baseUrl, routerKey: apiKey,\n tools: options.tools.map(tool => ({ type: 'function' as const,\n function: { name: tool.name, description: tool.description, parameters: tool.inputSchema } })),\n ...(options.priorMessages?.length ? { initialMessages: options.priorMessages } : {}),\n executeToolCall: async (name, args) => {\n signal.throwIfAborted()\n const tool = tools.get(name)\n if (!tool) throw new Error('Tool is not authorized for this execution')\n if (toolCalls >= options.maxToolCalls) {\n controller.abort(new Error('Execution tool-call limit reached'))\n signal.throwIfAborted()\n }\n toolCalls += 1\n const id = `tool-${toolCalls}`\n await emit({ type: 'tool_call', data: { id, name, arguments: args } })\n try {\n const result = await tool.run(args, { signal })\n await emit({ type: 'tool_result', data: { id, name, output: result } })\n return JSON.stringify(result)\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Tool failed'\n await emit({ type: 'tool_result', data: { id, name, error: message } })\n throw error\n }\n },\n })\n let completed = false\n let streamedText = ''\n for await (const event of streamAgentTurn({ kind: 'executor', factory, profile },\n { prompt: options.prompt }, { signal, callId: options.grant.reserve.executionId,\n timeoutMs: Math.max(1, options.grant.deadlineAtMs - Date.now()) })) {\n if (event.type === 'text_delta') streamedText += event.text\n if (event.type === 'text_delta' || event.type === 'reasoning_delta') {\n await emit({ type: 'message.part.updated', data: {\n part: { id: event.type, type: event.type === 'text_delta' ? 'text' : 'reasoning' }, delta: event.text,\n } })\n }\n if (event.type === 'final') {\n if (event.status !== 'completed') throw new Error(`Protected execution ${event.status}`)\n // Executor backends may emit only a final snapshot. The chat and\n // gateway wire append text, so emit its missing suffix exactly once.\n if (event.text !== undefined) {\n if (!event.text.startsWith(streamedText)) {\n throw new Error('Protected execution revised already streamed text')\n }\n const suffix = event.text.slice(streamedText.length)\n if (suffix) await emit({ type: 'message.part.updated', data: {\n part: { id: 'text_delta', type: 'text' }, delta: suffix,\n } })\n streamedText = event.text\n }\n completed = true\n await emit({ type: 'result', data: { finalText: event.text } })\n }\n }\n if (!completed) throw new Error('Protected execution ended without a terminal result')\n },\n }).then(async () => {\n await writer.close()\n }).catch(error => writer.abort(error))\n try {\n for (;;) {\n const next = await reader.read()\n if (next.done) break\n yield next.value\n }\n await execution\n } finally {\n controller.abort()\n await reader.cancel().catch(() => undefined)\n await execution\n reader.releaseLock()\n writer.releaseLock()\n }\n }\n\n const producer = createSandboxChatProducer({ events: events(), model: profile.model?.default })\n return {\n ...producer,\n stream: (async function* () {\n let failure: ChatRouteEvent | undefined\n for await (const event of producer.stream) {\n if (event.type === 'error') failure = event\n else yield event\n }\n if (usage.inputTokens !== undefined && usage.outputTokens !== undefined && usage.costUsd !== undefined) {\n yield { type: 'usage' as const, usage: { promptTokens: usage.inputTokens,\n completionTokens: usage.outputTokens, providerCostUsd: usage.costUsd,\n reasoningTokens: usage.reasoningTokens, toolTokens,\n toolCallCount: toolCalls, budgetEnforced } }\n }\n if (failure) yield failure\n })(),\n get model() { return model ?? profile.model?.default },\n usage: () => ({ ...usage }),\n }\n}\n","/**\n * Detached (autonomous) turn → live buffer bridge.\n *\n * The interactive lane (`createChatTurnRoutes`) already streams a user-typed\n * turn to the browser while it runs. An AUTONOMOUS turn — a mission step, a\n * queue job, an inbound-email review — runs detached (`dispatchPrompt`/\n * `streamPrompt` server-side so it survives no one watching) and, historically,\n * only persisted its FINAL message. A browser opening the session mid-run saw a\n * dead screen: the live tokens existed server-side but were never written to\n * the turn-event buffer the client re-attach path (`listRunning` + `/replay`)\n * reads.\n *\n * `runDetachedTurn` is that missing bridge, packaged. It taps the same buffer\n * the interactive lane uses (`createBufferedTurnTap`) with the same producer\n * mapping (`createSandboxChatProducer`), so an autonomous run is watchable\n * token-by-token exactly like an interactive one — while staying durable\n * (a durable driver re-invokes it after a crash; a turn that finished\n * server-side short-circuits instead of re-streaming). Products supply only the\n * domain seams: the raw sandbox event stream, the turn store, and the ids.\n *\n * This is app-shell mechanism (turn durability + live projection), not engine:\n * it owns no loop logic and imports no SDK — the event source is an injected\n * `AsyncIterable`.\n */\n\nimport { toChatMessageParts } from '../chat-store/parts'\nimport type { ModelFailoverAttempt } from '../model-resolution/failover'\nimport {\n coalesceDeltas,\n createBufferedTurnTap,\n type TurnEventStore,\n} from '../stream/index'\nimport {\n assistantRowIdForTurn,\n createAssistantDraftWriter,\n storeSupportsDraftPersistence,\n type AssistantDraftStore,\n type AssistantDraftWriter,\n type AssistantRowValues,\n type DraftStoredMessage,\n type DraftPersistenceTuning,\n} from './draft-persistence'\nimport {\n createSandboxChatProducer,\n type SandboxChatProducerOptions,\n} from './sandbox-producer'\nimport type { ChatTurnUsage } from './turn-routes'\n\n/** The normalized structured message body (tool-call / file / plan / interaction\n * parts) that `/chat-store` persists as the durable assistant row — the same\n * shape `createSandboxChatProducer().assistantParts()` returns. */\nexport type DetachedTurnParts = Array<Record<string, unknown>>\n\n/** Authoritative final receipt for a turn that finished server-side, or whose\n * live stream carried no usage (some harness paths only expose tokens via the\n * completed-turn record, e.g. `box.findCompletedTurn(turnId)`). */\nexport interface DetachedTurnFinal {\n text?: string\n usage?: ChatTurnUsage\n /** The structured parts to persist when this receipt is more complete than\n * the live stream (cached, finished server-side, or a fast stream that\n * delivered scalar text before its message-part events). Omitted when the\n * record only carries a usage receipt. */\n parts?: DetachedTurnParts\n}\n\n/** Define options for managing and projecting a detached turn event stream in a session */\nexport interface DetachedTurnOptions {\n store: TurnEventStore\n turnId: string\n /** Thread/session id — recorded as the buffer scope so a browser opening the\n * session mid-run rediscovers this turn via `listRunning(scopeId)` after it\n * has lost the turnId. */\n scopeId: string\n /** The raw sandbox event stream for this turn (e.g. `streamSandboxPrompt`).\n * Ownership of the box, prompt, tooling, and attachments stays with the\n * caller — this only projects the stream.\n *\n * An already-open stream is bound to one model and cannot fail over. Prefer\n * {@link openEvents}; exactly one of the two is required. */\n events?: AsyncIterable<unknown>\n /** Open the raw sandbox stream FOR A GIVEN MODEL — the failover-capable form\n * of {@link events}. Wiring it turns failover on with no further flag\n * whenever {@link fallbackModels} is non-empty. Requires {@link model}.\n *\n * An autonomous run is the case that most needs this: nobody is watching to\n * notice a dead upstream and retry by hand, so without failover the mission\n * step or queue job simply fails. Forwarded to the producer. */\n openEvents?: SandboxChatProducerOptions['openEvents']\n /** Models to try, in order, when `model`'s upstream is dead. Product config —\n * see the producer's note on why a same-family fallback is not automatically\n * safe and why every fallback is surfaced. */\n fallbackModels?: SandboxChatProducerOptions['fallbackModels']\n /** Opt out of failover while still using {@link openEvents}. */\n modelFailover?: false\n /** Maximum time to open/start one model event source through its first event.\n * Forwarded to the producer; default 120 seconds. */\n openTimeoutMs?: SandboxChatProducerOptions['openTimeoutMs']\n /** Hard deadline after the source's first lifecycle event for the first\n * answer-bearing event. Forwarded to the producer; default 60 seconds. */\n firstResponseTimeoutMs?: SandboxChatProducerOptions['firstResponseTimeoutMs']\n /** Fired when a model is abandoned mid-chain (telemetry/alerting). */\n onModelFallback?: SandboxChatProducerOptions['onModelFallback']\n /** The PREFERRED model. Recorded on the persisted assistant message + usage\n * receipt — unless failover moved the turn, in which case the model that\n * actually served is recorded instead and surfaced on the result. */\n model?: string\n /** Per-flush buffer coalescer. Default `coalesceDeltas`. */\n coalesce?: (events: unknown[]) => unknown[]\n /** Which ask kinds the product renders a card for; anything else is\n * auto-declined via {@link declineInteraction}. Forwarded to the producer. */\n isRenderableInteraction?: SandboxChatProducerOptions['isRenderableInteraction']\n /** Resolve a non-renderable ask so the run never hangs in the broker. An\n * autonomous turn has NO human watching to answer an ask, so a caller that\n * omits this risks the run blocking until the broker times out — wire it for\n * any unattended run. Forwarded to the producer. */\n declineInteraction?: SandboxChatProducerOptions['declineInteraction']\n /** Opt-in eager promotion of harness-emitted `file` parts. Forwarded to the\n * producer (see its docs). */\n promoteFilePart?: SandboxChatProducerOptions['promoteFilePart']\n /** Authoritative final receipt, consulted whenever a re-invoke finds a prior\n * buffer: (a) an already-`complete` turn returns it as the cached result,\n * (b) a `running` turn (crash mid-run) uses it to detect a run that finished\n * server-side, and (c) a clean run whose stream carried no usage or only\n * scalar text falls back to it. For Sandbox runs, use\n * `readCompletedSandboxTurn` so the exact completed session message\n * restores tool/file parts as well as text. */\n completedResult?: () => Promise<DetachedTurnFinal | null | undefined>\n /** Clear the prior partial buffer for `turnId` before a genuine re-stream.\n * A crash mid-run leaves buffered rows at seqs 1..N with status `running`;\n * re-streaming restarts the tap's seq at 0 and would duplicate/interleave\n * rows. Wire this (delete `turnId`'s buffered events) so a retry is clean.\n * A re-stream over an existing `running` buffer requires this operation to\n * succeed. Missing or failed reset leaves the existing stream untouched. */\n resetBuffer?: (turnId: string) => Promise<void>\n /** Own the durable assistant row for this turn instead of returning the body\n * for the caller to insert — and keep it in step with the stream.\n *\n * An autonomous run is exactly the case a late viewer hits: nobody is\n * watching when it starts, so by the time a browser opens the session the\n * streaming gateway's hot event buffer may already have expired it. Keeping\n * that buffer short is what makes it affordable at scale (its Redis\n * footprint is linear in `ttl x concurrent sessions`), so the durable row —\n * written incrementally here — is what serves the late viewer.\n *\n * WIRING THIS TRANSFERS ROW OWNERSHIP: the returned {@link\n * DetachedTurnResult.messageId} names the row this call wrote (draft rows\n * during the stream, authoritative values at the end, retraction when the\n * turn produced nothing). The caller must NOT insert its own assistant row\n * for the turn. Omit the seam and nothing changes — the result is returned\n * and the caller persists it exactly as today.\n *\n * Idempotency reuses the turn's own identity: the row id defaults to\n * `assistant:<turnId>`, so a durable driver re-invoking after a crash\n * patches the same row instead of duplicating parts. */\n persist?: DraftPersistenceTuning & {\n store: AssistantDraftStore\n threadId: string\n /** Deterministic row id. Default `assistant:<turnId>`. */\n messageId?: string\n /** Pre-persist text transform (`/redact`), applied to drafts AND the final\n * write — parity with the interactive lane's `transformFinalText`. */\n transformText?: (text: string) => string | Promise<string>\n }\n log?: (message: string, meta?: Record<string, unknown>) => void\n}\n\n/** Describe the result of a detached turn including state, text, parts, usage, and optional error or cache flag */\nexport interface DetachedTurnResult {\n /** `completed` — clean drain: persist + bill. `failed` — a terminal error\n * event, including the producer's structured `sandbox.stream_failed` event\n * when the raw sandbox stream throws: skip billing, render an error row. */\n state: 'completed' | 'failed'\n text: string\n /** The structured assistant body to persist (tool calls, file/plan/interaction\n * parts). Empty array when the run produced none. */\n parts: DetachedTurnParts\n usage: ChatTurnUsage\n /** Present when `state === 'failed'`. */\n error?: string\n /** True when a prior buffer meant this call returned a cached/finished result\n * WITHOUT re-streaming (durable-driver retry after a crash). */\n cached: boolean\n /** The durable assistant row this call wrote, when `persist` was wired.\n * `null` when the turn produced nothing and the row was retracted. Absent\n * when the caller owns persistence (today's behavior). */\n messageId?: string | null\n /** The model that SERVED the turn — the fallback's id when failover moved it.\n * A caller that bills or scores per model must read this, not the model it\n * requested. */\n model?: string\n /** The caller's explicit model request, before shell failover. */\n requestedModel?: string\n /** The effective model echoed by the downstream sandbox. */\n servedModel?: string\n /** The effective provider echoed by the downstream sandbox. */\n servedProvider?: string\n /** How the downstream sandbox selected the effective model. */\n servedSource?: 'request' | 'environment' | 'profile'\n /** True when the preferred model did not serve. Makes an autonomous\n * downgrade — which no human watched happen — attributable after the fact. */\n usedModelFallback?: boolean\n /** Every model tried, in order, with the reason each was abandoned. */\n modelAttempts?: ModelFailoverAttempt[]\n}\n\n/** Terminal failure event types a producer may forward verbatim. */\nconst TERMINAL_ERROR_TYPES = new Set(['error', 'session.run.failed'])\n\nfunction errorMessageOf(ev: unknown): string {\n const rec = ev as { data?: { message?: unknown; reason?: unknown }; message?: unknown } | null\n const raw = rec?.data?.message ?? rec?.data?.reason ?? rec?.message\n return typeof raw === 'string' && raw ? raw : 'run failed'\n}\n\nfunction hasUsage(usage: ChatTurnUsage): boolean {\n return typeof usage.inputTokens === 'number' && usage.inputTokens > 0\n}\n\nfunction cachedResultFrom(\n final: DetachedTurnFinal | null,\n persisted?: DraftStoredMessage,\n): DetachedTurnResult {\n if (!final && !persisted) {\n throw new Error('Completed turn has no retained result or assistant row; retry exact recovery')\n }\n // A text-only recovery must not erase usage already stored for this turn.\n // Unknown values stay absent; an explicit measured zero is preserved.\n const usage: ChatTurnUsage = {}\n for (const key of [\n 'inputTokens', 'outputTokens', 'reasoningTokens',\n 'cacheReadTokens', 'cacheWriteTokens', 'costUsd',\n ] as const) {\n const value = final?.usage?.[key] ?? persisted?.[key]\n if (typeof value === 'number' && Number.isFinite(value) && value >= 0) usage[key] = value\n }\n return {\n state: 'completed',\n text: final?.text ?? persisted?.content ?? '',\n parts:\n final?.parts ??\n (persisted?.parts as DetachedTurnParts | null | undefined) ??\n [],\n usage,\n cached: true,\n }\n}\n\n/**\n * Stream a detached turn into the live turn-event buffer, durably.\n *\n * - Idempotent: an already-`complete` turn returns the cached result without\n * re-streaming (a second event sequence would collide with the buffered one).\n * - Crash-safe: a `running` turn (a prior attempt crashed mid-tap) consults\n * `completedResult` to detect a run that finished server-side; only a run that\n * genuinely did not complete is re-streamed, and then over a `resetBuffer`-\n * cleared buffer so seqs don't corrupt.\n * - Marks the turn `running` under `scopeId` so a mid-run browser finds it.\n * - Settles `complete`/`error` so the client stops tailing and billing/render\n * can branch on `state`.\n */\nexport async function runDetachedTurn(opts: DetachedTurnOptions): Promise<DetachedTurnResult> {\n const { store, turnId, scopeId } = opts\n\n // Hoisted so the draft writer's snapshot can read the producer's live\n // accumulators; assigned once the idempotency branches decide to re-stream.\n let producer: ReturnType<typeof createSandboxChatProducer> | undefined\n\n /** The model that actually served. Read LAZILY (never captured up front): on\n * a cached/finished-server-side path no producer exists and the preferred\n * model is the best available answer, but on a live re-stream failover may\n * have moved the turn after the row was first drafted. */\n const servingModel = (): string | undefined => producer?.model ?? opts.model\n\n const persistedRow = async (): Promise<DraftStoredMessage | undefined> => {\n if (!opts.persist) return undefined\n return (await opts.persist.store.listMessages(opts.persist.threadId)).find(\n (message) =>\n message.id ===\n (opts.persist?.messageId ?? assistantRowIdForTurn(turnId)),\n )\n }\n\n // Durable row ownership (opt-in). Built before the idempotency branches so a\n // cached/finished-server-side re-invoke still converges the row instead of\n // leaving whatever partial state a crashed attempt wrote.\n let draft: AssistantDraftWriter | undefined\n if (opts.persist) {\n const { store: persistStore, threadId, messageId, transformText, ...tuning } = opts.persist\n if (!storeSupportsDraftPersistence(persistStore)) {\n throw new Error(\n 'runDetachedTurn persist requires a store with updateMessage() — `/chat-store`\\'s createChatStore has it',\n )\n }\n draft = createAssistantDraftWriter({\n ...tuning,\n store: persistStore,\n threadId,\n messageId: messageId ?? assistantRowIdForTurn(turnId),\n snapshot: () => (producer\n ? {\n content: producer.finalText?.() ?? '',\n ...(producer.draftParts ? { parts: producer.draftParts() } : {}),\n ...(producer.usage ? { usage: producer.usage() } : {}),\n ...(servingModel() ? { model: servingModel() } : {}),\n }\n : null),\n ...(transformText ? { transformText } : {}),\n ...(opts.log ? { log: opts.log } : {}),\n })\n }\n\n /** Settle the durable row with authoritative values (or retract it when the\n * turn produced nothing), and stamp the id onto the result. */\n const settleRow = async (\n base: DetachedTurnResult,\n cachedPersisted?: DraftStoredMessage,\n ): Promise<DetachedTurnResult> => {\n // A cached/reconciled return has no producer to replay the sidecar echo.\n // Preserve the attribution already written by the prior attempt instead\n // of replacing its served `model` with today's requested-model fallback.\n const persisted =\n cachedPersisted ?? (!producer ? await persistedRow() : undefined)\n const info = producer?.modelFailover?.()\n const attribution = producer?.modelAttribution?.()\n const model = producer?.model ?? persisted?.model ?? opts.model\n const requestedModel = attribution?.requestedModel ?? persisted?.requestedModel ?? opts.model\n const servedModel = attribution?.servedModel ?? persisted?.servedModel\n const servedProvider = attribution?.servedProvider ?? persisted?.servedProvider\n const servedSource = attribution?.servedSource ?? (\n persisted?.servedSource === 'request' ||\n persisted?.servedSource === 'environment' ||\n persisted?.servedSource === 'profile'\n ? persisted.servedSource\n : undefined\n )\n const result: DetachedTurnResult = {\n ...base,\n ...(model ? { model } : {}),\n ...(requestedModel ? { requestedModel } : {}),\n ...(servedModel ? { servedModel } : {}),\n ...(servedProvider ? { servedProvider } : {}),\n ...(servedSource ? { servedSource } : {}),\n ...(info ? { usedModelFallback: info.usedFallback, modelAttempts: info.attempts } : {}),\n }\n if (!draft) return result\n const transform = opts.persist?.transformText\n const content = transform ? await transform(result.text) : result.text\n const rawParts = transform\n ? await Promise.all(\n result.parts.map(async (part) =>\n String((part as { type?: unknown }).type ?? '') === 'text'\n ? { ...part, text: await transform(String((part as { text?: unknown }).text ?? '')) }\n : part,\n ),\n )\n : result.parts\n const parts = toChatMessageParts(rawParts)\n if (!content.trim() && parts.length === 0) {\n await draft.discard()\n return { ...result, messageId: null }\n }\n const values: AssistantRowValues = {\n content,\n ...(parts.length > 0 ? { parts } : {}),\n ...(result.model ? { model: result.model } : {}),\n ...(result.requestedModel ? { requestedModel: result.requestedModel } : {}),\n ...(result.servedModel ? { servedModel: result.servedModel } : {}),\n ...(result.servedProvider ? { servedProvider: result.servedProvider } : {}),\n ...(result.servedSource ? { servedSource: result.servedSource } : {}),\n ...(result.usage.inputTokens !== undefined ? { inputTokens: result.usage.inputTokens } : {}),\n ...(result.usage.outputTokens !== undefined ? { outputTokens: result.usage.outputTokens } : {}),\n ...(result.usage.reasoningTokens !== undefined ? { reasoningTokens: result.usage.reasoningTokens } : {}),\n ...(result.usage.cacheReadTokens !== undefined ? { cacheReadTokens: result.usage.cacheReadTokens } : {}),\n ...(result.usage.cacheWriteTokens !== undefined ? { cacheWriteTokens: result.usage.cacheWriteTokens } : {}),\n ...(result.usage.costUsd !== undefined ? { costUsd: result.usage.costUsd } : {}),\n }\n await draft.finalize(values)\n return { ...result, messageId: draft.rowId() ?? null }\n }\n\n const completed = async (): Promise<DetachedTurnFinal | null> => {\n if (!opts.completedResult) return null\n // An unavailable authoritative read must not become a fresh stream.\n return (await opts.completedResult()) ?? null\n }\n\n const prior = await store.getStatus(turnId)\n\n if (prior === 'complete') {\n const persisted = await persistedRow()\n return await settleRow(\n cachedResultFrom(await completed(), persisted),\n persisted,\n )\n }\n\n if (prior === 'running') {\n // A prior attempt marked the turn running and then this worker crashed. The\n // detached SESSION may have finished server-side while we were gone — the\n // authoritative check is `completedResult` (findCompletedTurn). If it\n // completed, settle the stuck `running` buffer and return it.\n const final = await completed()\n if (final) {\n await store.setStatus(turnId, 'complete', scopeId)\n const persisted = await persistedRow()\n return await settleRow(cachedResultFrom(final, persisted), persisted)\n }\n // Genuine re-run: clear the partial buffer first, or the fresh tap's seq\n // (restarting at 0) interleaves with the orphaned rows.\n if (!opts.resetBuffer) {\n throw new Error('Detached turn recovery requires resetBuffer before replaying an existing stream')\n }\n await opts.resetBuffer(turnId)\n }\n\n const tap = createBufferedTurnTap({\n store,\n turnId,\n scopeId,\n coalesce: opts.coalesce ?? coalesceDeltas,\n })\n // Leading turn marker: flips the buffer to `running` (so `listRunning` finds\n // it) and is the browser's `/replay` resume handle.\n await tap.onEvent({ type: 'turn', turnId })\n\n producer = createSandboxChatProducer({\n ...(opts.openEvents ? { openEvents: opts.openEvents } : { events: opts.events }),\n model: opts.model,\n ...(opts.fallbackModels ? { fallbackModels: opts.fallbackModels } : {}),\n ...(opts.modelFailover === false ? { modelFailover: false as const } : {}),\n ...(opts.openTimeoutMs !== undefined ? { openTimeoutMs: opts.openTimeoutMs } : {}),\n ...(opts.firstResponseTimeoutMs !== undefined\n ? { firstResponseTimeoutMs: opts.firstResponseTimeoutMs }\n : {}),\n ...(opts.onModelFallback ? { onModelFallback: opts.onModelFallback } : {}),\n isRenderableInteraction: opts.isRenderableInteraction,\n declineInteraction: opts.declineInteraction,\n promoteFilePart: opts.promoteFilePart,\n log: opts.log,\n })\n\n let runError: string | undefined\n try {\n for await (const ev of producer.stream) {\n const type = (ev as { type?: unknown }).type\n if (typeof type === 'string' && TERMINAL_ERROR_TYPES.has(type)) runError = errorMessageOf(ev)\n await tap.onEvent(ev)\n // Same coalesced cadence the interactive lane uses: the durable row\n // tracks the run so a viewer arriving after the hot buffer's short window\n // reads it from storage.\n draft?.notify(ev as { type?: unknown })\n }\n await tap.done(runError ? 'error' : 'complete')\n } catch (err) {\n await tap.done('error').catch(() => {})\n // Settle any in-flight draft before propagating so the partial row a\n // re-invoke will adopt is a complete write, not a half-landed one.\n await draft?.close().catch(() => {})\n throw err\n }\n\n const text = producer.finalText?.() ?? ''\n const parts = producer.assistantParts?.() ?? []\n let usage: ChatTurnUsage = producer.usage?.() ?? {}\n\n // A terminal result can supply scalar text even when the event subscriber\n // missed every structured message part. Reconcile that text-only shape with\n // the durable completion record before final persistence.\n const onlyTextParts = parts.every((part) =>\n String((part as { type?: unknown }).type ?? '') === 'text',\n )\n if (!runError && (!hasUsage(usage) || !text || onlyTextParts)) {\n const final = await completed()\n if (final?.usage) usage = { ...usage, ...final.usage }\n return await settleRow({\n state: 'completed',\n text: final?.text ?? text,\n parts: final?.parts?.length ? final.parts : parts,\n usage,\n cached: false,\n })\n }\n\n if (runError) return await settleRow({ state: 'failed', text, parts, usage, error: runError, cached: false })\n return await settleRow({ state: 'completed', text, parts, usage, cached: false })\n}\n","/**\n * Recover a detached Sandbox turn from its durable completed records.\n *\n * A fast detached run can finish before the live event subscriber receives\n * every message-part event. The Sandbox still retains two exact records: a\n * turn-id keyed result cache and the completed assistant message on the\n * session. This adapter joins them without guessing across turns, then returns\n * the same `DetachedTurnFinal` shape `runDetachedTurn` already consumes.\n */\n\nimport type { SandboxInstance, SessionMessage } from '@tangle-network/sandbox'\nimport {\n asRecord,\n asString,\n collapseRedundantTextParts,\n finalizeAssistantParts,\n getPartKey,\n mergePersistedPart,\n normalizePersistedPart,\n type JsonRecord,\n} from '../stream/index'\nimport { addStepFinishUsage } from './sandbox-turn-usage'\nimport type { DetachedTurnFinal } from './detached-turn'\nimport type { ChatTurnUsage } from './turn-routes'\n\n/** The official Sandbox methods needed for completed-turn recovery. */\nexport type CompletedSandboxTurnSource = Pick<\n SandboxInstance,\n 'findCompletedTurn' | 'session'\n>\n\n/** Options for resolving one exact detached turn. */\nexport interface ReadCompletedSandboxTurnOptions {\n turnId: string\n sessionId: string\n log?: (message: string, meta?: Record<string, unknown>) => void\n}\n\nfunction finiteNumber(value: unknown): number | undefined {\n if (typeof value === 'number') return Number.isFinite(value) ? value : undefined\n if (typeof value !== 'string' || !value.trim()) return undefined\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : undefined\n}\n\nfunction firstNumber(record: JsonRecord | undefined, keys: string[]): number | undefined {\n for (const key of keys) {\n const value = finiteNumber(record?.[key])\n if (value !== undefined) return value\n }\n return undefined\n}\n\nfunction usageFromResult(result: JsonRecord | undefined): ChatTurnUsage {\n const raw = asRecord(result?.usage) ?? asRecord(result?.tokenUsage)\n const inputTokens = firstNumber(raw, ['inputTokens', 'promptTokens', 'input'])\n const outputTokens = firstNumber(raw, ['outputTokens', 'completionTokens', 'output'])\n const reasoningTokens = firstNumber(raw, ['reasoningTokens', 'reasoning'])\n const cacheReadTokens = firstNumber(raw, [\n 'cacheReadTokens',\n 'cacheReadInputTokens',\n 'cacheRead',\n ])\n const cacheWriteTokens = firstNumber(raw, [\n 'cacheWriteTokens',\n 'cacheCreationInputTokens',\n 'cacheWrite',\n ])\n const costUsd = firstNumber(result, ['costUsd', 'totalCostUsd', 'cost'])\n ?? firstNumber(raw, ['costUsd', 'cost'])\n\n return {\n ...(inputTokens !== undefined ? { inputTokens } : {}),\n ...(outputTokens !== undefined ? { outputTokens } : {}),\n ...(reasoningTokens !== undefined ? { reasoningTokens } : {}),\n ...(cacheReadTokens !== undefined ? { cacheReadTokens } : {}),\n ...(cacheWriteTokens !== undefined ? { cacheWriteTokens } : {}),\n ...(costUsd !== undefined ? { costUsd } : {}),\n }\n}\n\nfunction textFromResult(result: JsonRecord | undefined): string | undefined {\n return asString(result?.response)\n ?? asString(result?.finalText)\n ?? asString(result?.text)\n ?? asString(result?.output)\n}\n\nfunction completedMessagesForTurn(\n messages: SessionMessage[],\n turnId: string,\n): SessionMessage[] {\n return messages.filter((message) =>\n message.role === 'assistant'\n && message.metadata?.turnId === turnId\n && message.metadata?.interrupted !== true\n && (\n message.metadata?.completed === true\n || message.metadata?.status === 'completed'\n ),\n )\n}\n\nfunction normalizedMessageParts(\n message: SessionMessage,\n): { parts: JsonRecord[]; usage: ChatTurnUsage; derivedText: string } {\n const normalized: JsonRecord[] = []\n const usage: ChatTurnUsage = {}\n for (const raw of message.parts) {\n const record = asRecord(raw)\n if (!record) continue\n const part = normalizePersistedPart(record)\n if (!part) continue\n normalized.push(part)\n if (part.type === 'step-finish') addStepFinishUsage(part, usage)\n }\n\n const collapsed = collapseRedundantTextParts(normalized)\n const derivedText = collapsed\n .filter((part) => part.type === 'text')\n .map((part) => String(part.text ?? ''))\n .join('')\n return { parts: normalized, usage, derivedText }\n}\n\nfunction projectMessageParts(parts: JsonRecord[], finalText: string): JsonRecord[] {\n const order: string[] = []\n const map = new Map<string, JsonRecord>()\n for (let index = 0; index < parts.length; index += 1) {\n const part = parts[index]!\n const type = String(part.type ?? '')\n // A session's aggregate message contains every step receipt. Preserve each\n // occurrence just as the live producer does; other kinds merge by their\n // canonical identity.\n const key = type === 'step-start' || type === 'step-finish'\n ? `${type}:#${index}`\n : getPartKey(part)\n if (!map.has(key)) order.push(key)\n map.set(key, mergePersistedPart(map.get(key), part))\n }\n return finalizeAssistantParts(order, map, finalText)\n}\n\nfunction hasUsage(usage: ChatTurnUsage): boolean {\n return Object.values(usage).some((value) =>\n typeof value === 'number' && Number.isFinite(value),\n )\n}\n\n/**\n * Read the exact completed turn from its keyed cache and completed message.\n * Never borrow a session-wide aggregate from a concurrently advancing session.\n * Observation failure is retryable and distinct from successful absent reads.\n */\nexport async function readCompletedSandboxTurn(\n box: CompletedSandboxTurnSource,\n options: ReadCompletedSandboxTurnOptions,\n): Promise<DetachedTurnFinal | null> {\n const { turnId, sessionId, log } = options\n const session = box.session(sessionId)\n const [cacheOutcome, messagesOutcome] = await Promise.allSettled([\n box.findCompletedTurn(turnId, { sessionId }),\n session.messages({ limit: 1_000 }),\n ])\n\n if (cacheOutcome.status === 'rejected') {\n log?.('[chat-routes] completed Sandbox turn cache lookup failed', {\n turnId,\n sessionId,\n error: String(cacheOutcome.reason),\n })\n }\n if (messagesOutcome.status === 'rejected') {\n log?.('[chat-routes] completed Sandbox session message lookup failed', {\n turnId,\n sessionId,\n error: String(messagesOutcome.reason),\n })\n }\n\n const rawCached = cacheOutcome.status === 'fulfilled' ? cacheOutcome.value : null\n const cached = rawCached\n && rawCached.turnId === turnId\n && rawCached.sessionId === sessionId\n ? rawCached\n : null\n if (rawCached && !cached) {\n log?.('[chat-routes] ignored mismatched completed Sandbox turn cache record', {\n turnId,\n sessionId,\n })\n }\n\n const messages = messagesOutcome.status === 'fulfilled'\n ? messagesOutcome.value\n : []\n const matchingMessages = completedMessagesForTurn(messages, turnId)\n const matchingCount = matchingMessages.length\n const message = matchingCount === 1 ? matchingMessages[0]! : null\n if (matchingCount > 1) {\n log?.('[chat-routes] ignored ambiguous completed Sandbox session messages', {\n turnId,\n sessionId,\n matchingCount,\n })\n }\n\n if (!cached && !message) {\n // A failed read is unknown, not proof that the turn is absent. The durable\n // owner must retry observation rather than admit another execution.\n if (cacheOutcome.status === 'rejected' || messagesOutcome.status === 'rejected') {\n throw new Error('Completed Sandbox turn could not be verified; retry the exact read')\n }\n if (rawCached || matchingCount > 1) {\n throw new Error('Completed Sandbox turn identity is inconsistent; reconciliation is required')\n }\n return null\n }\n\n // Only turn-addressed evidence is safe. A newer turn can complete between\n // reading the message list and reading a session-wide aggregate result.\n const result = cached ? asRecord(cached.result) : undefined\n\n const recovered = message ? normalizedMessageParts(message) : null\n const text = textFromResult(result) ?? recovered?.derivedText\n const resultUsage = usageFromResult(result)\n const usage = { ...(recovered?.usage ?? {}), ...resultUsage }\n const resultParts = Array.isArray(result?.parts)\n ? result.parts\n .map((part) => asRecord(part))\n .filter((part): part is JsonRecord => Boolean(part))\n .map((part) => normalizePersistedPart(part))\n .filter((part): part is JsonRecord => Boolean(part))\n : null\n const sourceParts = recovered?.parts ?? resultParts\n const parts = sourceParts\n ? projectMessageParts(sourceParts, text ?? recovered?.derivedText ?? '')\n : undefined\n\n return {\n ...(text !== undefined ? { text } : {}),\n ...(hasUsage(usage) ? { usage } : {}),\n ...(parts ? { parts } : {}),\n }\n}\n","import { getPartKey, mergePersistedPart, type StreamEvent } from '../stream/index'\nimport type { ChatTurnRouteProducer } from './turn-routes'\n\n/** Resolve chat route events and materialize their durable state records */\nexport interface ChatRouteDurableProjection {\n observe(event: unknown): void | Promise<void>\n materialize(): Array<Record<string, unknown>> | Promise<Array<Record<string, unknown>>>\n}\n\n/** Log chat route projection messages with optional metadata for durable processing */\nexport type ChatRouteDurableProjectionLogger =\n (message: string, meta?: Record<string, unknown>) => void\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/** Adds durable lifecycle projection to any producer lane without moving its\n * transport into agent-app. Purely STRUCTURAL: agent-app ships no implementer\n * of {@link ChatRouteDurableProjection} and deliberately does not — the one it\n * used to ship (`/durable-chat`) was removed in 0.44.0 with zero fleet imports.\n * Pass any `{ observe, materialize }` object backed by your own store. The projection is observed inline and its\n * materialized parts replace same-key pending snapshots after the stream\n * drains. Projection persistence is best-effort for the live lane: a store\n * outage must not terminate an otherwise healthy sandbox stream. Failures are\n * reported through the optional logger so products can retain diagnostics. */\nexport function withDurableChatProjection(\n producer: ChatTurnRouteProducer,\n projection: ChatRouteDurableProjection,\n log: ChatRouteDurableProjectionLogger = (message, meta) => console.error(message, meta ?? ''),\n): ChatTurnRouteProducer {\n let projected: Array<Record<string, unknown>> = []\n async function* stream(): AsyncGenerator<StreamEvent, void, unknown> {\n for await (const event of producer.stream) {\n try {\n await projection.observe(event)\n } catch (error) {\n log('[chat-routes] durable projection observe failed', {\n eventType: event.type,\n error: errorMessage(error),\n })\n }\n yield event\n }\n try {\n projected = await projection.materialize()\n } catch (error) {\n log('[chat-routes] durable projection materialize failed', {\n error: errorMessage(error),\n })\n }\n }\n return {\n ...producer,\n stream: stream(),\n assistantParts: () => {\n const parts = producer.assistantParts?.() ?? []\n const order: string[] = []\n const byKey = new Map<string, Record<string, unknown>>()\n for (const part of [...parts, ...projected]) {\n const key = getPartKey(part)\n if (!byKey.has(key)) order.push(key)\n byKey.set(key, mergePersistedPart(byKey.get(key), part))\n }\n return order.map((key) => byKey.get(key)!)\n },\n }\n}\n","/**\n * `createUploadRoute` — the multimodal middle. Accepts multipart file uploads\n * and returns `PromptInputPart`-shaped descriptors the client echoes back on\n * send (`ChatTurnRequestPayload.parts`):\n *\n * ≤ inlineMaxBytes (700 KiB default) → inline `data:` URI part — rides the\n * turn body directly, no sandbox round trip.\n * > inlineMaxBytes → written into the sandbox workspace (base64 through the\n * structural `write` seam — `box.fs` satisfies it) and referenced by\n * `path`. Mandatory two-step: the gateway caps request bodies at ~1 MiB,\n * so a large file can never ride the prompt POST.\n *\n * The sink is structural (no sandbox-SDK import); products pass `box.fs`.\n *\n * @remarks Sole consumer today is the `--chat` scaffold (`create-agent-app\n * --chat` → `template-chat/src/chat.ts`), the reference multimodal path — its\n * inline-`data:`-or-ephemeral-sandbox-workspace split stays the scaffold's\n * proven upload pattern, not a fleet primitive; keep that distinction in mind\n * before widening its surface. Fleet apps with a durable store of their own\n * (KV, or AES-GCM-encrypted R2) no longer need to hand-roll a vault upload\n * route: `createAttachmentUploadRoute` (`./attachment-upload`, agent-app#234)\n * is the shared hardened path for that persistence model — a content-sniffed\n * type gate, two-phase atomic batch writes, and per-kind/aggregate size caps,\n * all seamed through an injected `WriteAttachmentFn`. Point readers there\n * instead of widening this route to cover both models.\n */\n\nimport type { ChatTurnFilePartInput } from './wire'\n\n/** 700 KiB: base64 inflates ~4/3, so an inline part stays comfortably under\n * the ~1 MiB gateway body cap alongside the JSON envelope. */\nexport const UPLOAD_INLINE_MAX_BYTES = 700 * 1024\n\n/** 8 MiB default ceiling per file — one base64 `write` call handles it. Raise\n * it only with a sink that can take the bigger single write. */\nexport const UPLOAD_MAX_FILE_BYTES = 8 * 1024 * 1024\n\nconst DEFAULT_UPLOAD_DIRECTORY = '/workspace/uploads'\n\nfunction normalizeUploadDirectory(value: string): string | null {\n const withoutTrailingSlash = value.trim().replace(/\\/+$/, '')\n if (!withoutTrailingSlash.startsWith('/') || withoutTrailingSlash.length === 0) return null\n const segments = withoutTrailingSlash.slice(1).split('/')\n if (segments.some((segment) => segment.length === 0 || segment === '.' || segment === '..')) return null\n return `/${segments.join('/')}`\n}\n\n/** Structural match of the sandbox SDK's `box.fs` write surface (v0.10.5+:\n * `encoding: 'base64'` is the worker-safe binary path). */\nexport interface SandboxUploadSink {\n write(path: string, content: string, options?: { encoding?: 'utf8' | 'base64' }): Promise<unknown>\n}\n\n/** Resolve upload authorization status and provide upload destination or error response */\nexport type UploadAuthorization =\n | {\n ok: true\n /** Where large files land. Absent/null: only inline uploads are\n * accepted and an over-inline-cap file is rejected with 413. */\n sink?: SandboxUploadSink | null\n /** Per-request override of the workspace directory large files go to. */\n uploadDir?: string\n }\n | { ok: false; response: Response }\n\n/** Define options to authorize uploads and configure file size limits and upload directory */\nexport interface CreateUploadRouteOptions {\n /** Authenticate the caller and resolve the sandbox file sink (usually\n * `ensureWorkspaceSandbox(...)` → `box.fs`). */\n authorize(args: { request: Request }): Promise<UploadAuthorization>\n /** Inline-vs-sandbox threshold. Default {@link UPLOAD_INLINE_MAX_BYTES}. */\n inlineMaxBytes?: number\n /** Hard per-file cap. Default {@link UPLOAD_MAX_FILE_BYTES}. */\n maxFileBytes?: number\n /** Absolute workspace directory for path-ref files.\n * Default `'/workspace/uploads'`. */\n uploadDir?: string\n}\n\n/** One uploaded file, ready for the composer chip and the turn body. */\nexport interface UploadedChatFile {\n id: string\n name: string\n size: number\n mediaType: string\n /** True when the part carries the bytes inline (`data:` URI). */\n inline: boolean\n /** Echo this back verbatim in `ChatTurnRequestPayload.parts`. */\n part: ChatTurnFilePartInput\n}\n\n/** Path-safe file name: basename only, conservative charset, length-capped. */\nexport function sanitizeUploadFilename(name: string): string {\n const base = name.split(/[\\\\/]/).pop() ?? 'file'\n const safe = base.replace(/[^A-Za-z0-9._-]+/g, '_').replace(/^\\.+/, '_')\n return (safe || 'file').slice(0, 120)\n}\n\nconst BASE64_CHUNK = 0x8000\n\n/** Convert a Uint8Array of bytes into a base64-encoded string */\nexport function bytesToBase64(bytes: Uint8Array): string {\n let binary = ''\n for (let offset = 0; offset < bytes.length; offset += BASE64_CHUNK) {\n binary += String.fromCharCode(...bytes.subarray(offset, offset + BASE64_CHUNK))\n }\n return btoa(binary)\n}\n\nfunction uploadError(status: number, code: string, error: string): Response {\n return Response.json({ code, error }, { status })\n}\n\n/** Create an upload route handler that authorizes requests and processes file uploads with size limits */\nexport function createUploadRoute(options: CreateUploadRouteOptions): (request: Request) => Promise<Response> {\n const inlineMaxBytes = options.inlineMaxBytes ?? UPLOAD_INLINE_MAX_BYTES\n const maxFileBytes = options.maxFileBytes ?? UPLOAD_MAX_FILE_BYTES\n\n return async function upload(request: Request): Promise<Response> {\n const auth = await options.authorize({ request })\n if (!auth.ok) return auth.response\n const sink = auth.sink ?? null\n const configuredUploadDir = auth.uploadDir ?? options.uploadDir ?? DEFAULT_UPLOAD_DIRECTORY\n\n let form: FormData\n try {\n form = await request.formData()\n } catch {\n return uploadError(400, 'INVALID_UPLOAD', 'Expected a multipart/form-data body with file fields')\n }\n const files: File[] = []\n form.forEach((value) => {\n if (value instanceof File) files.push(value)\n })\n if (files.length === 0) {\n return uploadError(400, 'INVALID_UPLOAD', 'No files in the upload body')\n }\n\n const uploaded: UploadedChatFile[] = []\n for (const file of files) {\n const name = sanitizeUploadFilename(file.name)\n const mediaType = file.type || 'application/octet-stream'\n const partType: ChatTurnFilePartInput['type'] = mediaType.startsWith('image/') ? 'image' : 'file'\n\n if (file.size > maxFileBytes) {\n return uploadError(\n 413,\n 'FILE_TOO_LARGE',\n `${name} is ${file.size}B, over the ${maxFileBytes}B per-file cap`,\n )\n }\n\n const id = crypto.randomUUID()\n if (file.size <= inlineMaxBytes) {\n const base64 = bytesToBase64(new Uint8Array(await file.arrayBuffer()))\n uploaded.push({\n id,\n name,\n size: file.size,\n mediaType,\n inline: true,\n part: {\n type: partType,\n filename: name,\n mediaType,\n url: `data:${mediaType};base64,${base64}`,\n },\n })\n continue\n }\n\n if (!sink) {\n return uploadError(\n 413,\n 'SANDBOX_REQUIRED',\n `${name} is ${file.size}B, over the ${inlineMaxBytes}B inline cap, and no sandbox is available to hold it`,\n )\n }\n const uploadDir = normalizeUploadDirectory(configuredUploadDir)\n if (!uploadDir) {\n return uploadError(\n 500,\n 'INVALID_UPLOAD_DIRECTORY',\n 'uploadDir must be an absolute path without empty, \".\" or \"..\" segments',\n )\n }\n const path = `${uploadDir}/${id}-${name}`\n const base64 = bytesToBase64(new Uint8Array(await file.arrayBuffer()))\n await sink.write(path, base64, { encoding: 'base64' })\n uploaded.push({\n id,\n name,\n size: file.size,\n mediaType,\n inline: false,\n part: { type: partType, filename: name, mediaType, path },\n })\n }\n\n return Response.json({ files: uploaded })\n }\n}\n","/**\n * Storage seams for the chat-attachment vertical (`resolveChatAttachments`,\n * `buildDispatchParts`, `promoteAgentFilePart`). Structural function ports in\n * the same style as `upload.ts`'s `SandboxUploadSink`: REQUIRED injection, no\n * default implementation — agent-app owns the size/budget/idempotency\n * mechanism, the product owns where the bytes actually live.\n *\n * One reader, two callers: `resolveChatAttachments` reads only the\n * authoritative `size` (never trusting the client-reported size), while\n * `buildDispatchParts` reads the inline `content` (base64 or raw bytes) to\n * build a `data:` URI. Both fit behind ONE `ReadAttachmentFn` so a product\n * wires a single vault/object-store adapter, not two. gtm's KV vault (stores\n * base64 bodies) and `/object-store`'s `ObjectStore` (hands back raw bytes)\n * both satisfy this shape.\n */\n\n/**\n * The result of reading one stored attachment. `ok:true` MUST carry the\n * authoritative decoded byte `size` (the cap is meaningless against a\n * client-controlled number) and, when the caller needs to inline the file,\n * its content as `base64` and/or raw `bytes`. `mediaType` is the stored\n * content type when the store knows it (used as the fallback when the wire\n * part carried none). `ok:false` carries a human `reason` that SHOULD name the\n * offending path — it is surfaced verbatim in the caller's typed outcome.\n */\nexport type AttachmentReadResult =\n | {\n ok: true\n /** Authoritative decoded byte length of the stored content. */\n size: number\n /** Inline content as base64 — reused verbatim for a `data:` URI, so a\n * store holding already-base64 bodies never decodes-and-re-encodes. */\n base64?: string\n /** Inline content as raw bytes — base64-encoded by the caller when a\n * `data:` URI is needed. Ignored when `base64` is present. */\n bytes?: Uint8Array\n /** Stored content type, when the store tracks one. */\n mediaType?: string\n }\n | { ok: false; reason: string }\n\n/**\n * Read one stored attachment for `scopeId` (the product's workspace/tenant\n * key) at its store-relative `path`. Missing, deleted, or unreadable content\n * MUST resolve to `{ ok: false, reason }`, never throw — a store failure is a\n * per-attachment outcome the caller folds into its own typed result, not a\n * turn-level exception.\n */\nexport type ReadAttachmentFn = (scopeId: string, path: string) => Promise<AttachmentReadResult>\n\n/** The ownership identity for one attempted write. The upload and promotion\n * routes create a unique path for each attempt, and the product adapter stores\n * this id with it. */\nexport interface AttachmentWriteOwnership {\n readonly id: string\n readonly path: string\n}\n\n/** Public-safe outcome for a storage outage. Backend details belong in logs. */\nexport const ATTACHMENT_STORAGE_FAILURE_MESSAGE = 'Attachment storage is temporarily unavailable. Please try again.'\n\n/** Stable client error code when compensating cleanup did not complete. */\nexport const ATTACHMENT_ROLLBACK_FAILURE_CODE = 'rollback_failed'\n\n/** Public-safe outcome when compensating cleanup did not complete. */\nexport const ATTACHMENT_ROLLBACK_FAILURE_MESSAGE = 'Attachment cleanup failed. Please try again.'\n\n/** Add a path-safe ownership id to a logical path and return an immutable store key. */\nexport function immutableAttachmentPath(logicalPath: string, ownershipId: string): string {\n if (!/^[A-Za-z0-9_-]{1,128}$/.test(ownershipId)) {\n throw new Error('attachment ownership id must be a path-safe identifier')\n }\n return `${logicalPath}--${ownershipId}`\n}\n\n/** Compensation for one attachment write. The adapter MUST compare the stored\n * ownership id before deleting, so an older rollback cannot delete a newer\n * overwrite. */\nexport interface AttachmentWriteReceipt {\n rollback(): void | Promise<void>\n ownership: AttachmentWriteOwnership\n}\n\n/** Clean up a write whose function threw after the store may have committed.\n * The adapter must delete only the supplied ownership, never the logical path\n * unconditionally. */\nexport type AbortAttachmentWriteFn = (\n scopeId: string,\n ownership: AttachmentWriteOwnership,\n) => void | Promise<void>\n\n/**\n * The stable writer result from the original attachment-store contract.\n * Existing products may return this shape and keep using the legacy writer\n * lane. New products should use {@link AtomicAttachmentWriteResult}.\n */\nexport type AttachmentWriteResult = { ok: true } | { ok: false; reason: string }\n\n/** Options from the stable attachment-store contract. */\nexport interface AttachmentWriteOptions {\n mediaType?: string\n name?: string\n originalName?: string\n size?: number\n}\n\n/**\n * The stable writer port. It is intentionally unchanged so products released\n * before the ownership receipt contract continue to compile.\n *\n * The legacy lane does not promise batch rollback. Use\n * {@link createAtomicAttachmentWriter} for ownership-safe writes.\n */\nexport type WriteAttachmentFn = (\n scopeId: string,\n path: string,\n content: Uint8Array | string,\n opts: AttachmentWriteOptions,\n) => Promise<AttachmentWriteResult>\n\n/** Options for an ownership-safe writer. */\nexport interface AtomicAttachmentWriteOptions extends AttachmentWriteOptions {\n ownership: AttachmentWriteOwnership\n}\n\n/** Result for an ownership-safe writer. A receipt is required on both paths. */\nexport type AtomicAttachmentWriteResult =\n | { ok: true; receipt: AttachmentWriteReceipt }\n | { ok: false; reason: string; receipt: AttachmentWriteReceipt }\n\n/** Ownership-safe writer port used by the atomic upload and promotion lanes. */\nexport type AtomicWriteAttachmentFn = (\n scopeId: string,\n path: string,\n content: Uint8Array | string,\n opts: AtomicAttachmentWriteOptions,\n) => Promise<AtomicAttachmentWriteResult>\n\n/** A complete ownership-safe attachment store adapter. */\nexport interface AtomicAttachmentWriter {\n write: AtomicWriteAttachmentFn\n abort: AbortAttachmentWriteFn\n}\n\n/**\n * Build the explicit atomic adapter used by new routes.\n *\n * Keeping the write and abort functions together prevents a caller from\n * enabling ownership paths while forgetting the ambiguous-write cleanup.\n */\nexport function createAtomicAttachmentWriter(input: AtomicAttachmentWriter): AtomicAttachmentWriter {\n return {\n write: input.write.bind(input),\n abort: input.abort.bind(input),\n }\n}\n","/**\n * `resolveChatAttachments` — validate a turn body's `attachments` field into\n * persistable {@link ChatAttachmentPart}s. Every path is re-validated (a path\n * off the wire is never trusted to stay inside the store root) and every size\n * is re-derived from the STORED body via the injected {@link ReadAttachmentFn},\n * never the client-reported `size` — the upload path lets a caller rewrite its\n * own frontmatter, so a stored size cannot bound anything and the wire size can\n * be anything. Both the aggregate cap and the size carried on the returned part\n * come from the authoritative read.\n *\n * Storage-parameterized: the frontmatter parsing / base64 sizing that derives\n * the authoritative size lives BEHIND `readAttachment` (a product's vault or\n * object-store adapter), so this module is a pure validator + budget gate with\n * no store knowledge. Lifted from gtm-agent's `resolve-attachments.ts`\n * (workspaceId → scopeId, the vault read → the injected reader) and kept\n * behavior-identical for gtm-agent#618 adoption.\n */\n\nimport type { ChatAttachmentInput, ChatAttachmentKind } from './wire'\nimport { attachmentInputToPart, type ChatAttachmentPart } from '../chat-store/parts'\nimport type { ReadAttachmentFn } from './attachment-store'\nimport { ATTACHMENT_MAX_COUNT, MAX_ATTACHMENT_TOTAL_BYTES, attachmentTotalSizeErrorMessage } from './attachment-validation'\n\n/** Resolve the result of chat attachment processing with success status and corresponding data or error */\nexport type ResolveChatAttachmentsResult =\n | { succeeded: true; value: ChatAttachmentPart[] }\n | { succeeded: false; error: string }\n\n/** Verdict of a path check: OK, or a rejection naming why. Mirrors\n * `SandboxMentionPathCheck` in `./wire`. */\nexport type AttachmentPathCheck =\n | { succeeded: true }\n | { succeeded: false; error: string }\n\n/** Longest attachment display name accepted — bounds what gets echoed into the\n * prompt block and rendered as a chip label. */\nconst MAX_ATTACHMENT_NAME_LENGTH = 256\n\nfunction isAttachmentKind(value: unknown): value is ChatAttachmentKind {\n return value === 'image' || value === 'file'\n}\n\n/** C0 control characters (0x00–0x1F) plus DEL (0x7F) — covers `\\n`/`\\r`/`\\t`.\n * A `name` or `path` carrying one of these has no legitimate use here and\n * everything to gain from an attacker: {@link buildAttachmentPromptBlock} in\n * `/chat-store` renders both fields verbatim into the dispatched agent\n * prompt, so an embedded newline fabricates new prompt lines (a\n * prompt-injection vector) rather than naming a file. Rejected at this\n * boundary — not merely neutralized downstream — so the wire never accepts\n * the input in the first place; a legitimate name/path never contains one. */\nconst CONTROL_CHARS = /[\\x00-\\x1F\\x7F]/\n\n/**\n * Default path validator when a caller supplies none. Rejects the ways a path\n * picked in a client can escape the store root — traversal (`..` segment),\n * absolute (leading `/`), backslashes, null bytes, control characters (see\n * {@link CONTROL_CHARS} — a path also feeds {@link buildAttachmentPromptBlock}'s\n * `(vault: ${path})` pointer, so it is exposed to the same injection surface as\n * `name`) — plus a dotfile/hidden segment (a leading `.` on any segment).\n * Generalized from gtm's `validateVaultFilePath`, in the spirit of\n * `validateSandboxMentionPath` (`/chat-routes`'s wire mention-path validator) —\n * but the dotfile rejection here is INTENTIONALLY stricter than that sibling:\n * an uploaded attachment path is sanitized store-relative storage the product\n * itself assigned, whereas a mention path points at a file that already exists\n * in the sandbox and may legitimately live under a dotfile segment. A caller\n * that needs gtm's exact (looser) rule can supply `validatePath` to override\n * this default entirely.\n */\nexport function defaultValidateAttachmentPath(path: string): AttachmentPathCheck {\n if (path.includes('\\0')) return { succeeded: false, error: 'attachment path must not contain null bytes' }\n if (path.includes('\\\\')) return { succeeded: false, error: 'attachment path must not contain backslashes' }\n if (CONTROL_CHARS.test(path)) return { succeeded: false, error: 'attachment path must not contain control characters' }\n if (path.startsWith('/')) return { succeeded: false, error: 'attachment path must be store-relative, not absolute' }\n const segments = path.split('/')\n if (segments.some((segment) => segment === '..')) {\n return { succeeded: false, error: 'attachment path must not contain \"..\" segments' }\n }\n if (segments.some((segment) => segment.startsWith('.'))) {\n return { succeeded: false, error: 'attachment path must not contain a hidden (dotfile) segment' }\n }\n return { succeeded: true }\n}\n\nfunction parseAttachmentInput(\n value: unknown,\n index: number,\n validatePath: (path: string) => AttachmentPathCheck,\n): { succeeded: true; value: ChatAttachmentInput } | { succeeded: false; error: string } {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return { succeeded: false, error: `attachments[${index}] must be an object` }\n }\n const record = value as Record<string, unknown>\n\n const path = record.path\n if (typeof path !== 'string' || !path) {\n return { succeeded: false, error: `attachments[${index}].path must be a non-empty string` }\n }\n const name = record.name\n if (typeof name !== 'string' || !name.trim()) {\n return { succeeded: false, error: `attachments[${index}].name must be a non-empty string` }\n }\n if (name.length > MAX_ATTACHMENT_NAME_LENGTH) {\n return { succeeded: false, error: `attachments[${index}].name must not exceed ${MAX_ATTACHMENT_NAME_LENGTH} characters` }\n }\n if (CONTROL_CHARS.test(name)) {\n return { succeeded: false, error: `attachments[${index}].name must not contain control characters` }\n }\n const size = record.size\n if (typeof size !== 'number' || !Number.isFinite(size)) {\n return { succeeded: false, error: `attachments[${index}].size must be a finite number` }\n }\n if (size < 0) {\n return { succeeded: false, error: `attachments[${index}].size must not be negative` }\n }\n const mediaType = record.mediaType\n if (typeof mediaType !== 'string') {\n return { succeeded: false, error: `attachments[${index}].mediaType must be a string` }\n }\n const kind = record.kind\n if (!isAttachmentKind(kind)) {\n return { succeeded: false, error: `attachments[${index}].kind must be \"image\" or \"file\"` }\n }\n\n const pathCheck = validatePath(path)\n if (!pathCheck.succeeded) return { succeeded: false, error: pathCheck.error }\n\n return { succeeded: true, value: { path, name, size, mediaType, kind } }\n}\n\n/** Define options to resolve and validate chat attachments with size, count, and path constraints */\nexport interface ResolveChatAttachmentsOptions {\n /** The product's workspace/tenant key, passed to `readAttachment`. */\n scopeId: string\n /** Authoritative size + content reader — see {@link ReadAttachmentFn}. */\n readAttachment: ReadAttachmentFn\n /** Most attachments one request may carry. Default {@link ATTACHMENT_MAX_COUNT}. */\n maxCount?: number\n /** Aggregate raw-byte ceiling. Default {@link MAX_ATTACHMENT_TOTAL_BYTES}. */\n maxTotalBytes?: number\n /** Path validator override. Default {@link defaultValidateAttachmentPath}. */\n validatePath?: (path: string) => AttachmentPathCheck\n}\n\n/**\n * Validate and resolve a turn body's `attachments` field into persistable\n * parts. Every path is confirmed present (and not deleted) in the caller's own\n * store by `readAttachment` before it is trusted, and size is derived from the\n * authoritative read for both the aggregate cap and the returned part's size.\n */\nexport async function resolveChatAttachments(\n value: unknown,\n options: ResolveChatAttachmentsOptions,\n): Promise<ResolveChatAttachmentsResult> {\n const maxCount = options.maxCount ?? ATTACHMENT_MAX_COUNT\n const maxTotalBytes = options.maxTotalBytes ?? MAX_ATTACHMENT_TOTAL_BYTES\n const validatePath = options.validatePath ?? defaultValidateAttachmentPath\n\n if (value === undefined || value === null) return { succeeded: true, value: [] }\n if (!Array.isArray(value)) return { succeeded: false, error: 'attachments must be an array' }\n if (value.length > maxCount) {\n return { succeeded: false, error: `attachments must not exceed ${maxCount} entries` }\n }\n\n const inputs: ChatAttachmentInput[] = []\n const seenPaths = new Set<string>()\n for (let index = 0; index < value.length; index += 1) {\n const parsed = parseAttachmentInput(value[index], index, validatePath)\n if (!parsed.succeeded) return parsed\n if (seenPaths.has(parsed.value.path)) {\n return { succeeded: false, error: `attachments must not repeat a path: ${parsed.value.path}` }\n }\n seenPaths.add(parsed.value.path)\n inputs.push(parsed.value)\n }\n\n // Advisory only: client-controlled, so a dishonest caller can slip past it —\n // but an honest oversized request should fail before it costs a single store\n // read. The authoritative check runs per-attachment below, against the\n // body-derived size.\n const advisoryTotal = inputs.reduce((sum, input) => sum + input.size, 0)\n if (advisoryTotal > maxTotalBytes) {\n return { succeeded: false, error: attachmentTotalSizeErrorMessage(advisoryTotal, maxTotalBytes) }\n }\n\n // One attachment is read, sized, and dropped at a time rather than via\n // Promise.all — that would materialize every attachment's body in memory\n // simultaneously (ten truthful 10 MiB references is ~134 MiB of base64, over\n // a Workers heap) before the cap ever gets a chance to reject the request.\n // Sequential reads bound resident bytes to one attachment at a time and bail\n // the moment the running total exceeds the cap, at the cost of up to\n // `maxCount` serial reads instead of one parallel batch.\n let totalStoredBytes = 0\n for (const input of inputs) {\n const read = await options.readAttachment(options.scopeId, input.path)\n if (!read.ok) return { succeeded: false, error: read.reason }\n totalStoredBytes += read.size\n if (totalStoredBytes > maxTotalBytes) {\n return { succeeded: false, error: attachmentTotalSizeErrorMessage(totalStoredBytes, maxTotalBytes) }\n }\n input.size = read.size\n }\n\n return { succeeded: true, value: inputs.map(attachmentInputToPart) }\n}\n","import type {\n AtomicAttachmentWriteResult,\n AttachmentWriteOwnership,\n AttachmentWriteReceipt,\n AttachmentWriteResult,\n} from './attachment-store'\n\ntype ObjectLike = Record<PropertyKey, unknown>\n\nfunction objectLike(value: unknown): value is ObjectLike {\n return value !== null && (typeof value === 'object' || typeof value === 'function')\n}\n\n/** Read an injected writer result without allowing hostile getters to escape. */\nexport function inspectLegacyAttachmentWriteResult(\n value: unknown,\n): AttachmentWriteResult | undefined {\n try {\n if (!objectLike(value)) return undefined\n const ok = value.ok\n if (ok === true) return { ok: true }\n if (ok === false && typeof value.reason === 'string') {\n return { ok: false, reason: value.reason }\n }\n } catch {\n // A malformed or hostile adapter result is a storage failure.\n }\n return undefined\n}\n\n/**\n * Read an ownership-aware writer result with strict runtime checks.\n *\n * The public types protect TypeScript callers only. The product adapter is an\n * injection boundary, so a proxy, malformed value, or truthy non-boolean must\n * fail closed before the route invokes cleanup or publishes a path.\n */\nexport function inspectAtomicAttachmentWriteResult(\n value: unknown,\n ownership: AttachmentWriteOwnership,\n): AtomicAttachmentWriteResult | undefined {\n try {\n if (!objectLike(value)) return undefined\n const receiptValue = value.receipt\n if (!objectLike(receiptValue)) return undefined\n const receiptOwnership = receiptValue.ownership\n if (!objectLike(receiptOwnership)) return undefined\n if (\n receiptOwnership.id !== ownership.id ||\n receiptOwnership.path !== ownership.path ||\n typeof receiptOwnership.id !== 'string' ||\n typeof receiptOwnership.path !== 'string' ||\n typeof receiptValue.rollback !== 'function'\n ) {\n return undefined\n }\n if (value.ok === true) {\n return { ok: true, receipt: receiptValue as unknown as AttachmentWriteReceipt }\n }\n if (value.ok === false && typeof value.reason === 'string') {\n return {\n ok: false,\n reason: value.reason,\n receipt: receiptValue as unknown as AttachmentWriteReceipt,\n }\n }\n } catch {\n // A malformed or hostile adapter result is a storage failure.\n }\n return undefined\n}\n","/**\n * `promoteAgentFilePart` — turn a harness-emitted `type:\"file\"` stream part\n * into a store-backed {@link ChatAttachmentPart}. The harness hands back a URL\n * pointing at bytes it produced (a `data:` URI, or a path inside the sandbox);\n * nothing durable survives past the turn unless it is written into the\n * product's store, the same way a user upload is. Typed outcomes throughout:\n * every failure mode (unsupported scheme, no sandbox, oversize, store-write\n * failure, malformed part) resolves to `{ succeeded: false, filename, reason }`\n * rather than throwing past this boundary, so the caller folds a visible notice\n * instead of losing the file silently.\n *\n * Storage-parameterized port of gtm-agent's `promote-file-parts.ts` with the\n * refactor gtm never made: persistence goes through an injected stable writer\n * or ownership-safe adapter, the path strategy is the injected\n * `buildAttachmentPath` (neutral `uploads/agent/<date>/` default, no domain\n * bucket taxonomy baked), the MIME map is an injectable hook, and the date\n * segment reads an injectable clock. The logical `hash8(id ?? url ?? filename)`\n * naming is preserved. Atomic callers also receive a fresh ownership suffix so\n * an ambiguous write cannot overwrite an older object.\n */\n\nimport {\n statSandboxFileSize,\n readSandboxBinaryBytes,\n type SandboxExecChannel,\n} from '../sandbox/binary-read'\nimport { attachmentKindForMime, type ChatAttachmentKind, type ChatAttachmentPart } from '../chat-store/parts'\nimport { redactErrorMessage } from '../redact'\nimport {\n ATTACHMENT_STORAGE_FAILURE_MESSAGE,\n immutableAttachmentPath,\n type AtomicAttachmentWriter,\n type AttachmentWriteOwnership,\n type AttachmentWriteReceipt,\n type WriteAttachmentFn,\n} from './attachment-store'\nimport {\n inspectAtomicAttachmentWriteResult,\n inspectLegacyAttachmentWriteResult,\n} from './attachment-write-safety'\nimport { sanitizeAttachmentFileName } from './attachment-validation'\nimport { formatBytes } from './wire'\n\n/** Default ceiling on a promoted file's raw (pre-encoding) byte size. */\nexport const PROMOTE_MAX_FILE_BYTES = 10 * 1024 * 1024\n\n/** Define the structure for a raw file part with optional metadata and media type information */\nexport interface RawAgentFilePart {\n type: 'file'\n id?: string\n filename?: string\n /** AI-SDK-shaped parts carry the MIME type here… */\n mediaType?: string\n /** …but OpenCode's native FilePart calls the same field `mime`. */\n mime?: string\n url?: string\n}\n\n/** Resolve the result of promoting a file part with success status and relevant data or error details */\nexport type PromoteFilePartResult =\n | { succeeded: true; part: ChatAttachmentPart }\n | { succeeded: false; filename: string; reason: string }\n\ntype ByteResolution =\n | { succeeded: true; bytes: Uint8Array }\n | { succeeded: false; reason: string }\n\n/** Arguments handed to a {@link PromoteAgentFilePartOptions.buildAttachmentPath}\n * override — everything needed to place the file deterministically. */\nexport interface AttachmentPathArgs {\n /** Sanitized display filename (basename, safe charset). */\n filename: string\n /** First 8 hex chars of the SHA-256 idempotency digest. */\n hash8: string\n /** `YYYY-MM-DD` from the injected clock. */\n date: string\n /** Resolved media type. */\n mediaType: string\n /** `image`/`file` split of the media type. */\n kind: ChatAttachmentKind\n}\n\n/** Minimal extension→mime map — the last-resort media type when the part\n * carries none. Generic file typing, NOT a product accept-list (which is a\n * domain value the product supplies): an unknown extension falls to\n * `text/plain`, it never rejects. */\nconst EXT_TO_MIME: Record<string, string> = {\n md: 'text/markdown',\n markdown: 'text/markdown',\n txt: 'text/plain',\n log: 'text/plain',\n csv: 'text/csv',\n tsv: 'text/tab-separated-values',\n json: 'application/json',\n yaml: 'text/yaml',\n yml: 'text/yaml',\n xml: 'application/xml',\n html: 'text/html',\n htm: 'text/html',\n pdf: 'application/pdf',\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n webp: 'image/webp',\n avif: 'image/avif',\n heic: 'image/heic',\n heif: 'image/heif',\n svg: 'image/svg+xml',\n mp4: 'video/mp4',\n webm: 'video/webm',\n mov: 'video/quicktime',\n mp3: 'audio/mpeg',\n wav: 'audio/wav',\n ogg: 'audio/ogg',\n m4a: 'audio/mp4',\n aac: 'audio/aac',\n}\n\n/** Default MIME hook: extension → mime, or `text/plain` for the unknown. */\nexport function sniffMimeFromName(filename: string): string {\n const ext = filename.split('.').pop()?.toLowerCase()\n if (!ext) return 'text/plain'\n return EXT_TO_MIME[ext] ?? 'text/plain'\n}\n\n/** Decode base64 with `atob` (not `Buffer.from`, which SKIPS out-of-alphabet\n * characters and would decode a corrupt payload to something plausible). */\nfunction base64ToBytes(base64: string): Uint8Array {\n const binary = atob(base64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i)\n return bytes\n}\n\nfunction parseDataUrl(url: string): { base64: boolean; data: string } | null {\n const match = /^data:[^,]*,([\\s\\S]*)$/i.exec(url)\n if (!match) return null\n return { base64: /;base64,/i.test(url), data: match[1] ?? '' }\n}\n\n/** The MIME type embedded in a `data:` URI's header, if any — the last-resort\n * signal when the part itself carries no mediaType/mime field. */\nfunction dataUrlMime(url: string | undefined): string | undefined {\n if (!url) return undefined\n const match = /^data:([^;,]+)[;,]/i.exec(url)\n return match ? match[1] : undefined\n}\n\nfunction basenameFromUrl(url: string | undefined): string | undefined {\n if (!url || /^data:/i.test(url)) return undefined\n const withoutQuery = url.split(/[?#]/)[0] ?? url\n const segments = withoutQuery.split('/').filter(Boolean)\n return segments[segments.length - 1] || undefined\n}\n\n/** `file://<path>` strips to `<path>`; a bare absolute path passes through\n * unchanged. The remainder is percent-decoded — sidecar file URLs encode\n * spaces and other reserved characters. */\nfunction resolveFileUrlPath(url: string): { succeeded: true; path: string } | { succeeded: false; reason: string } {\n const withoutScheme = /^file:\\/\\//i.test(url) ? url.slice('file://'.length) : url\n try {\n return { succeeded: true, path: decodeURIComponent(withoutScheme) }\n } catch (err) {\n return { succeeded: false, reason: `malformed file path: ${redactErrorMessage(err)}` }\n }\n}\n\n/** Matches gtm's `attachmentSizeErrorMessage` (attachment-limits.ts:87-89)\n * verbatim, via the shared {@link formatBytes} — e.g. \"report.pdf is 10MB;\n * attachments are limited to 10MB\" — so an oversize promotion notice reads\n * identically whether gtm's original composed it or agent-app's promoter did. */\nfunction oversizeReason(filename: string, actual: number, limit: number): string {\n return `${filename} is ${formatBytes(actual)}; attachments are limited to ${formatBytes(limit)}`\n}\n\nfunction resolveDataUrlBytes(url: string, filename: string, maxBytes: number): ByteResolution {\n const parsed = parseDataUrl(url)\n if (!parsed) return { succeeded: false, reason: 'malformed data URI' }\n let bytes: Uint8Array\n try {\n bytes = parsed.base64 ? base64ToBytes(parsed.data) : new TextEncoder().encode(decodeURIComponent(parsed.data))\n } catch (err) {\n return { succeeded: false, reason: `failed to decode data URI: ${redactErrorMessage(err)}` }\n }\n if (bytes.byteLength > maxBytes) {\n return { succeeded: false, reason: oversizeReason(filename, bytes.byteLength, maxBytes) }\n }\n return { succeeded: true, bytes }\n}\n\nasync function resolveSandboxFileBytes(input: {\n path: string\n box: SandboxExecChannel\n sessionId: string\n filename: string\n maxBytes: number\n}): Promise<ByteResolution> {\n // exec can reject outright (box teardown, timeout, transport failure) — that\n // is a per-file failure, not a turn failure, so it must resolve to a typed\n // outcome like a nonzero exit code does.\n const stat = await statSandboxFileSize(input.box, input.path, { sessionId: input.sessionId })\n if (!stat.succeeded) {\n return { succeeded: false, reason: `could not stat agent file: ${redactErrorMessage(stat.error)}` }\n }\n // Rejected before the bytes are ever pulled — a base64 exec of an oversize\n // file would waste a full sandbox round trip only to be discarded.\n if (stat.value > input.maxBytes) {\n return { succeeded: false, reason: oversizeReason(input.filename, stat.value, input.maxBytes) }\n }\n\n const read = await readSandboxBinaryBytes(input.box, input.path, stat.value, { sessionId: input.sessionId })\n if (!read.succeeded) {\n return { succeeded: false, reason: `could not read agent file: ${redactErrorMessage(read.error)}` }\n }\n return { succeeded: true, bytes: read.value.bytes }\n}\n\nasync function resolveBytes(input: {\n raw: RawAgentFilePart\n box: SandboxExecChannel | undefined\n sessionId: string\n filename: string\n maxBytes: number\n}): Promise<ByteResolution> {\n const url = input.raw.url\n if (!url) return { succeeded: false, reason: 'the file part carries no url' }\n\n if (/^data:/i.test(url)) return resolveDataUrlBytes(url, input.filename, input.maxBytes)\n\n const isSandboxPath = /^file:\\/\\//i.test(url) || url.startsWith('/')\n if (!isSandboxPath) {\n const scheme = /^([A-Za-z][A-Za-z0-9+.-]*):/.exec(url)?.[1]?.toLowerCase() ?? 'unknown'\n return { succeeded: false, reason: `unsupported file URL scheme: ${scheme}` }\n }\n if (!input.box) return { succeeded: false, reason: 'no sandbox to read agent file' }\n\n const resolvedPath = resolveFileUrlPath(url)\n if (!resolvedPath.succeeded) return resolvedPath\n return resolveSandboxFileBytes({\n path: resolvedPath.path,\n box: input.box,\n sessionId: input.sessionId,\n filename: input.filename,\n maxBytes: input.maxBytes,\n })\n}\n\n/** First 8 hex chars of the SHA-256 of `seed` — deterministic (no\n * `Math.random`) so promoting the same source part twice, even across\n * requests, resolves to the same store path and overwrites in place. */\nasync function hash8(seed: string): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(seed))\n return Array.from(new Uint8Array(digest).slice(0, 4))\n .map((byte) => byte.toString(16).padStart(2, '0'))\n .join('')\n}\n\n/** Neutral default placement: everything under `uploads/agent/<date>/`, named\n * `<base>-<hash8><ext>`. No domain bucket taxonomy (assets/audio/videos…) —\n * a product that wants one supplies `buildAttachmentPath`. */\nfunction defaultBuildAttachmentPath(args: AttachmentPathArgs): string {\n const extensionMatch = /\\.[A-Za-z0-9]+$/.exec(args.filename)\n const extension = extensionMatch ? extensionMatch[0] : ''\n const base = extension ? args.filename.slice(0, -extension.length) : args.filename\n return `uploads/agent/${args.date}/${base}-${args.hash8}${extension}`\n}\n\ntype PromoteFilePartLogger = Pick<Console, 'error'>\n\nfunction logPromotionStorageError(\n logger: PromoteFilePartLogger,\n message: string,\n fields: Record<string, unknown>,\n): void {\n try {\n const safeFields = Object.fromEntries(\n Object.entries(fields).map(([key, value]) => [\n key,\n typeof value === 'string' ? redactErrorMessage(value) : value,\n ]),\n )\n logger.error(message, safeFields)\n } catch {\n // Logging must not turn a typed storage failure into a thrown response.\n }\n}\n\nasync function compensatePromotionWrite(\n writer: AtomicAttachmentWriter,\n scopeId: string,\n ownership: AttachmentWriteOwnership,\n receipt: AttachmentWriteReceipt,\n): Promise<{ rollbackError?: unknown; abortError?: unknown }> {\n try {\n await receipt.rollback()\n return {}\n } catch (rollbackError) {\n try {\n await writer.abort(scopeId, ownership)\n return { rollbackError }\n } catch (abortError) {\n return { rollbackError, abortError }\n }\n }\n}\n\ninterface PromoteFilePartCommonOptions {\n raw: RawAgentFilePart\n /** The turn's box — required only to promote a sandbox-path part; a `data:`\n * URI needs none. */\n box?: SandboxExecChannel\n /** The product's workspace/tenant key, passed to `writeAttachment`. */\n scopeId: string\n /** The turn's session id, used for the sandbox stat/read exec calls. */\n sessionId: string\n /** Store-path strategy. Default {@link defaultBuildAttachmentPath}. */\n buildAttachmentPath?: (args: AttachmentPathArgs) => string\n /** Raw-byte ceiling. Default {@link PROMOTE_MAX_FILE_BYTES}. */\n maxBytes?: number\n /** Last-resort media-type hook. Default {@link sniffMimeFromName}. */\n sniffMime?: (filename: string) => string\n /** Clock for the date path segment. Default `() => new Date()`. */\n now?: () => Date\n /** Unique id source for tests or a product's id service. */\n createWriteId?: () => string\n /** Server-side sink for redacted storage details. */\n logger?: PromoteFilePartLogger\n}\n\n/** Stable promotion options from the original writer contract. */\nexport interface PromoteAgentFilePartOptions extends PromoteFilePartCommonOptions {\n /** REQUIRED store writer — no default (the product owns its store). */\n writeAttachment: WriteAttachmentFn\n}\n\n/** Ownership-safe promotion options for new products. */\nexport interface AtomicPromoteAgentFilePartOptions extends PromoteFilePartCommonOptions {\n /** Complete writer + ambiguous-write cleanup adapter. */\n attachmentWriter: AtomicAttachmentWriter\n}\n\nfunction isAtomicPromotionOptions(\n options: PromoteAgentFilePartOptions | AtomicPromoteAgentFilePartOptions,\n): options is AtomicPromoteAgentFilePartOptions {\n return 'attachmentWriter' in options\n}\n\n/** Promote a part using the stable writer contract. */\nexport function promoteAgentFilePart(options: PromoteAgentFilePartOptions): Promise<PromoteFilePartResult>\n\n/** Promote a part using an ownership-safe writer and cleanup adapter. */\nexport function promoteAgentFilePart(options: AtomicPromoteAgentFilePartOptions): Promise<PromoteFilePartResult>\n\n/** Promote a part when the caller selects the writer contract at runtime. */\nexport function promoteAgentFilePart(\n options: PromoteAgentFilePartOptions | AtomicPromoteAgentFilePartOptions,\n): Promise<PromoteFilePartResult>\n\n/** Promote a part of an agent file with optional byte limits and MIME type detection. */\nexport async function promoteAgentFilePart(\n options: PromoteAgentFilePartOptions | AtomicPromoteAgentFilePartOptions,\n): Promise<PromoteFilePartResult> {\n const atomic = isAtomicPromotionOptions(options)\n const maxBytes = options.maxBytes ?? PROMOTE_MAX_FILE_BYTES\n const sniffMime = options.sniffMime ?? sniffMimeFromName\n const buildAttachmentPath = options.buildAttachmentPath ?? defaultBuildAttachmentPath\n const now = options.now ?? (() => new Date())\n const logger = options.logger ?? console\n const createWriteId = atomic\n ? options.createWriteId ?? (() => {\n if (typeof crypto === 'undefined' || typeof crypto.randomUUID !== 'function') {\n throw new Error('attachment promotion requires crypto.randomUUID')\n }\n return crypto.randomUUID()\n })\n : undefined\n\n const filename = sanitizeAttachmentFileName(\n options.raw.filename ?? basenameFromUrl(options.raw.url) ?? 'agent-file',\n )\n\n const resolved = await resolveBytes({\n raw: options.raw,\n box: options.box,\n sessionId: options.sessionId,\n filename,\n maxBytes,\n })\n if (!resolved.succeeded) return { succeeded: false, filename, reason: resolved.reason }\n\n let mediaType: string\n let kind: ChatAttachmentKind\n let logicalPath: string\n try {\n mediaType = (options.raw.mediaType ?? options.raw.mime ?? dataUrlMime(options.raw.url) ?? sniffMime(filename)).toLowerCase()\n kind = attachmentKindForMime(mediaType)\n const digest = await hash8(options.raw.id ?? options.raw.url ?? filename)\n const date = now().toISOString().split('T')[0] ?? ''\n logicalPath = buildAttachmentPath({ filename, hash8: digest, date, mediaType, kind })\n } catch (error) {\n logPromotionStorageError(logger, '[promote-file-part] path planning failed', {\n error: redactErrorMessage(error),\n })\n return { succeeded: false, filename, reason: ATTACHMENT_STORAGE_FAILURE_MESSAGE }\n }\n\n if (!atomic) {\n let written: unknown\n try {\n written = await options.writeAttachment(options.scopeId, logicalPath, resolved.bytes, {\n mediaType,\n name: filename,\n originalName: options.raw.filename ?? filename,\n size: resolved.bytes.byteLength,\n })\n } catch (error) {\n const reason = redactErrorMessage(error)\n logPromotionStorageError(logger, '[promote-file-part] legacy writer failed', {\n path: logicalPath,\n error: reason,\n })\n return { succeeded: false, filename, reason }\n }\n const result = inspectLegacyAttachmentWriteResult(written)\n if (!result) {\n logPromotionStorageError(logger, '[promote-file-part] legacy writer returned an invalid result', {\n path: logicalPath,\n })\n return { succeeded: false, filename, reason: ATTACHMENT_STORAGE_FAILURE_MESSAGE }\n }\n if (!result.ok) {\n const reason = redactErrorMessage(result.reason)\n logPromotionStorageError(logger, '[promote-file-part] legacy write rejected', {\n path: logicalPath,\n error: reason,\n })\n return { succeeded: false, filename, reason }\n }\n\n return {\n succeeded: true,\n part: {\n type: kind,\n path: logicalPath,\n name: filename,\n size: resolved.bytes.byteLength,\n mediaType,\n },\n }\n }\n\n let ownershipId: string\n let path: string\n try {\n ownershipId = createWriteId!()\n path = immutableAttachmentPath(logicalPath, ownershipId)\n } catch (error) {\n logPromotionStorageError(logger, '[promote-file-part] could not allocate ownership key', {\n path: logicalPath,\n error: redactErrorMessage(error),\n })\n return { succeeded: false, filename, reason: ATTACHMENT_STORAGE_FAILURE_MESSAGE }\n }\n const ownership: AttachmentWriteOwnership = Object.freeze({ id: ownershipId, path })\n\n // `name` is the sanitized filename already computed above; `originalName`\n // is the pre-sanitization source name (gtm's frontmatter `originalName`) —\n // the one field sanitization would otherwise destroy with no way back.\n let written: Awaited<ReturnType<AtomicAttachmentWriter['write']>>\n try {\n written = await options.attachmentWriter.write(options.scopeId, path, resolved.bytes, {\n mediaType,\n name: filename,\n originalName: options.raw.filename ?? filename,\n size: resolved.bytes.byteLength,\n ownership,\n })\n } catch (err) {\n let abortError: unknown\n try {\n await options.attachmentWriter.abort(options.scopeId, ownership)\n } catch (error) {\n abortError = error\n }\n logPromotionStorageError(logger, '[promote-file-part] write failed', {\n path,\n ownershipId: ownership.id,\n error: redactErrorMessage(err),\n ...(abortError === undefined ? {} : { abortError: redactErrorMessage(abortError) }),\n })\n return { succeeded: false, filename, reason: ATTACHMENT_STORAGE_FAILURE_MESSAGE }\n }\n const result = inspectAtomicAttachmentWriteResult(written, ownership)\n if (!result) {\n let abortError: unknown\n try {\n await options.attachmentWriter.abort(options.scopeId, ownership)\n } catch (error) {\n abortError = error\n }\n logPromotionStorageError(logger, '[promote-file-part] writer returned a mismatched ownership receipt', {\n path,\n ownershipId: ownership.id,\n ...(abortError === undefined ? {} : { abortError: redactErrorMessage(abortError) }),\n })\n return { succeeded: false, filename, reason: ATTACHMENT_STORAGE_FAILURE_MESSAGE }\n }\n if (!result.ok) {\n const compensation = await compensatePromotionWrite(\n options.attachmentWriter,\n options.scopeId,\n ownership,\n result.receipt,\n )\n logPromotionStorageError(logger, '[promote-file-part] write rejected', {\n path,\n ownershipId: ownership.id,\n error: redactErrorMessage(result.reason),\n ...(compensation.rollbackError === undefined\n ? {}\n : { rollbackError: redactErrorMessage(compensation.rollbackError) }),\n ...(compensation.abortError === undefined\n ? {}\n : { abortError: redactErrorMessage(compensation.abortError) }),\n })\n return { succeeded: false, filename, reason: ATTACHMENT_STORAGE_FAILURE_MESSAGE }\n }\n\n return {\n succeeded: true,\n part: {\n type: kind,\n path,\n name: filename,\n size: resolved.bytes.byteLength,\n mediaType,\n },\n }\n}\n","/**\n * `createAttachmentUploadRoute` — the fleet-primitive durable-store upload\n * route: a two-phase batch (every file is validated before any file is written),\n * with a stable legacy lane and an ownership-safe receipt lane for new callers,\n * a content-sniffed type gate\n * (`checkAttachmentType` over `sniffBinary`'s magic-byte read, not the\n * extension or the browser-reported MIME), binary/text caps with optional\n * per-sniffed-mime overrides, an aggregate byte cap, and sanitized filenames.\n * Storage is fully seamed through the injected\n * `WriteAttachmentFn` or `AtomicAttachmentWriter` (`./attachment-store`) — no\n * default store, the product owns where bytes actually live (vault, object\n * store, …), and auth/rate limiting is entirely the injected `authorize`\n * seam's job: this factory\n * never invents a 401 or 429 response, it only returns `auth.response`\n * verbatim on failure.\n *\n * Lifted from gtm-agent's `src/routes/api.vault.upload.ts` (the hardening\n * lineage other lifted modules in this vertical cite: gtm#584 binary\n * corruption, gtm#592 sniff gate/caps, gtm#593 batch-atomic writes) and\n * generalized the way `resolve-attachments.ts` generalized gtm's read path —\n * the vault-specific pieces (KV vault paths, frontmatter, per-user rate\n * limiting) are all injected seams here, while the validate-then-write phase\n * split and the type/size gate ordering survive byte-for-byte.\n *\n * @remarks Sibling to, NOT an extension of, `./upload.ts`'s\n * `createUploadRoute` — a different persistence model (durable product store\n * vs. inline-`data:`-or-ephemeral-sandbox-workspace). See that module's doc\n * comment for the up-to-date framing between the two.\n */\n\nimport type { ChatAttachmentInput, ChatAttachmentKind } from './wire'\nimport { attachmentKindForMime } from '../chat-store/parts'\nimport { redactErrorMessage } from '../redact'\nimport {\n ATTACHMENT_ROLLBACK_FAILURE_CODE,\n ATTACHMENT_ROLLBACK_FAILURE_MESSAGE,\n ATTACHMENT_STORAGE_FAILURE_MESSAGE,\n type AtomicAttachmentWriter,\n immutableAttachmentPath,\n type AttachmentWriteOwnership,\n type AttachmentWriteReceipt,\n type WriteAttachmentFn,\n} from './attachment-store'\nimport {\n inspectAtomicAttachmentWriteResult,\n inspectLegacyAttachmentWriteResult,\n} from './attachment-write-safety'\nimport {\n ALLOWED_ATTACHMENT_SNIFFED_MIMES,\n ATTACHMENT_MAX_COUNT,\n MAX_ATTACHMENT_TOTAL_BYTES,\n MAX_BINARY_ATTACHMENT_BYTES,\n MAX_TEXT_ATTACHMENT_BYTES,\n attachmentSizeErrorMessage,\n attachmentTotalSizeErrorMessage,\n checkAttachmentType,\n sanitizeAttachmentFileName,\n} from './attachment-validation'\nimport { sniffBinary } from './binary-sniff'\nimport { defaultValidateAttachmentPath, type AttachmentPathCheck } from './resolve-attachments'\nimport { sniffMimeFromName } from './promote-file-part'\n\n/** Stable authorization result for the original writer contract. */\nexport type AttachmentUploadAuthorization =\n | {\n ok: true\n scopeId: string\n writeAttachment?: WriteAttachmentFn\n }\n | { ok: false; response: Response }\n\n/** Authorization result for an ownership-safe attachment writer. */\nexport type AtomicAttachmentUploadAuthorization =\n | {\n ok: true\n scopeId: string\n attachmentWriter?: AtomicAttachmentWriter\n }\n | { ok: false; response: Response }\n\n/** The logger receives only sanitized backend details. The client receives the\n * opaque message below, regardless of the store's failure text. */\nexport type AttachmentUploadLogger = Pick<Console, 'error'>\n\nfunction logAttachmentUploadError(\n logger: AttachmentUploadLogger,\n message: string,\n fields: Record<string, unknown>,\n): void {\n try {\n const safeFields = Object.fromEntries(\n Object.entries(fields).map(([key, value]) => [\n key,\n typeof value === 'string' ? redactErrorMessage(value) : value,\n ]),\n )\n logger.error(message, safeFields)\n } catch {\n // Logging must not turn a typed storage failure into a thrown response.\n }\n}\n\ninterface AttachmentUploadRouteCommonOptions {\n /** Overridable caps. Defaults come from `./attachment-validation`. */\n limits?: {\n /** Most files one request may carry. Default {@link ATTACHMENT_MAX_COUNT}. */\n maxCount?: number\n /** Ceiling on a binary file's raw size. Default {@link MAX_BINARY_ATTACHMENT_BYTES}. */\n maxBinaryBytes?: number\n /** Optional binary-file ceilings keyed by the content-sniffed mime. */\n maxBytesBySniffedMime?: ReadonlyMap<string, number>\n /** Ceiling on a text file's raw size. Default {@link MAX_TEXT_ATTACHMENT_BYTES}. */\n maxTextBytes?: number\n /** Aggregate raw-byte ceiling. Default {@link MAX_ATTACHMENT_TOTAL_BYTES}. */\n maxTotalBytes?: number\n }\n /** Attachment kinds this route accepts. Default `['image', 'file']`. */\n allowedKinds?: ChatAttachmentKind[]\n /** Sniffed-mime allowlist fed to `checkAttachmentType`. */\n allowedSniffedMimes?: ReadonlySet<string>\n /** Sanitized-name → logical store path. */\n pathFor?: (name: string) => string\n /** Store-path validator. Default {@link defaultValidateAttachmentPath}. */\n validatePath?: (path: string) => AttachmentPathCheck\n /** Last-resort media-type hook for text content the sniffer cannot type. */\n sniffMime?: (name: string) => string\n /** Unique id source for atomic ownership keys. */\n createWriteId?: () => string\n /** Server-side error sink. Backend text is sanitized before it is logged. */\n logger?: AttachmentUploadLogger\n}\n\n/** Stable route options for products using the original writer contract. */\nexport interface CreateLegacyAttachmentUploadRouteOptions extends AttachmentUploadRouteCommonOptions {\n /** Authenticate the caller, rate-limit, and resolve the store scope\n * (workspace/tenant id) — never a query param. */\n authorize(args: { request: Request }): Promise<AttachmentUploadAuthorization>\n /** Stable writer. `authorize` may override it per-request. */\n writeAttachment: WriteAttachmentFn\n}\n\n/**\n * The original public route-options interface remains available for consumers\n * that extend it. Ownership-safe callers use the atomic interface below.\n */\nexport interface CreateAttachmentUploadRouteOptions extends CreateLegacyAttachmentUploadRouteOptions {}\n\n/** Ownership-safe route options for new products. */\nexport interface CreateAtomicAttachmentUploadRouteOptions extends AttachmentUploadRouteCommonOptions {\n /** Authenticate the caller, rate-limit, and resolve the store scope. */\n authorize(args: { request: Request }): Promise<AtomicAttachmentUploadAuthorization>\n /** Complete writer + ambiguous-write cleanup adapter. */\n attachmentWriter: AtomicAttachmentWriter\n}\n\nfunction attachmentUploadError(status: number, code: string, message: string, path?: string): Response {\n return Response.json(\n { error: path === undefined ? { code, message } : { code, message, path } },\n { status },\n )\n}\n\nfunction atomicAttachmentFailure(path: string, cleanupFailures = 0): Response {\n return attachmentUploadError(\n 503,\n cleanupFailures > 0 ? ATTACHMENT_ROLLBACK_FAILURE_CODE : 'attachment_store_unavailable',\n cleanupFailures > 0 ? ATTACHMENT_ROLLBACK_FAILURE_MESSAGE : ATTACHMENT_STORAGE_FAILURE_MESSAGE,\n path,\n )\n}\n\ninterface SuccessfulAttachmentWrite {\n path: string\n ownership: AttachmentWriteOwnership\n receipt: AttachmentWriteReceipt\n}\n\n/** Run every available compensation, from the newest write to the oldest. The\n * receipt owns the compare-and-delete operation; this route never deletes a\n * logical path itself. */\nasync function rollbackSuccessfulWrites(\n writes: readonly SuccessfulAttachmentWrite[],\n scopeId: string,\n writer: AtomicAttachmentWriter,\n logger: AttachmentUploadLogger,\n): Promise<number> {\n let failures = 0\n for (let index = writes.length - 1; index >= 0; index -= 1) {\n const write = writes[index]!\n try {\n await write.receipt.rollback()\n } catch (error) {\n failures += 1\n logAttachmentUploadError(logger, '[attachment-upload] cleanup failed', {\n path: write.path,\n ownershipId: write.ownership.id,\n error: redactErrorMessage(error),\n })\n failures += await abortOwnership(writer, scopeId, write.ownership, logger)\n }\n }\n return failures\n}\n\nasync function abortOwnership(\n writer: AtomicAttachmentWriter,\n scopeId: string,\n ownership: AttachmentWriteOwnership,\n logger: AttachmentUploadLogger,\n): Promise<number> {\n try {\n await writer.abort(scopeId, ownership)\n return 0\n } catch (error) {\n logAttachmentUploadError(logger, '[attachment-upload] ambiguous write cleanup failed', {\n path: ownership.path,\n ownershipId: ownership.id,\n error: redactErrorMessage(error),\n })\n return 1\n }\n}\n\nfunction isAtomicRouteOptions(\n options: CreateAttachmentUploadRouteOptions | CreateAtomicAttachmentUploadRouteOptions,\n): options is CreateAtomicAttachmentUploadRouteOptions {\n return 'attachmentWriter' in options\n}\n\n/** Resolve an attachment upload route handler with customizable limits and validation options. */\nexport function createAttachmentUploadRoute(\n options: CreateAttachmentUploadRouteOptions | CreateAtomicAttachmentUploadRouteOptions,\n): (request: Request) => Promise<Response> {\n const atomic = isAtomicRouteOptions(options)\n const maxCount = options.limits?.maxCount ?? ATTACHMENT_MAX_COUNT\n const maxBinaryBytes = options.limits?.maxBinaryBytes ?? MAX_BINARY_ATTACHMENT_BYTES\n const maxBytesBySniffedMime = options.limits?.maxBytesBySniffedMime\n const maxTextBytes = options.limits?.maxTextBytes ?? MAX_TEXT_ATTACHMENT_BYTES\n const maxTotalBytes = options.limits?.maxTotalBytes ?? MAX_ATTACHMENT_TOTAL_BYTES\n const allowedKinds: ChatAttachmentKind[] = options.allowedKinds ?? ['image', 'file']\n const allowedSniffedMimes = options.allowedSniffedMimes ?? ALLOWED_ATTACHMENT_SNIFFED_MIMES\n const pathFor = options.pathFor ?? ((name: string) => name)\n const validatePath = options.validatePath ?? defaultValidateAttachmentPath\n const sniffMime = options.sniffMime ?? sniffMimeFromName\n const createWriteId = atomic\n ? options.createWriteId ?? (() => {\n if (typeof crypto === 'undefined' || typeof crypto.randomUUID !== 'function') {\n throw new Error('attachment upload requires crypto.randomUUID')\n }\n return crypto.randomUUID()\n })\n : undefined\n const logger = options.logger ?? console\n\n return async function attachmentUpload(request: Request): Promise<Response> {\n const auth = await options.authorize({ request })\n if (!auth.ok) return auth.response\n const atomicWriter = atomic\n ? (auth as Extract<AtomicAttachmentUploadAuthorization, { ok: true }>).attachmentWriter ?? options.attachmentWriter\n : undefined\n const legacyWriter = atomic\n ? undefined\n : (auth as Extract<AttachmentUploadAuthorization, { ok: true }>).writeAttachment ?? options.writeAttachment\n if (atomic && !atomicWriter) {\n return attachmentUploadError(503, 'attachment_store_unavailable', ATTACHMENT_STORAGE_FAILURE_MESSAGE)\n }\n\n let form: FormData\n try {\n form = await request.formData()\n } catch {\n return attachmentUploadError(400, 'invalid_upload', 'Expected a multipart/form-data body with file fields')\n }\n // Collect every File value regardless of field name — the client may\n // send one field per file or a single repeated field.\n const files: File[] = []\n form.forEach((value) => {\n if (value instanceof File) files.push(value)\n })\n if (files.length === 0) {\n return attachmentUploadError(400, 'invalid_upload', 'No files in the upload body')\n }\n\n if (files.length > maxCount) {\n return attachmentUploadError(\n 400,\n 'attachment_count_exceeded',\n `Too many files — the ${maxCount}-file limit was exceeded`,\n )\n }\n\n // Advisory only: client-controlled `file.size`, so a dishonest caller can\n // slip past it — but an honest oversized batch should fail before a\n // single byte is read. The authoritative aggregate check (against the\n // sniffed/decoded byte length) runs per-file in phase 1, below.\n const advisoryTotal = files.reduce((sum, file) => sum + file.size, 0)\n if (advisoryTotal > maxTotalBytes) {\n return attachmentUploadError(\n 413,\n 'attachments_total_too_large',\n attachmentTotalSizeErrorMessage(advisoryTotal, maxTotalBytes),\n )\n }\n\n interface PreparedWrite {\n path: string\n ownershipId?: string\n name: string\n bytes: Uint8Array\n originalName: string\n size: number\n mediaType: string\n kind: ChatAttachmentKind\n }\n\n // Phase 1: validate every file and prepare its write input WITHOUT\n // writing anything. A batch fails atomically — any file's validation\n // error must reject the whole request before an earlier file in the\n // same batch is persisted.\n const prepared: PreparedWrite[] = []\n const seenLogicalPaths = new Set<string>()\n let totalBytes = 0\n\n for (const file of files) {\n const bytes = new Uint8Array(await file.arrayBuffer())\n const sniff = sniffBinary(bytes)\n // Attachment paths double as store keys, so the stored name must fit\n // the store charset; the as-uploaded name survives as `originalName`.\n const name = sanitizeAttachmentFileName(file.name)\n\n const typeCheck = checkAttachmentType(name, sniff, allowedSniffedMimes)\n if (!typeCheck.succeeded) {\n return attachmentUploadError(\n typeCheck.code === 'attachment_type_mismatch' ? 400 : 415,\n typeCheck.code,\n typeCheck.message,\n )\n }\n\n const kind = attachmentKindForMime(sniff.mime ?? '')\n if (!allowedKinds.includes(kind)) {\n return attachmentUploadError(\n 415,\n 'attachment_kind_not_allowed',\n `${name} is a \"${kind}\" attachment, which this upload route does not accept`,\n )\n }\n\n let limit = maxTextBytes\n if (sniff.binary) {\n limit = sniff.mime === null\n ? maxBinaryBytes\n : maxBytesBySniffedMime?.get(sniff.mime) ?? maxBinaryBytes\n }\n if (bytes.length > limit) {\n return attachmentUploadError(\n 413,\n 'attachment_too_large',\n attachmentSizeErrorMessage(name, bytes.length, limit),\n )\n }\n\n const logicalPath = pathFor(name)\n let ownershipId: string | undefined\n let path = logicalPath\n if (atomic) {\n try {\n ownershipId = createWriteId!()\n path = immutableAttachmentPath(logicalPath, ownershipId)\n } catch (error) {\n logAttachmentUploadError(logger, '[attachment-upload] could not allocate ownership key', {\n path: logicalPath,\n error: redactErrorMessage(error),\n })\n return attachmentUploadError(\n 503,\n 'attachment_store_unavailable',\n ATTACHMENT_STORAGE_FAILURE_MESSAGE,\n logicalPath,\n )\n }\n }\n const pathCheck = validatePath(path)\n if (!pathCheck.succeeded) {\n return attachmentUploadError(400, 'invalid_attachment_path', pathCheck.error, path)\n }\n // Small hardening over gtm: a batch whose sanitized names collide onto\n // the same store path (e.g. two variously-cased \"Report.PDF\" uploads)\n // would otherwise silently overwrite one with the other in phase 2.\n const duplicatePath = atomic ? logicalPath : path\n if (seenLogicalPaths.has(duplicatePath)) {\n return attachmentUploadError(\n 400,\n 'attachment_duplicate_path',\n `attachments must not repeat a path within one upload: ${duplicatePath}`,\n path,\n )\n }\n seenLogicalPaths.add(duplicatePath)\n\n // Authoritative aggregate check, against the actual decoded byte\n // length rather than the client-reported `file.size` checked above.\n totalBytes += bytes.length\n if (totalBytes > maxTotalBytes) {\n return attachmentUploadError(\n 413,\n 'attachments_total_too_large',\n attachmentTotalSizeErrorMessage(totalBytes, maxTotalBytes),\n )\n }\n\n const mediaType = sniff.mime ?? sniffMime(name)\n prepared.push({ path, ownershipId, name, bytes, originalName: file.name, size: bytes.length, mediaType, kind })\n }\n\n // Phase 2: every file in the batch passed validation — write them all.\n const uploaded: ChatAttachmentInput[] = []\n const successfulWrites: SuccessfulAttachmentWrite[] = []\n for (const input of prepared) {\n if (!atomic) {\n let written: unknown\n try {\n written = await legacyWriter!(auth.scopeId, input.path, input.bytes, {\n mediaType: input.mediaType,\n name: input.name,\n originalName: input.originalName,\n size: input.size,\n })\n } catch (error) {\n logAttachmentUploadError(logger, '[attachment-upload] legacy writer failed', {\n path: input.path,\n error: redactErrorMessage(error),\n })\n return attachmentUploadError(\n 503,\n 'attachment_store_unavailable',\n ATTACHMENT_STORAGE_FAILURE_MESSAGE,\n input.path,\n )\n }\n const result = inspectLegacyAttachmentWriteResult(written)\n if (!result) {\n logAttachmentUploadError(logger, '[attachment-upload] legacy writer returned an invalid result', {\n path: input.path,\n })\n return attachmentUploadError(\n 503,\n 'attachment_store_unavailable',\n ATTACHMENT_STORAGE_FAILURE_MESSAGE,\n input.path,\n )\n }\n if (!result.ok) {\n const reason = redactErrorMessage(result.reason)\n logAttachmentUploadError(logger, '[attachment-upload] legacy write rejected', {\n path: input.path,\n error: reason,\n })\n return attachmentUploadError(413, 'attachment_write_failed', reason, input.path)\n }\n uploaded.push({\n path: input.path,\n name: input.name,\n size: input.size,\n mediaType: input.mediaType,\n kind: input.kind,\n })\n continue\n }\n\n const ownershipId = input.ownershipId\n if (!ownershipId) {\n return attachmentUploadError(503, 'attachment_store_unavailable', ATTACHMENT_STORAGE_FAILURE_MESSAGE, input.path)\n }\n const ownership: AttachmentWriteOwnership = Object.freeze({ id: ownershipId, path: input.path })\n let written: Awaited<ReturnType<AtomicAttachmentWriter['write']>>\n try {\n written = await atomicWriter!.write(auth.scopeId, input.path, input.bytes, {\n mediaType: input.mediaType,\n name: input.name,\n originalName: input.originalName,\n size: input.size,\n ownership,\n })\n } catch (error) {\n const cleanupFailures = (await abortOwnership(atomicWriter!, auth.scopeId, ownership, logger)) +\n await rollbackSuccessfulWrites(successfulWrites, auth.scopeId, atomicWriter!, logger)\n logAttachmentUploadError(logger, '[attachment-upload] write failed', {\n path: input.path,\n ownershipId: ownership.id,\n cleanupFailures,\n error: redactErrorMessage(error),\n })\n return atomicAttachmentFailure(input.path, cleanupFailures)\n }\n const result = inspectAtomicAttachmentWriteResult(written, ownership)\n if (!result) {\n const cleanupFailures = (await abortOwnership(atomicWriter!, auth.scopeId, ownership, logger)) +\n await rollbackSuccessfulWrites(successfulWrites, auth.scopeId, atomicWriter!, logger)\n logAttachmentUploadError(logger, '[attachment-upload] writer returned a mismatched ownership receipt', {\n path: input.path,\n ownershipId: ownership.id,\n cleanupFailures,\n })\n return atomicAttachmentFailure(input.path, cleanupFailures)\n }\n if (!result.ok) {\n const cleanupFailures = (await rollbackSuccessfulWrites([\n { path: input.path, ownership, receipt: result.receipt },\n ], auth.scopeId, atomicWriter!, logger)) + await rollbackSuccessfulWrites(\n successfulWrites,\n auth.scopeId,\n atomicWriter!,\n logger,\n )\n logAttachmentUploadError(logger, '[attachment-upload] write rejected', {\n path: input.path,\n ownershipId: ownership.id,\n cleanupFailures,\n error: redactErrorMessage(result.reason),\n })\n return atomicAttachmentFailure(input.path, cleanupFailures)\n }\n successfulWrites.push({ path: input.path, ownership, receipt: result.receipt })\n uploaded.push({\n path: input.path,\n name: input.name,\n size: input.size,\n mediaType: input.mediaType,\n kind: input.kind,\n })\n }\n\n return Response.json({ files: uploaded })\n }\n}\n","/**\n * `buildDispatchParts` — assemble the `PromptInputPart[]` a turn carrying\n * attachments and/or `@`-mentions dispatches to the sandbox. `parts[0]` is\n * always the full prompt text (typed text plus the attachment + mention pointer\n * blocks); each attachment or mention becomes one media part. An attachment\n * (read from the product store via the injected reader) draws the inline byte\n * budget first; a mention (read from the LIVE box) takes what is left. A file\n * inlines as a `data:` URI when it fits the remaining budget, otherwise uses\n * the in-box file reference supported by the current sandbox prompt contract.\n * Every media part is deduped by its resolved absolute path. This module only\n * produces the parts array; the caller decides when a turn dispatches parts\n * instead of a plain string.\n *\n * Storage-parameterized port of gtm-agent's `dispatch-parts.ts`: the vault\n * default reader is dropped (`readAttachment` is REQUIRED — the product supplies\n * its store adapter), the `GTM_SANDBOX_VAULT_DIR` prefixing becomes the required\n * `resolveAttachmentPath` seam, the `GTM_MULTIMODAL_FORCE_PATH` env fallback\n * becomes an explicit `forcePath` flag, and every budget cap reads an overridable\n * `./wire` constant. Kept behavior-identical for gtm-agent#618 adoption (the\n * demotion math and emitted part shapes reproduce its dispatched prompt bytes).\n */\n\nimport { isWorkspaceFileExportable } from '../web/file-export'\nimport { flattenHistory, type PromptInputPart } from '../sandbox'\nimport {\n statSandboxFileSize,\n readSandboxBinaryBytes,\n type SandboxExecChannel,\n} from '../sandbox/binary-read'\nimport {\n ChatTurnInputError,\n mediaTypeForMentionPath,\n base64WireLen,\n DISPATCH_REQUEST_MAX_BYTES,\n DISPATCH_STRUCTURAL_RESERVE_BYTES,\n DISPATCH_MAX_PARTS,\n type ChatTurnPartInput,\n} from './wire'\nimport type { ChatAttachmentPart, ChatMentionPart } from '../chat-store/parts'\nimport { bytesToBase64 } from './upload'\nimport type { ReadAttachmentFn } from './attachment-store'\nimport { pathToFileURL } from 'node:url'\n\nexport type { PromptInputPart }\n\n/** Resolve the outcome of dispatching parts with success status and corresponding value or error message */\nexport type DispatchPartsOutcome =\n | { succeeded: true; value: PromptInputPart[] }\n | { succeeded: false; error: string }\n\n/** One mention file's size (always) and inline bytes (only when the caller\n * asked for them — a path-only mention never reads its bytes). */\ntype SandboxMentionReadOutcome =\n | { succeeded: true; value: { size: number; base64?: string } }\n | { succeeded: false; error: string }\n\n/** Resolve sandbox mention details by reading from a specified path with optional byte reading */\nexport type ReadSandboxMentionFn = (\n box: SandboxExecChannel,\n absolutePath: string,\n options: { readBytes: boolean },\n) => Promise<SandboxMentionReadOutcome>\n\nfunction byteLen(value: string): number {\n return new TextEncoder().encode(value).length\n}\n\n/**\n * Default mention reader: stats the in-box file (which also proves it still\n * exists — a since-deleted mention fails loud here), then reads its bytes only\n * when the caller wants to inline it. Both hops cross the sandbox exec channel\n * via the substrate's binary-read helpers.\n */\nasync function readSandboxMention(\n box: SandboxExecChannel,\n absolutePath: string,\n options: { readBytes: boolean },\n): Promise<SandboxMentionReadOutcome> {\n const stat = await statSandboxFileSize(box, absolutePath)\n if (!stat.succeeded) {\n return { succeeded: false, error: `mentioned sandbox file missing or unreadable: ${absolutePath} — ${stat.error}` }\n }\n if (!options.readBytes) return { succeeded: true, value: { size: stat.value } }\n\n const read = await readSandboxBinaryBytes(box, absolutePath, stat.value)\n if (!read.succeeded) {\n return { succeeded: false, error: `mentioned sandbox file read failed: ${absolutePath} — ${read.error}` }\n }\n return { succeeded: true, value: { size: stat.value, base64: bytesToBase64(read.value.bytes) } }\n}\n\n/** HARD INVARIANT: an image carries exactly one URL or absolute path, while a\n * file carries the URL required by the current sandbox prompt contract. */\nfunction violatesUrlPathXor(part: PromptInputPart): boolean {\n if (part.type === 'text') return false\n const hasUrl = typeof part.url === 'string' && (\n part.url.startsWith('data:')\n || part.url.startsWith('file://')\n )\n const hasPath = 'path' in part && typeof part.path === 'string' && part.path.startsWith('/')\n if (part.type === 'file') return !hasUrl || hasPath\n return hasUrl === hasPath\n}\n\nfunction sandboxFileUrl(absolutePath: string): string | undefined {\n if (!absolutePath.startsWith('/')) return undefined\n try {\n return pathToFileURL(absolutePath).href\n } catch {\n return undefined\n }\n}\n\n/**\n * Convert the browser-safe chat attachment contract into the current sandbox\n * prompt contract. Images carry exactly one URL or path; generic files carry a\n * filename and URL.\n */\nexport function normalizeChatPromptForSandbox(\n prompt: string | readonly ChatTurnPartInput[],\n): string | PromptInputPart[] {\n if (typeof prompt === 'string') return prompt\n return prompt.map((part): PromptInputPart => {\n if (part.type === 'text') return part\n if ('content' in part) {\n throw new ChatTurnInputError('Sandbox prompt parts do not accept inline content; provide a URL or path')\n }\n\n const url = typeof part.url === 'string' && part.url.length > 0 ? part.url : undefined\n const path = typeof part.path === 'string' && part.path.length > 0 ? part.path : undefined\n if (Boolean(url) === Boolean(path)) {\n throw new ChatTurnInputError(`Sandbox ${part.type} parts require exactly one URL or path`)\n }\n if (path && !path.startsWith('/')) {\n throw new ChatTurnInputError(`Sandbox ${part.type} paths must be absolute: ${path}`)\n }\n\n if (part.type === 'image') {\n return {\n type: 'image',\n ...(part.filename ? { filename: part.filename } : {}),\n ...(part.mediaType ? { mediaType: part.mediaType } : {}),\n ...(url ? { url } : { path: path! }),\n }\n }\n\n const filename = part.filename?.trim()\n if (!filename) {\n throw new ChatTurnInputError('Sandbox file parts require a filename')\n }\n const fileUrl = url ?? sandboxFileUrl(path!)\n if (!fileUrl) {\n throw new ChatTurnInputError(`Sandbox file paths must be absolute: ${path}`)\n }\n return {\n type: 'file',\n filename,\n ...(part.mediaType ? { mediaType: part.mediaType } : {}),\n url: fileUrl,\n }\n })\n}\n\n/** Build input parameters for dispatching chat message parts including text, attachments, mentions, and history */\nexport interface BuildDispatchPartsInput {\n text: string\n attachments: ChatAttachmentPart[]\n mentions?: ChatMentionPart[]\n history: Array<{ role: 'user' | 'assistant'; content: string }>\n systemPrompt: string\n /** Serialized size of the backend profile the SDK inlines into the same\n * prompt request body — a large, non-negotiable rider that must come out of\n * the inline budget or near-cap attachments 413 at the proxy instead of\n * demoting to path parts. */\n profileWireBytes: number\n /** The product's workspace/tenant key, passed to `readAttachment`. */\n scopeId: string\n /** Maps an attachment's store-relative path to the in-box absolute path a\n * local media reference uses (same style as `fileMentionsToParts`'s\n * `resolvePath`). */\n resolveAttachmentPath: (path: string) => string\n /** Maps a mention's workspace-relative path to its in-box absolute path.\n * Default: {@link BuildDispatchPartsInput.resolveAttachmentPath} — in gtm the\n * vault mount roots both; a product that mounts them apart overrides this. */\n resolveMentionPath?: (path: string) => string\n /** The turn's already-ensured box — required when `mentions` is non-empty\n * (mention bytes are read from the live box, not the store). */\n box?: SandboxExecChannel\n /** Force every media part to a local file reference, skipping all inlining. */\n forcePath?: boolean\n /** REQUIRED store reader for attachment content — no default (the product\n * owns its store; see {@link ReadAttachmentFn}). */\n readAttachment: ReadAttachmentFn\n readSandboxMention?: ReadSandboxMentionFn\n /** Whole-request proxy cap. Default {@link DISPATCH_REQUEST_MAX_BYTES}. */\n requestMaxBytes?: number\n /** JSON-envelope reserve off the top of the request cap. Default\n * {@link DISPATCH_STRUCTURAL_RESERVE_BYTES}. */\n structuralReserveBytes?: number\n /** Sidecar per-request parts-array cap. Default {@link DISPATCH_MAX_PARTS}. */\n maxParts?: number\n}\n\n/** Content of one attachment read, normalized to the base64 a `data:` URI\n * needs — `base64` reused verbatim, else `bytes` encoded once. */\nfunction readResultToBase64(read: { base64?: string; bytes?: Uint8Array }): string | undefined {\n if (typeof read.base64 === 'string') return read.base64\n if (read.bytes) return bytesToBase64(read.bytes)\n return undefined\n}\n\n/** Build dispatch parts from input by resolving mentions, paths, and applying size constraints asynchronously */\nexport async function buildDispatchParts(input: BuildDispatchPartsInput): Promise<DispatchPartsOutcome> {\n const readMention = input.readSandboxMention ?? readSandboxMention\n const resolveMentionPath = input.resolveMentionPath ?? input.resolveAttachmentPath\n const forcePath = input.forcePath ?? false\n const mentions = input.mentions ?? []\n const requestMaxBytes = input.requestMaxBytes ?? DISPATCH_REQUEST_MAX_BYTES\n const structuralReserveBytes = input.structuralReserveBytes ?? DISPATCH_STRUCTURAL_RESERVE_BYTES\n const maxParts = input.maxParts ?? DISPATCH_MAX_PARTS\n\n const parts: PromptInputPart[] = [{ type: 'text', text: input.text }]\n // Absolute in-box paths already emitted — a file both attached and mentioned\n // (or mentioned twice) rides as a single media part.\n const emittedAbsPaths = new Set<string>()\n\n const flattenedForSizing = flattenHistory(input.text, input.history)\n // May go negative when history/systemPrompt alone are large — every\n // attachment then fails the `runningInline + cost <= inlineBudget` check\n // below and uses a local file reference, which does not draw on this inline\n // budget.\n const inlineBudget =\n requestMaxBytes\n - base64WireLen(byteLen(flattenedForSizing))\n - byteLen(JSON.stringify(input.systemPrompt))\n - input.profileWireBytes\n - structuralReserveBytes\n\n let runningInline = 0\n\n // Stable input order: attachments dispatch in the order the user attached\n // them, and the pointer block in `input.text` already names them in that\n // same order.\n for (const attachment of input.attachments) {\n if (!attachment.path) {\n return { succeeded: false, error: `attachment path must be non-empty: ${attachment.name}` }\n }\n\n // The reader crosses an external boundary (store/coordinator) — a rejection\n // there must land in the typed outcome, not escape as a thrown error the\n // caller would misattribute to stream init.\n let read: Awaited<ReturnType<ReadAttachmentFn>>\n try {\n read = await input.readAttachment(input.scopeId, attachment.path)\n } catch (err) {\n return {\n succeeded: false,\n error: `attachment store read failed: ${attachment.path} — ${err instanceof Error ? err.message : String(err)}`,\n }\n }\n if (!read.ok) return { succeeded: false, error: read.reason }\n\n const base64 = readResultToBase64(read)\n if (base64 === undefined) {\n return { succeeded: false, error: `attachment store read produced no content: ${attachment.path}` }\n }\n\n const mediaType = attachment.mediaType ?? read.mediaType\n if (attachment.type === 'image' && !mediaType) {\n return { succeeded: false, error: `attachment is missing a mediaType required for an image data URI: ${attachment.path}` }\n }\n\n const absPath = input.resolveAttachmentPath(attachment.path)\n emittedAbsPaths.add(absPath)\n\n if (attachment.type === 'image') {\n const inlinePart: PromptInputPart = {\n type: 'image',\n filename: attachment.name,\n mediaType,\n url: `data:${mediaType};base64,${base64}`,\n }\n const cost = byteLen(JSON.stringify(inlinePart))\n if (!forcePath && runningInline + cost <= inlineBudget) {\n parts.push(inlinePart)\n runningInline += cost\n } else {\n parts.push({ type: 'image', filename: attachment.name, mediaType, path: absPath })\n }\n continue\n }\n\n // File parts use the current filename + URL contract. A file that is too\n // large to inline keeps its existing in-box file through a file:// URL.\n const fileMediaType = mediaType ?? 'application/octet-stream'\n const inlinePart: PromptInputPart = {\n type: 'file',\n filename: attachment.name,\n mediaType: fileMediaType,\n url: `data:${fileMediaType};base64,${base64}`,\n }\n const cost = byteLen(JSON.stringify(inlinePart))\n if (!forcePath && runningInline + cost <= inlineBudget) {\n parts.push(inlinePart)\n runningInline += cost\n } else {\n const url = sandboxFileUrl(absPath)\n if (!url) {\n return { succeeded: false, error: `resolved attachment path must be absolute: ${absPath}` }\n }\n parts.push({ type: 'file', filename: attachment.name, mediaType: fileMediaType, url })\n }\n }\n\n if (mentions.length > 0 && !input.box) {\n return { succeeded: false, error: 'internal error: sandbox mentions require a box to read from' }\n }\n for (const mention of mentions) {\n if (!mention.path) {\n return { succeeded: false, error: `mention path must be non-empty: ${mention.name}` }\n }\n if (!isWorkspaceFileExportable(mention.path)) {\n return { succeeded: false, error: `mention file is not exportable: ${mention.path}` }\n }\n const absPath = resolveMentionPath(mention.path)\n if (emittedAbsPaths.has(absPath)) continue\n emittedAbsPaths.add(absPath)\n\n const isImage = mention.mentionKind === 'image'\n const mediaType = isImage ? mediaTypeForMentionPath(mention.path) : undefined\n\n // Every mention is stat'd first: it proves the file still exists (a deleted\n // mention fails the turn loud) and gives the size for the inline budget\n // decision without pulling bytes across the exec channel.\n let stat: SandboxMentionReadOutcome\n try {\n stat = await readMention(input.box!, absPath, { readBytes: false })\n } catch (err) {\n return { succeeded: false, error: `mention read failed: ${absPath} — ${err instanceof Error ? err.message : String(err)}` }\n }\n if (!stat.succeeded) return { succeeded: false, error: stat.error }\n\n // Only an image that projects within the remaining budget reads its bytes\n // to inline; every other mention (and a budget-exceeding image) ships\n // by local reference, so a large file is never base64'd unnecessarily.\n const projectedInlineCost = base64WireLen(stat.value.size)\n + byteLen(JSON.stringify({ type: 'image', filename: mention.name, mediaType: mediaType ?? '', url: '' }))\n if (isImage && mediaType && !forcePath && runningInline + projectedInlineCost <= inlineBudget) {\n let read: SandboxMentionReadOutcome\n try {\n read = await readMention(input.box!, absPath, { readBytes: true })\n } catch (err) {\n return { succeeded: false, error: `mention read failed: ${absPath} — ${err instanceof Error ? err.message : String(err)}` }\n }\n if (!read.succeeded) return { succeeded: false, error: read.error }\n if (!read.value.base64) return { succeeded: false, error: `mentioned image produced no bytes: ${absPath}` }\n const inlinePart: PromptInputPart = {\n type: 'image',\n filename: mention.name,\n mediaType,\n url: `data:${mediaType};base64,${read.value.base64}`,\n }\n const cost = byteLen(JSON.stringify(inlinePart))\n // Re-check against the actual serialized cost — the projection can\n // undershoot; a real overshoot demotes to a path part rather than 413ing.\n if (runningInline + cost <= inlineBudget) {\n parts.push(inlinePart)\n runningInline += cost\n continue\n }\n }\n\n if (isImage && mediaType) {\n parts.push({ type: 'image', filename: mention.name, mediaType, path: absPath })\n } else {\n const url = sandboxFileUrl(absPath)\n if (!url) {\n return { succeeded: false, error: `resolved mention path must be absolute: ${absPath}` }\n }\n parts.push({ type: 'file', filename: mention.name, url })\n }\n }\n\n for (const part of parts) {\n if (violatesUrlPathXor(part)) {\n return { succeeded: false, error: 'internal error: emitted media part violates the url/path exclusivity invariant' }\n }\n }\n\n // Final whole-request check sized against what actually crosses the wire: the\n // history-merged text part (the substrate folds `history` into `parts[0]`\n // before dispatch), the media parts, the system prompt, and the inlined\n // backend profile riding the same body — the same terms `inlineBudget` was\n // derived from.\n const textPartSize = base64WireLen(byteLen(flattenedForSizing))\n const mediaPartsSize = parts.slice(1).reduce((total, part) => total + byteLen(JSON.stringify(part)), 0)\n const systemPromptSize = byteLen(JSON.stringify(input.systemPrompt))\n if (textPartSize + mediaPartsSize + systemPromptSize + input.profileWireBytes + structuralReserveBytes > requestMaxBytes) {\n return { succeeded: false, error: 'dispatch parts exceed the sandbox proxy request cap even after path demotion' }\n }\n\n // The sidecar rejects the whole request past its parts-array cap; the caller\n // selects which media ride natively, so overflow here is a caller bug\n // surfaced loudly rather than a truncation.\n if (parts.length > maxParts) {\n return { succeeded: false, error: `dispatch parts exceed the sidecar per-request cap of ${maxParts}` }\n }\n\n return { succeeded: true, value: parts }\n}\n"],"mappings":"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","names":["parts","hasUsage","base64","written","result","written","result","inlinePart","cost"]}
|
|
1
|
+
{"version":3,"sources":["../../src/chat-routes/model-failover-stream.ts","../../src/chat-routes/sandbox-turn-usage.ts","../../src/chat-routes/sandbox-producer.ts","../../src/chat-routes/protected-runtime-producer.ts","../../src/chat-routes/detached-turn.ts","../../src/chat-routes/completed-sandbox-turn.ts","../../src/chat-routes/native-completion.ts","../../src/chat-routes/durable-projection.ts","../../src/chat-routes/upload.ts","../../src/chat-routes/attachment-store.ts","../../src/chat-routes/resolve-attachments.ts","../../src/chat-routes/attachment-write-safety.ts","../../src/chat-routes/promote-file-part.ts","../../src/chat-routes/attachment-upload.ts","../../src/chat-routes/dispatch-parts.ts"],"sourcesContent":["/**\n * Model failover for a STREAMING turn.\n *\n * `/model-resolution`'s `runWithModelFailover` already owns the policy: walk a\n * chain, classify a resolved-or-thrown signal, re-throw a non-outage failure\n * immediately, and carry the attempt trail. This module does NOT re-implement\n * any of that — it composes it. What it adds is the one thing a whole-call\n * primitive cannot express, because a stream fails PARTWAY:\n *\n * **A turn may only fail over before its first client-visible byte.**\n *\n * Once a text delta, tool call, or ask has reached the browser (and the\n * persisted transcript), restarting on another model would duplicate the\n * answer. So each attempt is probed: open the stream, pull events into a small\n * buffer, and decide at the first meaningful event whether this model is\n * serving. Committing replays the buffer and hands the live iterator through;\n * abandoning discards the buffer (those events describe the dead model's\n * session — including its `step-finish` usage, which must never be billed) and\n * lets `runWithModelFailover` walk to the next model.\n *\n * `liveLifecycleEvents` relaxes the buffer for progress only. `start`,\n * `status`, `model-processing`, and session lifecycle carry no answer and no\n * receipt, so a product may take them the moment they arrive and show what\n * the turn is waiting on. Measured 2026-09-11 on a warm production box:\n * OpenCode reports whole parts, so the buffer held every event for 16 s and\n * the product could show nothing through provisioning and agent startup. Off\n * by default: with it on, an abandoned model's progress stays in the stream.\n *\n * The classification itself is `isUpstreamUnavailable` verbatim, so this path\n * inherits the measured facts from the 2026-07-25 outage — above all that an\n * outage is NOT always a thrown error: the sandbox RESOLVES a terminal `error`\n * event carrying `{ errorCode: 'provider_inference_unavailable' }`, which a\n * classifier inspecting only `catch` misses entirely. That resolved shape is\n * the whole reason the breakage went unnoticed, so it is classified here first.\n *\n * Conservative by construction:\n * - A terminal failure that is NOT an outage (400, bad schema, content filter)\n * COMMITS rather than failing over — it surfaces to the user exactly as it\n * does today. Those fail identically on every model; walking the chain would\n * only multiply latency and spend to reach the same error.\n * - A clean stream that produced nothing NEVER walks the chain. An empty answer\n * is not evidence of a dead upstream, and a silent re-roll on another model is\n * precisely the unattributable downgrade this work exists to prevent. Opt-in\n * `emptyTurnRetries` re-runs the SAME model instead, which leaves attribution\n * untouched — measured on production 2026-07-27, an empty turn is a transient\n * platform flake that a same-model re-run recovers (8 hard cases: 7/8\n * delivered on the first pass, 8/8 with one re-run, at a cost of 1 extra turn\n * in 9). Default `0`, so the behavior is unchanged unless a product asks.\n * - A responsive chain of length 1 costs no extra call or latency and remains\n * byte-identical to no failover. A silent chain is now deliberately bounded.\n */\n\nimport {\n isUpstreamUnavailable,\n ModelFailoverExhaustedError,\n readHttpStatusHint,\n runWithModelFailover,\n type ModelFailoverAttempt,\n} from '../model-resolution/failover'\nimport { asRecord, asString } from '../stream/index'\n\n/** Terminal event types that end an attempt with a failure verdict. */\nconst TERMINAL_FAILURE_TYPES = new Set(['error', 'session.run.failed'])\n\n/**\n * Part types that carry no transcript content. They are buffered like anything\n * else — replayed verbatim when the attempt commits — but their presence alone\n * never commits a model, so an outage arriving right after a `step-start` is\n * still recoverable. `step-finish` matters most: it carries the abandoned\n * attempt's token/cost receipt, which must be discarded rather than billed.\n */\nconst NON_COMMITTING_PART_TYPES = new Set(['step-start', 'step-finish'])\n\n/**\n * True when `event` puts content in front of the user (or in the persisted\n * transcript), making a restart on another model unsafe.\n *\n * Deliberately an allow-list of KNOWN-INERT types rather than a deny-list: an\n * unrecognized event commits. Getting this wrong in the safe direction costs a\n * missed failover; getting it wrong the other way duplicates a user's answer.\n */\nexport function isCommittingSandboxEvent(event: unknown): boolean {\n const record = asRecord(event)\n if (!record) return false\n const type = asString(record.type) ?? ''\n if (!type) return false\n\n if (type === 'message.part.updated') {\n const part = asRecord(asRecord(record.data)?.part)\n const partType = asString(part?.type) ?? ''\n if (NON_COMMITTING_PART_TYPES.has(partType)) return false\n // A text/reasoning part with NO content yet (the platform opens the part\n // with `text: \"\"` before the first token — verbatim in the 2026-07-26\n // capture) puts nothing in front of the user; discarding it cannot\n // duplicate an answer. The first real token commits.\n if (partType === 'text' || partType === 'reasoning') {\n const text = asString(part?.text) ?? asString(part?.content) ?? ''\n return text.length > 0\n }\n return true\n }\n\n // Session/turn lifecycle, progress status, and warnings belong to the\n // attempt, not the transcript — discardable with an abandoned model. Every\n // entry here was OBSERVED on a real box (2026-07-26 capture: a dead model\n // emits `start`, `execution.started`, `status`×3, `session.updated`×2,\n // `warning`×2 BEFORE its terminal `error` — any one of them committing\n // would pin the turn to the dead model and kill the failover).\n if (\n type === 'start' ||\n type === 'execution.started' ||\n type === 'status' ||\n type === 'model-processing' ||\n type === 'model.processing' ||\n type === 'session.created' ||\n type === 'session.updated' ||\n type === 'session.idle' ||\n type === 'step-start' ||\n type === 'step-finish' ||\n type === 'turn' ||\n type === 'warning'\n ) {\n return false\n }\n\n // Everything else — text/tool shapes the producer folds, asks, plans,\n // `result`/`done`, and any type this shell does not recognize — commits.\n return true\n}\n\n/**\n * Condense an abandoned attempt's raw failure text into something safe to show\n * a customer in the transcript.\n *\n * Measured on the live router 2026-07-27 19:40 UTC: an edge 5xx arrives as\n * Cloudflare's full HTML error PAGE, so the verbatim reason began\n * `<!DOCTYPE html>\\n<!--[if lt IE 7]> <html class=\"no-js ie6 oldie\"…` and the\n * fallback notice pasted 200 bytes of that markup into the answer the customer\n * reads. The cause is worth stating; the markup is not.\n *\n * Only the DISPLAY string is condensed. The full text stays on\n * `modelFailover.attempts[].reason` for the operator, so nothing is lost —\n * this narrows what the customer sees, never what telemetry records.\n */\nexport function summarizeFailoverReason(reason: string): string {\n const collapsed = reason.replace(/\\s+/g, ' ').trim()\n if (!collapsed) return 'upstream unavailable'\n\n // An HTML error page carries no message worth quoting — the status is the\n // whole content. Detected on the ORIGINAL text, before collapsing, because\n // that is the form the producer hands over.\n if (/<!doctype html|<html[\\s>]/i.test(collapsed)) {\n const status = readHttpStatusHint(collapsed)\n return status ? `HTTP ${status}` : 'upstream returned an error page'\n }\n\n // Anything else is a real message. Cap it so a verbose upstream cannot push\n // the answer off the screen, and cut on a word boundary when one is near.\n const LIMIT = 160\n if (collapsed.length <= LIMIT) return collapsed\n const clipped = collapsed.slice(0, LIMIT)\n const lastSpace = clipped.lastIndexOf(' ')\n return `${(lastSpace > LIMIT - 30 ? clipped.slice(0, lastSpace) : clipped).trimEnd()}…`\n}\n\n/** A terminal failure event, classified. `outage` decides failover vs surface. */\ninterface TerminalFailure {\n outage: boolean\n reason: string\n code?: string\n}\n\n/**\n * Classify a terminal failure event. Returns `null` for any non-terminal event.\n *\n * The RESOLVED shape is checked first and deliberately: the sandbox reports an\n * upstream outage by resolving `{ success: false, errorCode:\n * 'provider_inference_unavailable' }` inside a terminal `error` event's `data`,\n * never by throwing. Both `data` and the whole record are offered to\n * `isUpstreamUnavailable` so a payload nested either way is caught.\n */\n/**\n * Progress the consumer may see before the commit point: it names no model\n * output and carries no billing receipt, so streaming it live cannot duplicate\n * an answer or bill an abandoned attempt. `warning` and every\n * `message.part.updated` (including `step-start`/`step-finish`) stay buffered.\n */\nexport function isLiveLifecycleEvent(event: unknown): boolean {\n const type = asString(asRecord(event)?.type) ?? ''\n return (\n type === 'start' ||\n type === 'execution.started' ||\n type === 'status' ||\n type === 'model-processing' ||\n type === 'model.processing' ||\n type === 'session.created' ||\n type === 'session.updated' ||\n type === 'session.idle'\n )\n}\n\nexport function classifyTerminalFailure(event: unknown): TerminalFailure | null {\n const record = asRecord(event)\n if (!record) return null\n const type = asString(record.type) ?? ''\n if (!TERMINAL_FAILURE_TYPES.has(type)) return null\n\n const data = asRecord(record.data)\n const outage = isUpstreamUnavailable(data) || isUpstreamUnavailable(record)\n const reason =\n asString(data?.message) ??\n asString(data?.error) ??\n asString(data?.reason) ??\n asString(record.message) ??\n `sandbox stream reported ${type}`\n const code = asString(data?.errorCode) ?? asString(data?.code)\n return { outage, reason, ...(code ? { code } : {}) }\n}\n\n/** A model that served: its buffered preamble plus the still-live iterator. */\ninterface AttemptCommit {\n committed: true\n buffered: unknown[]\n /** `null` when the stream already ended during the probe. */\n iterator: AsyncIterator<unknown> | null\n /** Cancel transport work when this attempt is drained or abandoned. */\n abort: () => void\n}\n\n/**\n * A model whose upstream is down. `error`/`errorCode` are read by\n * `runWithModelFailover`'s attempt-trail describe(), so the trail names the\n * real upstream cause rather than a wrapper's own words.\n */\ninterface AttemptOutage {\n committed: false\n error: string\n errorCode?: string\n timeoutCode?: ModelAttemptTimeoutCode\n}\n\ntype AttemptOutcome = AttemptCommit | AttemptOutage\n\n/** Open the raw turn stream for one specific model. */\nexport type OpenModelStream = (args: {\n model: string\n /** 1 for the preferred model, 2 for the first fallback, and so on. */\n attempt: number\n /** Aborted when this attempt times out, is abandoned, or finishes. Forward\n * this to `streamSandboxPrompt(..., { signal })` so a blocked transport is\n * cancelled immediately rather than only receiving iterator.return(). */\n signal: AbortSignal\n}) => AsyncIterable<unknown> | Promise<AsyncIterable<unknown>>\n\n/** Fired when a model is abandoned and the next one is about to be tried. */\nexport interface ModelFallbackInfo {\n from: string\n to: string\n reason: string\n}\n\n/** Define inputs for streaming a turn across a model failover chain */\nexport interface ModelFailoverStreamOptions {\n /** Preferred model first, then fallbacks in descending preference. */\n models: readonly string[]\n open: OpenModelStream\n /**\n * Maximum time to open/start one model's event source, including its first\n * iterator pull. Default 120 seconds, matching the sandbox provisioning\n * ceiling so cold infrastructure startup is bounded independently from\n * provider inference.\n */\n openTimeoutMs?: number\n /**\n * Hard deadline from the source's first lifecycle event to its first\n * answer-bearing event. Default 60 seconds. Processing/lifecycle events do\n * not commit the attempt and do not extend this deadline.\n */\n firstResponseTimeoutMs?: number\n /** Override the commit-point rule. Default {@link isCommittingSandboxEvent}. */\n isCommitting?: (event: unknown) => boolean\n /**\n * Stream {@link isLiveLifecycleEvent} progress to the consumer as it arrives\n * instead of holding it until the attempt commits. Default `false`, which\n * keeps an abandoned model's events entirely off the wire. With it on, such\n * an event is never replayed from the buffer, and one from an abandoned\n * attempt stays in the stream — it describes the sandbox, not the answer.\n */\n liveLifecycleEvents?: boolean\n onFallback?: (info: ModelFallbackInfo) => void\n /**\n * How many times to RE-RUN THE SAME MODEL when a turn completes having\n * produced no assistant text at all. Default `0` — byte-identical to no\n * retry.\n *\n * This is deliberately not a chain walk. Falling over to a different model\n * on an empty answer is the unattributable downgrade this module refuses to\n * do; re-running the SAME model changes nothing about attribution, because\n * the model that serves is the model that was asked for.\n *\n * Bounded by the same commit rule as failover: only a turn whose ONLY\n * committing event is a terminal receipt with no text is retried, so nothing\n * that reached the user can ever be produced twice.\n */\n emptyTurnRetries?: number\n /** Fired when an empty turn is discarded and the same model re-run. */\n onEmptyTurnRetry?: (info: EmptyTurnRetryInfo) => void\n log?: (message: string, meta?: Record<string, unknown>) => void\n}\n\n/** One same-model re-run of a turn that completed with no assistant text. */\nexport interface EmptyTurnRetryInfo {\n model: string\n /** 1 for the first re-run. */\n retry: number\n /** How many re-runs remain after this one. */\n remaining: number\n}\n\n/** The failover-wrapped stream plus the attribution every consumer needs. */\nexport interface ModelFailoverStreamHandle {\n events: AsyncGenerator<unknown, void, unknown>\n /** The model that actually served. `undefined` until the first pull resolves it. */\n servingModel(): string | undefined\n /** Every model tried, in order, with the reason each was abandoned. */\n attempts(): ModelFailoverAttempt[]\n /** True when the preferred model did not serve — the attributability signal. */\n usedFallback(): boolean\n}\n\n/** Default ceiling for opening/starting one source through its first event. */\nexport const DEFAULT_MODEL_STREAM_OPEN_TIMEOUT_MS = 120_000\n\n/** Default ceiling for the first answer-bearing event after the source's first event. */\nexport const DEFAULT_MODEL_FIRST_RESPONSE_TIMEOUT_MS = 60_000\n\n/** Structured timeout codes surfaced by the producer on final exhaustion. */\nexport type ModelAttemptTimeoutCode =\n | 'model_stream_open_timeout'\n | 'provider_first_response_timeout'\n\n/** Every configured model was exhausted and the final one timed out. */\nexport class ModelFailoverTimeoutError extends ModelFailoverExhaustedError {\n readonly code: ModelAttemptTimeoutCode\n readonly model: string\n readonly timeoutMs: number\n\n constructor(input: {\n attempts: ModelFailoverAttempt[]\n code: ModelAttemptTimeoutCode\n model: string\n timeoutMs: number\n }) {\n super(input.attempts)\n this.name = 'ModelFailoverTimeoutError'\n this.code = input.code\n this.model = input.model\n this.timeoutMs = input.timeoutMs\n this.message = input.code === 'model_stream_open_timeout'\n ? `Opening the model event source for ${input.model} exceeded ${input.timeoutMs}ms.`\n : `Model ${input.model} produced no first answer-bearing event within ${input.timeoutMs}ms.`\n }\n}\n\nfunction resolveTimeoutMs(value: number | undefined, fallback: number, label: string): number {\n if (value === undefined) return fallback\n if (!Number.isFinite(value) || value <= 0) {\n throw new RangeError(`${label} must be a finite number greater than 0`)\n }\n return Math.max(1, Math.trunc(value))\n}\n\nconst TIMED_OUT = Symbol('model-attempt-timed-out')\n\nfunction deadlineAfter(timeoutMs: number): {\n promise: Promise<typeof TIMED_OUT>\n clear: () => void\n} {\n let timer: ReturnType<typeof setTimeout> | undefined\n const promise = new Promise<typeof TIMED_OUT>((resolve) => {\n timer = setTimeout(() => resolve(TIMED_OUT), timeoutMs)\n })\n return {\n promise,\n clear: () => {\n if (timer !== undefined) {\n clearTimeout(timer)\n timer = undefined\n }\n },\n }\n}\n\n/**\n * True when `event` is a terminal receipt (`result` / `done`) carrying no\n * assistant text.\n *\n * Measured on production `sandbox.tangle.tools` (2026-07-27, 28 turns through\n * the gtm-agent profile): a turn can end `{ outcome: { type: 'completed' } }`\n * with `finalText: ''` and zero token events — the platform reports success and\n * the customer's message is blank. It is not an outage, nothing throws, and\n * `isUpstreamUnavailable` correctly declines to classify it, so before this the\n * blank answer committed and shipped.\n */\nfunction isEmptyTerminalReceipt(event: unknown): boolean {\n const record = asRecord(event)\n if (!record) return false\n const type = asString(record.type) ?? ''\n if (type !== 'result' && type !== 'done') return false\n const data = asRecord(record.data)\n const text = asString(data?.finalText) ?? asString(data?.text) ?? ''\n return text.trim().length === 0\n}\n\n/**\n * Hard ceiling on same-model re-runs. A turn that comes back blank three times\n * running is not a flake this can retry away, and each pass costs a full\n * sandbox turn — so the budget is capped rather than trusted.\n */\nexport const MAX_EMPTY_TURN_RETRIES = 3\n\n/**\n * Coerce the caller's budget to a finite, bounded, non-negative integer.\n *\n * `Math.trunc(NaN)` is `NaN` and `retry >= NaN` is false for every `retry`, so\n * a naive clamp turns a bad config value into a loop that opens sandbox streams\n * until the worker dies. `Infinity` has the same shape. Both resolve to `0` —\n * an unusable budget disables the retry rather than running unbounded.\n */\nexport function resolveEmptyTurnRetries(value: number | undefined): number {\n if (typeof value !== 'number' || !Number.isFinite(value)) return 0\n const truncated = Math.trunc(value)\n if (truncated <= 0) return 0\n return Math.min(truncated, MAX_EMPTY_TURN_RETRIES)\n}\n\n/** Abandon a dead attempt's iterator. Never allowed to mask the outage. */\nasync function closeIterator(iterator: AsyncIterator<unknown>, log?: ModelFailoverStreamOptions['log']): Promise<void> {\n try {\n await iterator.return?.()\n } catch (err) {\n log?.('[chat-routes] abandoning a failed model stream threw', {\n error: err instanceof Error ? err.message : String(err),\n })\n }\n}\n\n/**\n * Wrap `open` in reactive model failover, streaming from the first model in\n * `models` that reaches its commit point.\n *\n * Zero added latency on the happy path: the preferred model is opened first and,\n * the moment it emits anything meaningful, its events flow straight through.\n *\n * @throws ModelFailoverTimeoutError when the final model times out,\n * ModelFailoverExhaustedError when every model's upstream is down, and\n * re-throws a non-outage error from the FIRST model without walking the\n * chain (the latter behaviors are inherited from\n * `runWithModelFailover`).\n */\nexport function streamWithModelFailover(\n options: ModelFailoverStreamOptions,\n): ModelFailoverStreamHandle {\n const committing = options.isCommitting ?? isCommittingSandboxEvent\n const emptyTurnRetries = resolveEmptyTurnRetries(options.emptyTurnRetries)\n const openTimeoutMs = resolveTimeoutMs(\n options.openTimeoutMs,\n DEFAULT_MODEL_STREAM_OPEN_TIMEOUT_MS,\n 'openTimeoutMs',\n )\n const firstResponseTimeoutMs = resolveTimeoutMs(\n options.firstResponseTimeoutMs,\n DEFAULT_MODEL_FIRST_RESPONSE_TIMEOUT_MS,\n 'firstResponseTimeoutMs',\n )\n const liveLifecycle = options.liveLifecycleEvents === true\n const progress = createProgressQueue()\n let serving: string | undefined\n let trail: ModelFailoverAttempt[] = []\n let fellBack = false\n let attemptIndex = 0\n let finalTimeout: {\n code: ModelAttemptTimeoutCode\n model: string\n timeoutMs: number\n } | undefined\n\n /**\n * One open-and-drain pass. `empty` marks the pass as an EMPTY TURN — the\n * only committing event was a terminal receipt with no assistant text, so\n * nothing reached the user and the pass can be discarded without any risk of\n * producing an answer twice. It still carries the outcome it would otherwise\n * have returned, so exhausting the retry budget is byte-identical to no\n * retry at all.\n */\n const drainOnce = async (model: string): Promise<{ outcome: AttemptOutcome; empty: boolean }> => {\n attemptIndex += 1\n const controller = new AbortController()\n const abort = (): void => {\n if (!controller.signal.aborted) controller.abort()\n }\n const opening = Promise.resolve().then(() => options.open({\n model,\n attempt: attemptIndex,\n signal: controller.signal,\n }))\n const openDeadline = deadlineAfter(openTimeoutMs)\n let opened: AsyncIterable<unknown> | typeof TIMED_OUT\n try {\n opened = await Promise.race([opening, openDeadline.promise])\n } catch (err) {\n openDeadline.clear()\n abort()\n throw err\n }\n if (opened === TIMED_OUT) {\n openDeadline.clear()\n abort()\n // A Promise cannot be forcibly cancelled. If the abandoned open settles\n // later, close its iterator immediately so it cannot leak a connection.\n void opening.then(\n (lateSource) => {\n try {\n void closeIterator(lateSource[Symbol.asyncIterator](), options.log)\n } catch (err) {\n options.log?.('[chat-routes] closing a late model stream threw', {\n error: err instanceof Error ? err.message : String(err),\n })\n }\n },\n (err) => {\n options.log?.('[chat-routes] abandoned model stream open rejected after timeout', {\n error: err instanceof Error ? err.message : String(err),\n })\n },\n )\n return {\n outcome: {\n committed: false,\n error: `Opening the model event source for ${model} exceeded ${openTimeoutMs}ms`,\n errorCode: 'model_stream_open_timeout',\n timeoutCode: 'model_stream_open_timeout',\n },\n empty: false,\n }\n }\n\n const source = opened\n let iterator: AsyncIterator<unknown>\n try {\n iterator = source[Symbol.asyncIterator]()\n } catch (err) {\n openDeadline.clear()\n abort()\n throw err\n }\n const buffered: unknown[] = []\n // Starting the source includes its first pull. `streamSandboxPrompt` is an\n // async generator, so its cold-start work does not execute until `next()`;\n // counting that time as provider inference would collapse the two phases.\n let next: IteratorResult<unknown> | typeof TIMED_OUT\n try {\n next = await Promise.race([\n Promise.resolve(iterator.next()),\n openDeadline.promise,\n ])\n } catch (err) {\n openDeadline.clear()\n abort()\n void closeIterator(iterator, options.log)\n throw err\n }\n openDeadline.clear()\n if (next === TIMED_OUT) {\n abort()\n void closeIterator(iterator, options.log)\n return {\n outcome: {\n committed: false,\n error: `Starting the model event source for ${model} exceeded ${openTimeoutMs}ms`,\n errorCode: 'model_stream_open_timeout',\n timeoutCode: 'model_stream_open_timeout',\n },\n empty: false,\n }\n }\n if (next.done) {\n abort()\n return { outcome: { committed: true, buffered, iterator: null, abort }, empty: true }\n }\n\n // One deadline for the WHOLE provider pre-response phase. Pulling a\n // lifecycle or processing event races the same promise; liveness cannot\n // reset the clock.\n const firstResponseDeadline = deadlineAfter(firstResponseTimeoutMs)\n\n for (;;) {\n const event = next.value\n let failure: TerminalFailure | null\n let commits = false\n try {\n failure = classifyTerminalFailure(event)\n commits = !failure && committing(event)\n } catch (err) {\n firstResponseDeadline.clear()\n abort()\n void closeIterator(iterator, options.log)\n throw err\n }\n if (failure?.outage) {\n firstResponseDeadline.clear()\n abort()\n void closeIterator(iterator, options.log)\n return {\n outcome: {\n committed: false,\n error: failure.reason,\n ...(failure.code ? { errorCode: failure.code } : {}),\n },\n empty: false,\n }\n }\n\n if (liveLifecycle && !failure && !commits && isLiveLifecycleEvent(event)) {\n progress.push(event)\n } else {\n buffered.push(event)\n }\n // A terminal NON-outage failure surfaces exactly as it does today.\n if (failure) {\n firstResponseDeadline.clear()\n return { outcome: { committed: true, buffered, iterator, abort }, empty: false }\n }\n if (commits) {\n firstResponseDeadline.clear()\n return { outcome: { committed: true, buffered, iterator, abort }, empty: isEmptyTerminalReceipt(event) }\n }\n\n // A THROWN failure propagates untouched: `runWithModelFailover` classifies\n // it (outage → next model, anything else → re-thrown to the caller).\n try {\n next = await Promise.race([\n Promise.resolve(iterator.next()),\n firstResponseDeadline.promise,\n ])\n } catch (err) {\n firstResponseDeadline.clear()\n abort()\n void closeIterator(iterator, options.log)\n throw err\n }\n if (next === TIMED_OUT) {\n firstResponseDeadline.clear()\n // Do not await `return()`: an async generator blocked inside `next()`\n // may not settle its return until that pull completes. Invoking it is\n // the cancellation signal; waiting for it would recreate the hang.\n abort()\n void closeIterator(iterator, options.log)\n return {\n outcome: {\n committed: false,\n error: `Model ${model} produced no first answer-bearing event within ${firstResponseTimeoutMs}ms`,\n errorCode: 'provider_first_response_timeout',\n timeoutCode: 'provider_first_response_timeout',\n },\n empty: false,\n }\n }\n if (next.done) {\n firstResponseDeadline.clear()\n abort()\n // Clean end with nothing committing. Not an outage — but also not an\n // answer, so it is re-runnable on the same model.\n return { outcome: { committed: true, buffered, iterator: null, abort }, empty: true }\n }\n }\n }\n\n const probe = async (model: string): Promise<AttemptOutcome> => {\n // Only the FINAL attempted model decides the exhaustion code. Clear a\n // prior model's timeout before this model can throw a different outage.\n finalTimeout = undefined\n for (let retry = 0; ; retry += 1) {\n const pass = await drainOnce(model)\n if (!pass.empty || retry >= emptyTurnRetries) {\n if (!pass.outcome.committed && pass.outcome.timeoutCode) {\n finalTimeout = {\n code: pass.outcome.timeoutCode,\n model,\n timeoutMs: pass.outcome.timeoutCode === 'model_stream_open_timeout'\n ? openTimeoutMs\n : firstResponseTimeoutMs,\n }\n } else {\n finalTimeout = undefined\n }\n return pass.outcome\n }\n // Discard the empty pass entirely — including its `step-finish` token\n // receipt, which must not be billed — and re-run the same model.\n const iterator = pass.outcome.committed ? pass.outcome.iterator : null\n if (pass.outcome.committed) pass.outcome.abort()\n if (iterator) void closeIterator(iterator, options.log)\n const info: EmptyTurnRetryInfo = { model, retry: retry + 1, remaining: emptyTurnRetries - retry - 1 }\n options.log?.('[chat-routes] turn completed with no assistant text; re-running the same model', {\n ...info,\n })\n options.onEmptyTurnRetry?.(info)\n }\n }\n\n const events = (async function* (): AsyncGenerator<unknown, void, unknown> {\n let handle: AttemptCommit\n // Progress arrives while the probe is still running. The queue closes only\n // after the verdict settles, so every event pushed before it is delivered\n // in order and ahead of the replayed buffer.\n const settled = runWithModelFailover<AttemptOutcome>({\n models: options.models,\n run: probe,\n // The probe has already classified the raw payload with\n // `isUpstreamUnavailable`; this reads its verdict rather than\n // re-classifying a wrapper object, so the two can never disagree.\n isUnavailableResult: (result) => result.committed === false,\n onFallback: (attempt, nextModel) => {\n const info: ModelFallbackInfo = {\n from: attempt.model,\n to: nextModel,\n reason: attempt.reason ?? 'upstream unavailable',\n }\n options.log?.('[chat-routes] model upstream unavailable; falling over', { ...info })\n options.onFallback?.(info)\n },\n }).finally(() => progress.close())\n // The verdict is read after the drain; mark the rejection handled until then.\n settled.catch(() => undefined)\n for (;;) {\n const item = await progress.pull()\n if (item === PROGRESS_CLOSED) break\n yield item\n }\n try {\n const outcome = await settled\n serving = outcome.model\n trail = outcome.attempts\n fellBack = outcome.usedFallback\n handle = outcome.value as AttemptCommit\n } catch (err) {\n // Exhaustion carries the full trail; keep it so the receipt still names\n // every model tried even though the turn produced nothing.\n const attempts = (err as { attempts?: ModelFailoverAttempt[] })?.attempts\n if (Array.isArray(attempts)) trail = attempts\n if (err instanceof ModelFailoverExhaustedError && finalTimeout) {\n throw new ModelFailoverTimeoutError({\n attempts: trail,\n ...finalTimeout,\n })\n }\n throw err\n }\n\n for (const event of handle.buffered) yield event\n\n const live = handle.iterator\n if (!live) {\n handle.abort()\n return\n }\n try {\n for (;;) {\n const next = await live.next()\n if (next.done) return\n yield next.value\n }\n } finally {\n // The consumer may abandon this generator mid-turn (client drop); close\n // the underlying stream rather than leaking it.\n handle.abort()\n await closeIterator(live, options.log)\n }\n })()\n\n return {\n events,\n servingModel: () => serving,\n attempts: () => trail,\n usedFallback: () => fellBack,\n }\n}\n\nconst PROGRESS_CLOSED: unique symbol = Symbol('progress-closed')\n\n/**\n * FIFO handoff from the probe (a plain promise chain) to the event generator.\n * `close` is idempotent and queues one sentinel behind everything pushed\n * before it, so a consumer never loses a progress event to the verdict.\n */\nfunction createProgressQueue(): {\n push(event: unknown): void\n close(): void\n pull(): Promise<unknown>\n} {\n const items: unknown[] = []\n const waiters: Array<(value: unknown) => void> = []\n let closed = false\n const deliver = (value: unknown): void => {\n const waiter = waiters.shift()\n if (waiter) waiter(value)\n else items.push(value)\n }\n return {\n push(event) {\n if (closed) return\n deliver(event)\n },\n close() {\n if (closed) return\n closed = true\n deliver(PROGRESS_CLOSED)\n },\n pull() {\n if (items.length > 0) return Promise.resolve(items.shift())\n if (closed) return Promise.resolve(PROGRESS_CLOSED)\n return new Promise((resolve) => waiters.push(resolve))\n },\n }\n}\n","import { asRecord, type JsonRecord } from '../stream/index'\nimport type { ChatTurnUsage } from './turn-routes'\n\n/** Add one canonical Sandbox `step-finish` receipt to a turn total. */\nexport function addStepFinishUsage(part: JsonRecord, usage: ChatTurnUsage): void {\n const tokens = asRecord(part.tokens)\n if (tokens) {\n const cache = asRecord(tokens.cache)\n const add = (current: number | undefined, value: unknown): number | undefined => {\n const n = Number(value)\n if (!Number.isFinite(n)) return current\n return (current ?? 0) + n\n }\n usage.inputTokens = add(usage.inputTokens, tokens.input)\n usage.outputTokens = add(usage.outputTokens, tokens.output)\n usage.reasoningTokens = add(usage.reasoningTokens, tokens.reasoning)\n if (cache) {\n usage.cacheReadTokens = add(usage.cacheReadTokens, cache.read)\n usage.cacheWriteTokens = add(usage.cacheWriteTokens, cache.write)\n }\n }\n const cost = Number(part.cost)\n if (Number.isFinite(cost)) usage.costUsd = (usage.costUsd ?? 0) + cost\n}\n","/**\n * Sandbox lane: bridge a raw sandbox event stream (`streamSandboxPrompt`) into\n * the `ChatTurnProducer` shape agent-runtime's `handleChatTurn` consumes AND\n * the client vocabulary `/web-react`'s `dispatchChatStreamLine` already parses\n * (`text` / `reasoning` / `tool_call` / `tool_result` / `usage` /\n * `notice` / structured `error` / `interaction`). Legal and tax each hand-rolled\n * this mapping differently;\n * this is that middle, composed from `/stream`'s normalizers — no new loop\n * logic, no SDK import (the event source is an injected `AsyncIterable`).\n *\n * Alongside the live mapping it accumulates the PERSISTED projection — the\n * `message.parts` rows `/chat-store` stores — via `normalizePersistedPart` /\n * `mergePersistedPart` / `finalizeAssistantParts`, plus the usage receipt from\n * `step-finish` parts. `createChatTurnRoutes` reads both after drain.\n */\n\nimport {\n cancelStatusFor,\n interactionPartKey,\n interactionToPersistedPart,\n isRenderableInteractionKind,\n noticePart,\n noticePartKey,\n parseInteractionCancel,\n parseInteractionRequest,\n} from '../interactions/contract'\nimport {\n parsePlanSubmittedEvent,\n planToPersistedPart,\n} from '../plans/index'\nimport {\n asRecord,\n asString,\n draftAssistantParts,\n finalizeAssistantParts,\n finalizePendingInteractionParts,\n getPartKey,\n mergePersistedPart,\n normalizePersistedPart,\n normalizeToolEvent,\n terminalizeDanglingAssistantToolUpdates,\n type JsonRecord,\n type StreamEvent,\n} from '../stream/index'\nimport { buildModelChain, type ModelFailoverAttempt } from '../model-resolution/failover'\nimport {\n ModelFailoverTimeoutError,\n resolveEmptyTurnRetries,\n streamWithModelFailover,\n summarizeFailoverReason,\n type EmptyTurnRetryInfo,\n type ModelFallbackInfo,\n type ModelFailoverStreamHandle,\n type OpenModelStream,\n} from './model-failover-stream'\nimport type {\n ChatTurnModelAttribution,\n ChatTurnRouteProducer,\n ChatTurnUsage,\n} from './turn-routes'\nimport { addStepFinishUsage } from './sandbox-turn-usage'\nimport type { ProducerWireEvent } from './wire'\n\n/** Outcome of a `promoteFilePart` attempt. `key`, when given, becomes the\n * persisted part's row key (e.g. `attachment:<path>`) so repeat promotions\n * of the same underlying file fold into one segment instead of appending;\n * omitted, the default `getPartKey` keying applies.\n *\n * On failure, `part` is an OPTIONAL substitute part to persist in place of\n * the raw url-bearing one — this is how a product swaps in a transcript\n * notice (gtm persists a `warning` notice part, never the transient url) for\n * a failed promotion instead of baking a `data:`/sandbox-path url into the\n * durable row. When `part` is present it is persisted (via the same\n * `recordPersistedPart` path as a success, honoring the optional `key`);\n * when absent, the existing raw-part fallback applies unchanged — so a\n * caller that only returns `{ succeeded: false, reason }` keeps today's\n * behavior verbatim. */\nexport type FilePartPromotionOutcome =\n | { succeeded: true; part: Record<string, unknown>; key?: string }\n | { succeeded: false; reason: string; part?: Record<string, unknown>; key?: string }\n\n/** Define options for producing sandbox chat events with rendering and interaction controls */\nexport interface SandboxChatProducerOptions {\n /** The raw sandbox event stream (e.g. `streamSandboxPrompt(...)`).\n *\n * An ALREADY-OPEN stream is bound to one model and cannot be reopened, so\n * this form can never fail over. Prefer {@link openEvents}; exactly one of\n * the two is required. */\n events?: AsyncIterable<unknown>\n /** Open the raw sandbox stream FOR A GIVEN MODEL — the failover-capable form\n * of {@link events}. The callback receives the model to run and returns the\n * same `AsyncIterable` `events` would have been (typically\n * `streamSandboxPrompt(shell, box, prompt, { ...opts, model })`).\n *\n * Wiring this turns failover ON with no further flag: whenever the resolved\n * chain (`model` + {@link fallbackModels}) holds more than one entry, a model\n * whose upstream is dead is abandoned BEFORE its first client-visible byte\n * and the next one is tried. A one-entry chain opens exactly once — no extra\n * call, no added latency, byte-identical to today.\n *\n * Requires {@link model}: failover has to know which model it is running. */\n openEvents?: OpenModelStream\n /** Recorded on the persisted assistant message. When failover moves the turn\n * to another model, the producer's `model` reports the model that ACTUALLY\n * served, not this preferred one — see {@link modelFailover}. */\n model?: string\n /** Models to try, in order, when `model`'s upstream is dead. Product config,\n * never a shell default: agent-app cannot know which ids are live, and a\n * baked list would rot into exactly the stale-liveness bug this guards\n * against. Empty/omitted → one attempt, today's behavior.\n *\n * Pick these deliberately. A same-family fallback is NOT automatically safe:\n * `gemini-2.5-flash` produced zero persisted deliverables in 2 of 3 measured\n * runs on a med-legal filing flow where `gemini-2.5-pro` succeeded. That is\n * why every fallback is surfaced (persisted `model`, a transcript notice, and\n * `modelFailover()`) instead of being applied silently. */\n fallbackModels?: readonly string[]\n /** Opt out of failover while still using {@link openEvents}. `false` collapses\n * the chain to `model` alone. */\n modelFailover?: false\n /** Maximum time to open/start one model event source through its first event.\n * Default 120 seconds. This is separate from provider first-output latency\n * so cold sandbox startup is not mislabeled as an inference failure. */\n openTimeoutMs?: number\n /** Hard deadline after the source's first lifecycle event for the first\n * answer-bearing event. Default 60 seconds. Processing/lifecycle events do\n * not extend it. */\n firstResponseTimeoutMs?: number\n /** Forward `start`/`status`/`model-processing`/session lifecycle to the\n * client as it arrives instead of after the commit point, so a product can\n * show what a turn is waiting on. Default `false`. An abandoned model's\n * progress then stays on the wire; its answer and receipt never do. */\n liveLifecycleEvents?: boolean\n /** Fired when a model is abandoned mid-chain (telemetry/alerting). The user\n * already sees a transcript notice; this is for the operator. */\n onModelFallback?: (info: ModelFallbackInfo) => void\n /** Re-run the SAME model this many times when a turn completes with no\n * assistant text at all. Default `0` (unchanged behavior). Requires\n * {@link openEvents} — there is nothing to re-open on a fixed stream.\n *\n * Distinct from {@link fallbackModels} on purpose: this never changes which\n * model answers, so it carries none of the attribution risk a downgrade\n * does. Measured on production 2026-07-27 through gtm-agent's profile, a\n * completed-but-blank turn is a transient platform flake — 8 hard cases went\n * 7/8 delivered to 8/8 with one re-run, costing 1 extra turn in 9. */\n emptyTurnRetries?: number\n /** Fired when a blank turn is discarded and the same model re-run. */\n onEmptyTurnRetry?: (info: EmptyTurnRetryInfo) => void\n /** Which ask kinds the product renders a card for. Anything else is\n * auto-declined (see `declineInteraction`) so the run never hangs in the\n * broker waiting on a card no client will show. Default: question/plan.\n * Products with per-turn plan mode can close over it without another option:\n * `(kind) => kind === 'question' || (kind === 'plan' && planEnabled)`. */\n isRenderableInteraction?: (kind: string) => boolean\n /** Resolve a non-renderable ask (wire `respondToSessionInteraction` with the\n * session's sidecar connection). Without it, a failure notice is emitted and\n * the run stays blocked until the broker times out. */\n declineInteraction?: (id: string) => Promise<void>\n /** Opt-in eager promotion of harness-emitted `file` parts. Unset, a `file`\n * part persists exactly as the harness sent it — a transient `url` (a\n * `data:` URI or in-sandbox path) baked into the transcript, which is\n * today's behavior and stays byte-identical if this is never wired. Set,\n * EVERY `file` part (never `image`, never any other kind) is routed\n * through this callback instead of `recordPersistedPart`'s default\n * fallback — including a part with NEITHER `id` NOR `url` (gtm always\n * attempts promotion; such a part simply fails \"carries no url\" and\n * resolves through the same failure path as any other rejection, rather\n * than being persisted raw and unpromoted) — so the product can durably\n * write the bytes and swap in a path-bearing part before the raw url ever\n * reaches the persisted transcript. Keyed per source-prefixed `id:<id>` /\n * `url:<url>` (an `id` and a `url` sharing the same text must never collide\n * onto one memo entry) and memoized by PROMISE (not result), so re-emitted\n * snapshot events for the same part —\n * the harness resends the whole part on every update, not just deltas —\n * fold onto the one in-flight or settled attempt rather than promoting\n * twice or racing two concurrent writes; a raw part with neither `id` nor\n * `url` cannot be keyed, so it is invoked UN-memoized (once per event) —\n * each occurrence is its own attempt. A rejecting promise is caught,\n * logged via `log`, and treated as `succeeded: false`. On `succeeded:\n * false` the outcome's optional `part` (a substitute — e.g. a warning\n * notice — see {@link FilePartPromotionOutcome}) persists in its place when\n * given; otherwise the raw part persists exactly as it does today — this\n * seam only decides whether to call the promoter and what to do with its\n * outcomes; the promotion mechanics (vault write, key derivation, notice\n * construction) live in the caller's callback, not here. */\n promoteFilePart?: (raw: JsonRecord) => Promise<FilePartPromotionOutcome>\n log?: (message: string, meta?: Record<string, unknown>) => void\n}\n\ninterface TextTracker {\n /** Full accumulated text per part key, to derive suffix deltas from\n * snapshot-only harness events. */\n seen: Map<string, string>\n}\n\n/** Delta to emit for one text/reasoning part update: prefer the harness's\n * explicit delta; otherwise diff the snapshot against what was already\n * emitted for that part (snapshot-only harnesses re-send the whole text). */\nfunction textDelta(tracker: TextTracker, key: string, part: JsonRecord, rawDelta: unknown): string {\n const explicit = typeof rawDelta === 'string' ? rawDelta : undefined\n const previous = tracker.seen.get(key) ?? ''\n if (explicit !== undefined) {\n tracker.seen.set(key, previous + explicit)\n return explicit\n }\n const snapshot = asString(part.text) ?? asString(part.content) ?? ''\n if (!snapshot) return ''\n if (snapshot.startsWith(previous)) {\n tracker.seen.set(key, snapshot)\n return snapshot.slice(previous.length)\n }\n // The snapshot replaced the text outright — emit it whole; the persisted\n // projection stays correct because finalText is authoritative at finalize.\n tracker.seen.set(key, snapshot)\n return snapshot\n}\n\nfunction parseEffectiveBackend(data: JsonRecord): Omit<\n ChatTurnModelAttribution,\n 'requestedModel' | 'echoReceived'\n> | undefined {\n const backend = asRecord(data.effectiveBackend)\n if (!backend) return undefined\n const provider = asString(backend.provider)\n const model = asString(backend.model)\n const rawSource = asString(backend.source)\n const source =\n rawSource === 'request' || rawSource === 'environment' || rawSource === 'profile'\n ? rawSource\n : undefined\n if (!provider && !model && !source) return undefined\n return {\n ...(model ? { servedModel: model } : {}),\n ...(provider ? { servedProvider: provider } : {}),\n ...(source ? { servedSource: source } : {}),\n }\n}\n\nfunction toFiniteNumber(value: unknown): number | null {\n if (typeof value === 'number') return Number.isFinite(value) ? value : null\n if (typeof value === 'string' && value.trim() !== '') {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : null\n }\n return null\n}\n\n/** Parse authoritative usage from a terminal sandbox `result` or `done`. */\nfunction extractReportedTurnUsage(data: JsonRecord | null | undefined): ChatTurnUsage | null {\n const tokenUsage = asRecord(data?.tokenUsage)\n if (!tokenUsage) return null\n\n const inputTokens = toFiniteNumber(tokenUsage.inputTokens)\n const outputTokens = toFiniteNumber(tokenUsage.outputTokens)\n if (inputTokens === null || outputTokens === null) return null\n\n const reported: ChatTurnUsage = { inputTokens, outputTokens }\n const reasoningTokens = toFiniteNumber(tokenUsage.reasoningTokens)\n if (reasoningTokens !== null) reported.reasoningTokens = reasoningTokens\n const cacheReadTokens = toFiniteNumber(tokenUsage.cacheReadInputTokens)\n if (cacheReadTokens !== null) reported.cacheReadTokens = cacheReadTokens\n const cacheWriteTokens = toFiniteNumber(tokenUsage.cacheCreationInputTokens)\n if (cacheWriteTokens !== null) reported.cacheWriteTokens = cacheWriteTokens\n const costUsd = toFiniteNumber(data?.totalCostUsd) ?? toFiniteNumber(tokenUsage.cost)\n if (costUsd !== null) reported.costUsd = costUsd\n return reported\n}\n\n/** Native runtime usage events contain cumulative totals, including repeated terminal snapshots. */\nfunction extractUsageSnapshot(value: JsonRecord | null | undefined): ChatTurnUsage | null {\n const inputTokens = toFiniteNumber(value?.promptTokens)\n const outputTokens = toFiniteNumber(value?.completionTokens)\n if (inputTokens === null || outputTokens === null || inputTokens < 0 || outputTokens < 0) return null\n const reported: ChatTurnUsage = { inputTokens, outputTokens }\n const reasoningTokens = toFiniteNumber(value?.reasoningTokens)\n if (reasoningTokens !== null && reasoningTokens >= 0) reported.reasoningTokens = reasoningTokens\n const costUsd = toFiniteNumber(value?.providerCostUsd)\n if (costUsd !== null && costUsd >= 0) reported.costUsd = costUsd\n return reported\n}\n\nfunction applyTerminalUsage(reported: ChatTurnUsage, usage: ChatTurnUsage): void {\n usage.inputTokens = reported.inputTokens\n usage.outputTokens = reported.outputTokens\n if (reported.reasoningTokens !== undefined) usage.reasoningTokens = reported.reasoningTokens\n if (reported.cacheReadTokens !== undefined) usage.cacheReadTokens = reported.cacheReadTokens\n if (reported.cacheWriteTokens !== undefined) usage.cacheWriteTokens = reported.cacheWriteTokens\n if (reported.costUsd !== undefined) usage.costUsd = reported.costUsd\n}\n\nfunction sandboxStreamErrorMessage(data: unknown): string {\n const record = asRecord(data)\n const message = asString(record?.message) ?? asString(record?.error)\n if (message) return message\n try {\n return JSON.stringify(data) || 'Sandbox stream returned an error event without details.'\n } catch {\n return 'Sandbox stream returned an error event without details.'\n }\n}\n\nfunction toProducerWireEvent(event: JsonRecord): ProducerWireEvent {\n return event as unknown as ProducerWireEvent\n}\n\nfunction sandboxStreamFailureDiagnostic(error: unknown): {\n userMessage: string\n failureNote: string\n code: string\n} {\n const streamMessage = (error as { streamMessage?: unknown })?.streamMessage\n const message = typeof streamMessage === 'string'\n ? streamMessage\n : error instanceof Error ? error.message : 'Sandbox stream failed'\n const diagnostics = asRecord((error as { diagnostics?: unknown })?.diagnostics)\n let diagnosticText: string | undefined\n if (diagnostics) {\n try {\n diagnosticText = JSON.stringify(diagnostics)\n } catch {\n diagnosticText = '[unserializable diagnostics]'\n }\n }\n const userMessage = [\n 'The sandbox model stream stopped before a clean completion.',\n `Error: ${message}`,\n 'Please retry. If it repeats, send these support details to the team.',\n ].join('\\n\\n')\n const failureNote = diagnosticText\n ? `sandbox-stream: ${message}; ${diagnosticText}`\n : `sandbox-stream: ${message}`\n const code = error instanceof ModelFailoverTimeoutError\n ? error.code\n : 'sandbox.stream_failed'\n return { userMessage, failureNote, code }\n}\n\n/** Create a sandbox chat producer that manages chat turn routing with logging and interaction rendering options */\nexport function createSandboxChatProducer(options: SandboxChatProducerOptions): ChatTurnRouteProducer {\n const log = options.log ?? ((message, meta) => console.error(message, meta ?? ''))\n const renderable = options.isRenderableInteraction ?? isRenderableInteractionKind\n\n // ── model failover ────────────────────────────────────────────────────────\n // ON by default whenever the caller can reopen the stream (`openEvents`) and\n // named more than one model. There is no `enabled` flag on purpose: the class\n // of failure this fixes was a capability that shipped switched off, and a\n // second opt-in on top of supplying the chain would reproduce it exactly.\n if (options.openEvents && !options.model) {\n throw new Error(\n 'createSandboxChatProducer: `openEvents` requires `model` — failover must know which model it is running',\n )\n }\n if (!options.openEvents && !options.events) {\n throw new Error('createSandboxChatProducer: pass `openEvents` (failover-capable) or `events`')\n }\n // A fixed `events` stream cannot be re-opened, so the retry silently does\n // nothing. A config that asks for recovery and receives none is the exact\n // silent-no-op class this seam exists to remove — refuse it at construction\n // rather than let a product believe blank turns are being retried.\n if (!options.openEvents && resolveEmptyTurnRetries(options.emptyTurnRetries) > 0) {\n throw new Error(\n 'createSandboxChatProducer: `emptyTurnRetries` requires `openEvents` — a fixed `events` stream cannot be re-opened',\n )\n }\n const chain = options.model\n ? buildModelChain(options.model, options.modelFailover === false ? [] : (options.fallbackModels ?? []))\n : []\n\n /** Fallback notices waiting to be emitted. `onFallback` fires from inside the\n * first pull of the failover stream, where a generator cannot yield, so the\n * notice is queued and drained at the top of the next loop iteration —\n * landing immediately before the serving model's first event. */\n const pendingModelNotices: Array<{ id: string; text: string }> = []\n let modelNoticeCount = 0\n\n let failover: ModelFailoverStreamHandle | undefined\n let source: AsyncIterable<unknown>\n if (options.openEvents) {\n failover = streamWithModelFailover({\n models: chain,\n open: options.openEvents,\n log,\n ...(options.openTimeoutMs !== undefined ? { openTimeoutMs: options.openTimeoutMs } : {}),\n ...(options.firstResponseTimeoutMs !== undefined\n ? { firstResponseTimeoutMs: options.firstResponseTimeoutMs }\n : {}),\n ...(options.emptyTurnRetries !== undefined ? { emptyTurnRetries: options.emptyTurnRetries } : {}),\n ...(options.onEmptyTurnRetry ? { onEmptyTurnRetry: options.onEmptyTurnRetry } : {}),\n ...(options.liveLifecycleEvents !== undefined ? { liveLifecycleEvents: options.liveLifecycleEvents } : {}),\n onFallback: (info) => {\n modelNoticeCount += 1\n pendingModelNotices.push({\n id: `model-fallback-${modelNoticeCount}`,\n // Named models on both sides: a quality regression after a downgrade\n // must be attributable to the model that actually answered. The\n // REASON is condensed — a real edge 5xx arrives as Cloudflare's HTML\n // error page, and the raw text put `<!DOCTYPE html><!--[if lt IE 7]>…`\n // into the customer's transcript. The verbatim text is still on\n // `modelFailover.attempts[].reason` for the operator.\n text: `${info.from} was unavailable (${summarizeFailoverReason(info.reason)}) — answered with ${info.to} instead.`,\n })\n options.onModelFallback?.(info)\n },\n })\n source = failover.events\n } else {\n source = options.events!\n }\n\n /** The model that ACTUALLY served this turn. Read lazily everywhere (the\n * `model` getter, the draft snapshot, the persisted row) so a fallback that\n * happens after the row was drafted still lands on the final write. */\n const servingModel = (): string | undefined => failover?.servingModel() ?? options.model\n let effectiveBackend: ReturnType<typeof parseEffectiveBackend>\n let substitutionNoticeQueued = false\n const servedModel = (): string | undefined => effectiveBackend?.servedModel ?? servingModel()\n\n const captureEffectiveBackend = (data: JsonRecord | undefined): void => {\n if (!data) return\n const parsed = parseEffectiveBackend(data)\n if (!parsed) return\n effectiveBackend = parsed\n const sentModel = servingModel()\n if (\n substitutionNoticeQueued ||\n !options.model ||\n !sentModel ||\n !parsed.servedModel ||\n parsed.servedModel === sentModel\n ) return\n substitutionNoticeQueued = true\n pendingModelNotices.push({\n id: 'model-substitution-1',\n text: `Requested ${sentModel} — the sandbox answered with ${parsed.servedModel} instead.`,\n })\n }\n\n let fullText = ''\n const partOrder: string[] = []\n const partMap = new Map<string, JsonRecord>()\n const tracker: TextTracker = { seen: new Map() }\n const usage: ChatTurnUsage = {}\n /** Whether each announced tool_call carried populated arguments. */\n const announcedToolArgs = new Map<string, boolean>()\n const settledTools = new Set<string>()\n /** Id-less step boundaries: one occurrence per key, never merged. */\n let stepCounter = 0\n let danglingToolsTerminalized = false\n let interactionOutcome: 'answered' | 'expired' = 'answered'\n let warningCount = 0\n /** `promoteFilePart` memo, keyed by `id ?? url` of the raw part. Holds the\n * PROMISE (never the settled result) so concurrent duplicate events for\n * the same part await the one in-flight attempt instead of each starting\n * their own; the promise never rejects (see below), so a later duplicate\n * reading a settled entry reuses that first outcome — success or\n * failure — rather than retrying. */\n const promotedFileParts = new Map<string, Promise<FilePartPromotionOutcome>>()\n\n function recordPersistedPart(part: JsonRecord, delta: string | undefined, keyOverride?: string): void {\n const persisted = normalizePersistedPart(part)\n if (!persisted) return\n const key = keyOverride ?? getPartKey(persisted)\n if (!partMap.has(key)) partOrder.push(key)\n partMap.set(key, mergePersistedPart(partMap.get(key), persisted, delta))\n }\n\n function* emitTerminalizedTools(): Generator<ProducerWireEvent, void, unknown> {\n if (danglingToolsTerminalized) return\n danglingToolsTerminalized = true\n const updates = terminalizeDanglingAssistantToolUpdates(partOrder, partMap, fullText)\n for (const part of updates) {\n const toolId = asString(part.id)\n if (!toolId || settledTools.has(toolId)) continue\n settledTools.add(toolId)\n const state = asRecord(part.state)\n yield {\n type: 'tool_result',\n toolCallId: toolId,\n toolName: asString(part.tool) ?? 'tool',\n outcome: {\n ok: false,\n ...(asString(state?.error) ? { message: asString(state?.error) } : {}),\n },\n }\n }\n }\n\n function* emitFinalUsage(): Generator<ProducerWireEvent, void, unknown> {\n const promptTokens = usage.inputTokens ?? 0\n const completionTokens = usage.outputTokens ?? 0\n if (promptTokens || completionTokens) {\n yield {\n type: 'usage',\n usage: {\n promptTokens,\n completionTokens,\n ...(usage.reasoningTokens !== undefined ? { reasoningTokens: usage.reasoningTokens } : {}),\n ...(usage.costUsd !== undefined ? { providerCostUsd: usage.costUsd } : {}),\n },\n }\n }\n }\n\n /** Emit any queued fallback notices, persisting each so the downgrade is in\n * the durable transcript and not only in the live stream. Uses the existing\n * `warning` notice kind deliberately: every shipped client already renders\n * it, whereas a bespoke kind would be dropped by `dispatchChatStreamLine`'s\n * filter — an attribution nobody sees is not attribution. */\n function* drainModelNotices(): Generator<ProducerWireEvent, void, unknown> {\n while (pendingModelNotices.length > 0) {\n const queued = pendingModelNotices.shift()!\n const notice = noticePart('warning', queued.id, queued.text)\n recordPersistedPart(notice, undefined, noticePartKey(notice.id))\n yield { type: 'notice', id: notice.id, noticeKind: 'warning', text: queued.text }\n }\n }\n\n async function* stream(): AsyncGenerator<ProducerWireEvent, void, unknown> {\n try {\n for await (const raw of source) {\n const record = asRecord(raw)\n captureEffectiveBackend(asRecord(record?.data))\n yield* drainModelNotices()\n if (!record || typeof record.type !== 'string') continue\n // Fold bare tool_call/tool_result shapes into the canonical part event;\n // everything else keeps its original record (verbatim forwarding must\n // not strip fields outside `data`).\n const normalized = normalizeToolEvent({ type: record.type, data: asRecord(record.data) })\n const event: StreamEvent = normalized.type === 'message.part.updated'\n ? normalized\n : { type: record.type, data: asRecord(record.data) }\n\n if (event.type === 'message.part.updated') {\n const part = asRecord(event.data?.part)\n if (!part) continue\n const rawDelta = event.data?.delta\n const partType = String(part.type ?? '')\n\n if (partType === 'text' || partType === 'reasoning') {\n const key = getPartKey(part)\n const delta = textDelta(tracker, key, part, rawDelta)\n recordPersistedPart(part, delta || undefined)\n if (delta) {\n if (partType === 'text') fullText += delta\n yield { type: partType, text: delta }\n }\n continue\n }\n\n if (partType === 'tool') {\n recordPersistedPart(part, undefined)\n const persisted = partMap.get(getPartKey(part))\n const state = asRecord(persisted?.state)\n const toolId = String(persisted?.id ?? '')\n const toolName = String(persisted?.tool ?? 'tool')\n const args = asRecord(state?.input) ?? {}\n const hasPopulatedArgs = Object.keys(args).length > 0\n const announcedWithPopulatedArgs = announcedToolArgs.get(toolId)\n if (\n toolId &&\n (announcedWithPopulatedArgs === undefined ||\n (!announcedWithPopulatedArgs && hasPopulatedArgs && !settledTools.has(toolId)))\n ) {\n announcedToolArgs.set(toolId, hasPopulatedArgs)\n yield {\n type: 'tool_call',\n call: { toolCallId: toolId, toolName, args },\n }\n }\n const status = String(state?.status ?? '')\n if (toolId && (status === 'completed' || status === 'error') && !settledTools.has(toolId)) {\n settledTools.add(toolId)\n yield {\n type: 'tool_result',\n toolCallId: toolId,\n toolName,\n outcome: {\n ok: status === 'completed',\n ...(state?.output !== undefined ? { result: state.output } : {}),\n ...(asString(state?.error) ? { message: asString(state?.error) } : {}),\n },\n }\n }\n continue\n }\n\n if (partType === 'step-finish') {\n addStepFinishUsage(part, usage)\n // Persist the per-step receipt too (unique key per occurrence: the\n // parts have no id and two receipts must never merge into one).\n recordPersistedPart(part, undefined, `step-finish:#${stepCounter++}`)\n const promptTokens = usage.inputTokens ?? 0\n const completionTokens = usage.outputTokens ?? 0\n if (promptTokens || completionTokens) {\n yield {\n type: 'usage',\n usage: {\n promptTokens,\n completionTokens,\n ...(usage.reasoningTokens !== undefined\n ? { reasoningTokens: usage.reasoningTokens }\n : {}),\n ...(usage.costUsd !== undefined ? { providerCostUsd: usage.costUsd } : {}),\n },\n }\n }\n continue\n }\n\n if (partType === 'step-start') {\n recordPersistedPart(part, undefined, `step-start:#${stepCounter}`)\n continue\n }\n\n if (partType === 'file' && options.promoteFilePart) {\n const promote = options.promoteFilePart\n // Prefixed by source (`id:`/`url:`) rather than the bare raw string —\n // an `id` and a `url` that happen to share the same text (e.g. both\n // `\"abc\"`) would otherwise collide onto one memo entry and fold two\n // unrelated parts' promotions together. Internal Map key only; never\n // observable outside this module.\n const rawId = asString(part.id)\n const rawUrl = asString(part.url)\n const memoKey = rawId ? `id:${rawId}` : rawUrl ? `url:${rawUrl}` : undefined\n // Always invoke the callback for a `file` part when one is wired —\n // gtm never skips promotion outright, even for a part with neither\n // `id` nor `url` (it simply fails \"carries no url\" and resolves\n // through the ordinary failure path). Memoization by PROMISE only\n // applies when there is something to key it on; keyless parts are\n // invoked un-memoized, once per occurrence.\n //\n // `Promise.resolve().then(...)` (rather than calling `promote(part)`\n // directly) so a SYNC throw from a non-async `promoteFilePart` is\n // captured into the promise chain instead of escaping this function\n // call outright — `.catch` below only ever sees a rejection, never a\n // thrown exception that unwinds past `attempt()`.\n const attempt = (): Promise<FilePartPromotionOutcome> =>\n Promise.resolve()\n .then(() => promote(part))\n .catch((err) => {\n const reason = err instanceof Error ? err.message : String(err)\n log('[chat-routes] file part promotion threw', { key: memoKey ?? '(keyless)', error: reason })\n return { succeeded: false as const, reason }\n })\n\n let pending: Promise<FilePartPromotionOutcome>\n if (memoKey) {\n pending = promotedFileParts.get(memoKey) ?? attempt()\n promotedFileParts.set(memoKey, pending)\n } else {\n pending = attempt()\n }\n\n const outcome = await pending\n if (outcome.succeeded) {\n recordPersistedPart(outcome.part, undefined, outcome.key)\n } else if (outcome.part) {\n // Substitute part (e.g. a warning notice) takes the raw part's\n // place in the transcript — the transient url never lands.\n recordPersistedPart(outcome.part, undefined, outcome.key)\n } else {\n recordPersistedPart(part, undefined)\n }\n continue\n }\n\n // Remaining storable kinds (file/image/subtask) have no live\n // vocabulary line; they persist so the transcript keeps them.\n recordPersistedPart(part, undefined)\n continue\n }\n\n if (event.type === 'interaction') {\n const parsed = parseInteractionRequest(asRecord(record.data))\n if (!parsed.succeeded) {\n log('[chat-routes] dropping malformed interaction event', { error: parsed.error })\n continue\n }\n if (renderable(parsed.value.kind)) {\n recordPersistedPart(\n interactionToPersistedPart(parsed.value, 'pending'),\n undefined,\n interactionPartKey(parsed.value.id),\n )\n yield toProducerWireEvent(record)\n continue\n }\n // Non-renderable ask: the run is blocked in the broker until someone\n // answers. Decline it so the turn proceeds instead of hanging.\n let declineFailed = false\n if (options.declineInteraction) {\n try {\n await options.declineInteraction(parsed.value.id)\n } catch (err) {\n declineFailed = true\n log('[chat-routes] failed to auto-decline interaction', {\n id: parsed.value.id,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n } else {\n declineFailed = true\n log('[chat-routes] non-renderable interaction with no declineInteraction wired', {\n id: parsed.value.id,\n kind: parsed.value.kind,\n })\n }\n const text = declineFailed\n ? `The agent requested ${parsed.value.kind} approval; declining it failed — it will expire on its own.`\n : `The agent requested ${parsed.value.kind} approval — auto-declined by policy.`\n const notice = noticePart('auto-declined', `auto-declined-${parsed.value.id}`, text)\n recordPersistedPart(notice, undefined, noticePartKey(notice.id))\n yield { type: 'notice', id: notice.id, noticeKind: 'auto-declined', text }\n continue\n }\n\n if (event.type === 'interaction.cancel') {\n const parsed = parseInteractionCancel(asRecord(record.data))\n if (!parsed.succeeded) {\n log('[chat-routes] dropping malformed interaction.cancel event', { error: parsed.error })\n continue\n }\n const key = interactionPartKey(parsed.value.id)\n const existing = partMap.get(key)\n if (existing?.type === 'interaction' && existing.status === 'pending') {\n recordPersistedPart({\n ...existing,\n status: cancelStatusFor(parsed.value.reason),\n ...(parsed.value.reason ? { cancelReason: parsed.value.reason } : {}),\n }, undefined, key)\n }\n yield toProducerWireEvent(record)\n continue\n }\n\n if (event.type === 'plan.submitted') {\n const parsed = parsePlanSubmittedEvent(record)\n if (!parsed.succeeded) {\n log('[chat-routes] dropping malformed plan.submitted event', { error: parsed.error })\n continue\n }\n recordPersistedPart(planToPersistedPart(parsed.value), undefined)\n yield toProducerWireEvent(record)\n continue\n }\n\n if (event.type === 'warning') {\n const message = asString(event.data?.message)\n if (message) {\n warningCount += 1\n const code = asString(event.data?.code)\n const text = code ? `${code}: ${message}` : message\n const notice = noticePart('warning', `warning-${warningCount}`, text)\n recordPersistedPart(notice, undefined, noticePartKey(notice.id))\n yield { type: 'notice', id: notice.id, noticeKind: 'warning', text }\n }\n // The flattened notice is additive; existing raw-warning consumers keep\n // receiving the original event too.\n yield toProducerWireEvent(record)\n continue\n }\n\n if (event.type === 'usage') {\n const reported = extractUsageSnapshot(asRecord(record.usage))\n if (reported) applyTerminalUsage(reported, usage)\n yield toProducerWireEvent(record)\n continue\n }\n\n if (event.type === 'result') {\n const finalText = asString(event.data?.finalText)\n if (finalText) fullText = finalText\n const reported = extractReportedTurnUsage(asRecord(event.data))\n if (reported) {\n applyTerminalUsage(reported, usage)\n yield* emitFinalUsage()\n } else {\n // Legacy result shape kept as a secondary fallback.\n const resultUsage = asRecord(event.data?.usage)\n if (resultUsage) {\n const input = Number(resultUsage.inputTokens)\n const output = Number(resultUsage.outputTokens)\n if (Number.isFinite(input)) usage.inputTokens = input\n if (Number.isFinite(output)) usage.outputTokens = output\n }\n }\n yield* emitTerminalizedTools()\n continue\n }\n\n if (event.type === 'done') {\n const reported = extractReportedTurnUsage(asRecord(event.data))\n if (reported) {\n applyTerminalUsage(reported, usage)\n yield* emitFinalUsage()\n }\n yield* emitTerminalizedTools()\n yield toProducerWireEvent(record)\n continue\n }\n\n if (event.type === 'error') {\n const message = sandboxStreamErrorMessage(event.data)\n const errorContent = fullText.trim()\n ? `The sandbox model stream stopped before a clean completion.\\n\\nError: ${message}`\n : `The sandbox agent returned an error before producing a visible answer.\\n\\nError: ${message}`\n const errorDelta = fullText ? `\\n\\n---\\n${errorContent}` : errorContent\n fullText += errorDelta\n interactionOutcome = 'expired'\n yield { type: 'text', text: errorDelta }\n yield* emitTerminalizedTools()\n yield toProducerWireEvent(record)\n continue\n }\n\n // Remaining lifecycle events forward verbatim; the client parser ignores\n // unknown types.\n yield toProducerWireEvent(record)\n }\n // A model that fell over and then produced nothing never entered the loop\n // body; the downgrade must still be recorded.\n yield* drainModelNotices()\n } catch (streamErr) {\n const diagnostic = sandboxStreamFailureDiagnostic(streamErr)\n log('[chat-routes] sandbox stream failed', {\n failureNote: diagnostic.failureNote,\n error: streamErr instanceof Error ? streamErr.message : String(streamErr),\n })\n const errorDelta = fullText\n ? `\\n\\n---\\n${diagnostic.userMessage}`\n : diagnostic.userMessage\n fullText += errorDelta\n interactionOutcome = 'expired'\n yield { type: 'text', text: errorDelta }\n yield* emitTerminalizedTools()\n yield {\n type: 'error',\n data: {\n message: diagnostic.userMessage,\n code: diagnostic.code,\n details: { failureNote: diagnostic.failureNote },\n },\n }\n }\n }\n\n return {\n stream: stream(),\n finalText: () => fullText,\n assistantParts: () => finalizePendingInteractionParts(\n finalizeAssistantParts(partOrder, partMap, fullText),\n interactionOutcome,\n ),\n // Mid-stream snapshot for incremental persistence: the same accumulators,\n // WITHOUT the two completion-time settlements (`terminalizeDanglingTool*`\n // and `finalizePendingInteractionParts`). A running tool and an unanswered\n // ask are the correct live state; settling them early would persist\n // phantom failures the final write then reverses.\n draftParts: () => draftAssistantParts(partOrder, partMap, fullText),\n usage: () => usage,\n // A GETTER, not a captured value: `turn-routes` reads `producer.model` at\n // draft-snapshot and persist time, both of which happen after the stream\n // resolved which model serves. A plain property would freeze the PREFERRED\n // model into the row and make a downgrade unattributable — the exact\n // failure mode this work exists to prevent.\n get model(): string | undefined {\n return servedModel()\n },\n modelFailover: () => ({\n model: servingModel(),\n attempts: failover?.attempts() ?? [],\n usedFallback: failover?.usedFallback() ?? false,\n }),\n modelAttribution: () => ({\n ...(options.model ? { requestedModel: options.model } : {}),\n ...(effectiveBackend ?? {}),\n echoReceived: effectiveBackend !== undefined,\n }),\n }\n}\n","import type { AgentProfile } from '@tangle-network/agent-interface'\nimport { createExecutor, streamAgentTurn } from '@tangle-network/agent-runtime/kernel'\nimport {\n runProtectedAgentCandidateModelGrant,\n type RunProtectedAgentCandidateModelGrantOptions,\n} from '@tangle-network/agent-runtime/candidate-execution'\nimport { ProtectedModelSettlementError } from '../runtime/protected-model'\nimport type { McpToolDefinition } from '../tools/mcp-rpc'\nimport { createSandboxChatProducer } from './sandbox-producer'\nimport type { ChatRouteEvent, ChatTurnRouteProducer, ChatTurnUsage } from './turn-routes'\n\nexport interface ProtectedRuntimeChatOptions {\n profile: AgentProfile\n prompt: string\n priorMessages?: ReadonlyArray<{ role: string; content: string }>\n /** Runtime owns reservation, activation, revocation and authoritative settlement. */\n grant: Omit<RunProtectedAgentCandidateModelGrantOptions<void>, 'execute'>\n /** Only tools authorized for this execution. Each tool validates its arguments and effects. */\n tools: readonly McpToolDefinition<{ signal: AbortSignal }>[]\n maxToolCalls: number\n signal?: AbortSignal\n}\n\n/** Run a profile through the protected Router tool executor and the shared chat projection. */\nexport function createProtectedRuntimeChatProducer(options: ProtectedRuntimeChatOptions): ChatTurnRouteProducer {\n if (!Number.isSafeInteger(options.maxToolCalls) || options.maxToolCalls < 0) {\n throw new Error('maxToolCalls must be a non-negative integer')\n }\n options = { ...options,\n grant: { ...options.grant, resolve: structuredClone(options.grant.resolve), reserve: structuredClone(options.grant.reserve) },\n tools: options.tools.map(tool => ({ ...tool, inputSchema: structuredClone(tool.inputSchema) })),\n priorMessages: options.priorMessages ? structuredClone(options.priorMessages) : undefined,\n }\n const tools = new Map(options.tools.map(tool => [tool.name, tool]))\n if (tools.size !== options.tools.length) throw new Error('Duplicate protected tool name')\n const profile = structuredClone(options.profile)\n let toolCalls = 0\n let usage: ChatTurnUsage = {}\n let started = false\n let model: string | undefined\n let budgetEnforced = false\n let toolTokens: number | undefined\n\n async function* events(): AsyncGenerator<unknown> {\n if (started) throw new Error('A protected chat producer can only execute once')\n started = true\n const controller = new AbortController()\n options.signal?.throwIfAborted()\n if (Date.now() >= options.grant.deadlineAtMs) throw new Error('Protected execution deadline expired')\n const signal = AbortSignal.any([\n controller.signal, AbortSignal.timeout(Math.max(1, options.grant.deadlineAtMs - Date.now())),\n ...(options.signal ? [options.signal] : []),\n ])\n const channel = new TransformStream<unknown, unknown>()\n const writer = channel.writable.getWriter()\n const reader = channel.readable.getReader()\n const emit = (event: unknown) => writer.write(event)\n const execution = runProtectedAgentCandidateModelGrant({\n ...options.grant,\n port: {\n resolve: input => options.grant.port.resolve(input),\n reserveGrant: input => options.grant.port.reserveGrant(input),\n activateGrant: input => options.grant.port.activateGrant(input),\n // Runtime settles failed executions before rethrowing. Retain that receipt too.\n settleGrant: async input => {\n let auditError: ProtectedModelSettlementError | undefined\n const settlement = await options.grant.port.settleGrant(input).catch(error => {\n if (!(error instanceof ProtectedModelSettlementError)) throw error\n auditError = error\n return error.settlement\n })\n usage = {\n inputTokens: settlement.calls.reduce((sum, call) => sum + call.accountedInputTokens, 0),\n outputTokens: settlement.calls.reduce((sum, call) => sum + call.outputTokens, 0),\n reasoningTokens: settlement.calls.reduce((sum, call) => sum + call.reasoningTokens, 0),\n costUsd: settlement.calls.reduce((sum, call) => sum + call.costUsdNanos, 0) / 1e9,\n }\n budgetEnforced = settlement.usageWithinLimits\n if (!budgetEnforced) throw new Error('Provider settlement exceeded execution limits')\n if (auditError) throw auditError\n return settlement\n },\n },\n execute: async ({ activation, resolved }) => {\n signal.throwIfAborted()\n if (Date.now() >= options.grant.deadlineAtMs) throw new Error('Protected execution deadline expired')\n model = resolved.model\n // Protected Anthropic requests permit only client tools. Their result\n // text is included in input tokens, with no separately billed hosted tools.\n if (resolved.provider !== 'anthropic' || resolved.reasoningEffort !== 'none') {\n throw new Error('Protected chat currently requires Anthropic without reasoning')\n }\n toolTokens = 0\n const apiKey = activation.env.OPENAI_API_KEY\n const baseUrl = activation.env.OPENAI_BASE_URL\n if (!apiKey || !baseUrl) throw new Error('Protected Router activation is missing')\n const factory = createExecutor({\n backend: 'router-tools', routerBaseUrl: baseUrl, routerKey: apiKey,\n tools: options.tools.map(tool => ({ type: 'function' as const,\n function: { name: tool.name, description: tool.description, parameters: tool.inputSchema } })),\n ...(options.priorMessages?.length ? { initialMessages: options.priorMessages } : {}),\n executeToolCall: async (name, args) => {\n signal.throwIfAborted()\n const tool = tools.get(name)\n if (!tool) throw new Error('Tool is not authorized for this execution')\n if (toolCalls >= options.maxToolCalls) {\n controller.abort(new Error('Execution tool-call limit reached'))\n signal.throwIfAborted()\n }\n toolCalls += 1\n const id = `tool-${toolCalls}`\n await emit({ type: 'tool_call', data: { id, name, arguments: args } })\n try {\n const result = await tool.run(args, { signal })\n await emit({ type: 'tool_result', data: { id, name, output: result } })\n return JSON.stringify(result)\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Tool failed'\n await emit({ type: 'tool_result', data: { id, name, error: message } })\n throw error\n }\n },\n })\n let completed = false\n let streamedText = ''\n for await (const event of streamAgentTurn({ kind: 'executor', factory, profile },\n { prompt: options.prompt }, { signal, callId: options.grant.reserve.executionId,\n timeoutMs: Math.max(1, options.grant.deadlineAtMs - Date.now()) })) {\n if (event.type === 'text_delta') streamedText += event.text\n if (event.type === 'text_delta' || event.type === 'reasoning_delta') {\n await emit({ type: 'message.part.updated', data: {\n part: { id: event.type, type: event.type === 'text_delta' ? 'text' : 'reasoning' }, delta: event.text,\n } })\n }\n if (event.type === 'final') {\n if (event.status !== 'completed') throw new Error(`Protected execution ${event.status}`)\n // Executor backends may emit only a final snapshot. The chat and\n // gateway wire append text, so emit its missing suffix exactly once.\n if (event.text !== undefined) {\n if (!event.text.startsWith(streamedText)) {\n throw new Error('Protected execution revised already streamed text')\n }\n const suffix = event.text.slice(streamedText.length)\n if (suffix) await emit({ type: 'message.part.updated', data: {\n part: { id: 'text_delta', type: 'text' }, delta: suffix,\n } })\n streamedText = event.text\n }\n completed = true\n await emit({ type: 'result', data: { finalText: event.text } })\n }\n }\n if (!completed) throw new Error('Protected execution ended without a terminal result')\n },\n }).then(async () => {\n await writer.close()\n }).catch(error => writer.abort(error))\n try {\n for (;;) {\n const next = await reader.read()\n if (next.done) break\n yield next.value\n }\n await execution\n } finally {\n controller.abort()\n await reader.cancel().catch(() => undefined)\n await execution\n reader.releaseLock()\n writer.releaseLock()\n }\n }\n\n const producer = createSandboxChatProducer({ events: events(), model: profile.model?.default })\n return {\n ...producer,\n stream: (async function* () {\n let failure: ChatRouteEvent | undefined\n for await (const event of producer.stream) {\n if (event.type === 'error') failure = event\n else yield event\n }\n if (usage.inputTokens !== undefined && usage.outputTokens !== undefined && usage.costUsd !== undefined) {\n yield { type: 'usage' as const, usage: { promptTokens: usage.inputTokens,\n completionTokens: usage.outputTokens, providerCostUsd: usage.costUsd,\n reasoningTokens: usage.reasoningTokens, toolTokens,\n toolCallCount: toolCalls, budgetEnforced } }\n }\n if (failure) yield failure\n })(),\n get model() { return model ?? profile.model?.default },\n usage: () => ({ ...usage }),\n }\n}\n","/**\n * Detached (autonomous) turn → live buffer bridge.\n *\n * The interactive lane (`createChatTurnRoutes`) already streams a user-typed\n * turn to the browser while it runs. An AUTONOMOUS turn — a mission step, a\n * queue job, an inbound-email review — runs detached (`dispatchPrompt`/\n * `streamPrompt` server-side so it survives no one watching) and, historically,\n * only persisted its FINAL message. A browser opening the session mid-run saw a\n * dead screen: the live tokens existed server-side but were never written to\n * the turn-event buffer the client re-attach path (`listRunning` + `/replay`)\n * reads.\n *\n * `runDetachedTurn` is that missing bridge, packaged. It taps the same buffer\n * the interactive lane uses (`createBufferedTurnTap`) with the same producer\n * mapping (`createSandboxChatProducer`), so an autonomous run is watchable\n * token-by-token exactly like an interactive one — while staying durable\n * (a durable driver re-invokes it after a crash; a turn that finished\n * server-side short-circuits instead of re-streaming). Products supply only the\n * domain seams: the raw sandbox event stream, the turn store, and the ids.\n *\n * This is app-shell mechanism (turn durability + live projection), not engine:\n * it owns no loop logic and imports no SDK — the event source is an injected\n * `AsyncIterable`.\n */\n\nimport { toChatMessageParts } from '../chat-store/parts'\nimport type { ModelFailoverAttempt } from '../model-resolution/failover'\nimport {\n coalesceDeltas,\n createBufferedTurnTap,\n type TurnEventStore,\n} from '../stream/index'\nimport {\n assistantRowIdForTurn,\n createAssistantDraftWriter,\n storeSupportsDraftPersistence,\n type AssistantDraftStore,\n type AssistantDraftWriter,\n type AssistantRowValues,\n type DraftStoredMessage,\n type DraftPersistenceTuning,\n} from './draft-persistence'\nimport {\n createSandboxChatProducer,\n type SandboxChatProducerOptions,\n} from './sandbox-producer'\nimport type { ChatTurnUsage } from './turn-routes'\n\n/** The normalized structured message body (tool-call / file / plan / interaction\n * parts) that `/chat-store` persists as the durable assistant row — the same\n * shape `createSandboxChatProducer().assistantParts()` returns. */\nexport type DetachedTurnParts = Array<Record<string, unknown>>\n\n/** Authoritative final receipt for a turn that finished server-side, or whose\n * live stream carried no usage (some harness paths only expose tokens via the\n * completed-turn record, e.g. `box.findCompletedTurn(turnId)`). */\nexport interface DetachedTurnFinal {\n text?: string\n usage?: ChatTurnUsage\n /** The structured parts to persist when this receipt is more complete than\n * the live stream (cached, finished server-side, or a fast stream that\n * delivered scalar text before its message-part events). Omitted when the\n * record only carries a usage receipt. */\n parts?: DetachedTurnParts\n}\n\n/** Define options for managing and projecting a detached turn event stream in a session */\nexport interface DetachedTurnOptions {\n store: TurnEventStore\n turnId: string\n /** Thread/session id — recorded as the buffer scope so a browser opening the\n * session mid-run rediscovers this turn via `listRunning(scopeId)` after it\n * has lost the turnId. */\n scopeId: string\n /** The raw sandbox event stream for this turn (e.g. `streamSandboxPrompt`).\n * Ownership of the box, prompt, tooling, and attachments stays with the\n * caller — this only projects the stream.\n *\n * An already-open stream is bound to one model and cannot fail over. Prefer\n * {@link openEvents}; exactly one of the two is required. */\n events?: AsyncIterable<unknown>\n /** Open the raw sandbox stream FOR A GIVEN MODEL — the failover-capable form\n * of {@link events}. Wiring it turns failover on with no further flag\n * whenever {@link fallbackModels} is non-empty. Requires {@link model}.\n *\n * An autonomous run is the case that most needs this: nobody is watching to\n * notice a dead upstream and retry by hand, so without failover the mission\n * step or queue job simply fails. Forwarded to the producer. */\n openEvents?: SandboxChatProducerOptions['openEvents']\n /** Models to try, in order, when `model`'s upstream is dead. Product config —\n * see the producer's note on why a same-family fallback is not automatically\n * safe and why every fallback is surfaced. */\n fallbackModels?: SandboxChatProducerOptions['fallbackModels']\n /** Opt out of failover while still using {@link openEvents}. */\n modelFailover?: false\n /** Maximum time to open/start one model event source through its first event.\n * Forwarded to the producer; default 120 seconds. */\n openTimeoutMs?: SandboxChatProducerOptions['openTimeoutMs']\n /** Hard deadline after the source's first lifecycle event for the first\n * answer-bearing event. Forwarded to the producer; default 60 seconds. */\n firstResponseTimeoutMs?: SandboxChatProducerOptions['firstResponseTimeoutMs']\n /** Fired when a model is abandoned mid-chain (telemetry/alerting). */\n onModelFallback?: SandboxChatProducerOptions['onModelFallback']\n /** The PREFERRED model. Recorded on the persisted assistant message + usage\n * receipt — unless failover moved the turn, in which case the model that\n * actually served is recorded instead and surfaced on the result. */\n model?: string\n /** Per-flush buffer coalescer. Default `coalesceDeltas`. */\n coalesce?: (events: unknown[]) => unknown[]\n /** Which ask kinds the product renders a card for; anything else is\n * auto-declined via {@link declineInteraction}. Forwarded to the producer. */\n isRenderableInteraction?: SandboxChatProducerOptions['isRenderableInteraction']\n /** Resolve a non-renderable ask so the run never hangs in the broker. An\n * autonomous turn has NO human watching to answer an ask, so a caller that\n * omits this risks the run blocking until the broker times out — wire it for\n * any unattended run. Forwarded to the producer. */\n declineInteraction?: SandboxChatProducerOptions['declineInteraction']\n /** Opt-in eager promotion of harness-emitted `file` parts. Forwarded to the\n * producer (see its docs). */\n promoteFilePart?: SandboxChatProducerOptions['promoteFilePart']\n /** Authoritative final receipt, consulted whenever a re-invoke finds a prior\n * buffer: (a) an already-`complete` turn returns it as the cached result,\n * (b) a `running` turn (crash mid-run) uses it to detect a run that finished\n * server-side, and (c) a clean run whose stream carried no usage or only\n * scalar text falls back to it. For Sandbox runs, use\n * `readCompletedSandboxTurn` so the exact completed session message\n * restores tool/file parts as well as text. */\n completedResult?: () => Promise<DetachedTurnFinal | null | undefined>\n /** Clear the prior partial buffer for `turnId` before a genuine re-stream.\n * A crash mid-run leaves buffered rows at seqs 1..N with status `running`;\n * re-streaming restarts the tap's seq at 0 and would duplicate/interleave\n * rows. Wire this (delete `turnId`'s buffered events) so a retry is clean.\n * A re-stream over an existing `running` buffer requires this operation to\n * succeed. Missing or failed reset leaves the existing stream untouched. */\n resetBuffer?: (turnId: string) => Promise<void>\n /** Own the durable assistant row for this turn instead of returning the body\n * for the caller to insert — and keep it in step with the stream.\n *\n * An autonomous run is exactly the case a late viewer hits: nobody is\n * watching when it starts, so by the time a browser opens the session the\n * streaming gateway's hot event buffer may already have expired it. Keeping\n * that buffer short is what makes it affordable at scale (its Redis\n * footprint is linear in `ttl x concurrent sessions`), so the durable row —\n * written incrementally here — is what serves the late viewer.\n *\n * WIRING THIS TRANSFERS ROW OWNERSHIP: the returned {@link\n * DetachedTurnResult.messageId} names the row this call wrote (draft rows\n * during the stream, authoritative values at the end, retraction when the\n * turn produced nothing). The caller must NOT insert its own assistant row\n * for the turn. Omit the seam and nothing changes — the result is returned\n * and the caller persists it exactly as today.\n *\n * Idempotency reuses the turn's own identity: the row id defaults to\n * `assistant:<turnId>`, so a durable driver re-invoking after a crash\n * patches the same row instead of duplicating parts. */\n persist?: DraftPersistenceTuning & {\n store: AssistantDraftStore\n threadId: string\n /** Deterministic row id. Default `assistant:<turnId>`. */\n messageId?: string\n /** Pre-persist text transform (`/redact`), applied to drafts AND the final\n * write — parity with the interactive lane's `transformFinalText`. */\n transformText?: (text: string) => string | Promise<string>\n }\n log?: (message: string, meta?: Record<string, unknown>) => void\n}\n\n/** Describe the result of a detached turn including state, text, parts, usage, and optional error or cache flag */\nexport interface DetachedTurnResult {\n /** `completed` — clean drain: persist + bill. `failed` — a terminal error\n * event, including the producer's structured `sandbox.stream_failed` event\n * when the raw sandbox stream throws: skip billing, render an error row. */\n state: 'completed' | 'failed'\n text: string\n /** The structured assistant body to persist (tool calls, file/plan/interaction\n * parts). Empty array when the run produced none. */\n parts: DetachedTurnParts\n usage: ChatTurnUsage\n /** Present when `state === 'failed'`. */\n error?: string\n /** True when a prior buffer meant this call returned a cached/finished result\n * WITHOUT re-streaming (durable-driver retry after a crash). */\n cached: boolean\n /** The durable assistant row this call wrote, when `persist` was wired.\n * `null` when the turn produced nothing and the row was retracted. Absent\n * when the caller owns persistence (today's behavior). */\n messageId?: string | null\n /** The model that SERVED the turn — the fallback's id when failover moved it.\n * A caller that bills or scores per model must read this, not the model it\n * requested. */\n model?: string\n /** The caller's explicit model request, before shell failover. */\n requestedModel?: string\n /** The effective model echoed by the downstream sandbox. */\n servedModel?: string\n /** The effective provider echoed by the downstream sandbox. */\n servedProvider?: string\n /** How the downstream sandbox selected the effective model. */\n servedSource?: 'request' | 'environment' | 'profile'\n /** True when the preferred model did not serve. Makes an autonomous\n * downgrade — which no human watched happen — attributable after the fact. */\n usedModelFallback?: boolean\n /** Every model tried, in order, with the reason each was abandoned. */\n modelAttempts?: ModelFailoverAttempt[]\n}\n\n/** Terminal failure event types a producer may forward verbatim. */\nconst TERMINAL_ERROR_TYPES = new Set(['error', 'session.run.failed'])\n\nfunction errorMessageOf(ev: unknown): string {\n const rec = ev as { data?: { message?: unknown; reason?: unknown }; message?: unknown } | null\n const raw = rec?.data?.message ?? rec?.data?.reason ?? rec?.message\n return typeof raw === 'string' && raw ? raw : 'run failed'\n}\n\nfunction hasUsage(usage: ChatTurnUsage): boolean {\n return typeof usage.inputTokens === 'number' && usage.inputTokens > 0\n}\n\nfunction cachedResultFrom(\n final: DetachedTurnFinal | null,\n persisted?: DraftStoredMessage,\n): DetachedTurnResult {\n if (!final && !persisted) {\n throw new Error('Completed turn has no retained result or assistant row; retry exact recovery')\n }\n // A text-only recovery must not erase usage already stored for this turn.\n // Unknown values stay absent; an explicit measured zero is preserved.\n const usage: ChatTurnUsage = {}\n for (const key of [\n 'inputTokens', 'outputTokens', 'reasoningTokens',\n 'cacheReadTokens', 'cacheWriteTokens', 'costUsd',\n ] as const) {\n const value = final?.usage?.[key] ?? persisted?.[key]\n if (typeof value === 'number' && Number.isFinite(value) && value >= 0) usage[key] = value\n }\n return {\n state: 'completed',\n text: final?.text ?? persisted?.content ?? '',\n parts:\n final?.parts ??\n (persisted?.parts as DetachedTurnParts | null | undefined) ??\n [],\n usage,\n cached: true,\n }\n}\n\n/**\n * Stream a detached turn into the live turn-event buffer, durably.\n *\n * - Idempotent: an already-`complete` turn returns the cached result without\n * re-streaming (a second event sequence would collide with the buffered one).\n * - Crash-safe: a `running` turn (a prior attempt crashed mid-tap) consults\n * `completedResult` to detect a run that finished server-side; only a run that\n * genuinely did not complete is re-streamed, and then over a `resetBuffer`-\n * cleared buffer so seqs don't corrupt.\n * - Marks the turn `running` under `scopeId` so a mid-run browser finds it.\n * - Settles `complete`/`error` so the client stops tailing and billing/render\n * can branch on `state`.\n */\nexport async function runDetachedTurn(opts: DetachedTurnOptions): Promise<DetachedTurnResult> {\n const { store, turnId, scopeId } = opts\n\n // Hoisted so the draft writer's snapshot can read the producer's live\n // accumulators; assigned once the idempotency branches decide to re-stream.\n let producer: ReturnType<typeof createSandboxChatProducer> | undefined\n\n /** The model that actually served. Read LAZILY (never captured up front): on\n * a cached/finished-server-side path no producer exists and the preferred\n * model is the best available answer, but on a live re-stream failover may\n * have moved the turn after the row was first drafted. */\n const servingModel = (): string | undefined => producer?.model ?? opts.model\n\n const persistedRow = async (): Promise<DraftStoredMessage | undefined> => {\n if (!opts.persist) return undefined\n return (await opts.persist.store.listMessages(opts.persist.threadId)).find(\n (message) =>\n message.id ===\n (opts.persist?.messageId ?? assistantRowIdForTurn(turnId)),\n )\n }\n\n // Durable row ownership (opt-in). Built before the idempotency branches so a\n // cached/finished-server-side re-invoke still converges the row instead of\n // leaving whatever partial state a crashed attempt wrote.\n let draft: AssistantDraftWriter | undefined\n if (opts.persist) {\n const { store: persistStore, threadId, messageId, transformText, ...tuning } = opts.persist\n if (!storeSupportsDraftPersistence(persistStore)) {\n throw new Error(\n 'runDetachedTurn persist requires a store with updateMessage() — `/chat-store`\\'s createChatStore has it',\n )\n }\n draft = createAssistantDraftWriter({\n ...tuning,\n store: persistStore,\n threadId,\n messageId: messageId ?? assistantRowIdForTurn(turnId),\n snapshot: () => (producer\n ? {\n content: producer.finalText?.() ?? '',\n ...(producer.draftParts ? { parts: producer.draftParts() } : {}),\n ...(producer.usage ? { usage: producer.usage() } : {}),\n ...(servingModel() ? { model: servingModel() } : {}),\n }\n : null),\n ...(transformText ? { transformText } : {}),\n ...(opts.log ? { log: opts.log } : {}),\n })\n }\n\n /** Settle the durable row with authoritative values (or retract it when the\n * turn produced nothing), and stamp the id onto the result. */\n const settleRow = async (\n base: DetachedTurnResult,\n cachedPersisted?: DraftStoredMessage,\n ): Promise<DetachedTurnResult> => {\n // A cached/reconciled return has no producer to replay the sidecar echo.\n // Preserve the attribution already written by the prior attempt instead\n // of replacing its served `model` with today's requested-model fallback.\n const persisted =\n cachedPersisted ?? (!producer ? await persistedRow() : undefined)\n const info = producer?.modelFailover?.()\n const attribution = producer?.modelAttribution?.()\n const model = producer?.model ?? persisted?.model ?? opts.model\n const requestedModel = attribution?.requestedModel ?? persisted?.requestedModel ?? opts.model\n const servedModel = attribution?.servedModel ?? persisted?.servedModel\n const servedProvider = attribution?.servedProvider ?? persisted?.servedProvider\n const servedSource = attribution?.servedSource ?? (\n persisted?.servedSource === 'request' ||\n persisted?.servedSource === 'environment' ||\n persisted?.servedSource === 'profile'\n ? persisted.servedSource\n : undefined\n )\n const result: DetachedTurnResult = {\n ...base,\n ...(model ? { model } : {}),\n ...(requestedModel ? { requestedModel } : {}),\n ...(servedModel ? { servedModel } : {}),\n ...(servedProvider ? { servedProvider } : {}),\n ...(servedSource ? { servedSource } : {}),\n ...(info ? { usedModelFallback: info.usedFallback, modelAttempts: info.attempts } : {}),\n }\n if (!draft) return result\n const transform = opts.persist?.transformText\n const content = transform ? await transform(result.text) : result.text\n const rawParts = transform\n ? await Promise.all(\n result.parts.map(async (part) =>\n String((part as { type?: unknown }).type ?? '') === 'text'\n ? { ...part, text: await transform(String((part as { text?: unknown }).text ?? '')) }\n : part,\n ),\n )\n : result.parts\n const parts = toChatMessageParts(rawParts)\n if (!content.trim() && parts.length === 0) {\n await draft.discard()\n return { ...result, messageId: null }\n }\n const values: AssistantRowValues = {\n content,\n ...(parts.length > 0 ? { parts } : {}),\n ...(result.model ? { model: result.model } : {}),\n ...(result.requestedModel ? { requestedModel: result.requestedModel } : {}),\n ...(result.servedModel ? { servedModel: result.servedModel } : {}),\n ...(result.servedProvider ? { servedProvider: result.servedProvider } : {}),\n ...(result.servedSource ? { servedSource: result.servedSource } : {}),\n ...(result.usage.inputTokens !== undefined ? { inputTokens: result.usage.inputTokens } : {}),\n ...(result.usage.outputTokens !== undefined ? { outputTokens: result.usage.outputTokens } : {}),\n ...(result.usage.reasoningTokens !== undefined ? { reasoningTokens: result.usage.reasoningTokens } : {}),\n ...(result.usage.cacheReadTokens !== undefined ? { cacheReadTokens: result.usage.cacheReadTokens } : {}),\n ...(result.usage.cacheWriteTokens !== undefined ? { cacheWriteTokens: result.usage.cacheWriteTokens } : {}),\n ...(result.usage.costUsd !== undefined ? { costUsd: result.usage.costUsd } : {}),\n }\n await draft.finalize(values)\n return { ...result, messageId: draft.rowId() ?? null }\n }\n\n const completed = async (): Promise<DetachedTurnFinal | null> => {\n if (!opts.completedResult) return null\n // An unavailable authoritative read must not become a fresh stream.\n return (await opts.completedResult()) ?? null\n }\n\n const prior = await store.getStatus(turnId)\n\n if (prior === 'complete') {\n const persisted = await persistedRow()\n return await settleRow(\n cachedResultFrom(await completed(), persisted),\n persisted,\n )\n }\n\n if (prior === 'running') {\n // A prior attempt marked the turn running and then this worker crashed. The\n // detached SESSION may have finished server-side while we were gone — the\n // authoritative check is `completedResult` (findCompletedTurn). If it\n // completed, settle the stuck `running` buffer and return it.\n const final = await completed()\n if (final) {\n await store.setStatus(turnId, 'complete', scopeId)\n const persisted = await persistedRow()\n return await settleRow(cachedResultFrom(final, persisted), persisted)\n }\n // Genuine re-run: clear the partial buffer first, or the fresh tap's seq\n // (restarting at 0) interleaves with the orphaned rows.\n if (!opts.resetBuffer) {\n throw new Error('Detached turn recovery requires resetBuffer before replaying an existing stream')\n }\n await opts.resetBuffer(turnId)\n }\n\n const tap = createBufferedTurnTap({\n store,\n turnId,\n scopeId,\n coalesce: opts.coalesce ?? coalesceDeltas,\n })\n // Leading turn marker: flips the buffer to `running` (so `listRunning` finds\n // it) and is the browser's `/replay` resume handle.\n await tap.onEvent({ type: 'turn', turnId })\n\n producer = createSandboxChatProducer({\n ...(opts.openEvents ? { openEvents: opts.openEvents } : { events: opts.events }),\n model: opts.model,\n ...(opts.fallbackModels ? { fallbackModels: opts.fallbackModels } : {}),\n ...(opts.modelFailover === false ? { modelFailover: false as const } : {}),\n ...(opts.openTimeoutMs !== undefined ? { openTimeoutMs: opts.openTimeoutMs } : {}),\n ...(opts.firstResponseTimeoutMs !== undefined\n ? { firstResponseTimeoutMs: opts.firstResponseTimeoutMs }\n : {}),\n ...(opts.onModelFallback ? { onModelFallback: opts.onModelFallback } : {}),\n isRenderableInteraction: opts.isRenderableInteraction,\n declineInteraction: opts.declineInteraction,\n promoteFilePart: opts.promoteFilePart,\n log: opts.log,\n })\n\n let runError: string | undefined\n try {\n for await (const ev of producer.stream) {\n const type = (ev as { type?: unknown }).type\n if (typeof type === 'string' && TERMINAL_ERROR_TYPES.has(type)) runError = errorMessageOf(ev)\n await tap.onEvent(ev)\n // Same coalesced cadence the interactive lane uses: the durable row\n // tracks the run so a viewer arriving after the hot buffer's short window\n // reads it from storage.\n draft?.notify(ev as { type?: unknown })\n }\n await tap.done(runError ? 'error' : 'complete')\n } catch (err) {\n await tap.done('error').catch(() => {})\n // Settle any in-flight draft before propagating so the partial row a\n // re-invoke will adopt is a complete write, not a half-landed one.\n await draft?.close().catch(() => {})\n throw err\n }\n\n const text = producer.finalText?.() ?? ''\n const parts = producer.assistantParts?.() ?? []\n let usage: ChatTurnUsage = producer.usage?.() ?? {}\n\n // A terminal result can supply scalar text even when the event subscriber\n // missed every structured message part. Reconcile that text-only shape with\n // the durable completion record before final persistence.\n const onlyTextParts = parts.every((part) =>\n String((part as { type?: unknown }).type ?? '') === 'text',\n )\n if (!runError && (!hasUsage(usage) || !text || onlyTextParts)) {\n const final = await completed()\n if (final?.usage) usage = { ...usage, ...final.usage }\n return await settleRow({\n state: 'completed',\n text: final?.text ?? text,\n parts: final?.parts?.length ? final.parts : parts,\n usage,\n cached: false,\n })\n }\n\n if (runError) return await settleRow({ state: 'failed', text, parts, usage, error: runError, cached: false })\n return await settleRow({ state: 'completed', text, parts, usage, cached: false })\n}\n","/**\n * Recover a detached Sandbox turn from its durable completed records.\n *\n * A fast detached run can finish before the live event subscriber receives\n * every message-part event. The Sandbox still retains two exact records: a\n * turn-id keyed result cache and the completed assistant message on the\n * session. This adapter joins them without guessing across turns, then returns\n * the same `DetachedTurnFinal` shape `runDetachedTurn` already consumes.\n */\n\nimport type { SandboxInstance, SessionMessage } from '@tangle-network/sandbox'\nimport {\n asRecord,\n asString,\n collapseRedundantTextParts,\n finalizeAssistantParts,\n getPartKey,\n mergePersistedPart,\n normalizePersistedPart,\n type JsonRecord,\n} from '../stream/index'\nimport { addStepFinishUsage } from './sandbox-turn-usage'\nimport type { DetachedTurnFinal } from './detached-turn'\nimport type { ChatTurnUsage } from './turn-routes'\n\n/** The official Sandbox methods needed for completed-turn recovery. */\nexport type CompletedSandboxTurnSource = Pick<\n SandboxInstance,\n 'findCompletedTurn' | 'session'\n>\n\n/** Options for resolving one exact detached turn. */\nexport interface ReadCompletedSandboxTurnOptions {\n turnId: string\n sessionId: string\n log?: (message: string, meta?: Record<string, unknown>) => void\n}\n\nfunction finiteNumber(value: unknown): number | undefined {\n if (typeof value === 'number') return Number.isFinite(value) ? value : undefined\n if (typeof value !== 'string' || !value.trim()) return undefined\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : undefined\n}\n\nfunction firstNumber(record: JsonRecord | undefined, keys: string[]): number | undefined {\n for (const key of keys) {\n const value = finiteNumber(record?.[key])\n if (value !== undefined) return value\n }\n return undefined\n}\n\nfunction usageFromResult(result: JsonRecord | undefined): ChatTurnUsage {\n const raw = asRecord(result?.usage) ?? asRecord(result?.tokenUsage)\n const inputTokens = firstNumber(raw, ['inputTokens', 'promptTokens', 'input'])\n const outputTokens = firstNumber(raw, ['outputTokens', 'completionTokens', 'output'])\n const reasoningTokens = firstNumber(raw, ['reasoningTokens', 'reasoning'])\n const cacheReadTokens = firstNumber(raw, [\n 'cacheReadTokens',\n 'cacheReadInputTokens',\n 'cacheRead',\n ])\n const cacheWriteTokens = firstNumber(raw, [\n 'cacheWriteTokens',\n 'cacheCreationInputTokens',\n 'cacheWrite',\n ])\n const costUsd = firstNumber(result, ['costUsd', 'totalCostUsd', 'cost'])\n ?? firstNumber(raw, ['costUsd', 'cost'])\n\n return {\n ...(inputTokens !== undefined ? { inputTokens } : {}),\n ...(outputTokens !== undefined ? { outputTokens } : {}),\n ...(reasoningTokens !== undefined ? { reasoningTokens } : {}),\n ...(cacheReadTokens !== undefined ? { cacheReadTokens } : {}),\n ...(cacheWriteTokens !== undefined ? { cacheWriteTokens } : {}),\n ...(costUsd !== undefined ? { costUsd } : {}),\n }\n}\n\nfunction textFromResult(result: JsonRecord | undefined): string | undefined {\n return asString(result?.response)\n ?? asString(result?.finalText)\n ?? asString(result?.text)\n ?? asString(result?.output)\n}\n\nfunction completedMessagesForTurn(\n messages: SessionMessage[],\n turnId: string,\n): SessionMessage[] {\n return messages.filter((message) =>\n message.role === 'assistant'\n && message.metadata?.turnId === turnId\n && message.metadata?.interrupted !== true\n && (\n message.metadata?.completed === true\n || message.metadata?.status === 'completed'\n ),\n )\n}\n\nfunction normalizedMessageParts(\n message: SessionMessage,\n): { parts: JsonRecord[]; usage: ChatTurnUsage; derivedText: string } {\n const normalized: JsonRecord[] = []\n const usage: ChatTurnUsage = {}\n for (const raw of message.parts) {\n const record = asRecord(raw)\n if (!record) continue\n const part = normalizePersistedPart(record)\n if (!part) continue\n normalized.push(part)\n if (part.type === 'step-finish') addStepFinishUsage(part, usage)\n }\n\n const collapsed = collapseRedundantTextParts(normalized)\n const derivedText = collapsed\n .filter((part) => part.type === 'text')\n .map((part) => String(part.text ?? ''))\n .join('')\n return { parts: normalized, usage, derivedText }\n}\n\nfunction projectMessageParts(parts: JsonRecord[], finalText: string): JsonRecord[] {\n const order: string[] = []\n const map = new Map<string, JsonRecord>()\n for (let index = 0; index < parts.length; index += 1) {\n const part = parts[index]!\n const type = String(part.type ?? '')\n // A session's aggregate message contains every step receipt. Preserve each\n // occurrence just as the live producer does; other kinds merge by their\n // canonical identity.\n const key = type === 'step-start' || type === 'step-finish'\n ? `${type}:#${index}`\n : getPartKey(part)\n if (!map.has(key)) order.push(key)\n map.set(key, mergePersistedPart(map.get(key), part))\n }\n return finalizeAssistantParts(order, map, finalText)\n}\n\n/**\n * Recover the durable content a Sandbox recorder attached to one assistant\n * message. The caller is responsible for proving that the message belongs to\n * the exact terminal turn; this projection deliberately makes no conclusion\n * about whether that terminal state was completed or interrupted.\n */\nexport function recoverSandboxAssistantMessage(message: SessionMessage): DetachedTurnFinal {\n const recovered = normalizedMessageParts(message)\n return {\n ...(recovered.derivedText ? { text: recovered.derivedText } : {}),\n ...(hasUsage(recovered.usage) ? { usage: recovered.usage } : {}),\n parts: projectMessageParts(recovered.parts, recovered.derivedText),\n }\n}\n\nfunction hasUsage(usage: ChatTurnUsage): boolean {\n return Object.values(usage).some((value) =>\n typeof value === 'number' && Number.isFinite(value),\n )\n}\n\n/**\n * Read the exact completed turn from its keyed cache and completed message.\n * Never borrow a session-wide aggregate from a concurrently advancing session.\n * Observation failure is retryable and distinct from successful absent reads.\n */\nexport async function readCompletedSandboxTurn(\n box: CompletedSandboxTurnSource,\n options: ReadCompletedSandboxTurnOptions,\n): Promise<DetachedTurnFinal | null> {\n const { turnId, sessionId, log } = options\n const session = box.session(sessionId)\n const [cacheOutcome, messagesOutcome] = await Promise.allSettled([\n box.findCompletedTurn(turnId, { sessionId }),\n session.messages({ limit: 1_000 }),\n ])\n\n if (cacheOutcome.status === 'rejected') {\n log?.('[chat-routes] completed Sandbox turn cache lookup failed', {\n turnId,\n sessionId,\n error: String(cacheOutcome.reason),\n })\n }\n if (messagesOutcome.status === 'rejected') {\n log?.('[chat-routes] completed Sandbox session message lookup failed', {\n turnId,\n sessionId,\n error: String(messagesOutcome.reason),\n })\n }\n\n const rawCached = cacheOutcome.status === 'fulfilled' ? cacheOutcome.value : null\n const cached = rawCached\n && rawCached.turnId === turnId\n && rawCached.sessionId === sessionId\n ? rawCached\n : null\n if (rawCached && !cached) {\n log?.('[chat-routes] ignored mismatched completed Sandbox turn cache record', {\n turnId,\n sessionId,\n })\n }\n\n const messages = messagesOutcome.status === 'fulfilled'\n ? messagesOutcome.value\n : []\n const matchingMessages = completedMessagesForTurn(messages, turnId)\n const matchingCount = matchingMessages.length\n const message = matchingCount === 1 ? matchingMessages[0]! : null\n if (matchingCount > 1) {\n log?.('[chat-routes] ignored ambiguous completed Sandbox session messages', {\n turnId,\n sessionId,\n matchingCount,\n })\n }\n\n if (!cached && !message) {\n // A failed read is unknown, not proof that the turn is absent. The durable\n // owner must retry observation rather than admit another execution.\n if (cacheOutcome.status === 'rejected' || messagesOutcome.status === 'rejected') {\n throw new Error('Completed Sandbox turn could not be verified; retry the exact read')\n }\n if (rawCached || matchingCount > 1) {\n throw new Error('Completed Sandbox turn identity is inconsistent; reconciliation is required')\n }\n return null\n }\n\n // Only turn-addressed evidence is safe. A newer turn can complete between\n // reading the message list and reading a session-wide aggregate result.\n const result = cached ? asRecord(cached.result) : undefined\n\n const recovered = message ? normalizedMessageParts(message) : null\n const text = textFromResult(result) ?? recovered?.derivedText\n const resultUsage = usageFromResult(result)\n const usage = { ...(recovered?.usage ?? {}), ...resultUsage }\n const resultParts = Array.isArray(result?.parts)\n ? result.parts\n .map((part) => asRecord(part))\n .filter((part): part is JsonRecord => Boolean(part))\n .map((part) => normalizePersistedPart(part))\n .filter((part): part is JsonRecord => Boolean(part))\n : null\n const sourceParts = recovered?.parts ?? resultParts\n const parts = sourceParts\n ? projectMessageParts(sourceParts, text ?? recovered?.derivedText ?? '')\n : undefined\n\n return {\n ...(text !== undefined ? { text } : {}),\n ...(hasUsage(usage) ? { usage } : {}),\n ...(parts ? { parts } : {}),\n }\n}\n","/**\n * Exact terminal observation for a native Sandbox execution that another\n * request already admitted. This module never sends a prompt, calls\n * `driveTurn`, or cancels a run.\n */\n\nimport type { SandboxInstance, SessionInfo, SessionMessage } from '@tangle-network/sandbox'\nimport type { ChatTurnUsage } from './turn-routes'\nimport { readCompletedSandboxTurn, recoverSandboxAssistantMessage } from './completed-sandbox-turn'\n\nexport interface NativeCompletionReceipt {\n state: 'completed' | 'failed'\n text: string\n parts: Array<Record<string, unknown>>\n /** An empty object is an unknown usage receipt, never a measured zero. */\n usage: ChatTurnUsage\n servedModel?: string\n servedProvider?: string\n servedSource?: 'request' | 'environment' | 'profile'\n error?: string\n completedTurnIds: string[]\n}\n\nexport interface NativeCompletionAdmission {\n executionId: string\n state: 'open' | 'closed'\n admittedTurnIds: readonly string[]\n ownerLeaseUntil: Date | number\n updatedAt?: Date | number\n closedAt?: Date | number | null\n}\n\n/** Product persistence for the one admission that authorizes this observer. */\nexport interface NativeCompletionAdmissionStore {\n read(executionId: string): Promise<NativeCompletionAdmission | null>\n renew(executionId: string, now: Date): Promise<NativeCompletionAdmission | null>\n closeExpired(executionId: string, now: Date): Promise<NativeCompletionAdmission | null>\n}\n\n/** The official Sandbox reads needed for exact native completion observation. */\nexport type NativeCompletionSessionSource = Pick<SandboxInstance, 'findCompletedTurn' | 'session'>\n\nexport interface NativeCompletionObservationOptions {\n /** Null when the product cannot currently resolve the admitted Sandbox. */\n source: NativeCompletionSessionSource | null\n admissionStore: NativeCompletionAdmissionStore\n executionId: string\n sessionId: string\n turnId: string\n registeredAt: number\n absentDispatchDeadlineMs?: number\n receiptDeadlineMs?: number\n now?: number\n}\n\nexport type NativeCompletionObservation =\n | { state: 'running' }\n | { state: 'completed'; receipt: NativeCompletionReceipt }\n | { state: 'failed'; receipt: NativeCompletionReceipt }\n\n/** One exact turn receipt before an admission's ordered aggregate. */\nexport interface NativeCompletionTurnReceipt {\n turnId: string\n state: 'completed' | 'failed'\n text: string\n parts: Array<Record<string, unknown>>\n usage: ChatTurnUsage\n servedModel?: string\n servedProvider?: string\n servedSource?: 'request' | 'environment' | 'profile'\n error?: string\n}\n\nconst DEFAULT_ABSENT_DISPATCH_DEADLINE_MS = 10 * 60_000\nconst DEFAULT_RECEIPT_DEADLINE_MS = 10 * 60_000\n\nfunction timestamp(value: Date | number | null | undefined): number | undefined {\n if (value instanceof Date) return value.getTime()\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined\n}\n\nfunction nonEmptyString(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() ? value : undefined\n}\n\nfunction record(value: unknown): Record<string, unknown> | undefined {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n ? value as Record<string, unknown>\n : undefined\n}\n\nfunction isSandboxNotFoundError(value: unknown): boolean {\n const error = record(value)\n return error?.name === 'NotFoundError' || error?.code === 'NOT_FOUND' || error?.status === 404\n}\n\nfunction attribution(result: Record<string, unknown> | undefined): Pick<\n NativeCompletionTurnReceipt,\n 'servedModel' | 'servedProvider' | 'servedSource'\n> {\n const metadata = record(result?.metadata)\n const modelAttribution = record(result?.modelAttribution) ?? record(metadata?.modelAttribution)\n const backend = record(result?.effectiveBackend) ?? record(metadata?.effectiveBackend)\n const source = nonEmptyString(result?.servedSource)\n ?? nonEmptyString(modelAttribution?.servedSource)\n ?? nonEmptyString(backend?.source)\n const servedSource = source === 'request' || source === 'environment' || source === 'profile'\n ? source\n : undefined\n const servedModel = nonEmptyString(result?.servedModel)\n ?? nonEmptyString(modelAttribution?.servedModel)\n ?? nonEmptyString(backend?.model)\n const servedProvider = nonEmptyString(result?.servedProvider)\n ?? nonEmptyString(modelAttribution?.servedProvider)\n ?? nonEmptyString(backend?.provider)\n return {\n ...(servedModel ? { servedModel } : {}),\n ...(servedProvider ? { servedProvider } : {}),\n ...(servedSource ? { servedSource } : {}),\n }\n}\n\nfunction usageFromResult(result: Record<string, unknown> | undefined): ChatTurnUsage {\n const raw = record(result?.usage) ?? record(result?.tokenUsage)\n const number = (value: unknown): number | undefined =>\n typeof value === 'number' && Number.isFinite(value) ? value : undefined\n return {\n ...(number(raw?.inputTokens) !== undefined ? { inputTokens: number(raw?.inputTokens) } : {}),\n ...(number(raw?.outputTokens) !== undefined ? { outputTokens: number(raw?.outputTokens) } : {}),\n ...(number(raw?.cacheReadTokens) !== undefined ? { cacheReadTokens: number(raw?.cacheReadTokens) } : {}),\n ...(number(raw?.cacheWriteTokens) !== undefined ? { cacheWriteTokens: number(raw?.cacheWriteTokens) } : {}),\n ...(number(result?.costUsd) !== undefined ? { costUsd: number(result?.costUsd) } : {}),\n }\n}\n\nfunction interruptedMessages(messages: SessionMessage[], turnId: string): SessionMessage[] {\n return messages.filter((message) =>\n message.role === 'assistant'\n && message.metadata?.turnId === turnId\n && (message.metadata.interrupted === true || message.metadata.status === 'interrupted'),\n )\n}\n\nfunction mergeUsage(messageUsage: ChatTurnUsage, resultUsage: ChatTurnUsage): ChatTurnUsage {\n return { ...messageUsage, ...resultUsage }\n}\n\nfunction consistent<T>(values: Array<T | undefined>): T | undefined {\n if (values.length === 0 || values.some((value) => value === undefined)) return undefined\n const first = values[0] as T\n return values.every((value) => value === first) ? first : undefined\n}\n\nfunction sum(values: Array<number | undefined>): number | undefined {\n if (values.some((value) => value === undefined)) return undefined\n return (values as number[]).reduce((total, value) => total + value, 0)\n}\n\n/** Assemble admitted exact receipts in dispatch order. Missing usage stays unknown. */\nexport function aggregateNativeCompletionReceipts(turns: readonly NativeCompletionTurnReceipt[]): NativeCompletionReceipt {\n const failed = turns.some((turn) => turn.state === 'failed')\n const usage: ChatTurnUsage = {}\n for (const key of ['inputTokens', 'outputTokens', 'reasoningTokens', 'cacheReadTokens', 'cacheWriteTokens', 'costUsd'] as const) {\n const total = sum(turns.map((turn) => turn.usage[key]))\n if (total !== undefined) usage[key] = total\n }\n // A missing exact receipt is the terminal observer failure even when an\n // earlier execution also failed. Preserve that earlier partial transcript,\n // but surface why this aggregate could not be fully reconciled.\n const error = turns.find((turn) =>\n turn.error?.startsWith('Admitted native execution did not produce')\n || turn.error?.startsWith('Native Sandbox execution ledger is missing'),\n )?.error ?? turns.find((turn) => turn.error)?.error\n return {\n state: failed ? 'failed' : 'completed',\n text: turns.map((turn) => turn.text).join(''),\n parts: turns.flatMap((turn) => turn.parts),\n usage,\n ...(consistent(turns.map((turn) => turn.servedModel)) ? { servedModel: consistent(turns.map((turn) => turn.servedModel)) } : {}),\n ...(consistent(turns.map((turn) => turn.servedProvider)) ? { servedProvider: consistent(turns.map((turn) => turn.servedProvider)) } : {}),\n ...(consistent(turns.map((turn) => turn.servedSource)) ? { servedSource: consistent(turns.map((turn) => turn.servedSource)) } : {}),\n ...(error ? { error } : {}),\n completedTurnIds: turns.map((turn) => turn.turnId),\n }\n}\n\nfunction missingTurnReceipt(turnId: string, error: string): NativeCompletionTurnReceipt {\n // An execution ledger entry proves this turn reached a terminal state, but\n // not that its terminal transcript was retained. Keep any earlier exact\n // evidence when the bounded wait expires; `{}` means aggregate usage is\n // unknown because this turn has no usage receipt.\n return { turnId, state: 'failed', text: '', parts: [], usage: {}, error }\n}\n\n/**\n * Observe only the turns admitted under `executionId`. Transport failures throw\n * so the durable owner retries. A missing or partial terminal record remains\n * `running` until the configured deadline; it never starts another run.\n */\nexport async function observeNativeCompletion(\n options: NativeCompletionObservationOptions,\n): Promise<NativeCompletionObservation> {\n const now = options.now ?? Date.now()\n let admission = await options.admissionStore.read(options.executionId)\n if (!admission) throw new Error('Native completion admission is missing')\n if (admission.executionId !== options.executionId) throw new Error('Native completion admission identity conflict')\n const leaseUntil = timestamp(admission.ownerLeaseUntil) ?? 0\n const source = options.source\n const session = source?.session(options.sessionId)\n let status: SessionInfo | null\n if (!session) {\n status = null\n } else {\n try {\n status = await session.status()\n } catch (error) {\n if (!isSandboxNotFoundError(error)) throw error\n status = null\n }\n }\n\n if (!status || !session || !source) {\n if (admission.state === 'open' && leaseUntil <= now) {\n admission = await options.admissionStore.closeExpired(options.executionId, new Date(now)) ?? admission\n }\n if (admission.state === 'open' || now - options.registeredAt < (options.absentDispatchDeadlineMs ?? DEFAULT_ABSENT_DISPATCH_DEADLINE_MS)) {\n return { state: 'running' }\n }\n return {\n state: 'failed',\n receipt: aggregateNativeCompletionReceipts([\n missingTurnReceipt(options.turnId, 'Native Sandbox dispatch was not observed before the admission deadline'),\n ]),\n }\n }\n const exactSession = session\n\n if (admission.state === 'open') {\n if (status.status === 'queued' || status.status === 'running') {\n admission = await options.admissionStore.renew(options.executionId, new Date(now)) ?? admission\n } else if (leaseUntil <= now) {\n admission = await options.admissionStore.closeExpired(options.executionId, new Date(now)) ?? admission\n }\n if (admission.state === 'open') return { state: 'running' }\n }\n // `closeExpired` may return the current row after another owner appended a\n // continuation. Read its full ordered ledger rather than observing the\n // earlier snapshot from before the close attempt.\n const admittedTurnIds = [...admission.admittedTurnIds]\n if (admittedTurnIds.length === 0 || admittedTurnIds[0] !== options.turnId) {\n throw new Error('Native completion admission has an invalid turn sequence')\n }\n\n const runs = await exactSession.runs()\n const runsByTurnId = new Map(runs.map((run) => [run.executionId, run]))\n const missingExecution = admittedTurnIds.find((turnId) => !runsByTurnId.has(turnId))\n if (missingExecution && now - options.registeredAt < (options.absentDispatchDeadlineMs ?? DEFAULT_ABSENT_DISPATCH_DEADLINE_MS)) {\n return { state: 'running' }\n }\n if (admittedTurnIds.some((turnId) => runsByTurnId.get(turnId)?.status === 'active')) return { state: 'running' }\n\n const messages = await exactSession.messages({ limit: 1_000 })\n const recovered: NativeCompletionTurnReceipt[] = []\n for (const turnId of admittedTurnIds) {\n const run = runsByTurnId.get(turnId)\n if (!run) {\n recovered.push(missingTurnReceipt(turnId, 'Native Sandbox execution ledger is missing the admitted turn'))\n continue\n }\n const completed = run.status === 'completed'\n ? await readCompletedSandboxTurn(source, { turnId, sessionId: options.sessionId })\n : null\n const cached = run.status === 'completed'\n ? await source.findCompletedTurn(turnId, { sessionId: options.sessionId })\n : null\n if (run.status === 'completed' && completed) {\n recovered.push({\n turnId,\n state: 'completed',\n text: completed.text ?? '',\n parts: completed.parts ?? [],\n usage: completed.usage ?? {},\n ...attribution(cached?.result),\n })\n continue\n }\n const interrupted = interruptedMessages(messages, turnId)\n if ((run.status === 'failed' || run.status === 'cancelled') && interrupted.length === 1) {\n const message = recoverSandboxAssistantMessage(interrupted[0]!)\n const result = await exactSession.result({ executionId: turnId })\n recovered.push({\n turnId,\n state: 'failed',\n text: message.text ?? result.response ?? '',\n parts: message.parts ?? [],\n usage: mergeUsage(message.usage ?? {}, {\n ...usageFromResult(result as unknown as Record<string, unknown>),\n ...(result.costUsd !== undefined ? { costUsd: result.costUsd } : {}),\n }),\n ...attribution(result as unknown as Record<string, unknown>),\n error: result.error ?? interrupted[0]!.metadata?.interruptReason ?? status.failureReason?.message,\n })\n continue\n }\n const closedAt = timestamp(admission.closedAt)\n ?? timestamp(admission.updatedAt)\n ?? options.registeredAt\n if (now - closedAt < (options.receiptDeadlineMs ?? DEFAULT_RECEIPT_DEADLINE_MS)) return { state: 'running' }\n recovered.push(missingTurnReceipt(\n turnId,\n `Admitted native execution did not produce an exact completion: ${turnId}`,\n ))\n }\n const receipt = aggregateNativeCompletionReceipts(recovered)\n return receipt.state === 'completed'\n ? { state: 'completed', receipt }\n : { state: 'failed', receipt }\n}\n","import { getPartKey, mergePersistedPart, type StreamEvent } from '../stream/index'\nimport type { ChatTurnRouteProducer } from './turn-routes'\n\n/** Resolve chat route events and materialize their durable state records */\nexport interface ChatRouteDurableProjection {\n observe(event: unknown): void | Promise<void>\n materialize(): Array<Record<string, unknown>> | Promise<Array<Record<string, unknown>>>\n}\n\n/** Log chat route projection messages with optional metadata for durable processing */\nexport type ChatRouteDurableProjectionLogger =\n (message: string, meta?: Record<string, unknown>) => void\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/** Adds durable lifecycle projection to any producer lane without moving its\n * transport into agent-app. Purely STRUCTURAL: agent-app ships no implementer\n * of {@link ChatRouteDurableProjection} and deliberately does not — the one it\n * used to ship (`/durable-chat`) was removed in 0.44.0 with zero fleet imports.\n * Pass any `{ observe, materialize }` object backed by your own store. The projection is observed inline and its\n * materialized parts replace same-key pending snapshots after the stream\n * drains. Projection persistence is best-effort for the live lane: a store\n * outage must not terminate an otherwise healthy sandbox stream. Failures are\n * reported through the optional logger so products can retain diagnostics. */\nexport function withDurableChatProjection(\n producer: ChatTurnRouteProducer,\n projection: ChatRouteDurableProjection,\n log: ChatRouteDurableProjectionLogger = (message, meta) => console.error(message, meta ?? ''),\n): ChatTurnRouteProducer {\n let projected: Array<Record<string, unknown>> = []\n async function* stream(): AsyncGenerator<StreamEvent, void, unknown> {\n for await (const event of producer.stream) {\n try {\n await projection.observe(event)\n } catch (error) {\n log('[chat-routes] durable projection observe failed', {\n eventType: event.type,\n error: errorMessage(error),\n })\n }\n yield event\n }\n try {\n projected = await projection.materialize()\n } catch (error) {\n log('[chat-routes] durable projection materialize failed', {\n error: errorMessage(error),\n })\n }\n }\n return {\n ...producer,\n stream: stream(),\n assistantParts: () => {\n const parts = producer.assistantParts?.() ?? []\n const order: string[] = []\n const byKey = new Map<string, Record<string, unknown>>()\n for (const part of [...parts, ...projected]) {\n const key = getPartKey(part)\n if (!byKey.has(key)) order.push(key)\n byKey.set(key, mergePersistedPart(byKey.get(key), part))\n }\n return order.map((key) => byKey.get(key)!)\n },\n }\n}\n","/**\n * `createUploadRoute` — the multimodal middle. Accepts multipart file uploads\n * and returns `PromptInputPart`-shaped descriptors the client echoes back on\n * send (`ChatTurnRequestPayload.parts`):\n *\n * ≤ inlineMaxBytes (700 KiB default) → inline `data:` URI part — rides the\n * turn body directly, no sandbox round trip.\n * > inlineMaxBytes → written into the sandbox workspace (base64 through the\n * structural `write` seam — `box.fs` satisfies it) and referenced by\n * `path`. Mandatory two-step: the gateway caps request bodies at ~1 MiB,\n * so a large file can never ride the prompt POST.\n *\n * The sink is structural (no sandbox-SDK import); products pass `box.fs`.\n *\n * @remarks Sole consumer today is the `--chat` scaffold (`create-agent-app\n * --chat` → `template-chat/src/chat.ts`), the reference multimodal path — its\n * inline-`data:`-or-ephemeral-sandbox-workspace split stays the scaffold's\n * proven upload pattern, not a fleet primitive; keep that distinction in mind\n * before widening its surface. Fleet apps with a durable store of their own\n * (KV, or AES-GCM-encrypted R2) no longer need to hand-roll a vault upload\n * route: `createAttachmentUploadRoute` (`./attachment-upload`, agent-app#234)\n * is the shared hardened path for that persistence model — a content-sniffed\n * type gate, two-phase atomic batch writes, and per-kind/aggregate size caps,\n * all seamed through an injected `WriteAttachmentFn`. Point readers there\n * instead of widening this route to cover both models.\n */\n\nimport type { ChatTurnFilePartInput } from './wire'\n\n/** 700 KiB: base64 inflates ~4/3, so an inline part stays comfortably under\n * the ~1 MiB gateway body cap alongside the JSON envelope. */\nexport const UPLOAD_INLINE_MAX_BYTES = 700 * 1024\n\n/** 8 MiB default ceiling per file — one base64 `write` call handles it. Raise\n * it only with a sink that can take the bigger single write. */\nexport const UPLOAD_MAX_FILE_BYTES = 8 * 1024 * 1024\n\nconst DEFAULT_UPLOAD_DIRECTORY = '/workspace/uploads'\n\nfunction normalizeUploadDirectory(value: string): string | null {\n const withoutTrailingSlash = value.trim().replace(/\\/+$/, '')\n if (!withoutTrailingSlash.startsWith('/') || withoutTrailingSlash.length === 0) return null\n const segments = withoutTrailingSlash.slice(1).split('/')\n if (segments.some((segment) => segment.length === 0 || segment === '.' || segment === '..')) return null\n return `/${segments.join('/')}`\n}\n\n/** Structural match of the sandbox SDK's `box.fs` write surface (v0.10.5+:\n * `encoding: 'base64'` is the worker-safe binary path). */\nexport interface SandboxUploadSink {\n write(path: string, content: string, options?: { encoding?: 'utf8' | 'base64' }): Promise<unknown>\n}\n\n/** Resolve upload authorization status and provide upload destination or error response */\nexport type UploadAuthorization =\n | {\n ok: true\n /** Where large files land. Absent/null: only inline uploads are\n * accepted and an over-inline-cap file is rejected with 413. */\n sink?: SandboxUploadSink | null\n /** Per-request override of the workspace directory large files go to. */\n uploadDir?: string\n }\n | { ok: false; response: Response }\n\n/** Define options to authorize uploads and configure file size limits and upload directory */\nexport interface CreateUploadRouteOptions {\n /** Authenticate the caller and resolve the sandbox file sink (usually\n * `ensureWorkspaceSandbox(...)` → `box.fs`). */\n authorize(args: { request: Request }): Promise<UploadAuthorization>\n /** Inline-vs-sandbox threshold. Default {@link UPLOAD_INLINE_MAX_BYTES}. */\n inlineMaxBytes?: number\n /** Hard per-file cap. Default {@link UPLOAD_MAX_FILE_BYTES}. */\n maxFileBytes?: number\n /** Absolute workspace directory for path-ref files.\n * Default `'/workspace/uploads'`. */\n uploadDir?: string\n}\n\n/** One uploaded file, ready for the composer chip and the turn body. */\nexport interface UploadedChatFile {\n id: string\n name: string\n size: number\n mediaType: string\n /** True when the part carries the bytes inline (`data:` URI). */\n inline: boolean\n /** Echo this back verbatim in `ChatTurnRequestPayload.parts`. */\n part: ChatTurnFilePartInput\n}\n\n/** Path-safe file name: basename only, conservative charset, length-capped. */\nexport function sanitizeUploadFilename(name: string): string {\n const base = name.split(/[\\\\/]/).pop() ?? 'file'\n const safe = base.replace(/[^A-Za-z0-9._-]+/g, '_').replace(/^\\.+/, '_')\n return (safe || 'file').slice(0, 120)\n}\n\nconst BASE64_CHUNK = 0x8000\n\n/** Convert a Uint8Array of bytes into a base64-encoded string */\nexport function bytesToBase64(bytes: Uint8Array): string {\n let binary = ''\n for (let offset = 0; offset < bytes.length; offset += BASE64_CHUNK) {\n binary += String.fromCharCode(...bytes.subarray(offset, offset + BASE64_CHUNK))\n }\n return btoa(binary)\n}\n\nfunction uploadError(status: number, code: string, error: string): Response {\n return Response.json({ code, error }, { status })\n}\n\n/** Create an upload route handler that authorizes requests and processes file uploads with size limits */\nexport function createUploadRoute(options: CreateUploadRouteOptions): (request: Request) => Promise<Response> {\n const inlineMaxBytes = options.inlineMaxBytes ?? UPLOAD_INLINE_MAX_BYTES\n const maxFileBytes = options.maxFileBytes ?? UPLOAD_MAX_FILE_BYTES\n\n return async function upload(request: Request): Promise<Response> {\n const auth = await options.authorize({ request })\n if (!auth.ok) return auth.response\n const sink = auth.sink ?? null\n const configuredUploadDir = auth.uploadDir ?? options.uploadDir ?? DEFAULT_UPLOAD_DIRECTORY\n\n let form: FormData\n try {\n form = await request.formData()\n } catch {\n return uploadError(400, 'INVALID_UPLOAD', 'Expected a multipart/form-data body with file fields')\n }\n const files: File[] = []\n form.forEach((value) => {\n if (value instanceof File) files.push(value)\n })\n if (files.length === 0) {\n return uploadError(400, 'INVALID_UPLOAD', 'No files in the upload body')\n }\n\n const uploaded: UploadedChatFile[] = []\n for (const file of files) {\n const name = sanitizeUploadFilename(file.name)\n const mediaType = file.type || 'application/octet-stream'\n const partType: ChatTurnFilePartInput['type'] = mediaType.startsWith('image/') ? 'image' : 'file'\n\n if (file.size > maxFileBytes) {\n return uploadError(\n 413,\n 'FILE_TOO_LARGE',\n `${name} is ${file.size}B, over the ${maxFileBytes}B per-file cap`,\n )\n }\n\n const id = crypto.randomUUID()\n if (file.size <= inlineMaxBytes) {\n const base64 = bytesToBase64(new Uint8Array(await file.arrayBuffer()))\n uploaded.push({\n id,\n name,\n size: file.size,\n mediaType,\n inline: true,\n part: {\n type: partType,\n filename: name,\n mediaType,\n url: `data:${mediaType};base64,${base64}`,\n },\n })\n continue\n }\n\n if (!sink) {\n return uploadError(\n 413,\n 'SANDBOX_REQUIRED',\n `${name} is ${file.size}B, over the ${inlineMaxBytes}B inline cap, and no sandbox is available to hold it`,\n )\n }\n const uploadDir = normalizeUploadDirectory(configuredUploadDir)\n if (!uploadDir) {\n return uploadError(\n 500,\n 'INVALID_UPLOAD_DIRECTORY',\n 'uploadDir must be an absolute path without empty, \".\" or \"..\" segments',\n )\n }\n const path = `${uploadDir}/${id}-${name}`\n const base64 = bytesToBase64(new Uint8Array(await file.arrayBuffer()))\n await sink.write(path, base64, { encoding: 'base64' })\n uploaded.push({\n id,\n name,\n size: file.size,\n mediaType,\n inline: false,\n part: { type: partType, filename: name, mediaType, path },\n })\n }\n\n return Response.json({ files: uploaded })\n }\n}\n","/**\n * Storage seams for the chat-attachment vertical (`resolveChatAttachments`,\n * `buildDispatchParts`, `promoteAgentFilePart`). Structural function ports in\n * the same style as `upload.ts`'s `SandboxUploadSink`: REQUIRED injection, no\n * default implementation — agent-app owns the size/budget/idempotency\n * mechanism, the product owns where the bytes actually live.\n *\n * One reader, two callers: `resolveChatAttachments` reads only the\n * authoritative `size` (never trusting the client-reported size), while\n * `buildDispatchParts` reads the inline `content` (base64 or raw bytes) to\n * build a `data:` URI. Both fit behind ONE `ReadAttachmentFn` so a product\n * wires a single vault/object-store adapter, not two. gtm's KV vault (stores\n * base64 bodies) and `/object-store`'s `ObjectStore` (hands back raw bytes)\n * both satisfy this shape.\n */\n\n/**\n * The result of reading one stored attachment. `ok:true` MUST carry the\n * authoritative decoded byte `size` (the cap is meaningless against a\n * client-controlled number) and, when the caller needs to inline the file,\n * its content as `base64` and/or raw `bytes`. `mediaType` is the stored\n * content type when the store knows it (used as the fallback when the wire\n * part carried none). `ok:false` carries a human `reason` that SHOULD name the\n * offending path — it is surfaced verbatim in the caller's typed outcome.\n */\nexport type AttachmentReadResult =\n | {\n ok: true\n /** Authoritative decoded byte length of the stored content. */\n size: number\n /** Inline content as base64 — reused verbatim for a `data:` URI, so a\n * store holding already-base64 bodies never decodes-and-re-encodes. */\n base64?: string\n /** Inline content as raw bytes — base64-encoded by the caller when a\n * `data:` URI is needed. Ignored when `base64` is present. */\n bytes?: Uint8Array\n /** Stored content type, when the store tracks one. */\n mediaType?: string\n }\n | { ok: false; reason: string }\n\n/**\n * Read one stored attachment for `scopeId` (the product's workspace/tenant\n * key) at its store-relative `path`. Missing, deleted, or unreadable content\n * MUST resolve to `{ ok: false, reason }`, never throw — a store failure is a\n * per-attachment outcome the caller folds into its own typed result, not a\n * turn-level exception.\n */\nexport type ReadAttachmentFn = (scopeId: string, path: string) => Promise<AttachmentReadResult>\n\n/** The ownership identity for one attempted write. The upload and promotion\n * routes create a unique path for each attempt, and the product adapter stores\n * this id with it. */\nexport interface AttachmentWriteOwnership {\n readonly id: string\n readonly path: string\n}\n\n/** Public-safe outcome for a storage outage. Backend details belong in logs. */\nexport const ATTACHMENT_STORAGE_FAILURE_MESSAGE = 'Attachment storage is temporarily unavailable. Please try again.'\n\n/** Stable client error code when compensating cleanup did not complete. */\nexport const ATTACHMENT_ROLLBACK_FAILURE_CODE = 'rollback_failed'\n\n/** Public-safe outcome when compensating cleanup did not complete. */\nexport const ATTACHMENT_ROLLBACK_FAILURE_MESSAGE = 'Attachment cleanup failed. Please try again.'\n\n/** Add a path-safe ownership id to a logical path and return an immutable store key. */\nexport function immutableAttachmentPath(logicalPath: string, ownershipId: string): string {\n if (!/^[A-Za-z0-9_-]{1,128}$/.test(ownershipId)) {\n throw new Error('attachment ownership id must be a path-safe identifier')\n }\n return `${logicalPath}--${ownershipId}`\n}\n\n/** Compensation for one attachment write. The adapter MUST compare the stored\n * ownership id before deleting, so an older rollback cannot delete a newer\n * overwrite. */\nexport interface AttachmentWriteReceipt {\n rollback(): void | Promise<void>\n ownership: AttachmentWriteOwnership\n}\n\n/** Clean up a write whose function threw after the store may have committed.\n * The adapter must delete only the supplied ownership, never the logical path\n * unconditionally. */\nexport type AbortAttachmentWriteFn = (\n scopeId: string,\n ownership: AttachmentWriteOwnership,\n) => void | Promise<void>\n\n/**\n * The stable writer result from the original attachment-store contract.\n * Existing products may return this shape and keep using the legacy writer\n * lane. New products should use {@link AtomicAttachmentWriteResult}.\n */\nexport type AttachmentWriteResult = { ok: true } | { ok: false; reason: string }\n\n/** Options from the stable attachment-store contract. */\nexport interface AttachmentWriteOptions {\n mediaType?: string\n name?: string\n originalName?: string\n size?: number\n}\n\n/**\n * The stable writer port. It is intentionally unchanged so products released\n * before the ownership receipt contract continue to compile.\n *\n * The legacy lane does not promise batch rollback. Use\n * {@link createAtomicAttachmentWriter} for ownership-safe writes.\n */\nexport type WriteAttachmentFn = (\n scopeId: string,\n path: string,\n content: Uint8Array | string,\n opts: AttachmentWriteOptions,\n) => Promise<AttachmentWriteResult>\n\n/** Options for an ownership-safe writer. */\nexport interface AtomicAttachmentWriteOptions extends AttachmentWriteOptions {\n ownership: AttachmentWriteOwnership\n}\n\n/** Result for an ownership-safe writer. A receipt is required on both paths. */\nexport type AtomicAttachmentWriteResult =\n | { ok: true; receipt: AttachmentWriteReceipt }\n | { ok: false; reason: string; receipt: AttachmentWriteReceipt }\n\n/** Ownership-safe writer port used by the atomic upload and promotion lanes. */\nexport type AtomicWriteAttachmentFn = (\n scopeId: string,\n path: string,\n content: Uint8Array | string,\n opts: AtomicAttachmentWriteOptions,\n) => Promise<AtomicAttachmentWriteResult>\n\n/** A complete ownership-safe attachment store adapter. */\nexport interface AtomicAttachmentWriter {\n write: AtomicWriteAttachmentFn\n abort: AbortAttachmentWriteFn\n}\n\n/**\n * Build the explicit atomic adapter used by new routes.\n *\n * Keeping the write and abort functions together prevents a caller from\n * enabling ownership paths while forgetting the ambiguous-write cleanup.\n */\nexport function createAtomicAttachmentWriter(input: AtomicAttachmentWriter): AtomicAttachmentWriter {\n return {\n write: input.write.bind(input),\n abort: input.abort.bind(input),\n }\n}\n","/**\n * `resolveChatAttachments` — validate a turn body's `attachments` field into\n * persistable {@link ChatAttachmentPart}s. Every path is re-validated (a path\n * off the wire is never trusted to stay inside the store root) and every size\n * is re-derived from the STORED body via the injected {@link ReadAttachmentFn},\n * never the client-reported `size` — the upload path lets a caller rewrite its\n * own frontmatter, so a stored size cannot bound anything and the wire size can\n * be anything. Both the aggregate cap and the size carried on the returned part\n * come from the authoritative read.\n *\n * Storage-parameterized: the frontmatter parsing / base64 sizing that derives\n * the authoritative size lives BEHIND `readAttachment` (a product's vault or\n * object-store adapter), so this module is a pure validator + budget gate with\n * no store knowledge. Lifted from gtm-agent's `resolve-attachments.ts`\n * (workspaceId → scopeId, the vault read → the injected reader) and kept\n * behavior-identical for gtm-agent#618 adoption.\n */\n\nimport type { ChatAttachmentInput, ChatAttachmentKind } from './wire'\nimport { attachmentInputToPart, type ChatAttachmentPart } from '../chat-store/parts'\nimport type { ReadAttachmentFn } from './attachment-store'\nimport { ATTACHMENT_MAX_COUNT, MAX_ATTACHMENT_TOTAL_BYTES, attachmentTotalSizeErrorMessage } from './attachment-validation'\n\n/** Resolve the result of chat attachment processing with success status and corresponding data or error */\nexport type ResolveChatAttachmentsResult =\n | { succeeded: true; value: ChatAttachmentPart[] }\n | { succeeded: false; error: string }\n\n/** Verdict of a path check: OK, or a rejection naming why. Mirrors\n * `SandboxMentionPathCheck` in `./wire`. */\nexport type AttachmentPathCheck =\n | { succeeded: true }\n | { succeeded: false; error: string }\n\n/** Longest attachment display name accepted — bounds what gets echoed into the\n * prompt block and rendered as a chip label. */\nconst MAX_ATTACHMENT_NAME_LENGTH = 256\n\nfunction isAttachmentKind(value: unknown): value is ChatAttachmentKind {\n return value === 'image' || value === 'file'\n}\n\n/** C0 control characters (0x00–0x1F) plus DEL (0x7F) — covers `\\n`/`\\r`/`\\t`.\n * A `name` or `path` carrying one of these has no legitimate use here and\n * everything to gain from an attacker: {@link buildAttachmentPromptBlock} in\n * `/chat-store` renders both fields verbatim into the dispatched agent\n * prompt, so an embedded newline fabricates new prompt lines (a\n * prompt-injection vector) rather than naming a file. Rejected at this\n * boundary — not merely neutralized downstream — so the wire never accepts\n * the input in the first place; a legitimate name/path never contains one. */\nconst CONTROL_CHARS = /[\\x00-\\x1F\\x7F]/\n\n/**\n * Default path validator when a caller supplies none. Rejects the ways a path\n * picked in a client can escape the store root — traversal (`..` segment),\n * absolute (leading `/`), backslashes, null bytes, control characters (see\n * {@link CONTROL_CHARS} — a path also feeds {@link buildAttachmentPromptBlock}'s\n * `(vault: ${path})` pointer, so it is exposed to the same injection surface as\n * `name`) — plus a dotfile/hidden segment (a leading `.` on any segment).\n * Generalized from gtm's `validateVaultFilePath`, in the spirit of\n * `validateSandboxMentionPath` (`/chat-routes`'s wire mention-path validator) —\n * but the dotfile rejection here is INTENTIONALLY stricter than that sibling:\n * an uploaded attachment path is sanitized store-relative storage the product\n * itself assigned, whereas a mention path points at a file that already exists\n * in the sandbox and may legitimately live under a dotfile segment. A caller\n * that needs gtm's exact (looser) rule can supply `validatePath` to override\n * this default entirely.\n */\nexport function defaultValidateAttachmentPath(path: string): AttachmentPathCheck {\n if (path.includes('\\0')) return { succeeded: false, error: 'attachment path must not contain null bytes' }\n if (path.includes('\\\\')) return { succeeded: false, error: 'attachment path must not contain backslashes' }\n if (CONTROL_CHARS.test(path)) return { succeeded: false, error: 'attachment path must not contain control characters' }\n if (path.startsWith('/')) return { succeeded: false, error: 'attachment path must be store-relative, not absolute' }\n const segments = path.split('/')\n if (segments.some((segment) => segment === '..')) {\n return { succeeded: false, error: 'attachment path must not contain \"..\" segments' }\n }\n if (segments.some((segment) => segment.startsWith('.'))) {\n return { succeeded: false, error: 'attachment path must not contain a hidden (dotfile) segment' }\n }\n return { succeeded: true }\n}\n\nfunction parseAttachmentInput(\n value: unknown,\n index: number,\n validatePath: (path: string) => AttachmentPathCheck,\n): { succeeded: true; value: ChatAttachmentInput } | { succeeded: false; error: string } {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return { succeeded: false, error: `attachments[${index}] must be an object` }\n }\n const record = value as Record<string, unknown>\n\n const path = record.path\n if (typeof path !== 'string' || !path) {\n return { succeeded: false, error: `attachments[${index}].path must be a non-empty string` }\n }\n const name = record.name\n if (typeof name !== 'string' || !name.trim()) {\n return { succeeded: false, error: `attachments[${index}].name must be a non-empty string` }\n }\n if (name.length > MAX_ATTACHMENT_NAME_LENGTH) {\n return { succeeded: false, error: `attachments[${index}].name must not exceed ${MAX_ATTACHMENT_NAME_LENGTH} characters` }\n }\n if (CONTROL_CHARS.test(name)) {\n return { succeeded: false, error: `attachments[${index}].name must not contain control characters` }\n }\n const size = record.size\n if (typeof size !== 'number' || !Number.isFinite(size)) {\n return { succeeded: false, error: `attachments[${index}].size must be a finite number` }\n }\n if (size < 0) {\n return { succeeded: false, error: `attachments[${index}].size must not be negative` }\n }\n const mediaType = record.mediaType\n if (typeof mediaType !== 'string') {\n return { succeeded: false, error: `attachments[${index}].mediaType must be a string` }\n }\n const kind = record.kind\n if (!isAttachmentKind(kind)) {\n return { succeeded: false, error: `attachments[${index}].kind must be \"image\" or \"file\"` }\n }\n\n const pathCheck = validatePath(path)\n if (!pathCheck.succeeded) return { succeeded: false, error: pathCheck.error }\n\n return { succeeded: true, value: { path, name, size, mediaType, kind } }\n}\n\n/** Define options to resolve and validate chat attachments with size, count, and path constraints */\nexport interface ResolveChatAttachmentsOptions {\n /** The product's workspace/tenant key, passed to `readAttachment`. */\n scopeId: string\n /** Authoritative size + content reader — see {@link ReadAttachmentFn}. */\n readAttachment: ReadAttachmentFn\n /** Most attachments one request may carry. Default {@link ATTACHMENT_MAX_COUNT}. */\n maxCount?: number\n /** Aggregate raw-byte ceiling. Default {@link MAX_ATTACHMENT_TOTAL_BYTES}. */\n maxTotalBytes?: number\n /** Path validator override. Default {@link defaultValidateAttachmentPath}. */\n validatePath?: (path: string) => AttachmentPathCheck\n}\n\n/**\n * Validate and resolve a turn body's `attachments` field into persistable\n * parts. Every path is confirmed present (and not deleted) in the caller's own\n * store by `readAttachment` before it is trusted, and size is derived from the\n * authoritative read for both the aggregate cap and the returned part's size.\n */\nexport async function resolveChatAttachments(\n value: unknown,\n options: ResolveChatAttachmentsOptions,\n): Promise<ResolveChatAttachmentsResult> {\n const maxCount = options.maxCount ?? ATTACHMENT_MAX_COUNT\n const maxTotalBytes = options.maxTotalBytes ?? MAX_ATTACHMENT_TOTAL_BYTES\n const validatePath = options.validatePath ?? defaultValidateAttachmentPath\n\n if (value === undefined || value === null) return { succeeded: true, value: [] }\n if (!Array.isArray(value)) return { succeeded: false, error: 'attachments must be an array' }\n if (value.length > maxCount) {\n return { succeeded: false, error: `attachments must not exceed ${maxCount} entries` }\n }\n\n const inputs: ChatAttachmentInput[] = []\n const seenPaths = new Set<string>()\n for (let index = 0; index < value.length; index += 1) {\n const parsed = parseAttachmentInput(value[index], index, validatePath)\n if (!parsed.succeeded) return parsed\n if (seenPaths.has(parsed.value.path)) {\n return { succeeded: false, error: `attachments must not repeat a path: ${parsed.value.path}` }\n }\n seenPaths.add(parsed.value.path)\n inputs.push(parsed.value)\n }\n\n // Advisory only: client-controlled, so a dishonest caller can slip past it —\n // but an honest oversized request should fail before it costs a single store\n // read. The authoritative check runs per-attachment below, against the\n // body-derived size.\n const advisoryTotal = inputs.reduce((sum, input) => sum + input.size, 0)\n if (advisoryTotal > maxTotalBytes) {\n return { succeeded: false, error: attachmentTotalSizeErrorMessage(advisoryTotal, maxTotalBytes) }\n }\n\n // One attachment is read, sized, and dropped at a time rather than via\n // Promise.all — that would materialize every attachment's body in memory\n // simultaneously (ten truthful 10 MiB references is ~134 MiB of base64, over\n // a Workers heap) before the cap ever gets a chance to reject the request.\n // Sequential reads bound resident bytes to one attachment at a time and bail\n // the moment the running total exceeds the cap, at the cost of up to\n // `maxCount` serial reads instead of one parallel batch.\n let totalStoredBytes = 0\n for (const input of inputs) {\n const read = await options.readAttachment(options.scopeId, input.path)\n if (!read.ok) return { succeeded: false, error: read.reason }\n totalStoredBytes += read.size\n if (totalStoredBytes > maxTotalBytes) {\n return { succeeded: false, error: attachmentTotalSizeErrorMessage(totalStoredBytes, maxTotalBytes) }\n }\n input.size = read.size\n }\n\n return { succeeded: true, value: inputs.map(attachmentInputToPart) }\n}\n","import type {\n AtomicAttachmentWriteResult,\n AttachmentWriteOwnership,\n AttachmentWriteReceipt,\n AttachmentWriteResult,\n} from './attachment-store'\n\ntype ObjectLike = Record<PropertyKey, unknown>\n\nfunction objectLike(value: unknown): value is ObjectLike {\n return value !== null && (typeof value === 'object' || typeof value === 'function')\n}\n\n/** Read an injected writer result without allowing hostile getters to escape. */\nexport function inspectLegacyAttachmentWriteResult(\n value: unknown,\n): AttachmentWriteResult | undefined {\n try {\n if (!objectLike(value)) return undefined\n const ok = value.ok\n if (ok === true) return { ok: true }\n if (ok === false && typeof value.reason === 'string') {\n return { ok: false, reason: value.reason }\n }\n } catch {\n // A malformed or hostile adapter result is a storage failure.\n }\n return undefined\n}\n\n/**\n * Read an ownership-aware writer result with strict runtime checks.\n *\n * The public types protect TypeScript callers only. The product adapter is an\n * injection boundary, so a proxy, malformed value, or truthy non-boolean must\n * fail closed before the route invokes cleanup or publishes a path.\n */\nexport function inspectAtomicAttachmentWriteResult(\n value: unknown,\n ownership: AttachmentWriteOwnership,\n): AtomicAttachmentWriteResult | undefined {\n try {\n if (!objectLike(value)) return undefined\n const receiptValue = value.receipt\n if (!objectLike(receiptValue)) return undefined\n const receiptOwnership = receiptValue.ownership\n if (!objectLike(receiptOwnership)) return undefined\n if (\n receiptOwnership.id !== ownership.id ||\n receiptOwnership.path !== ownership.path ||\n typeof receiptOwnership.id !== 'string' ||\n typeof receiptOwnership.path !== 'string' ||\n typeof receiptValue.rollback !== 'function'\n ) {\n return undefined\n }\n if (value.ok === true) {\n return { ok: true, receipt: receiptValue as unknown as AttachmentWriteReceipt }\n }\n if (value.ok === false && typeof value.reason === 'string') {\n return {\n ok: false,\n reason: value.reason,\n receipt: receiptValue as unknown as AttachmentWriteReceipt,\n }\n }\n } catch {\n // A malformed or hostile adapter result is a storage failure.\n }\n return undefined\n}\n","/**\n * `promoteAgentFilePart` — turn a harness-emitted `type:\"file\"` stream part\n * into a store-backed {@link ChatAttachmentPart}. The harness hands back a URL\n * pointing at bytes it produced (a `data:` URI, or a path inside the sandbox);\n * nothing durable survives past the turn unless it is written into the\n * product's store, the same way a user upload is. Typed outcomes throughout:\n * every failure mode (unsupported scheme, no sandbox, oversize, store-write\n * failure, malformed part) resolves to `{ succeeded: false, filename, reason }`\n * rather than throwing past this boundary, so the caller folds a visible notice\n * instead of losing the file silently.\n *\n * Storage-parameterized port of gtm-agent's `promote-file-parts.ts` with the\n * refactor gtm never made: persistence goes through an injected stable writer\n * or ownership-safe adapter, the path strategy is the injected\n * `buildAttachmentPath` (neutral `uploads/agent/<date>/` default, no domain\n * bucket taxonomy baked), the MIME map is an injectable hook, and the date\n * segment reads an injectable clock. The logical `hash8(id ?? url ?? filename)`\n * naming is preserved. Atomic callers also receive a fresh ownership suffix so\n * an ambiguous write cannot overwrite an older object.\n */\n\nimport {\n statSandboxFileSize,\n readSandboxBinaryBytes,\n type SandboxExecChannel,\n} from '../sandbox/binary-read'\nimport { attachmentKindForMime, type ChatAttachmentKind, type ChatAttachmentPart } from '../chat-store/parts'\nimport { redactErrorMessage } from '../redact'\nimport {\n ATTACHMENT_STORAGE_FAILURE_MESSAGE,\n immutableAttachmentPath,\n type AtomicAttachmentWriter,\n type AttachmentWriteOwnership,\n type AttachmentWriteReceipt,\n type WriteAttachmentFn,\n} from './attachment-store'\nimport {\n inspectAtomicAttachmentWriteResult,\n inspectLegacyAttachmentWriteResult,\n} from './attachment-write-safety'\nimport { sanitizeAttachmentFileName } from './attachment-validation'\nimport { formatBytes } from './wire'\n\n/** Default ceiling on a promoted file's raw (pre-encoding) byte size. */\nexport const PROMOTE_MAX_FILE_BYTES = 10 * 1024 * 1024\n\n/** Define the structure for a raw file part with optional metadata and media type information */\nexport interface RawAgentFilePart {\n type: 'file'\n id?: string\n filename?: string\n /** AI-SDK-shaped parts carry the MIME type here… */\n mediaType?: string\n /** …but OpenCode's native FilePart calls the same field `mime`. */\n mime?: string\n url?: string\n}\n\n/** Resolve the result of promoting a file part with success status and relevant data or error details */\nexport type PromoteFilePartResult =\n | { succeeded: true; part: ChatAttachmentPart }\n | { succeeded: false; filename: string; reason: string }\n\ntype ByteResolution =\n | { succeeded: true; bytes: Uint8Array }\n | { succeeded: false; reason: string }\n\n/** Arguments handed to a {@link PromoteAgentFilePartOptions.buildAttachmentPath}\n * override — everything needed to place the file deterministically. */\nexport interface AttachmentPathArgs {\n /** Sanitized display filename (basename, safe charset). */\n filename: string\n /** First 8 hex chars of the SHA-256 idempotency digest. */\n hash8: string\n /** `YYYY-MM-DD` from the injected clock. */\n date: string\n /** Resolved media type. */\n mediaType: string\n /** `image`/`file` split of the media type. */\n kind: ChatAttachmentKind\n}\n\n/** Minimal extension→mime map — the last-resort media type when the part\n * carries none. Generic file typing, NOT a product accept-list (which is a\n * domain value the product supplies): an unknown extension falls to\n * `text/plain`, it never rejects. */\nconst EXT_TO_MIME: Record<string, string> = {\n md: 'text/markdown',\n markdown: 'text/markdown',\n txt: 'text/plain',\n log: 'text/plain',\n csv: 'text/csv',\n tsv: 'text/tab-separated-values',\n json: 'application/json',\n yaml: 'text/yaml',\n yml: 'text/yaml',\n xml: 'application/xml',\n html: 'text/html',\n htm: 'text/html',\n pdf: 'application/pdf',\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n webp: 'image/webp',\n avif: 'image/avif',\n heic: 'image/heic',\n heif: 'image/heif',\n svg: 'image/svg+xml',\n mp4: 'video/mp4',\n webm: 'video/webm',\n mov: 'video/quicktime',\n mp3: 'audio/mpeg',\n wav: 'audio/wav',\n ogg: 'audio/ogg',\n m4a: 'audio/mp4',\n aac: 'audio/aac',\n}\n\n/** Default MIME hook: extension → mime, or `text/plain` for the unknown. */\nexport function sniffMimeFromName(filename: string): string {\n const ext = filename.split('.').pop()?.toLowerCase()\n if (!ext) return 'text/plain'\n return EXT_TO_MIME[ext] ?? 'text/plain'\n}\n\n/** Decode base64 with `atob` (not `Buffer.from`, which SKIPS out-of-alphabet\n * characters and would decode a corrupt payload to something plausible). */\nfunction base64ToBytes(base64: string): Uint8Array {\n const binary = atob(base64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i)\n return bytes\n}\n\nfunction parseDataUrl(url: string): { base64: boolean; data: string } | null {\n const match = /^data:[^,]*,([\\s\\S]*)$/i.exec(url)\n if (!match) return null\n return { base64: /;base64,/i.test(url), data: match[1] ?? '' }\n}\n\n/** The MIME type embedded in a `data:` URI's header, if any — the last-resort\n * signal when the part itself carries no mediaType/mime field. */\nfunction dataUrlMime(url: string | undefined): string | undefined {\n if (!url) return undefined\n const match = /^data:([^;,]+)[;,]/i.exec(url)\n return match ? match[1] : undefined\n}\n\nfunction basenameFromUrl(url: string | undefined): string | undefined {\n if (!url || /^data:/i.test(url)) return undefined\n const withoutQuery = url.split(/[?#]/)[0] ?? url\n const segments = withoutQuery.split('/').filter(Boolean)\n return segments[segments.length - 1] || undefined\n}\n\n/** `file://<path>` strips to `<path>`; a bare absolute path passes through\n * unchanged. The remainder is percent-decoded — sidecar file URLs encode\n * spaces and other reserved characters. */\nfunction resolveFileUrlPath(url: string): { succeeded: true; path: string } | { succeeded: false; reason: string } {\n const withoutScheme = /^file:\\/\\//i.test(url) ? url.slice('file://'.length) : url\n try {\n return { succeeded: true, path: decodeURIComponent(withoutScheme) }\n } catch (err) {\n return { succeeded: false, reason: `malformed file path: ${redactErrorMessage(err)}` }\n }\n}\n\n/** Matches gtm's `attachmentSizeErrorMessage` (attachment-limits.ts:87-89)\n * verbatim, via the shared {@link formatBytes} — e.g. \"report.pdf is 10MB;\n * attachments are limited to 10MB\" — so an oversize promotion notice reads\n * identically whether gtm's original composed it or agent-app's promoter did. */\nfunction oversizeReason(filename: string, actual: number, limit: number): string {\n return `${filename} is ${formatBytes(actual)}; attachments are limited to ${formatBytes(limit)}`\n}\n\nfunction resolveDataUrlBytes(url: string, filename: string, maxBytes: number): ByteResolution {\n const parsed = parseDataUrl(url)\n if (!parsed) return { succeeded: false, reason: 'malformed data URI' }\n let bytes: Uint8Array\n try {\n bytes = parsed.base64 ? base64ToBytes(parsed.data) : new TextEncoder().encode(decodeURIComponent(parsed.data))\n } catch (err) {\n return { succeeded: false, reason: `failed to decode data URI: ${redactErrorMessage(err)}` }\n }\n if (bytes.byteLength > maxBytes) {\n return { succeeded: false, reason: oversizeReason(filename, bytes.byteLength, maxBytes) }\n }\n return { succeeded: true, bytes }\n}\n\nasync function resolveSandboxFileBytes(input: {\n path: string\n box: SandboxExecChannel\n sessionId: string\n filename: string\n maxBytes: number\n}): Promise<ByteResolution> {\n // exec can reject outright (box teardown, timeout, transport failure) — that\n // is a per-file failure, not a turn failure, so it must resolve to a typed\n // outcome like a nonzero exit code does.\n const stat = await statSandboxFileSize(input.box, input.path, { sessionId: input.sessionId })\n if (!stat.succeeded) {\n return { succeeded: false, reason: `could not stat agent file: ${redactErrorMessage(stat.error)}` }\n }\n // Rejected before the bytes are ever pulled — a base64 exec of an oversize\n // file would waste a full sandbox round trip only to be discarded.\n if (stat.value > input.maxBytes) {\n return { succeeded: false, reason: oversizeReason(input.filename, stat.value, input.maxBytes) }\n }\n\n const read = await readSandboxBinaryBytes(input.box, input.path, stat.value, { sessionId: input.sessionId })\n if (!read.succeeded) {\n return { succeeded: false, reason: `could not read agent file: ${redactErrorMessage(read.error)}` }\n }\n return { succeeded: true, bytes: read.value.bytes }\n}\n\nasync function resolveBytes(input: {\n raw: RawAgentFilePart\n box: SandboxExecChannel | undefined\n sessionId: string\n filename: string\n maxBytes: number\n}): Promise<ByteResolution> {\n const url = input.raw.url\n if (!url) return { succeeded: false, reason: 'the file part carries no url' }\n\n if (/^data:/i.test(url)) return resolveDataUrlBytes(url, input.filename, input.maxBytes)\n\n const isSandboxPath = /^file:\\/\\//i.test(url) || url.startsWith('/')\n if (!isSandboxPath) {\n const scheme = /^([A-Za-z][A-Za-z0-9+.-]*):/.exec(url)?.[1]?.toLowerCase() ?? 'unknown'\n return { succeeded: false, reason: `unsupported file URL scheme: ${scheme}` }\n }\n if (!input.box) return { succeeded: false, reason: 'no sandbox to read agent file' }\n\n const resolvedPath = resolveFileUrlPath(url)\n if (!resolvedPath.succeeded) return resolvedPath\n return resolveSandboxFileBytes({\n path: resolvedPath.path,\n box: input.box,\n sessionId: input.sessionId,\n filename: input.filename,\n maxBytes: input.maxBytes,\n })\n}\n\n/** First 8 hex chars of the SHA-256 of `seed` — deterministic (no\n * `Math.random`) so promoting the same source part twice, even across\n * requests, resolves to the same store path and overwrites in place. */\nasync function hash8(seed: string): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(seed))\n return Array.from(new Uint8Array(digest).slice(0, 4))\n .map((byte) => byte.toString(16).padStart(2, '0'))\n .join('')\n}\n\n/** Neutral default placement: everything under `uploads/agent/<date>/`, named\n * `<base>-<hash8><ext>`. No domain bucket taxonomy (assets/audio/videos…) —\n * a product that wants one supplies `buildAttachmentPath`. */\nfunction defaultBuildAttachmentPath(args: AttachmentPathArgs): string {\n const extensionMatch = /\\.[A-Za-z0-9]+$/.exec(args.filename)\n const extension = extensionMatch ? extensionMatch[0] : ''\n const base = extension ? args.filename.slice(0, -extension.length) : args.filename\n return `uploads/agent/${args.date}/${base}-${args.hash8}${extension}`\n}\n\ntype PromoteFilePartLogger = Pick<Console, 'error'>\n\nfunction logPromotionStorageError(\n logger: PromoteFilePartLogger,\n message: string,\n fields: Record<string, unknown>,\n): void {\n try {\n const safeFields = Object.fromEntries(\n Object.entries(fields).map(([key, value]) => [\n key,\n typeof value === 'string' ? redactErrorMessage(value) : value,\n ]),\n )\n logger.error(message, safeFields)\n } catch {\n // Logging must not turn a typed storage failure into a thrown response.\n }\n}\n\nasync function compensatePromotionWrite(\n writer: AtomicAttachmentWriter,\n scopeId: string,\n ownership: AttachmentWriteOwnership,\n receipt: AttachmentWriteReceipt,\n): Promise<{ rollbackError?: unknown; abortError?: unknown }> {\n try {\n await receipt.rollback()\n return {}\n } catch (rollbackError) {\n try {\n await writer.abort(scopeId, ownership)\n return { rollbackError }\n } catch (abortError) {\n return { rollbackError, abortError }\n }\n }\n}\n\ninterface PromoteFilePartCommonOptions {\n raw: RawAgentFilePart\n /** The turn's box — required only to promote a sandbox-path part; a `data:`\n * URI needs none. */\n box?: SandboxExecChannel\n /** The product's workspace/tenant key, passed to `writeAttachment`. */\n scopeId: string\n /** The turn's session id, used for the sandbox stat/read exec calls. */\n sessionId: string\n /** Store-path strategy. Default {@link defaultBuildAttachmentPath}. */\n buildAttachmentPath?: (args: AttachmentPathArgs) => string\n /** Raw-byte ceiling. Default {@link PROMOTE_MAX_FILE_BYTES}. */\n maxBytes?: number\n /** Last-resort media-type hook. Default {@link sniffMimeFromName}. */\n sniffMime?: (filename: string) => string\n /** Clock for the date path segment. Default `() => new Date()`. */\n now?: () => Date\n /** Unique id source for tests or a product's id service. */\n createWriteId?: () => string\n /** Server-side sink for redacted storage details. */\n logger?: PromoteFilePartLogger\n}\n\n/** Stable promotion options from the original writer contract. */\nexport interface PromoteAgentFilePartOptions extends PromoteFilePartCommonOptions {\n /** REQUIRED store writer — no default (the product owns its store). */\n writeAttachment: WriteAttachmentFn\n}\n\n/** Ownership-safe promotion options for new products. */\nexport interface AtomicPromoteAgentFilePartOptions extends PromoteFilePartCommonOptions {\n /** Complete writer + ambiguous-write cleanup adapter. */\n attachmentWriter: AtomicAttachmentWriter\n}\n\nfunction isAtomicPromotionOptions(\n options: PromoteAgentFilePartOptions | AtomicPromoteAgentFilePartOptions,\n): options is AtomicPromoteAgentFilePartOptions {\n return 'attachmentWriter' in options\n}\n\n/** Promote a part using the stable writer contract. */\nexport function promoteAgentFilePart(options: PromoteAgentFilePartOptions): Promise<PromoteFilePartResult>\n\n/** Promote a part using an ownership-safe writer and cleanup adapter. */\nexport function promoteAgentFilePart(options: AtomicPromoteAgentFilePartOptions): Promise<PromoteFilePartResult>\n\n/** Promote a part when the caller selects the writer contract at runtime. */\nexport function promoteAgentFilePart(\n options: PromoteAgentFilePartOptions | AtomicPromoteAgentFilePartOptions,\n): Promise<PromoteFilePartResult>\n\n/** Promote a part of an agent file with optional byte limits and MIME type detection. */\nexport async function promoteAgentFilePart(\n options: PromoteAgentFilePartOptions | AtomicPromoteAgentFilePartOptions,\n): Promise<PromoteFilePartResult> {\n const atomic = isAtomicPromotionOptions(options)\n const maxBytes = options.maxBytes ?? PROMOTE_MAX_FILE_BYTES\n const sniffMime = options.sniffMime ?? sniffMimeFromName\n const buildAttachmentPath = options.buildAttachmentPath ?? defaultBuildAttachmentPath\n const now = options.now ?? (() => new Date())\n const logger = options.logger ?? console\n const createWriteId = atomic\n ? options.createWriteId ?? (() => {\n if (typeof crypto === 'undefined' || typeof crypto.randomUUID !== 'function') {\n throw new Error('attachment promotion requires crypto.randomUUID')\n }\n return crypto.randomUUID()\n })\n : undefined\n\n const filename = sanitizeAttachmentFileName(\n options.raw.filename ?? basenameFromUrl(options.raw.url) ?? 'agent-file',\n )\n\n const resolved = await resolveBytes({\n raw: options.raw,\n box: options.box,\n sessionId: options.sessionId,\n filename,\n maxBytes,\n })\n if (!resolved.succeeded) return { succeeded: false, filename, reason: resolved.reason }\n\n let mediaType: string\n let kind: ChatAttachmentKind\n let logicalPath: string\n try {\n mediaType = (options.raw.mediaType ?? options.raw.mime ?? dataUrlMime(options.raw.url) ?? sniffMime(filename)).toLowerCase()\n kind = attachmentKindForMime(mediaType)\n const digest = await hash8(options.raw.id ?? options.raw.url ?? filename)\n const date = now().toISOString().split('T')[0] ?? ''\n logicalPath = buildAttachmentPath({ filename, hash8: digest, date, mediaType, kind })\n } catch (error) {\n logPromotionStorageError(logger, '[promote-file-part] path planning failed', {\n error: redactErrorMessage(error),\n })\n return { succeeded: false, filename, reason: ATTACHMENT_STORAGE_FAILURE_MESSAGE }\n }\n\n if (!atomic) {\n let written: unknown\n try {\n written = await options.writeAttachment(options.scopeId, logicalPath, resolved.bytes, {\n mediaType,\n name: filename,\n originalName: options.raw.filename ?? filename,\n size: resolved.bytes.byteLength,\n })\n } catch (error) {\n const reason = redactErrorMessage(error)\n logPromotionStorageError(logger, '[promote-file-part] legacy writer failed', {\n path: logicalPath,\n error: reason,\n })\n return { succeeded: false, filename, reason }\n }\n const result = inspectLegacyAttachmentWriteResult(written)\n if (!result) {\n logPromotionStorageError(logger, '[promote-file-part] legacy writer returned an invalid result', {\n path: logicalPath,\n })\n return { succeeded: false, filename, reason: ATTACHMENT_STORAGE_FAILURE_MESSAGE }\n }\n if (!result.ok) {\n const reason = redactErrorMessage(result.reason)\n logPromotionStorageError(logger, '[promote-file-part] legacy write rejected', {\n path: logicalPath,\n error: reason,\n })\n return { succeeded: false, filename, reason }\n }\n\n return {\n succeeded: true,\n part: {\n type: kind,\n path: logicalPath,\n name: filename,\n size: resolved.bytes.byteLength,\n mediaType,\n },\n }\n }\n\n let ownershipId: string\n let path: string\n try {\n ownershipId = createWriteId!()\n path = immutableAttachmentPath(logicalPath, ownershipId)\n } catch (error) {\n logPromotionStorageError(logger, '[promote-file-part] could not allocate ownership key', {\n path: logicalPath,\n error: redactErrorMessage(error),\n })\n return { succeeded: false, filename, reason: ATTACHMENT_STORAGE_FAILURE_MESSAGE }\n }\n const ownership: AttachmentWriteOwnership = Object.freeze({ id: ownershipId, path })\n\n // `name` is the sanitized filename already computed above; `originalName`\n // is the pre-sanitization source name (gtm's frontmatter `originalName`) —\n // the one field sanitization would otherwise destroy with no way back.\n let written: Awaited<ReturnType<AtomicAttachmentWriter['write']>>\n try {\n written = await options.attachmentWriter.write(options.scopeId, path, resolved.bytes, {\n mediaType,\n name: filename,\n originalName: options.raw.filename ?? filename,\n size: resolved.bytes.byteLength,\n ownership,\n })\n } catch (err) {\n let abortError: unknown\n try {\n await options.attachmentWriter.abort(options.scopeId, ownership)\n } catch (error) {\n abortError = error\n }\n logPromotionStorageError(logger, '[promote-file-part] write failed', {\n path,\n ownershipId: ownership.id,\n error: redactErrorMessage(err),\n ...(abortError === undefined ? {} : { abortError: redactErrorMessage(abortError) }),\n })\n return { succeeded: false, filename, reason: ATTACHMENT_STORAGE_FAILURE_MESSAGE }\n }\n const result = inspectAtomicAttachmentWriteResult(written, ownership)\n if (!result) {\n let abortError: unknown\n try {\n await options.attachmentWriter.abort(options.scopeId, ownership)\n } catch (error) {\n abortError = error\n }\n logPromotionStorageError(logger, '[promote-file-part] writer returned a mismatched ownership receipt', {\n path,\n ownershipId: ownership.id,\n ...(abortError === undefined ? {} : { abortError: redactErrorMessage(abortError) }),\n })\n return { succeeded: false, filename, reason: ATTACHMENT_STORAGE_FAILURE_MESSAGE }\n }\n if (!result.ok) {\n const compensation = await compensatePromotionWrite(\n options.attachmentWriter,\n options.scopeId,\n ownership,\n result.receipt,\n )\n logPromotionStorageError(logger, '[promote-file-part] write rejected', {\n path,\n ownershipId: ownership.id,\n error: redactErrorMessage(result.reason),\n ...(compensation.rollbackError === undefined\n ? {}\n : { rollbackError: redactErrorMessage(compensation.rollbackError) }),\n ...(compensation.abortError === undefined\n ? {}\n : { abortError: redactErrorMessage(compensation.abortError) }),\n })\n return { succeeded: false, filename, reason: ATTACHMENT_STORAGE_FAILURE_MESSAGE }\n }\n\n return {\n succeeded: true,\n part: {\n type: kind,\n path,\n name: filename,\n size: resolved.bytes.byteLength,\n mediaType,\n },\n }\n}\n","/**\n * `createAttachmentUploadRoute` — the fleet-primitive durable-store upload\n * route: a two-phase batch (every file is validated before any file is written),\n * with a stable legacy lane and an ownership-safe receipt lane for new callers,\n * a content-sniffed type gate\n * (`checkAttachmentType` over `sniffBinary`'s magic-byte read, not the\n * extension or the browser-reported MIME), binary/text caps with optional\n * per-sniffed-mime overrides, an aggregate byte cap, and sanitized filenames.\n * Storage is fully seamed through the injected\n * `WriteAttachmentFn` or `AtomicAttachmentWriter` (`./attachment-store`) — no\n * default store, the product owns where bytes actually live (vault, object\n * store, …), and auth/rate limiting is entirely the injected `authorize`\n * seam's job: this factory\n * never invents a 401 or 429 response, it only returns `auth.response`\n * verbatim on failure.\n *\n * Lifted from gtm-agent's `src/routes/api.vault.upload.ts` (the hardening\n * lineage other lifted modules in this vertical cite: gtm#584 binary\n * corruption, gtm#592 sniff gate/caps, gtm#593 batch-atomic writes) and\n * generalized the way `resolve-attachments.ts` generalized gtm's read path —\n * the vault-specific pieces (KV vault paths, frontmatter, per-user rate\n * limiting) are all injected seams here, while the validate-then-write phase\n * split and the type/size gate ordering survive byte-for-byte.\n *\n * @remarks Sibling to, NOT an extension of, `./upload.ts`'s\n * `createUploadRoute` — a different persistence model (durable product store\n * vs. inline-`data:`-or-ephemeral-sandbox-workspace). See that module's doc\n * comment for the up-to-date framing between the two.\n */\n\nimport type { ChatAttachmentInput, ChatAttachmentKind } from './wire'\nimport { attachmentKindForMime } from '../chat-store/parts'\nimport { redactErrorMessage } from '../redact'\nimport {\n ATTACHMENT_ROLLBACK_FAILURE_CODE,\n ATTACHMENT_ROLLBACK_FAILURE_MESSAGE,\n ATTACHMENT_STORAGE_FAILURE_MESSAGE,\n type AtomicAttachmentWriter,\n immutableAttachmentPath,\n type AttachmentWriteOwnership,\n type AttachmentWriteReceipt,\n type WriteAttachmentFn,\n} from './attachment-store'\nimport {\n inspectAtomicAttachmentWriteResult,\n inspectLegacyAttachmentWriteResult,\n} from './attachment-write-safety'\nimport {\n ALLOWED_ATTACHMENT_SNIFFED_MIMES,\n ATTACHMENT_MAX_COUNT,\n MAX_ATTACHMENT_TOTAL_BYTES,\n MAX_BINARY_ATTACHMENT_BYTES,\n MAX_TEXT_ATTACHMENT_BYTES,\n attachmentSizeErrorMessage,\n attachmentTotalSizeErrorMessage,\n checkAttachmentType,\n sanitizeAttachmentFileName,\n} from './attachment-validation'\nimport { sniffBinary } from './binary-sniff'\nimport { defaultValidateAttachmentPath, type AttachmentPathCheck } from './resolve-attachments'\nimport { sniffMimeFromName } from './promote-file-part'\n\n/** Stable authorization result for the original writer contract. */\nexport type AttachmentUploadAuthorization =\n | {\n ok: true\n scopeId: string\n writeAttachment?: WriteAttachmentFn\n }\n | { ok: false; response: Response }\n\n/** Authorization result for an ownership-safe attachment writer. */\nexport type AtomicAttachmentUploadAuthorization =\n | {\n ok: true\n scopeId: string\n attachmentWriter?: AtomicAttachmentWriter\n }\n | { ok: false; response: Response }\n\n/** The logger receives only sanitized backend details. The client receives the\n * opaque message below, regardless of the store's failure text. */\nexport type AttachmentUploadLogger = Pick<Console, 'error'>\n\nfunction logAttachmentUploadError(\n logger: AttachmentUploadLogger,\n message: string,\n fields: Record<string, unknown>,\n): void {\n try {\n const safeFields = Object.fromEntries(\n Object.entries(fields).map(([key, value]) => [\n key,\n typeof value === 'string' ? redactErrorMessage(value) : value,\n ]),\n )\n logger.error(message, safeFields)\n } catch {\n // Logging must not turn a typed storage failure into a thrown response.\n }\n}\n\ninterface AttachmentUploadRouteCommonOptions {\n /** Overridable caps. Defaults come from `./attachment-validation`. */\n limits?: {\n /** Most files one request may carry. Default {@link ATTACHMENT_MAX_COUNT}. */\n maxCount?: number\n /** Ceiling on a binary file's raw size. Default {@link MAX_BINARY_ATTACHMENT_BYTES}. */\n maxBinaryBytes?: number\n /** Optional binary-file ceilings keyed by the content-sniffed mime. */\n maxBytesBySniffedMime?: ReadonlyMap<string, number>\n /** Ceiling on a text file's raw size. Default {@link MAX_TEXT_ATTACHMENT_BYTES}. */\n maxTextBytes?: number\n /** Aggregate raw-byte ceiling. Default {@link MAX_ATTACHMENT_TOTAL_BYTES}. */\n maxTotalBytes?: number\n }\n /** Attachment kinds this route accepts. Default `['image', 'file']`. */\n allowedKinds?: ChatAttachmentKind[]\n /** Sniffed-mime allowlist fed to `checkAttachmentType`. */\n allowedSniffedMimes?: ReadonlySet<string>\n /** Sanitized-name → logical store path. */\n pathFor?: (name: string) => string\n /** Store-path validator. Default {@link defaultValidateAttachmentPath}. */\n validatePath?: (path: string) => AttachmentPathCheck\n /** Last-resort media-type hook for text content the sniffer cannot type. */\n sniffMime?: (name: string) => string\n /** Unique id source for atomic ownership keys. */\n createWriteId?: () => string\n /** Server-side error sink. Backend text is sanitized before it is logged. */\n logger?: AttachmentUploadLogger\n}\n\n/** Stable route options for products using the original writer contract. */\nexport interface CreateLegacyAttachmentUploadRouteOptions extends AttachmentUploadRouteCommonOptions {\n /** Authenticate the caller, rate-limit, and resolve the store scope\n * (workspace/tenant id) — never a query param. */\n authorize(args: { request: Request }): Promise<AttachmentUploadAuthorization>\n /** Stable writer. `authorize` may override it per-request. */\n writeAttachment: WriteAttachmentFn\n}\n\n/**\n * The original public route-options interface remains available for consumers\n * that extend it. Ownership-safe callers use the atomic interface below.\n */\nexport interface CreateAttachmentUploadRouteOptions extends CreateLegacyAttachmentUploadRouteOptions {}\n\n/** Ownership-safe route options for new products. */\nexport interface CreateAtomicAttachmentUploadRouteOptions extends AttachmentUploadRouteCommonOptions {\n /** Authenticate the caller, rate-limit, and resolve the store scope. */\n authorize(args: { request: Request }): Promise<AtomicAttachmentUploadAuthorization>\n /** Complete writer + ambiguous-write cleanup adapter. */\n attachmentWriter: AtomicAttachmentWriter\n}\n\nfunction attachmentUploadError(status: number, code: string, message: string, path?: string): Response {\n return Response.json(\n { error: path === undefined ? { code, message } : { code, message, path } },\n { status },\n )\n}\n\nfunction atomicAttachmentFailure(path: string, cleanupFailures = 0): Response {\n return attachmentUploadError(\n 503,\n cleanupFailures > 0 ? ATTACHMENT_ROLLBACK_FAILURE_CODE : 'attachment_store_unavailable',\n cleanupFailures > 0 ? ATTACHMENT_ROLLBACK_FAILURE_MESSAGE : ATTACHMENT_STORAGE_FAILURE_MESSAGE,\n path,\n )\n}\n\ninterface SuccessfulAttachmentWrite {\n path: string\n ownership: AttachmentWriteOwnership\n receipt: AttachmentWriteReceipt\n}\n\n/** Run every available compensation, from the newest write to the oldest. The\n * receipt owns the compare-and-delete operation; this route never deletes a\n * logical path itself. */\nasync function rollbackSuccessfulWrites(\n writes: readonly SuccessfulAttachmentWrite[],\n scopeId: string,\n writer: AtomicAttachmentWriter,\n logger: AttachmentUploadLogger,\n): Promise<number> {\n let failures = 0\n for (let index = writes.length - 1; index >= 0; index -= 1) {\n const write = writes[index]!\n try {\n await write.receipt.rollback()\n } catch (error) {\n failures += 1\n logAttachmentUploadError(logger, '[attachment-upload] cleanup failed', {\n path: write.path,\n ownershipId: write.ownership.id,\n error: redactErrorMessage(error),\n })\n failures += await abortOwnership(writer, scopeId, write.ownership, logger)\n }\n }\n return failures\n}\n\nasync function abortOwnership(\n writer: AtomicAttachmentWriter,\n scopeId: string,\n ownership: AttachmentWriteOwnership,\n logger: AttachmentUploadLogger,\n): Promise<number> {\n try {\n await writer.abort(scopeId, ownership)\n return 0\n } catch (error) {\n logAttachmentUploadError(logger, '[attachment-upload] ambiguous write cleanup failed', {\n path: ownership.path,\n ownershipId: ownership.id,\n error: redactErrorMessage(error),\n })\n return 1\n }\n}\n\nfunction isAtomicRouteOptions(\n options: CreateAttachmentUploadRouteOptions | CreateAtomicAttachmentUploadRouteOptions,\n): options is CreateAtomicAttachmentUploadRouteOptions {\n return 'attachmentWriter' in options\n}\n\n/** Resolve an attachment upload route handler with customizable limits and validation options. */\nexport function createAttachmentUploadRoute(\n options: CreateAttachmentUploadRouteOptions | CreateAtomicAttachmentUploadRouteOptions,\n): (request: Request) => Promise<Response> {\n const atomic = isAtomicRouteOptions(options)\n const maxCount = options.limits?.maxCount ?? ATTACHMENT_MAX_COUNT\n const maxBinaryBytes = options.limits?.maxBinaryBytes ?? MAX_BINARY_ATTACHMENT_BYTES\n const maxBytesBySniffedMime = options.limits?.maxBytesBySniffedMime\n const maxTextBytes = options.limits?.maxTextBytes ?? MAX_TEXT_ATTACHMENT_BYTES\n const maxTotalBytes = options.limits?.maxTotalBytes ?? MAX_ATTACHMENT_TOTAL_BYTES\n const allowedKinds: ChatAttachmentKind[] = options.allowedKinds ?? ['image', 'file']\n const allowedSniffedMimes = options.allowedSniffedMimes ?? ALLOWED_ATTACHMENT_SNIFFED_MIMES\n const pathFor = options.pathFor ?? ((name: string) => name)\n const validatePath = options.validatePath ?? defaultValidateAttachmentPath\n const sniffMime = options.sniffMime ?? sniffMimeFromName\n const createWriteId = atomic\n ? options.createWriteId ?? (() => {\n if (typeof crypto === 'undefined' || typeof crypto.randomUUID !== 'function') {\n throw new Error('attachment upload requires crypto.randomUUID')\n }\n return crypto.randomUUID()\n })\n : undefined\n const logger = options.logger ?? console\n\n return async function attachmentUpload(request: Request): Promise<Response> {\n const auth = await options.authorize({ request })\n if (!auth.ok) return auth.response\n const atomicWriter = atomic\n ? (auth as Extract<AtomicAttachmentUploadAuthorization, { ok: true }>).attachmentWriter ?? options.attachmentWriter\n : undefined\n const legacyWriter = atomic\n ? undefined\n : (auth as Extract<AttachmentUploadAuthorization, { ok: true }>).writeAttachment ?? options.writeAttachment\n if (atomic && !atomicWriter) {\n return attachmentUploadError(503, 'attachment_store_unavailable', ATTACHMENT_STORAGE_FAILURE_MESSAGE)\n }\n\n let form: FormData\n try {\n form = await request.formData()\n } catch {\n return attachmentUploadError(400, 'invalid_upload', 'Expected a multipart/form-data body with file fields')\n }\n // Collect every File value regardless of field name — the client may\n // send one field per file or a single repeated field.\n const files: File[] = []\n form.forEach((value) => {\n if (value instanceof File) files.push(value)\n })\n if (files.length === 0) {\n return attachmentUploadError(400, 'invalid_upload', 'No files in the upload body')\n }\n\n if (files.length > maxCount) {\n return attachmentUploadError(\n 400,\n 'attachment_count_exceeded',\n `Too many files — the ${maxCount}-file limit was exceeded`,\n )\n }\n\n // Advisory only: client-controlled `file.size`, so a dishonest caller can\n // slip past it — but an honest oversized batch should fail before a\n // single byte is read. The authoritative aggregate check (against the\n // sniffed/decoded byte length) runs per-file in phase 1, below.\n const advisoryTotal = files.reduce((sum, file) => sum + file.size, 0)\n if (advisoryTotal > maxTotalBytes) {\n return attachmentUploadError(\n 413,\n 'attachments_total_too_large',\n attachmentTotalSizeErrorMessage(advisoryTotal, maxTotalBytes),\n )\n }\n\n interface PreparedWrite {\n path: string\n ownershipId?: string\n name: string\n bytes: Uint8Array\n originalName: string\n size: number\n mediaType: string\n kind: ChatAttachmentKind\n }\n\n // Phase 1: validate every file and prepare its write input WITHOUT\n // writing anything. A batch fails atomically — any file's validation\n // error must reject the whole request before an earlier file in the\n // same batch is persisted.\n const prepared: PreparedWrite[] = []\n const seenLogicalPaths = new Set<string>()\n let totalBytes = 0\n\n for (const file of files) {\n const bytes = new Uint8Array(await file.arrayBuffer())\n const sniff = sniffBinary(bytes)\n // Attachment paths double as store keys, so the stored name must fit\n // the store charset; the as-uploaded name survives as `originalName`.\n const name = sanitizeAttachmentFileName(file.name)\n\n const typeCheck = checkAttachmentType(name, sniff, allowedSniffedMimes)\n if (!typeCheck.succeeded) {\n return attachmentUploadError(\n typeCheck.code === 'attachment_type_mismatch' ? 400 : 415,\n typeCheck.code,\n typeCheck.message,\n )\n }\n\n const kind = attachmentKindForMime(sniff.mime ?? '')\n if (!allowedKinds.includes(kind)) {\n return attachmentUploadError(\n 415,\n 'attachment_kind_not_allowed',\n `${name} is a \"${kind}\" attachment, which this upload route does not accept`,\n )\n }\n\n let limit = maxTextBytes\n if (sniff.binary) {\n limit = sniff.mime === null\n ? maxBinaryBytes\n : maxBytesBySniffedMime?.get(sniff.mime) ?? maxBinaryBytes\n }\n if (bytes.length > limit) {\n return attachmentUploadError(\n 413,\n 'attachment_too_large',\n attachmentSizeErrorMessage(name, bytes.length, limit),\n )\n }\n\n const logicalPath = pathFor(name)\n let ownershipId: string | undefined\n let path = logicalPath\n if (atomic) {\n try {\n ownershipId = createWriteId!()\n path = immutableAttachmentPath(logicalPath, ownershipId)\n } catch (error) {\n logAttachmentUploadError(logger, '[attachment-upload] could not allocate ownership key', {\n path: logicalPath,\n error: redactErrorMessage(error),\n })\n return attachmentUploadError(\n 503,\n 'attachment_store_unavailable',\n ATTACHMENT_STORAGE_FAILURE_MESSAGE,\n logicalPath,\n )\n }\n }\n const pathCheck = validatePath(path)\n if (!pathCheck.succeeded) {\n return attachmentUploadError(400, 'invalid_attachment_path', pathCheck.error, path)\n }\n // Small hardening over gtm: a batch whose sanitized names collide onto\n // the same store path (e.g. two variously-cased \"Report.PDF\" uploads)\n // would otherwise silently overwrite one with the other in phase 2.\n const duplicatePath = atomic ? logicalPath : path\n if (seenLogicalPaths.has(duplicatePath)) {\n return attachmentUploadError(\n 400,\n 'attachment_duplicate_path',\n `attachments must not repeat a path within one upload: ${duplicatePath}`,\n path,\n )\n }\n seenLogicalPaths.add(duplicatePath)\n\n // Authoritative aggregate check, against the actual decoded byte\n // length rather than the client-reported `file.size` checked above.\n totalBytes += bytes.length\n if (totalBytes > maxTotalBytes) {\n return attachmentUploadError(\n 413,\n 'attachments_total_too_large',\n attachmentTotalSizeErrorMessage(totalBytes, maxTotalBytes),\n )\n }\n\n const mediaType = sniff.mime ?? sniffMime(name)\n prepared.push({ path, ownershipId, name, bytes, originalName: file.name, size: bytes.length, mediaType, kind })\n }\n\n // Phase 2: every file in the batch passed validation — write them all.\n const uploaded: ChatAttachmentInput[] = []\n const successfulWrites: SuccessfulAttachmentWrite[] = []\n for (const input of prepared) {\n if (!atomic) {\n let written: unknown\n try {\n written = await legacyWriter!(auth.scopeId, input.path, input.bytes, {\n mediaType: input.mediaType,\n name: input.name,\n originalName: input.originalName,\n size: input.size,\n })\n } catch (error) {\n logAttachmentUploadError(logger, '[attachment-upload] legacy writer failed', {\n path: input.path,\n error: redactErrorMessage(error),\n })\n return attachmentUploadError(\n 503,\n 'attachment_store_unavailable',\n ATTACHMENT_STORAGE_FAILURE_MESSAGE,\n input.path,\n )\n }\n const result = inspectLegacyAttachmentWriteResult(written)\n if (!result) {\n logAttachmentUploadError(logger, '[attachment-upload] legacy writer returned an invalid result', {\n path: input.path,\n })\n return attachmentUploadError(\n 503,\n 'attachment_store_unavailable',\n ATTACHMENT_STORAGE_FAILURE_MESSAGE,\n input.path,\n )\n }\n if (!result.ok) {\n const reason = redactErrorMessage(result.reason)\n logAttachmentUploadError(logger, '[attachment-upload] legacy write rejected', {\n path: input.path,\n error: reason,\n })\n return attachmentUploadError(413, 'attachment_write_failed', reason, input.path)\n }\n uploaded.push({\n path: input.path,\n name: input.name,\n size: input.size,\n mediaType: input.mediaType,\n kind: input.kind,\n })\n continue\n }\n\n const ownershipId = input.ownershipId\n if (!ownershipId) {\n return attachmentUploadError(503, 'attachment_store_unavailable', ATTACHMENT_STORAGE_FAILURE_MESSAGE, input.path)\n }\n const ownership: AttachmentWriteOwnership = Object.freeze({ id: ownershipId, path: input.path })\n let written: Awaited<ReturnType<AtomicAttachmentWriter['write']>>\n try {\n written = await atomicWriter!.write(auth.scopeId, input.path, input.bytes, {\n mediaType: input.mediaType,\n name: input.name,\n originalName: input.originalName,\n size: input.size,\n ownership,\n })\n } catch (error) {\n const cleanupFailures = (await abortOwnership(atomicWriter!, auth.scopeId, ownership, logger)) +\n await rollbackSuccessfulWrites(successfulWrites, auth.scopeId, atomicWriter!, logger)\n logAttachmentUploadError(logger, '[attachment-upload] write failed', {\n path: input.path,\n ownershipId: ownership.id,\n cleanupFailures,\n error: redactErrorMessage(error),\n })\n return atomicAttachmentFailure(input.path, cleanupFailures)\n }\n const result = inspectAtomicAttachmentWriteResult(written, ownership)\n if (!result) {\n const cleanupFailures = (await abortOwnership(atomicWriter!, auth.scopeId, ownership, logger)) +\n await rollbackSuccessfulWrites(successfulWrites, auth.scopeId, atomicWriter!, logger)\n logAttachmentUploadError(logger, '[attachment-upload] writer returned a mismatched ownership receipt', {\n path: input.path,\n ownershipId: ownership.id,\n cleanupFailures,\n })\n return atomicAttachmentFailure(input.path, cleanupFailures)\n }\n if (!result.ok) {\n const cleanupFailures = (await rollbackSuccessfulWrites([\n { path: input.path, ownership, receipt: result.receipt },\n ], auth.scopeId, atomicWriter!, logger)) + await rollbackSuccessfulWrites(\n successfulWrites,\n auth.scopeId,\n atomicWriter!,\n logger,\n )\n logAttachmentUploadError(logger, '[attachment-upload] write rejected', {\n path: input.path,\n ownershipId: ownership.id,\n cleanupFailures,\n error: redactErrorMessage(result.reason),\n })\n return atomicAttachmentFailure(input.path, cleanupFailures)\n }\n successfulWrites.push({ path: input.path, ownership, receipt: result.receipt })\n uploaded.push({\n path: input.path,\n name: input.name,\n size: input.size,\n mediaType: input.mediaType,\n kind: input.kind,\n })\n }\n\n return Response.json({ files: uploaded })\n }\n}\n","/**\n * `buildDispatchParts` — assemble the `PromptInputPart[]` a turn carrying\n * attachments and/or `@`-mentions dispatches to the sandbox. `parts[0]` is\n * always the full prompt text (typed text plus the attachment + mention pointer\n * blocks); each attachment or mention becomes one media part. An attachment\n * (read from the product store via the injected reader) draws the inline byte\n * budget first; a mention (read from the LIVE box) takes what is left. A file\n * inlines as a `data:` URI when it fits the remaining budget, otherwise uses\n * the in-box file reference supported by the current sandbox prompt contract.\n * Every media part is deduped by its resolved absolute path. This module only\n * produces the parts array; the caller decides when a turn dispatches parts\n * instead of a plain string.\n *\n * Storage-parameterized port of gtm-agent's `dispatch-parts.ts`: the vault\n * default reader is dropped (`readAttachment` is REQUIRED — the product supplies\n * its store adapter), the `GTM_SANDBOX_VAULT_DIR` prefixing becomes the required\n * `resolveAttachmentPath` seam, the `GTM_MULTIMODAL_FORCE_PATH` env fallback\n * becomes an explicit `forcePath` flag, and every budget cap reads an overridable\n * `./wire` constant. Kept behavior-identical for gtm-agent#618 adoption (the\n * demotion math and emitted part shapes reproduce its dispatched prompt bytes).\n */\n\nimport { isWorkspaceFileExportable } from '../web/file-export'\nimport { flattenHistory, type PromptInputPart } from '../sandbox'\nimport {\n statSandboxFileSize,\n readSandboxBinaryBytes,\n type SandboxExecChannel,\n} from '../sandbox/binary-read'\nimport {\n ChatTurnInputError,\n mediaTypeForMentionPath,\n base64WireLen,\n DISPATCH_REQUEST_MAX_BYTES,\n DISPATCH_STRUCTURAL_RESERVE_BYTES,\n DISPATCH_MAX_PARTS,\n type ChatTurnPartInput,\n} from './wire'\nimport type { ChatAttachmentPart, ChatMentionPart } from '../chat-store/parts'\nimport { bytesToBase64 } from './upload'\nimport type { ReadAttachmentFn } from './attachment-store'\nimport { pathToFileURL } from 'node:url'\n\nexport type { PromptInputPart }\n\n/** Resolve the outcome of dispatching parts with success status and corresponding value or error message */\nexport type DispatchPartsOutcome =\n | { succeeded: true; value: PromptInputPart[] }\n | { succeeded: false; error: string }\n\n/** One mention file's size (always) and inline bytes (only when the caller\n * asked for them — a path-only mention never reads its bytes). */\ntype SandboxMentionReadOutcome =\n | { succeeded: true; value: { size: number; base64?: string } }\n | { succeeded: false; error: string }\n\n/** Resolve sandbox mention details by reading from a specified path with optional byte reading */\nexport type ReadSandboxMentionFn = (\n box: SandboxExecChannel,\n absolutePath: string,\n options: { readBytes: boolean },\n) => Promise<SandboxMentionReadOutcome>\n\nfunction byteLen(value: string): number {\n return new TextEncoder().encode(value).length\n}\n\n/**\n * Default mention reader: stats the in-box file (which also proves it still\n * exists — a since-deleted mention fails loud here), then reads its bytes only\n * when the caller wants to inline it. Both hops cross the sandbox exec channel\n * via the substrate's binary-read helpers.\n */\nasync function readSandboxMention(\n box: SandboxExecChannel,\n absolutePath: string,\n options: { readBytes: boolean },\n): Promise<SandboxMentionReadOutcome> {\n const stat = await statSandboxFileSize(box, absolutePath)\n if (!stat.succeeded) {\n return { succeeded: false, error: `mentioned sandbox file missing or unreadable: ${absolutePath} — ${stat.error}` }\n }\n if (!options.readBytes) return { succeeded: true, value: { size: stat.value } }\n\n const read = await readSandboxBinaryBytes(box, absolutePath, stat.value)\n if (!read.succeeded) {\n return { succeeded: false, error: `mentioned sandbox file read failed: ${absolutePath} — ${read.error}` }\n }\n return { succeeded: true, value: { size: stat.value, base64: bytesToBase64(read.value.bytes) } }\n}\n\n/** HARD INVARIANT: an image carries exactly one URL or absolute path, while a\n * file carries the URL required by the current sandbox prompt contract. */\nfunction violatesUrlPathXor(part: PromptInputPart): boolean {\n if (part.type === 'text') return false\n const hasUrl = typeof part.url === 'string' && (\n part.url.startsWith('data:')\n || part.url.startsWith('file://')\n )\n const hasPath = 'path' in part && typeof part.path === 'string' && part.path.startsWith('/')\n if (part.type === 'file') return !hasUrl || hasPath\n return hasUrl === hasPath\n}\n\nfunction sandboxFileUrl(absolutePath: string): string | undefined {\n if (!absolutePath.startsWith('/')) return undefined\n try {\n return pathToFileURL(absolutePath).href\n } catch {\n return undefined\n }\n}\n\n/**\n * Convert the browser-safe chat attachment contract into the current sandbox\n * prompt contract. Images carry exactly one URL or path; generic files carry a\n * filename and URL.\n */\nexport function normalizeChatPromptForSandbox(\n prompt: string | readonly ChatTurnPartInput[],\n): string | PromptInputPart[] {\n if (typeof prompt === 'string') return prompt\n return prompt.map((part): PromptInputPart => {\n if (part.type === 'text') return part\n if ('content' in part) {\n throw new ChatTurnInputError('Sandbox prompt parts do not accept inline content; provide a URL or path')\n }\n\n const url = typeof part.url === 'string' && part.url.length > 0 ? part.url : undefined\n const path = typeof part.path === 'string' && part.path.length > 0 ? part.path : undefined\n if (Boolean(url) === Boolean(path)) {\n throw new ChatTurnInputError(`Sandbox ${part.type} parts require exactly one URL or path`)\n }\n if (path && !path.startsWith('/')) {\n throw new ChatTurnInputError(`Sandbox ${part.type} paths must be absolute: ${path}`)\n }\n\n if (part.type === 'image') {\n return {\n type: 'image',\n ...(part.filename ? { filename: part.filename } : {}),\n ...(part.mediaType ? { mediaType: part.mediaType } : {}),\n ...(url ? { url } : { path: path! }),\n }\n }\n\n const filename = part.filename?.trim()\n if (!filename) {\n throw new ChatTurnInputError('Sandbox file parts require a filename')\n }\n const fileUrl = url ?? sandboxFileUrl(path!)\n if (!fileUrl) {\n throw new ChatTurnInputError(`Sandbox file paths must be absolute: ${path}`)\n }\n return {\n type: 'file',\n filename,\n ...(part.mediaType ? { mediaType: part.mediaType } : {}),\n url: fileUrl,\n }\n })\n}\n\n/** Build input parameters for dispatching chat message parts including text, attachments, mentions, and history */\nexport interface BuildDispatchPartsInput {\n text: string\n attachments: ChatAttachmentPart[]\n mentions?: ChatMentionPart[]\n history: Array<{ role: 'user' | 'assistant'; content: string }>\n systemPrompt: string\n /** Serialized size of the backend profile the SDK inlines into the same\n * prompt request body — a large, non-negotiable rider that must come out of\n * the inline budget or near-cap attachments 413 at the proxy instead of\n * demoting to path parts. */\n profileWireBytes: number\n /** The product's workspace/tenant key, passed to `readAttachment`. */\n scopeId: string\n /** Maps an attachment's store-relative path to the in-box absolute path a\n * local media reference uses (same style as `fileMentionsToParts`'s\n * `resolvePath`). */\n resolveAttachmentPath: (path: string) => string\n /** Maps a mention's workspace-relative path to its in-box absolute path.\n * Default: {@link BuildDispatchPartsInput.resolveAttachmentPath} — in gtm the\n * vault mount roots both; a product that mounts them apart overrides this. */\n resolveMentionPath?: (path: string) => string\n /** The turn's already-ensured box — required when `mentions` is non-empty\n * (mention bytes are read from the live box, not the store). */\n box?: SandboxExecChannel\n /** Force every media part to a local file reference, skipping all inlining. */\n forcePath?: boolean\n /** REQUIRED store reader for attachment content — no default (the product\n * owns its store; see {@link ReadAttachmentFn}). */\n readAttachment: ReadAttachmentFn\n readSandboxMention?: ReadSandboxMentionFn\n /** Whole-request proxy cap. Default {@link DISPATCH_REQUEST_MAX_BYTES}. */\n requestMaxBytes?: number\n /** JSON-envelope reserve off the top of the request cap. Default\n * {@link DISPATCH_STRUCTURAL_RESERVE_BYTES}. */\n structuralReserveBytes?: number\n /** Sidecar per-request parts-array cap. Default {@link DISPATCH_MAX_PARTS}. */\n maxParts?: number\n}\n\n/** Content of one attachment read, normalized to the base64 a `data:` URI\n * needs — `base64` reused verbatim, else `bytes` encoded once. */\nfunction readResultToBase64(read: { base64?: string; bytes?: Uint8Array }): string | undefined {\n if (typeof read.base64 === 'string') return read.base64\n if (read.bytes) return bytesToBase64(read.bytes)\n return undefined\n}\n\n/** Build dispatch parts from input by resolving mentions, paths, and applying size constraints asynchronously */\nexport async function buildDispatchParts(input: BuildDispatchPartsInput): Promise<DispatchPartsOutcome> {\n const readMention = input.readSandboxMention ?? readSandboxMention\n const resolveMentionPath = input.resolveMentionPath ?? input.resolveAttachmentPath\n const forcePath = input.forcePath ?? false\n const mentions = input.mentions ?? []\n const requestMaxBytes = input.requestMaxBytes ?? DISPATCH_REQUEST_MAX_BYTES\n const structuralReserveBytes = input.structuralReserveBytes ?? DISPATCH_STRUCTURAL_RESERVE_BYTES\n const maxParts = input.maxParts ?? DISPATCH_MAX_PARTS\n\n const parts: PromptInputPart[] = [{ type: 'text', text: input.text }]\n // Absolute in-box paths already emitted — a file both attached and mentioned\n // (or mentioned twice) rides as a single media part.\n const emittedAbsPaths = new Set<string>()\n\n const flattenedForSizing = flattenHistory(input.text, input.history)\n // May go negative when history/systemPrompt alone are large — every\n // attachment then fails the `runningInline + cost <= inlineBudget` check\n // below and uses a local file reference, which does not draw on this inline\n // budget.\n const inlineBudget =\n requestMaxBytes\n - base64WireLen(byteLen(flattenedForSizing))\n - byteLen(JSON.stringify(input.systemPrompt))\n - input.profileWireBytes\n - structuralReserveBytes\n\n let runningInline = 0\n\n // Stable input order: attachments dispatch in the order the user attached\n // them, and the pointer block in `input.text` already names them in that\n // same order.\n for (const attachment of input.attachments) {\n if (!attachment.path) {\n return { succeeded: false, error: `attachment path must be non-empty: ${attachment.name}` }\n }\n\n // The reader crosses an external boundary (store/coordinator) — a rejection\n // there must land in the typed outcome, not escape as a thrown error the\n // caller would misattribute to stream init.\n let read: Awaited<ReturnType<ReadAttachmentFn>>\n try {\n read = await input.readAttachment(input.scopeId, attachment.path)\n } catch (err) {\n return {\n succeeded: false,\n error: `attachment store read failed: ${attachment.path} — ${err instanceof Error ? err.message : String(err)}`,\n }\n }\n if (!read.ok) return { succeeded: false, error: read.reason }\n\n const base64 = readResultToBase64(read)\n if (base64 === undefined) {\n return { succeeded: false, error: `attachment store read produced no content: ${attachment.path}` }\n }\n\n const mediaType = attachment.mediaType ?? read.mediaType\n if (attachment.type === 'image' && !mediaType) {\n return { succeeded: false, error: `attachment is missing a mediaType required for an image data URI: ${attachment.path}` }\n }\n\n const absPath = input.resolveAttachmentPath(attachment.path)\n emittedAbsPaths.add(absPath)\n\n if (attachment.type === 'image') {\n const inlinePart: PromptInputPart = {\n type: 'image',\n filename: attachment.name,\n mediaType,\n url: `data:${mediaType};base64,${base64}`,\n }\n const cost = byteLen(JSON.stringify(inlinePart))\n if (!forcePath && runningInline + cost <= inlineBudget) {\n parts.push(inlinePart)\n runningInline += cost\n } else {\n parts.push({ type: 'image', filename: attachment.name, mediaType, path: absPath })\n }\n continue\n }\n\n // File parts use the current filename + URL contract. A file that is too\n // large to inline keeps its existing in-box file through a file:// URL.\n const fileMediaType = mediaType ?? 'application/octet-stream'\n const inlinePart: PromptInputPart = {\n type: 'file',\n filename: attachment.name,\n mediaType: fileMediaType,\n url: `data:${fileMediaType};base64,${base64}`,\n }\n const cost = byteLen(JSON.stringify(inlinePart))\n if (!forcePath && runningInline + cost <= inlineBudget) {\n parts.push(inlinePart)\n runningInline += cost\n } else {\n const url = sandboxFileUrl(absPath)\n if (!url) {\n return { succeeded: false, error: `resolved attachment path must be absolute: ${absPath}` }\n }\n parts.push({ type: 'file', filename: attachment.name, mediaType: fileMediaType, url })\n }\n }\n\n if (mentions.length > 0 && !input.box) {\n return { succeeded: false, error: 'internal error: sandbox mentions require a box to read from' }\n }\n for (const mention of mentions) {\n if (!mention.path) {\n return { succeeded: false, error: `mention path must be non-empty: ${mention.name}` }\n }\n if (!isWorkspaceFileExportable(mention.path)) {\n return { succeeded: false, error: `mention file is not exportable: ${mention.path}` }\n }\n const absPath = resolveMentionPath(mention.path)\n if (emittedAbsPaths.has(absPath)) continue\n emittedAbsPaths.add(absPath)\n\n const isImage = mention.mentionKind === 'image'\n const mediaType = isImage ? mediaTypeForMentionPath(mention.path) : undefined\n\n // Every mention is stat'd first: it proves the file still exists (a deleted\n // mention fails the turn loud) and gives the size for the inline budget\n // decision without pulling bytes across the exec channel.\n let stat: SandboxMentionReadOutcome\n try {\n stat = await readMention(input.box!, absPath, { readBytes: false })\n } catch (err) {\n return { succeeded: false, error: `mention read failed: ${absPath} — ${err instanceof Error ? err.message : String(err)}` }\n }\n if (!stat.succeeded) return { succeeded: false, error: stat.error }\n\n // Only an image that projects within the remaining budget reads its bytes\n // to inline; every other mention (and a budget-exceeding image) ships\n // by local reference, so a large file is never base64'd unnecessarily.\n const projectedInlineCost = base64WireLen(stat.value.size)\n + byteLen(JSON.stringify({ type: 'image', filename: mention.name, mediaType: mediaType ?? '', url: '' }))\n if (isImage && mediaType && !forcePath && runningInline + projectedInlineCost <= inlineBudget) {\n let read: SandboxMentionReadOutcome\n try {\n read = await readMention(input.box!, absPath, { readBytes: true })\n } catch (err) {\n return { succeeded: false, error: `mention read failed: ${absPath} — ${err instanceof Error ? err.message : String(err)}` }\n }\n if (!read.succeeded) return { succeeded: false, error: read.error }\n if (!read.value.base64) return { succeeded: false, error: `mentioned image produced no bytes: ${absPath}` }\n const inlinePart: PromptInputPart = {\n type: 'image',\n filename: mention.name,\n mediaType,\n url: `data:${mediaType};base64,${read.value.base64}`,\n }\n const cost = byteLen(JSON.stringify(inlinePart))\n // Re-check against the actual serialized cost — the projection can\n // undershoot; a real overshoot demotes to a path part rather than 413ing.\n if (runningInline + cost <= inlineBudget) {\n parts.push(inlinePart)\n runningInline += cost\n continue\n }\n }\n\n if (isImage && mediaType) {\n parts.push({ type: 'image', filename: mention.name, mediaType, path: absPath })\n } else {\n const url = sandboxFileUrl(absPath)\n if (!url) {\n return { succeeded: false, error: `resolved mention path must be absolute: ${absPath}` }\n }\n parts.push({ type: 'file', filename: mention.name, url })\n }\n }\n\n for (const part of parts) {\n if (violatesUrlPathXor(part)) {\n return { succeeded: false, error: 'internal error: emitted media part violates the url/path exclusivity invariant' }\n }\n }\n\n // Final whole-request check sized against what actually crosses the wire: the\n // history-merged text part (the substrate folds `history` into `parts[0]`\n // before dispatch), the media parts, the system prompt, and the inlined\n // backend profile riding the same body — the same terms `inlineBudget` was\n // derived from.\n const textPartSize = base64WireLen(byteLen(flattenedForSizing))\n const mediaPartsSize = parts.slice(1).reduce((total, part) => total + byteLen(JSON.stringify(part)), 0)\n const systemPromptSize = byteLen(JSON.stringify(input.systemPrompt))\n if (textPartSize + mediaPartsSize + systemPromptSize + input.profileWireBytes + structuralReserveBytes > requestMaxBytes) {\n return { succeeded: false, error: 'dispatch parts exceed the sandbox proxy request cap even after path demotion' }\n }\n\n // The sidecar rejects the whole request past its parts-array cap; the caller\n // selects which media ride natively, so overflow here is a caller bug\n // surfaced loudly rather than a truncation.\n if (parts.length > maxParts) {\n return { succeeded: false, error: `dispatch parts exceed the sidecar per-request cap of ${maxParts}` }\n }\n\n return { succeeded: true, value: parts }\n}\n"],"mappings":"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","names":["record","record","sum","attribution","parts","record","hasUsage","usageFromResult","base64","record","sum","written","result","sum","written","result","inlinePart","cost"]}
|