@tangle-network/agent-app 0.44.54 → 0.44.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/chat-routes/turn-routes.ts","../../src/chat-routes/draft-persistence.ts","../../src/chat-routes/model-failover-stream.ts","../../src/chat-routes/sandbox-producer.ts","../../src/chat-routes/detached-turn.ts","../../src/chat-routes/durable-projection.ts","../../src/chat-routes/upload.ts","../../src/chat-routes/resolve-attachments.ts","../../src/chat-routes/promote-file-part.ts","../../src/chat-routes/attachment-upload.ts","../../src/chat-routes/dispatch-parts.ts"],"sourcesContent":["/**\n * `createChatTurnRoutes` — the assembled server chat vertical (issue #188\n * Phase 1). One factory composing the pieces every product re-wired by hand:\n *\n * body parse/validate → `/web` `parseJsonObjectBody` + `./wire`\n * turn identity → `/stream` `resolveChatTurn` + agent-runtime `/durable`\n * `deriveExecutionId`\n * producer → injected seam (sandbox lane via\n * `createSandboxChatProducer`; router lane is the\n * product's own `ChatTurnProducer`)\n * turn engine → agent-runtime `/durable` `handleChatTurn` (verbatim)\n * durability → `/stream` turn-buffer tap, wired BY DEFAULT\n * (tee + drain keeps the turn running after a\n * client drop; replay serves the buffered tail)\n * persistence → injected `/chat-store`-shaped store\n * (user row on send, assistant row on completion)\n * interactions answer → `/interactions` `createInteractionAnswerRoute`\n *\n * Handlers are web-standard `Request → Response` (Workers, Node 18+, Deno) —\n * no router import. Auth/access is one injected `authorize` seam, composable\n * with `/app-auth` guards but not coupled to them.\n *\n * Optional product seams let a complex turn-orchestrator compose the vertical\n * instead of hand-rolling a generator — each omittable to the exact behavior\n * above: `turnLock` (single-flight acquire/release around the turn),\n * `contextGate` (pre-producer domain-readiness short-circuit), `beforeTurn`\n * (observe + augment the producer input), `lifecycle` (deterministic\n * start/complete/error telemetry), `heartbeat` (keepalive during silent\n * producer waits), and `onRawEvent` (the raw producer events, for telemetry) —\n * plus the `authorize` result's `insertUserMessage` flag (suppress the user row\n * for a product-dispatched turn). `handleChatTurn` stays the engine — the seams\n * only wrap its input, its producer stream, and its settle.\n *\n * Seam stability: all of the above are STABLE and safe to depend on. They\n * graduated in #227 against the bar this package holds itself to — a seam is\n * provisional until two INDEPENDENT consumers exercise it, because one\n * consumer's shape is indistinguishable from that consumer's assumptions.\n * `turnLock` cleared it with `/turn-stream`'s shared DO adapter (#221);\n * `contextGate`, `beforeTurn`, `onRawEvent` and `insertUserMessage` each\n * cleared it with two product verticals reading them differently — and the\n * review found no leaked assumptions to fix, only `ChatRouteEvent` to export.\n *\n * They stay FLAT top-level options (not grouped under a `hooks` object): that\n * grouping would break every shipped consumer's call for no mechanism gain, and\n * this package's exports are additive-only. For the same reason `onRawEvent`\n * keeps its two-argument `(event, context)` signature rather than being\n * normalized to the single-args shape the other seams take.\n */\n\nimport {\n deriveExecutionId,\n handleChatTurn,\n type ChatTurnIdentity,\n type ChatTurnProducer,\n} from '@tangle-network/agent-runtime/durable'\nimport { mentionInputToPart, toChatMessageParts, type ChatMessagePart } from '../chat-store/parts'\nimport {\n assistantRowIdForTurn,\n createAssistantDraftWriter,\n rowIdOf,\n storeSupportsDraftPersistence,\n type AssistantDraftStore,\n type AssistantDraftWriter,\n type DraftPersistenceTuning,\n} from './draft-persistence'\nimport {\n createInteractionAnswerRoute,\n type InteractionAnswerRoute,\n type InteractionAnswerRouteOptions,\n} from '../interactions/route'\nimport {\n coalesceDeltas,\n createBufferedTurnTap,\n normalizeClientTurnId,\n replayTurnEvents,\n resolveChatTurn,\n stampReplaySeq,\n type PersistedChatMessageForTurn,\n type TurnEventStore,\n} from '../stream/index'\nimport type { ModelFailoverAttempt } from '../model-resolution/failover'\nimport { parseJsonObjectBody } from '../web/index'\nimport {\n assertPromptPartsWithinCap,\n ChatTurnInputError,\n parseChatTurnParts,\n parseFileMentions,\n type ChatTurnFilePartInput,\n type ChatTurnPartInput,\n type ChatTurnRequestPayload,\n type FileMention,\n} from './wire'\n\n// ── seams ───────────────────────────────────────────────────────────────────\n\n/** Usage receipt persisted onto the assistant message (the flattened\n * `step-finish` shape `/chat-store`'s columns mirror). */\nexport interface ChatTurnUsage {\n inputTokens?: number\n outputTokens?: number\n reasoningTokens?: number\n cacheReadTokens?: number\n cacheWriteTokens?: number\n costUsd?: number\n}\n\n/** What the route persists — a structural subset of `/chat-store`'s\n * `ChatStore`, so `createChatStore(db, tables)` satisfies it directly and a\n * product with its own persistence adapts without importing drizzle. */\nexport interface ChatTurnMessageStore {\n listMessages(threadId: string): Promise<Array<{\n id: string\n role: 'user' | 'assistant' | 'system' | 'tool'\n content: string\n parts?: ChatMessagePart[] | null\n }>>\n appendMessage(input: {\n /** Caller-assigned row id. Honored by `/chat-store`'s `createChatStore`;\n * a product store free to ignore it (the writer then adopts whatever id\n * the insert returns). Set only by incremental persistence. */\n id?: string\n threadId: string\n role: 'user' | 'assistant'\n content: string\n parts?: ChatMessagePart[]\n model?: string | null\n requestedModel?: string | null\n servedModel?: string | null\n servedProvider?: string | null\n servedSource?: string | null\n inputTokens?: number | null\n outputTokens?: number | null\n reasoningTokens?: number | null\n cacheReadTokens?: number | null\n cacheWriteTokens?: number | null\n costUsd?: number | null\n }): Promise<unknown>\n /** Patch an existing row. Its PRESENCE is what enables incremental assistant\n * persistence — a store without it keeps today's exact single-write-on-\n * completion behavior, which is what makes this additive. */\n updateMessage?(id: string, patch: {\n content?: string\n parts?: ChatMessagePart[]\n model?: string | null\n requestedModel?: string | null\n servedModel?: string | null\n servedProvider?: string | null\n servedSource?: string | null\n inputTokens?: number | null\n outputTokens?: number | null\n reasoningTokens?: number | null\n cacheReadTokens?: number | null\n cacheWriteTokens?: number | null\n costUsd?: number | null\n }): Promise<unknown>\n /** Remove a row. Used to retract a draft assistant row for a turn that ended\n * producing nothing, so the empty-turn case still leaves no row at all. */\n deleteMessage?(id: string): Promise<unknown>\n}\n\n/** `ChatTurnProducer` plus the persisted projection the assembly reads after\n * drain. `createSandboxChatProducer` returns this; a router-lane producer\n * may omit the optional members (finalText persists as a single text part). */\nexport interface ChatTurnRouteProducer extends ChatTurnProducer {\n assistantParts?(): Array<Record<string, unknown>>\n /** MID-STREAM snapshot of the same projection, safe to call while the turn\n * is running: no dangling-tool terminalization, no pending-ask settlement\n * (see `/stream`'s `draftAssistantParts`). Read by incremental persistence.\n * A producer that omits it still drafts — the scalar text — but persists no\n * parts until the turn completes. */\n draftParts?(): Array<Record<string, unknown>>\n usage?(): ChatTurnUsage\n /** The model that SERVED the turn. With failover wired this is the model that\n * actually answered, which is not necessarily the one the caller preferred —\n * read it after the stream drains, never before. */\n model?: string\n /** Model-failover attribution, when the producer supports it. Reported onto\n * the usage/billing receipt so a downgrade is never silent. */\n modelFailover?(): ChatTurnModelFailover\n /** Requested-versus-served attribution reported by the sandbox sidecar.\n * `echoReceived` distinguishes a missing echo from a partial echo. */\n modelAttribution?(): ChatTurnModelAttribution\n}\n\n/** Which model served, and what it took to get there. */\nexport interface ChatTurnModelFailover {\n /** The model that served the turn. */\n model?: string\n /** Every model tried, in order, with the reason each was abandoned. */\n attempts: ModelFailoverAttempt[]\n /** True when the preferred model did not serve. */\n usedFallback: boolean\n}\n\n/** Requested and effective model attribution for one sandbox turn. */\nexport interface ChatTurnModelAttribution {\n /** The model explicitly requested by the caller, before shell failover. */\n requestedModel?: string\n /** The model the downstream sandbox reports actually served the turn. */\n servedModel?: string\n /** The provider that served the turn, when echoed by the sandbox. */\n servedProvider?: string\n /** How the sandbox selected the served model. */\n servedSource?: 'request' | 'environment' | 'profile'\n /** True when a structurally valid effective-backend echo was observed. */\n echoReceived: boolean\n}\n\n/** Resolve authorization status and context for a chat turn including tenant and user identification */\nexport type ChatTurnAuthorization<TContext> =\n | {\n ok: true\n tenantId: string\n userId: string\n context: TContext\n /** When `false`, skip the `role:'user'` message insert for this turn — for\n * a product-dispatched / synthetic turn (e.g. a follow-up the product\n * raised itself) that must not surface a new user row. Composes with —\n * never overrides — the engine's retry-dedup: `authorize` runs before\n * turn identity is resolved, so it cannot tell a retry from a fresh turn;\n * a turn already deduped stays deduped. Omit / `true` → today's behavior.\n *\n * Settled shape (#227). The validated case in both consumers is the same:\n * a durable plan-approval follow-up re-entering an execution the decision\n * route already enqueued — a decision, not a typed message, so it must\n * not surface a user bubble. Suppressing the insert also propagates:\n * `ChatTurnProduceArgs.userMessageId` is `null` for the rest of the turn\n * when there is no row to reuse, so a seam anchoring to the user row must\n * handle that arm rather than assume a string. */\n insertUserMessage?: boolean\n }\n | { ok: false; response: Response }\n\n/** Define arguments required to authorize a chat turn based on intent and request details */\nexport interface ChatTurnAuthorizeArgs {\n request: Request\n intent: 'turn' | 'replay' | 'running'\n /** Parsed, validated POST body (turn intent only). */\n body?: ChatTurnRequestPayload\n /** The buffered turn id being replayed (replay intent only). */\n turnId?: string\n /** The thread whose running turns are being discovered (running intent only). */\n threadId?: string\n}\n\n/** Define the arguments required to produce a chat turn with context and messaging details */\nexport interface ChatTurnProduceArgs<TContext> {\n request: Request\n body: ChatTurnRequestPayload\n identity: ChatTurnIdentity\n context: TContext\n /** The message to send: plain text, or parts when the client attached\n * files (a text part is prepended from `content` when present). */\n prompt: string | ChatTurnPartInput[]\n /** Stable id for cross-process reconnect (`deriveExecutionId`). */\n executionId: string\n /** The turn-buffer id announced to the client for replay. */\n turnStreamId: string\n priorMessages: PersistedChatMessageForTurn[]\n /** The durable `role:'user'` row this turn is anchored to — the row the\n * factory just inserted, or the one retry-dedup REUSED. Products anchor\n * optimistic-bubble swaps, retry targeting, and stop-polling to it, and it\n * is the only way to name a REUSED row (which `priorMessages` excludes).\n *\n * Three states, all meaningful:\n * - `undefined` — not resolved yet. `turnLock.acquire` is the one seam that\n * sees this: it runs before any side effect by contract, so the row does\n * not exist when it reads the args.\n * - `null` — resolved, no row: `authorize` returned `insertUserMessage:\n * false` on a turn with nothing to reuse, or the store's `appendMessage`\n * resolved without a usable id.\n * - a string — the row id, for `contextGate`, `beforeTurn`, and `produce`. */\n userMessageId?: string | null\n}\n\n/** One event as it crosses the route: the producer's own vocabulary, or an\n * injected keepalive. Same shape the engine forwards verbatim.\n *\n * Public because it IS the vocabulary of two seams — `onRawEvent`'s parameter\n * and `heartbeat.event`'s return. Exported so a product can declare a\n * standalone handler (`function onRawEvent(e: ChatRouteEvent, ctx: T)`) rather\n * than depending on contextual typing from an inline object literal. */\nexport type ChatRouteEvent = { type: string; data?: Record<string, unknown> }\n\n/** Best-effort human-readable cause from a terminal `error` /\n * `session.run.failed` event's `data`. */\nfunction failureReasonOf(data: Record<string, unknown> | undefined): string | undefined {\n if (!data) return undefined\n const message = data.message ?? data.error ?? data.reason\n if (typeof message === 'string' && message.length > 0) return message\n return undefined\n}\n\n/** Keepalive emitted while the producer is quiet (long tool calls, first-token\n * wait) so client watchdogs stay re-armed. One is emitted each time\n * `intervalMs` elapses with no producer event; the window resets on every real\n * event, so a chatty producer never triggers one. The product owns the event\n * shape (`type` + `data`). Omit → no keepalives (today's behavior). */\nexport interface ChatTurnHeartbeat {\n intervalMs: number\n event(info: { elapsedMs: number; tick: number }): ChatRouteEvent\n}\n\n/** Patch a `beforeTurn` hook returns to augment the producer's input. Omitted\n * fields keep the route-assembled value; the product's `produce` still owns\n * the system prompt. */\nexport interface ChatTurnInputPatch {\n prompt?: string | ChatTurnPartInput[]\n priorMessages?: PersistedChatMessageForTurn[]\n}\n\n/** Pre-turn readiness verdict — proceed, or short-circuit with the product's\n * own `Response` (e.g. a canned assistant reply asking for missing context).\n * Distinct from `authorize`: this gates domain readiness, not access. */\nexport type ChatTurnGateResult =\n | { proceed: true }\n | { proceed: false; response: Response }\n\n/** Single-flight lock verdict — acquired (with an opaque handle passed back to\n * `release`), or already held (short-circuit with the product's 409-style\n * `Response`). */\nexport type ChatTurnLockResult =\n | { acquired: true; handle?: unknown }\n | { acquired: false; response: Response }\n\n/** Async acquire/release wrapped around the turn. `acquire` runs before any\n * side effect; `release` runs once when the turn settles — including on a\n * short-circuit or a throw. */\nexport interface ChatTurnLock<TContext> {\n acquire(args: ChatTurnProduceArgs<TContext>): ChatTurnLockResult | Promise<ChatTurnLockResult>\n release(handle: unknown): void | Promise<void>\n}\n\ninterface ChatTurnLifecycleBase<TContext> {\n identity: ChatTurnIdentity\n executionId: string\n turnStreamId: string\n context: TContext\n}\n/** Define lifecycle start event with context and timestamp for a chat turn */\nexport interface ChatTurnLifecycleStart<TContext> extends ChatTurnLifecycleBase<TContext> {\n startedAt: number\n}\n/** Define the structure representing the completion state of a chat turn lifecycle with usage data */\nexport interface ChatTurnLifecycleComplete<TContext> extends ChatTurnLifecycleBase<TContext> {\n finalText: string\n usage: ChatTurnUsage\n durationMs: number\n /** The model that SERVED the turn — the fallback's id when failover moved it.\n * `usage` is that model's, so telemetry that splits cost or quality by model\n * must key on this and not on the requested one. */\n model?: string\n /** Requested-versus-served attribution. A difference is detectable from\n * this receipt and from the persisted assistant row independently. */\n requestedModel?: string\n servedModel?: string\n servedProvider?: string\n servedSource?: 'request' | 'environment' | 'profile'\n /** Attribution for a downgrade: which models were tried and why each failed.\n * `undefined` when the producer reports no failover support. */\n modelFailover?: ChatTurnModelFailover\n /** The durable `role:'assistant'` row this turn wrote, or `null` when it\n * wrote none (an empty turn leaves no row — a draft started mid-stream is\n * retracted). The detached lane surfaces the same id as\n * `DetachedTurnResult.messageId`; one contract, two lanes. */\n assistantMessageId: string | null\n /** Set when `contextGate` answered this turn and the producer never ran.\n *\n * Present so telemetry can tell \"the model produced nothing\" apart from \"the\n * model was never asked\" — they look identical here (`finalText: ''`,\n * empty `usage`) and mean opposite things. A gated turn writes no assistant\n * row, so `assistantMessageId` is `null`, and the billing/persistence\n * `onTurnComplete` is deliberately NOT fired for it. */\n gated?: true\n}\n/** Represent an error occurring during a chat turn lifecycle with context and duration information */\nexport interface ChatTurnLifecycleError<TContext> extends ChatTurnLifecycleBase<TContext> {\n error: unknown\n durationMs: number\n}\n\n/** Deterministic run telemetry: `onTurnStart` fires before the producer runs;\n * exactly one of `onTurnComplete` / `onTurnError` fires after the turn\n * settles, always after `onTurnStart`. Failure is derived from the turn's own\n * `error` / `session.run.failed` events (or a drain throw), not the engine's\n * lifecycle envelope. Hook errors are swallowed — telemetry never fails a\n * turn. */\nexport interface ChatTurnLifecycle<TContext> {\n onTurnStart?(info: ChatTurnLifecycleStart<TContext>): void | Promise<void>\n onTurnComplete?(info: ChatTurnLifecycleComplete<TContext>): void | Promise<void>\n onTurnError?(info: ChatTurnLifecycleError<TContext>): void | Promise<void>\n}\n\n/** What a settled turn reports to `onTurnComplete` — the product's\n * post-processing seam (billing, titles, audit). */\nexport interface ChatTurnCompleteInput<TContext> {\n identity: ChatTurnIdentity\n finalText: string\n context: TContext\n failed: boolean\n failureReason?: string\n /** The model that SERVED this turn. With failover wired it may differ from\n * the requested one, so a product that bills or scores per model MUST read\n * it here rather than assuming the model it asked for. */\n model?: string\n /** Requested-versus-served attribution. A difference is detectable from\n * this receipt and from the persisted assistant row independently. */\n requestedModel?: string\n servedModel?: string\n servedProvider?: string\n servedSource?: 'request' | 'environment' | 'profile'\n /** Present when the producer supports failover: the full attempt trail, and\n * `usedFallback` — the flag that makes a silent downgrade impossible. */\n modelFailover?: ChatTurnModelFailover\n /** The durable `role:'assistant'` row this turn wrote, or `null` when it\n * wrote none (an empty turn leaves no row).\n *\n * Populated even when `failed` is true — a terminal error event still\n * persists whatever partial answer arrived, and that pairing is exactly\n * what lets a product render an error row against a REAL message instead of\n * hunting for the newest row in the thread. The detached lane surfaces the\n * same id as `DetachedTurnResult.messageId`. */\n assistantMessageId: string | null\n}\n\n/** Define options to configure chat turn routes including authorization, storage, and event buffering */\nexport interface CreateChatTurnRoutesOptions<TContext = void> {\n /** Names the product in `deriveExecutionId` so retries land on the same\n * substrate execution. */\n projectId: string\n /** Authenticate + authorize the caller for a turn or a replay. The only\n * product-supplied access step: session auth, thread/workspace access,\n * seat/balance gates, rate limits all live here. */\n authorize(args: ChatTurnAuthorizeArgs): Promise<ChatTurnAuthorization<TContext>>\n /** Thread/message persistence (`/chat-store`'s store or a product adapter). */\n store: ChatTurnMessageStore\n /** Turn-event buffer (`createD1TurnEventStore(env.DB)` or `/turn-stream`'s\n * `createDurableObjectTurnEventStore(env.TURN_STREAM_DO)` in production,\n * `createMemoryTurnEventStore()` in tests). Wired by default — every turn\n * is buffered and replayable. */\n turnStore: TurnEventStore\n /** Build the turn's event stream. Sandbox lane: `streamSandboxPrompt(...)`\n * wrapped in `createSandboxChatProducer`. Router/openai-compat lane: the\n * product's own producer. May be async (box resolution). */\n produce(args: ChatTurnProduceArgs<TContext>): ChatTurnRouteProducer | Promise<ChatTurnRouteProducer>\n /** Single-flight lock acquired before any side effect and released once when\n * the turn settles (including short-circuit/throw). `/turn-stream`'s\n * `createDurableTurnLock` is the shared DO-backed implementation. Omit →\n * no lock. */\n turnLock?: ChatTurnLock<TContext>\n /** Pre-turn readiness gate that can short-circuit with a product `Response`\n * before the producer runs (the user row is already persisted). Runs after\n * `turnLock.acquire`, before `beforeTurn`. Omit → always proceed.\n *\n * Settled shape (#227). What a consumer may depend on:\n * - `args.userMessageId` is RESOLVED here — a string, or `null` when the\n * insert was suppressed with nothing to reuse. (`turnLock.acquire` is the\n * only seam that sees it `undefined`.)\n * - `{proceed:false}` releases the lock and returns the product's `Response`\n * verbatim: `beforeTurn` and `produce` never run, no assistant row is\n * written, and the user row already inserted is KEPT — a real user turn\n * whose assistant side is the gate's own response.\n * - Always returning `{proceed:true}` is supported, not a misuse: the seam\n * doubles as the one place that runs after the user row exists and before\n * the producer, which is where per-turn analytics and readiness\n * precomputation belong. A gate that never gates is a valid consumer. */\n contextGate?(args: ChatTurnProduceArgs<TContext>): ChatTurnGateResult | Promise<ChatTurnGateResult>\n /** Observe the assembled producer input and optionally augment it (rewrite\n * the prompt / prior messages) before the producer runs. Omit → no change.\n *\n * Settled shape (#227). BOTH return arms are contract:\n * - a `ChatTurnInputPatch` shallow-merges over the route-assembled args, so\n * an omitted field keeps the route's value;\n * - `void` means \"no patch\" — and mutating `args.context` in place is the\n * supported way to thread request-scoped state forward to `produce` /\n * `lifecycle` / `onTurnComplete`, which all receive the same object.\n *\n * A throw propagates (the turn fails with the lock released). It runs BEFORE\n * `lifecycle.onTurnStart`, so a throw here fires no terminal lifecycle hook —\n * the span never opened. Telemetry for a failure in this seam belongs in the\n * seam, not in `lifecycle`. */\n beforeTurn?(args: ChatTurnProduceArgs<TContext>): ChatTurnInputPatch | void | Promise<ChatTurnInputPatch | void>\n /** Deterministic run telemetry (start / complete / error) with identity and\n * timing. Omit → no telemetry. */\n lifecycle?: ChatTurnLifecycle<TContext>\n /** Keepalive injected while the producer is quiet. Omit → no keepalives. */\n heartbeat?: ChatTurnHeartbeat\n /** Observe each event the producer emits, before the engine frames it and\n * before any heartbeat injection (the raw sidecar-producer events, for\n * telemetry). Never alters the stream; errors are swallowed. Distinct from\n * `onEvent`, which sees the engine-framed stream incl. lifecycle envelopes.\n *\n * Settled shape (#227). What a consumer may depend on:\n * - it sees EXACTLY the producer's own events — no engine lifecycle\n * envelopes, and no injected keepalives (`heartbeat` wraps the stream\n * downstream of this tap, so a synthetic event never reaches a trace);\n * - a throw is caught and logged, never surfaced: a broken telemetry sink\n * cannot fail a turn or truncate the client's stream;\n * - it is an OBSERVER — the return value is ignored, and the event object\n * continues downstream. Mutating it mutates the stream; don't.\n *\n * Takes `(event, context)` rather than one args object, matching both\n * shipped consumers; see the module header on why it stays that way. */\n onRawEvent?(event: ChatRouteEvent, context: TContext): void | Promise<void>\n /** Pre-persist transform of the final text (e.g. `/redact`'s `redactPII`).\n * Live stream is never altered. */\n transformFinalText?(text: string): string | Promise<string>\n /** Incremental persistence of the assistant row WHILE the turn streams\n * (`./draft-persistence`), so a viewer arriving mid-run is served from\n * durable storage instead of the streaming gateway's hot event buffer.\n *\n * This is what lets the gateway keep that hot buffer SHORT at scale: its\n * Redis footprint is one sorted set per session refreshed on every push, so\n * memory grows LINEARLY with `ttl x concurrent sessions`. Stretching the\n * TTL to cover late viewers buys memory proportional to the increase and\n * still serves nothing past the new horizon. The buffer stays a reconnect\n * window; durable storage — kept at most one cadence interval stale by this\n * option — is the history tier.\n *\n * Enabled by DEFAULT whenever `store.updateMessage` exists (every\n * `/chat-store` consumer); a store without it keeps today's exact\n * single-write behavior. Pass `false` to opt out, or an object to tune the\n * cadence. Wiring it against a store that cannot patch rows throws at route\n * construction rather than silently no-op'ing. */\n incrementalPersistence?: false | DraftPersistenceTuning\n /** Deterministic durable row id for this turn's assistant message. Default\n * `assistant:<executionId>` — already stable across retries because\n * `deriveExecutionId` is. Override only if the product's message ids have a\n * format constraint; it MUST stay deterministic per turn or a re-entered\n * turn will duplicate its row instead of converging. */\n draftMessageId?(args: { identity: ChatTurnIdentity; executionId: string; threadId: string }): string\n /** Post-processing after a turn settles (billing, titles, audit). Fires with\n * `failed:true` + `failureReason` when the turn carried a terminal error\n * event (model 402 / rate-limit / server error) instead of a clean\n * completion, so products skip the deduct and render an error row rather\n * than billing an empty turn and marking it done. A turn that THROWS never\n * reaches this hook (the engine skips it on a producer throw). Errors are\n * swallowed by the engine — they never fail a streamed turn. */\n onTurnComplete?(input: ChatTurnCompleteInput<TContext>): Promise<void>\n /** Per-event side channel (product broadcast). The turn-buffer tap is\n * already wired; this runs in addition. */\n onEvent?(event: { type: string; data?: Record<string, unknown> }, context: TContext): void | Promise<void>\n /** Trace flush handed to `waitUntil` (OTLP export). */\n traceFlush?(context: TContext): Promise<void>\n /** Compose the interaction-answer endpoints (`/interactions`). Omit when the\n * product has no sidecar ask channel. */\n interactions?: InteractionAnswerRouteOptions\n /** Byte budget for inline prompt parts. Default `INLINE_PARTS_MAX_BYTES`. */\n maxInlinePartBytes?: number\n /** Per-flush coalescer for the turn buffer. Default `coalesceDeltas` (this\n * assembly streams the client vocabulary's `{type:'text'|'reasoning',\n * text}` lines, which it merges). A producer streaming raw\n * `message.part.updated` events passes `coalesceChatStreamEvents`. */\n coalesceTurnEvents?: (events: unknown[]) => unknown[]\n replay?: { pollMs?: number; timeoutMs?: number }\n log?: (message: string, meta?: Record<string, unknown>) => void\n}\n\n/** Define routes to run, replay, and list running chat turns with streaming and reconnect support */\nexport interface ChatTurnRoutes {\n /** POST — run one turn, streaming NDJSON. First line is\n * `{type:'turn', turnId}` (the replay handle); the rest is the engine's\n * event protocol. Pass the platform's `waitUntil` so the turn keeps\n * running (and buffering) after a client disconnect. */\n turn(request: Request, ctx?: { waitUntil?(p: Promise<unknown>): void }): Promise<Response>\n /** GET — replay a buffered turn from `?fromSeq=` (0 = everything), then\n * follow it live until it completes. */\n replay(request: Request, params: { turnId: string }): Promise<Response>\n /** GET `?threadId=` — the reconnect-discovery endpoint: the turn ids still\n * running on a thread, so a client that reloaded mid-turn can re-attach to\n * the live stream via {@link replay} instead of losing it. Returns `[]` when\n * the turn store cannot enumerate running turns (`listRunning` unimplemented). */\n running(request: Request): Promise<Response>\n /** list/answer endpoints from `/interactions`; null when not configured. */\n interactions: InteractionAnswerRoute | null\n}\n\n// ── body validation ────────────────────────────────────────────────────────\n\nfunction errorResponse(err: ChatTurnInputError): Response {\n return Response.json({ code: err.code, error: err.message }, { status: err.status })\n}\n\ninterface ParsedTurnBody {\n payload: ChatTurnRequestPayload\n content: string\n fileParts: ChatTurnFilePartInput[]\n mentions: FileMention[]\n turnId: string | undefined\n}\n\nfunction validateTurnBody(body: Record<string, unknown>, maxInlinePartBytes: number | undefined): ParsedTurnBody {\n const threadId = typeof body.threadId === 'string' ? body.threadId.trim() : ''\n if (!threadId) throw new ChatTurnInputError('Missing threadId')\n const rawContent = body.content ?? body.message ?? ''\n if (typeof rawContent !== 'string') throw new ChatTurnInputError('content must be a string')\n const content = rawContent.trim()\n const fileParts = parseChatTurnParts(body.parts)\n // Path references, not bytes — validated for traversal/charset/count, never\n // counted against the inline-parts byte budget below.\n const mentions = parseFileMentions(body.mentions)\n // A mention is a turn's whole payload often enough to count: \"@chart.png\"\n // with no prose is a real ask, and the pointer block the mentions produce is\n // prompt content the model reads.\n const hasAttachments = Array.isArray(body.attachments) && body.attachments.length > 0\n if (!content && fileParts.length === 0 && mentions.length === 0 && !hasAttachments) {\n throw new ChatTurnInputError('Missing content (send text, parts, mentions, attachments, or any combination)')\n }\n assertPromptPartsWithinCap(fileParts, maxInlinePartBytes)\n let turnId: string | undefined\n try {\n turnId = normalizeClientTurnId(body.turnId)\n } catch (err) {\n throw new ChatTurnInputError(err instanceof Error ? err.message : 'Invalid turnId')\n }\n return {\n // The VALIDATED, deduped mention list replaces the raw one on the payload,\n // so every downstream seam (`authorize`, `contextGate`, `beforeTurn`,\n // `produce`) reads checked paths and never the request's own.\n payload: { ...body, threadId, content, mentions } as ChatTurnRequestPayload,\n content,\n fileParts,\n mentions,\n turnId,\n }\n}\n\n/** File parts persist onto the user message verbatim — the wire shape is the\n * persisted `ChatFilePart`/`ChatImagePart` vocabulary already. Mentions get\n * the one mapping step their own vocabulary needs. The typed projection is\n * `/chat-store`'s (same boundary as the assistant hop).\n *\n * Mentions persist as parts rather than being folded into the prompt because\n * the prompt is not readable back: a retry rebuilds the turn from the stored\n * row, and a transcript draws its pills from it. Turning them INTO prompt\n * text stays the product's job — only the product knows how to resolve a\n * workspace-relative path to an in-box one (`fileMentionsToParts`'\n * `resolvePath` seam), so the route never dispatches them itself. */\nfunction userPartsWithFiles(\n userParts: Array<Record<string, unknown>>,\n fileParts: ChatTurnFilePartInput[],\n mentions: FileMention[],\n): ChatMessagePart[] {\n return toChatMessageParts([\n ...userParts,\n ...fileParts.map((part) => ({ ...part })),\n ...mentions.map((mention) => ({ ...mentionInputToPart(mention) })),\n ])\n}\n\n// ── producer-stream wrappers (heartbeat + raw tap) ───────────────────────────\n\n/** Fire `onRawEvent` for each producer event, before the engine frames it.\n * Best-effort — a telemetry throw is logged, never propagated. */\nasync function* tapRawEvents(\n source: AsyncIterable<ChatRouteEvent>,\n onRawEvent: (event: ChatRouteEvent) => void | Promise<void>,\n log: (message: string, meta?: Record<string, unknown>) => void,\n): AsyncGenerator<ChatRouteEvent, void, unknown> {\n for await (const event of source) {\n try {\n await onRawEvent(event)\n } catch (err) {\n log('[chat-routes] onRawEvent failed', { error: err instanceof Error ? err.message : String(err) })\n }\n yield event\n }\n}\n\n/** Inject a keepalive whenever `intervalMs` elapses with no source event. The\n * silent window (elapsed + tick) resets on every real event, so a producer\n * that keeps emitting never triggers a heartbeat. Closes the source on early\n * return, matching a `for await` over it. */\nasync function* withStreamHeartbeat(\n source: AsyncIterable<ChatRouteEvent>,\n intervalMs: number,\n makeEvent: (info: { elapsedMs: number; tick: number }) => ChatRouteEvent,\n): AsyncGenerator<ChatRouteEvent, void, unknown> {\n const iterator = source[Symbol.asyncIterator]()\n try {\n let pending = iterator.next()\n let windowStart = Date.now()\n let tick = 0\n for (;;) {\n let timer: ReturnType<typeof setTimeout> | undefined\n let winner: 'event' | 'heartbeat'\n try {\n const heartbeat = new Promise<'heartbeat'>((resolve) => {\n timer = setTimeout(() => resolve('heartbeat'), intervalMs)\n })\n winner = await Promise.race([pending.then(() => 'event' as const), heartbeat])\n } finally {\n // Clear the pending timer on EVERY exit — including a `pending`\n // rejection — so a rejected source never orphans a setTimeout that\n // keeps the runtime alive for up to `intervalMs`.\n if (timer !== undefined) clearTimeout(timer)\n }\n if (winner === 'heartbeat') {\n tick += 1\n yield makeEvent({ elapsedMs: Date.now() - windowStart, tick })\n continue\n }\n const result = await pending\n if (result.done) return\n yield result.value\n pending = iterator.next()\n windowStart = Date.now()\n tick = 0\n }\n } finally {\n await iterator.return?.()\n }\n}\n\n// ── the factory ────────────────────────────────────────────────────────────\n\n/** Build chat turn routes to handle and validate incoming chat requests with optional logging */\nexport function createChatTurnRoutes<TContext = void>(\n options: CreateChatTurnRoutesOptions<TContext>,\n): ChatTurnRoutes {\n const log = options.log ?? ((message, meta) => console.error(message, meta ?? ''))\n\n // Incremental persistence is on whenever the store can patch a row. An\n // EXPLICIT opt-in against a store that cannot is a wiring bug, so it fails\n // here — at construction — instead of silently degrading every turn.\n const draftStore: AssistantDraftStore = options.store\n if (options.incrementalPersistence && !storeSupportsDraftPersistence(draftStore)) {\n throw new Error(\n 'incrementalPersistence requires a store with updateMessage() — `/chat-store`\\'s createChatStore has it; a product store must implement it or omit the option',\n )\n }\n const draftTuning: DraftPersistenceTuning | null =\n options.incrementalPersistence === false || !storeSupportsDraftPersistence(draftStore)\n ? null\n : (options.incrementalPersistence ?? {})\n\n async function turn(request: Request, ctx?: { waitUntil?(p: Promise<unknown>): void }): Promise<Response> {\n const [rawBody, badBody] = await parseJsonObjectBody(request)\n if (badBody) return badBody\n\n let parsed: ParsedTurnBody\n try {\n parsed = validateTurnBody(rawBody, options.maxInlinePartBytes)\n } catch (err) {\n if (err instanceof ChatTurnInputError) return errorResponse(err)\n throw err\n }\n const { payload, content, fileParts, mentions, turnId } = parsed\n\n const auth = await options.authorize({ request, intent: 'turn', body: payload })\n if (!auth.ok) return auth.response\n const { tenantId, userId, context } = auth\n\n // Turn identity: reuse the just-persisted user row on a retry (same\n // turnId or identical trailing content) instead of double-inserting.\n const existingMessages = (await options.store.listMessages(payload.threadId)).map((m) => ({\n id: m.id,\n role: m.role,\n content: m.content,\n parts: (m.parts ?? null) as PersistedChatMessageForTurn['parts'],\n }))\n // An assistant row already trailing the thread is either this turn's\n // in-flight draft (retry — walk past it) or a settled answer (a genuine\n // repeat — new turn). The turn buffer's running index is the discriminator;\n // it is only consulted in the one case that can be ambiguous, so the common\n // path costs nothing.\n let hasRunningTurn = false\n if (draftTuning && existingMessages.at(-1)?.role === 'assistant') {\n try {\n hasRunningTurn = ((await options.turnStore.listRunning?.(payload.threadId)) ?? []).length > 0\n } catch (err) {\n log('[chat-routes] listRunning probe failed; treating the thread as idle', {\n threadId: payload.threadId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n const chatTurn = resolveChatTurn({ existingMessages, userContent: content, turnId, hasRunningTurn })\n\n const identity: ChatTurnIdentity = {\n tenantId,\n sessionId: payload.threadId,\n userId,\n turnIndex: chatTurn.turnIndex,\n }\n const executionId = deriveExecutionId({\n projectId: options.projectId,\n sessionId: payload.threadId,\n turnIndex: chatTurn.turnIndex,\n })\n const turnStreamId = crypto.randomUUID()\n\n const prompt: string | ChatTurnPartInput[] =\n fileParts.length === 0\n ? content\n : content\n ? [{ type: 'text', text: content }, ...fileParts]\n : [...fileParts]\n\n // The producer input every pre-turn seam reads. Replaced — never mutated\n // in place — as later steps resolve more of it (`userMessageId` after the\n // insert, `beforeTurn`'s patch after that), so a seam that captured the\n // object never sees it change underneath. `produce` reads the last\n // version because the engine defers its first pull.\n let produceArgs: ChatTurnProduceArgs<TContext> = {\n request,\n body: payload,\n identity,\n context,\n prompt,\n executionId,\n turnStreamId,\n priorMessages: chatTurn.priorMessages,\n }\n\n // Single-flight lock: acquire before any side effect. `release` runs\n // exactly once — in the drain's `finally` on a normal turn, or right here\n // on a short-circuit / throw.\n let lockAcquired = false\n let lockHandle: unknown\n let lockReleased = false\n const releaseLock = async (): Promise<void> => {\n if (!lockAcquired || lockReleased) return\n lockReleased = true\n try {\n await options.turnLock!.release(lockHandle)\n } catch (err) {\n log('[chat-routes] turnLock.release failed', {\n turnId: turnStreamId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n if (options.turnLock) {\n const acquired = await options.turnLock.acquire(produceArgs)\n if (!acquired.acquired) return acquired.response\n lockAcquired = true\n lockHandle = acquired.handle\n }\n\n // Turn state, hoisted so the pre-stream `catch` can settle the lifecycle\n // (fire `onTurnError`, close the span) even when a seam throws\n // synchronously before the drain — the drain would otherwise be the only\n // path that runs the terminal hook.\n let producer: ChatTurnRouteProducer | undefined\n let draft: AssistantDraftWriter | undefined\n // Set once persistence settles; `undefined` means it never ran.\n let assistantMessageId: string | null | undefined\n /** The assistant row this turn ended with. Falls back to the draft writer\n * so a turn whose producer THREW — skipping `persistAssistantMessage`\n * entirely — still names the partial row it left behind. */\n const assistantRowId = (): string | null => assistantMessageId ?? draft?.rowId() ?? null\n let runFailed = false\n // Data of the event that marked the run failed — handed to `onTurnError`\n // when no drain throw supplies a richer cause.\n let lastFailureData: Record<string, unknown> | undefined\n let turnStartedAtMs = 0\n let turnStarted = false\n let lifecycleSettled = false\n // Set when `contextGate` answered the turn without running the producer.\n let gatedTurn = false\n\n // Exactly one terminal lifecycle hook, after the turn settles (idempotent).\n // Failure is this route's own verdict (`runFailed` from error/failed\n // events, or a drain/sync throw), not the engine's envelope.\n const fireTerminalLifecycle = async (failed: boolean, terminalError: unknown): Promise<void> => {\n if (lifecycleSettled) return\n lifecycleSettled = true\n const lifecycle = options.lifecycle\n if (!lifecycle) return\n const durationMs = Date.now() - turnStartedAtMs\n try {\n if (failed) {\n await lifecycle.onTurnError?.({\n identity, executionId, turnStreamId, context, durationMs,\n error: terminalError ?? lastFailureData ?? new Error('chat turn failed'),\n })\n } else {\n const failoverInfo = producer?.modelFailover?.()\n const attribution = producer?.modelAttribution?.()\n await lifecycle.onTurnComplete?.({\n identity, executionId, turnStreamId, context, durationMs,\n finalText: producer?.finalText() ?? '',\n usage: producer?.usage?.() ?? {},\n assistantMessageId: assistantRowId(),\n ...(gatedTurn ? { gated: true } : {}),\n ...(producer?.model ? { model: producer.model } : {}),\n ...(attribution?.requestedModel ? { requestedModel: attribution.requestedModel } : {}),\n ...(attribution?.servedModel ? { servedModel: attribution.servedModel } : {}),\n ...(attribution?.servedProvider ? { servedProvider: attribution.servedProvider } : {}),\n ...(attribution?.servedSource ? { servedSource: attribution.servedSource } : {}),\n ...(failoverInfo ? { modelFailover: failoverInfo } : {}),\n })\n }\n } catch (err) {\n log('[chat-routes] lifecycle terminal hook failed', {\n turnId: turnStreamId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n try {\n // The product (via `authorize`) may suppress the user-row insert for a\n // dispatched/synthetic turn. AND-composition: it can only subtract, never\n // resurrect a turn the engine already deduped as a retry.\n const insertUserMessage = chatTurn.shouldInsertUserMessage && (auth.insertUserMessage ?? true)\n // Name the row this turn is anchored to. Insert and reuse are mutually\n // exclusive (`shouldInsertUserMessage` is false exactly when a reusable\n // row was found), so one source or the other — never both, never a\n // guess. Products used to re-derive this by decorating the store and\n // falling back to the newest row, which mis-resolves under sub-second\n // same-thread turns.\n let userMessageId: string | null = chatTurn.reusedUserMessageId ?? null\n if (insertUserMessage) {\n userMessageId = rowIdOf(\n await options.store.appendMessage({\n threadId: payload.threadId,\n role: 'user',\n content,\n parts: userPartsWithFiles(chatTurn.userParts, fileParts, mentions),\n }),\n )\n }\n produceArgs = { ...produceArgs, userMessageId }\n\n // Domain-readiness gate: may short-circuit with the product's own\n // response before the producer runs. The user row above is kept (a real\n // user turn); the gate's response is the assistant side of it.\n if (options.contextGate) {\n const gate = await options.contextGate(produceArgs)\n if (!gate.proceed) {\n await releaseLock()\n // A gated turn is still a TURN, and until now it was the one answer\n // path that fired no lifecycle hook at all — `turnStarted` is set\n // further down, so an early return here left telemetry with no record\n // that anything happened. A product whose gate answers everything\n // therefore looks IDLE rather than broken, which is indistinguishable\n // from healthy on every dashboard.\n //\n // Only the LIFECYCLE hooks fire (telemetry, errors swallowed); the\n // billing/persistence `onTurnComplete` deliberately does not, because\n // no model ran and no assistant row was written. `gated` marks the\n // completion so a consumer counts it without mistaking it for a model\n // turn that produced nothing.\n gatedTurn = true\n turnStartedAtMs = Date.now()\n if (options.lifecycle?.onTurnStart) {\n try {\n await options.lifecycle.onTurnStart({\n identity, executionId, turnStreamId, context, startedAt: turnStartedAtMs,\n })\n } catch (err) {\n log('[chat-routes] lifecycle.onTurnStart failed', {\n turnId: turnStreamId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n await fireTerminalLifecycle(false, undefined)\n return gate.response\n }\n }\n\n // Observe + optionally augment the assembled producer input.\n if (options.beforeTurn) {\n const patch = await options.beforeTurn(produceArgs)\n if (patch) produceArgs = { ...produceArgs, ...patch }\n }\n\n // Durability tap: every engine event buffers (coalesced) so a dropped\n // client replays the tail. Live delivery rides the Response body, not the\n // tap, so `write` is intentionally absent.\n const tap = createBufferedTurnTap({\n store: options.turnStore,\n turnId: turnStreamId,\n scopeId: payload.threadId,\n coalesce: options.coalesceTurnEvents ?? coalesceDeltas,\n })\n const turnMarker = { type: 'turn', turnId: turnStreamId }\n await tap.onEvent(turnMarker)\n\n // Durable projection: the turn buffer above serves LIVE reconnect; this\n // keeps the persisted transcript at most one cadence interval behind, so\n // a viewer arriving after the buffer's (deliberately short) hot window\n // reads the in-flight answer from storage instead of an empty thread.\n // Row identity is the turn's own `executionId`, so a re-entered turn\n // patches the row a previous attempt started instead of duplicating it.\n if (draftTuning) {\n draft = createAssistantDraftWriter({\n ...draftTuning,\n store: draftStore,\n threadId: payload.threadId,\n messageId: options.draftMessageId\n ? options.draftMessageId({ identity, executionId, threadId: payload.threadId })\n : assistantRowIdForTurn(executionId),\n snapshot: () => {\n if (!producer) return null\n return {\n content: producer.finalText(),\n ...(producer.draftParts ? { parts: producer.draftParts() } : {}),\n ...(producer.usage ? { usage: producer.usage() } : {}),\n ...(producer.model ? { model: producer.model } : {}),\n }\n },\n ...(options.transformFinalText ? { transformText: options.transformFinalText } : {}),\n log,\n })\n }\n\n turnStartedAtMs = Date.now()\n turnStarted = true\n if (options.lifecycle?.onTurnStart) {\n try {\n await options.lifecycle.onTurnStart({\n identity, executionId, turnStreamId, context, startedAt: turnStartedAtMs,\n })\n } catch (err) {\n log('[chat-routes] lifecycle.onTurnStart failed', {\n turnId: turnStreamId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n const result = handleChatTurn({\n identity,\n waitUntil: ctx?.waitUntil,\n log,\n hooks: {\n // The engine wants a synchronous producer; box resolution is async —\n // defer it into the generator's first pull.\n produce: () => ({\n stream: (async function* () {\n producer = await options.produce(produceArgs)\n let source: AsyncIterable<ChatRouteEvent> = producer.stream\n if (options.onRawEvent) {\n source = tapRawEvents(source, (event) => options.onRawEvent!(event, context), log)\n }\n if (options.heartbeat) {\n source = withStreamHeartbeat(source, options.heartbeat.intervalMs, options.heartbeat.event)\n }\n for await (const event of source) yield event\n })(),\n finalText: () => producer?.finalText() ?? '',\n }),\n onEvent: async (event) => {\n if (event.type === 'session.run.failed' || event.type === 'error') {\n runFailed = true\n lastFailureData = event.data\n }\n await tap.onEvent(event)\n // Synchronous bookkeeping; the write it may start is fire-and-\n // forget, so store latency never back-pressures the live stream.\n draft?.notify(event)\n if (options.onEvent) await options.onEvent(event, context)\n },\n ...(options.transformFinalText ? { transformFinalText: options.transformFinalText } : {}),\n persistAssistantMessage: async ({ finalText }) => {\n // The typed boundary: stream-normalizer records → stored vocabulary\n // (validating projection owned by /chat-store — no cast here). The\n // scalar `finalText` arrives already transformed by the engine; the\n // producer's text PARTS are raw, so the same transform must run over\n // each text segment before persistence or a redaction (legal PII)\n // leaks at rest through message.parts.\n const rawParts = producer?.assistantParts ? producer.assistantParts() : undefined\n const projected =\n rawParts && options.transformFinalText\n ? await Promise.all(\n rawParts.map(async (part) =>\n String((part as { type?: unknown }).type ?? '') === 'text'\n ? { ...part, text: await options.transformFinalText!(String((part as { text?: unknown }).text ?? '')) }\n : part,\n ),\n )\n : rawParts\n const parts = projected ? toChatMessageParts(projected) : undefined\n if (!finalText.trim() && (!parts || parts.length === 0)) {\n // Empty turn: today this leaves no assistant row at all, so a\n // draft row started earlier is retracted, not left behind.\n await draft?.discard()\n // Read the writer rather than assuming `null`: `discard` is a\n // no-op on a store without `deleteMessage` (drafting needs only\n // `updateMessage`), and there the draft row genuinely survives.\n assistantMessageId = draft?.rowId() ?? null\n return\n }\n const usage = producer?.usage?.() ?? {}\n const attribution = producer?.modelAttribution?.()\n const values = {\n content: finalText,\n ...(parts && parts.length > 0 ? { parts } : {}),\n ...(producer?.model ? { model: producer.model } : {}),\n ...(attribution?.requestedModel ? { requestedModel: attribution.requestedModel } : {}),\n ...(attribution?.servedModel ? { servedModel: attribution.servedModel } : {}),\n ...(attribution?.servedProvider ? { servedProvider: attribution.servedProvider } : {}),\n ...(attribution?.servedSource ? { servedSource: attribution.servedSource } : {}),\n ...(usage.inputTokens !== undefined ? { inputTokens: usage.inputTokens } : {}),\n ...(usage.outputTokens !== undefined ? { outputTokens: usage.outputTokens } : {}),\n ...(usage.reasoningTokens !== undefined ? { reasoningTokens: usage.reasoningTokens } : {}),\n ...(usage.cacheReadTokens !== undefined ? { cacheReadTokens: usage.cacheReadTokens } : {}),\n ...(usage.cacheWriteTokens !== undefined ? { cacheWriteTokens: usage.cacheWriteTokens } : {}),\n ...(usage.costUsd !== undefined ? { costUsd: usage.costUsd } : {}),\n }\n // ONE row per turn either way. With drafting, `finalize` settles\n // any in-flight draft and then insert-or-patches the same\n // deterministic id — so the row this turn ends with is identical to\n // the one the single-write path produces. Without it, today's\n // append is untouched.\n if (draft) {\n await draft.finalize(values)\n assistantMessageId = draft.rowId() ?? null\n return\n }\n assistantMessageId = rowIdOf(\n await options.store.appendMessage({\n threadId: payload.threadId,\n role: 'assistant',\n ...values,\n }),\n )\n },\n ...(options.onTurnComplete\n ? {\n // Wired into the engine's completion hook, which fires only when\n // the stream ended without throwing. A terminal error EVENT\n // (not a throw) still lands here — so surface `runFailed` so the\n // product skips billing an errored turn instead of marking it\n // complete with empty text.\n onTurnComplete: ({ identity: turnIdentity, finalText }: { identity: ChatTurnIdentity; finalText: string }) => {\n // Read AFTER the drain, so the model reported to billing is\n // the one that actually served — a fallback included.\n const failoverInfo = producer?.modelFailover?.()\n const attribution = producer?.modelAttribution?.()\n return options.onTurnComplete!({\n identity: turnIdentity,\n finalText,\n context,\n failed: runFailed,\n assistantMessageId: assistantRowId(),\n ...(runFailed ? { failureReason: failureReasonOf(lastFailureData) } : {}),\n ...(producer?.model ? { model: producer.model } : {}),\n ...(attribution?.requestedModel ? { requestedModel: attribution.requestedModel } : {}),\n ...(attribution?.servedModel ? { servedModel: attribution.servedModel } : {}),\n ...(attribution?.servedProvider ? { servedProvider: attribution.servedProvider } : {}),\n ...(attribution?.servedSource ? { servedSource: attribution.servedSource } : {}),\n ...(failoverInfo ? { modelFailover: failoverInfo } : {}),\n })\n },\n }\n : {}),\n ...(options.traceFlush ? { traceFlush: () => options.traceFlush!(context) } : {}),\n },\n })\n\n // Tee: one branch to the live client, one drained under waitUntil so the\n // turn (and its buffering via onEvent) runs to completion after a client\n // drop — the engine body executes as it is pulled.\n const [clientBody, drainBody] = result.body.tee()\n const drained = (async () => {\n const reader = drainBody.getReader()\n let drainError: unknown\n try {\n for (;;) {\n const { done } = await reader.read()\n if (done) break\n }\n } catch (err) {\n drainError = err\n log('[chat-routes] turn drain failed', {\n turnId: turnStreamId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n const failed = runFailed || drainError !== undefined\n // Settle any in-flight draft INSIDE the waitUntil-tracked drain. On the\n // path where the producer throws, `persistAssistantMessage` never runs\n // to close the writer, and an unawaited write would race the isolate's\n // teardown — leaving a torn row instead of a clean partial one for a\n // re-entered turn to adopt.\n await draft?.close()\n try {\n await tap.done(failed ? 'error' : 'complete')\n } catch (err) {\n log('[chat-routes] turn buffer finalize failed', {\n turnId: turnStreamId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n await fireTerminalLifecycle(failed, drainError)\n await releaseLock()\n })()\n if (ctx?.waitUntil) ctx.waitUntil(drained)\n else void drained.catch(() => {})\n\n // Announce the replay handle before the engine's first event.\n const encoder = new TextEncoder()\n const marker = new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(encoder.encode(`${JSON.stringify(turnMarker)}\\n`))\n controller.close()\n },\n })\n const body = concatStreams([marker, clientBody])\n\n return new Response(body, {\n headers: {\n 'Content-Type': result.contentType,\n 'Cache-Control': 'no-cache',\n },\n })\n } catch (err) {\n // A throw before the turn began streaming (user-insert, gate, beforeTurn,\n // lifecycle-start, tap setup, engine construction, tee). If the turn had\n // already started, settle the lifecycle with `onTurnError` (close the\n // span) — the drain never ran to do it. Then release the lock, propagate.\n if (turnStarted) await fireTerminalLifecycle(true, err)\n await releaseLock()\n throw err\n }\n }\n\n async function replay(request: Request, params: { turnId: string }): Promise<Response> {\n const turnId = params.turnId?.trim()\n if (!turnId) return Response.json({ error: 'Missing turnId' }, { status: 400 })\n const auth = await options.authorize({ request, intent: 'replay', turnId })\n if (!auth.ok) return auth.response\n\n const fromSeqRaw = new URL(request.url).searchParams.get('fromSeq')\n const fromSeq = fromSeqRaw ? Math.max(0, Math.trunc(Number(fromSeqRaw)) || 0) : 0\n\n const encoder = new TextEncoder()\n const events = replayTurnEvents({\n store: options.turnStore,\n turnId,\n fromSeq,\n ...(options.replay?.pollMs !== undefined ? { pollMs: options.replay.pollMs } : {}),\n ...(options.replay?.timeoutMs !== undefined ? { timeoutMs: options.replay.timeoutMs } : {}),\n })\n const body = new ReadableStream<Uint8Array>({\n async pull(controller) {\n const { done, value } = await events.next()\n if (done) {\n controller.close()\n return\n }\n // Stamp the buffer ordinal so a reconnecting client resumes from\n // `?fromSeq=<lastSeq>` instead of refetching the turn from 0 and\n // re-applying every delta onto already-rendered state.\n controller.enqueue(encoder.encode(`${stampReplaySeq(value)}\\n`))\n },\n cancel() {\n void events.return(undefined)\n },\n })\n return new Response(body, {\n headers: {\n 'Content-Type': 'application/x-ndjson',\n 'Cache-Control': 'no-cache',\n },\n })\n }\n\n async function running(request: Request): Promise<Response> {\n const threadId = new URL(request.url).searchParams.get('threadId')?.trim()\n if (!threadId) return Response.json({ error: 'Missing threadId' }, { status: 400 })\n const auth = await options.authorize({ request, intent: 'running', threadId })\n if (!auth.ok) return auth.response\n // `listRunning` is optional on the store; a store that cannot enumerate\n // running turns simply reports none — the client falls back to the persisted\n // transcript, never to a hang.\n const ids = (await options.turnStore.listRunning?.(threadId)) ?? []\n return Response.json({ running: ids })\n }\n\n return {\n turn,\n replay,\n running,\n interactions: options.interactions ? createInteractionAnswerRoute(options.interactions) : null,\n }\n}\n\n/** Sequential concat of byte streams (marker line, then the engine body). */\nfunction concatStreams(streams: ReadableStream<Uint8Array>[]): ReadableStream<Uint8Array> {\n let index = 0\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n for (;;) {\n if (!reader) {\n const next = streams[index++]\n if (!next) {\n controller.close()\n return\n }\n reader = next.getReader()\n }\n const { done, value } = await reader.read()\n if (done) {\n reader = null\n continue\n }\n controller.enqueue(value)\n return\n }\n },\n async cancel(reason) {\n await reader?.cancel(reason)\n for (const stream of streams.slice(index)) await stream.cancel(reason)\n },\n })\n}\n","/**\n * Incremental (\"draft\") persistence of the assistant row WHILE a turn streams.\n *\n * Why this exists — the scale argument, not a convenience:\n *\n * A live turn is readable from two places. The hot path is the session\n * gateway's in-memory/Redis event buffer, which exists so a viewer survives a\n * network blip: it is keyed one sorted set per session, refreshed on every\n * push, and expires on a TTL. Its memory cost is `arrival_rate x TTL x\n * bytes_per_session` — strictly LINEAR in the TTL. Stretching that TTL to\n * cover \"a viewer who opens the tab later\" is a category error: it buys memory\n * proportional to the increase and still serves nothing to a viewer who\n * arrives past the new horizon.\n *\n * So the hot buffer must stay SHORT (live delivery + reconnect only), and\n * durable storage must serve history. That only works if durable storage\n * actually HAS the in-flight turn — which, before this module, it did not: the\n * assistant row was written once, after the stream drained. A viewer arriving\n * mid-turn past the hot window read an empty transcript.\n *\n * This module closes that gap: the assistant row is inserted early and patched\n * on a coalesced cadence, so the durable transcript is at most one interval\n * (default 2 s) behind the live stream and the hot buffer never has to be the\n * history tier.\n *\n * Mechanism only. It owns no vocabulary: the caller supplies the snapshot\n * function, the store, and the deterministic row id.\n *\n * Four properties the cadence guarantees:\n * - **Time-floored.** At most one write per `intervalMs`, never one per token.\n * - **Dirty-gated.** Only content-bearing events arm a write; heartbeats,\n * status pings, and lifecycle envelopes never do.\n * - **Single-flight.** A write already in flight suppresses the next trigger\n * instead of queueing; the final write is authoritative regardless of how\n * many drafts landed.\n * - **Best-effort.** A store failure is logged and swallowed — a durability\n * optimization must never kill a healthy stream (the same rule\n * `withDurableChatProjection` already states).\n */\n\nimport { toChatMessageParts, type ChatMessagePart } from '../chat-store/parts'\nimport type { ChatTurnUsage } from './turn-routes'\n\n/** Message row shape the writer reads back when re-entering a turn. */\nexport interface DraftStoredMessage {\n id: string\n role: 'user' | 'assistant' | 'system' | 'tool'\n content: string\n parts?: ChatMessagePart[] | null\n model?: string | null\n requestedModel?: string | null\n servedModel?: string | null\n servedProvider?: string | null\n servedSource?: string | null\n}\n\n/** Values written to the assistant row — the intersection of the append and\n * patch shapes, so one snapshot serves both. */\nexport interface AssistantRowValues {\n content: string\n parts?: ChatMessagePart[]\n model?: string | null\n requestedModel?: string | null\n servedModel?: string | null\n servedProvider?: string | null\n servedSource?: string | null\n inputTokens?: number | null\n outputTokens?: number | null\n reasoningTokens?: number | null\n cacheReadTokens?: number | null\n cacheWriteTokens?: number | null\n costUsd?: number | null\n}\n\n/** The store capability incremental persistence needs on top of\n * `appendMessage`. A store without `updateMessage` cannot patch a row, so it\n * cannot draft at all — the caller keeps today's single-write behavior. */\nexport interface AssistantDraftStore {\n listMessages(threadId: string): Promise<DraftStoredMessage[]>\n appendMessage(input: AssistantRowValues & {\n id?: string\n threadId: string\n role: 'user' | 'assistant'\n }): Promise<unknown>\n updateMessage?(id: string, patch: AssistantRowValues): Promise<unknown>\n deleteMessage?(id: string): Promise<unknown>\n}\n\n/** Live snapshot of the assistant body, taken from the producer's own\n * accumulators. `parts` is the DRAFT projection (`draftParts()`), never the\n * finalized one — see `draftAssistantParts`. */\nexport interface AssistantDraftSnapshot {\n content: string\n parts?: Array<Record<string, unknown>>\n usage?: ChatTurnUsage\n model?: string\n}\n\n/** Product-tunable cadence. Defaults are stated on each field; a product with\n * a chattier or heavier workload moves them without forking the writer. */\nexport interface DraftPersistenceTuning {\n /** Minimum wall-clock gap between draft writes, in ms. Default 2000.\n *\n * Justification for 2 s, from a measured tool-heavy production run (517\n * stream events over ~90 s wall = ~5.7 events/s): a 2 s floor with the\n * dirty gate turns 517 candidate writes into <= 45, while leaving the\n * durable row at most 2 s stale — an order of magnitude below the time it\n * takes a viewer to open a tab and render, so a late viewer never perceives\n * the lag. Fleet arithmetic at 10k concurrent 60 s runs: <= 30 updates per\n * run x 167 run-starts/s = ~334 row-updates/s spread over per-tenant\n * shards. Lower it and write amplification grows with no perceptible\n * freshness gain; raise it past ~5 s and a late viewer starts seeing a\n * visibly truncated answer. */\n intervalMs?: number\n /** Serialized-parts size (bytes) past which the interval backs off, so a\n * turn accumulating a megabyte-scale `parts` blob does not rewrite it every\n * interval. Default 262144 (256 KiB) -> interval x 2.5; ten times that ->\n * interval x 5. The final write is never throttled. */\n backoffBytes?: number\n /** Per-tool-part output cap applied to DRAFTS ONLY (bytes). A tool returning\n * a large blob would otherwise be rewritten in full on every draft. The\n * final write always carries the untruncated value. Default 32768 (32 KiB);\n * 0 disables truncation. */\n maxDraftToolOutputBytes?: number\n}\n\n/** Define the inputs required to construct an assistant draft writer */\nexport interface AssistantDraftWriterOptions extends DraftPersistenceTuning {\n store: AssistantDraftStore\n threadId: string\n /** DETERMINISTIC row id for this turn's assistant message — the whole\n * idempotency mechanism. Derived from the turn's existing identity\n * (`deriveExecutionId` in the interactive lane, the turn id in the detached\n * lane), so a re-entered turn addresses the SAME row: the writer looks the\n * id up before its first insert and patches what it finds. */\n messageId: string\n /** Read the producer's live accumulators. Returns null before the producer\n * is resolved (the assembly defers box resolution into the first pull). */\n snapshot(): AssistantDraftSnapshot | null\n /** Pre-persist text transform (`/redact`'s `redactPII`). Applied to the\n * draft's scalar content AND every draft text part — parity with the final\n * write, or incremental persistence would re-open the at-rest PII leak that\n * transform closed, just seconds earlier and on every turn. */\n transformText?(text: string): string | Promise<string>\n log?: (message: string, meta?: Record<string, unknown>) => void\n}\n\n/** Coalescing writer that keeps one durable assistant row in step with a\n * streaming turn. Created per turn; not reusable. */\nexport interface AssistantDraftWriter {\n /** Arm/trigger a draft write from one engine event. Synchronous by design —\n * the write itself is fire-and-forget so the stream is never blocked on\n * store latency. */\n notify(event: { type?: unknown }): void\n /** Stop drafting and settle any in-flight write. Called before the final\n * write so a late draft can never clobber the authoritative row. */\n close(): Promise<void>\n /** Write the AUTHORITATIVE completion values onto this turn's row —\n * insert-or-patch under the same deterministic id, un-throttled and\n * un-truncated. Errors propagate: the final write is the one that must not\n * fail silently. Implies {@link close}. */\n finalize(values: AssistantRowValues): Promise<void>\n /** The durable row this turn is writing, once one exists. */\n rowId(): string | undefined\n /** Retract the row for a turn that produced nothing (mirrors the final\n * write's empty-turn skip, which leaves no row at all today). Also retracts\n * a row a PREVIOUS attempt left behind, so a re-entered turn that ends\n * empty converges on \"no row\" rather than a stale partial. */\n discard(): Promise<void>\n /** Diagnostics: how many draft writes actually reached the store. */\n writeCount(): number\n}\n\n/** Event types that carry assistant content and therefore arm a draft write.\n * An allowlist on purpose: an unknown type (a product heartbeat, a bespoke\n * passthrough) must NEVER arm a write, or a silent producer would rewrite the\n * same row forever. */\nconst CONTENT_EVENT_TYPES: ReadonlySet<string> = new Set([\n 'text',\n 'reasoning',\n 'tool_call',\n 'tool_result',\n 'usage',\n 'notice',\n 'error',\n 'file',\n 'interaction',\n 'interaction.cancel',\n 'plan.submitted',\n 'message.part.updated',\n])\n\nconst DEFAULT_INTERVAL_MS = 2000\nconst DEFAULT_BACKOFF_BYTES = 262144\nconst DEFAULT_MAX_DRAFT_TOOL_OUTPUT_BYTES = 32768\n\n/** True when this event should arm a draft write. */\nexport function isDraftContentEvent(event: { type?: unknown }): boolean {\n return typeof event?.type === 'string' && CONTENT_EVENT_TYPES.has(event.type)\n}\n\n/** Caps one tool part's `output` for a DRAFT write. The value is replaced by a\n * truncated string plus a marker, so a reader can tell a clipped draft from a\n * real short output; the final write restores the untruncated value. */\nfunction capDraftToolOutput(part: Record<string, unknown>, maxBytes: number): Record<string, unknown> {\n if (maxBytes <= 0) return part\n if (String(part.type ?? '') !== 'tool') return part\n const state = part.state\n if (!state || typeof state !== 'object') return part\n const record = state as Record<string, unknown>\n const output = record.output\n if (output === undefined || output === null) return part\n const serialized = typeof output === 'string' ? output : safeStringify(output)\n if (serialized.length <= maxBytes) return part\n const metadata = (record.metadata && typeof record.metadata === 'object' ? record.metadata : {}) as Record<string, unknown>\n return {\n ...part,\n state: {\n ...record,\n output: `${serialized.slice(0, maxBytes)}…[draft-truncated ${serialized.length - maxBytes} chars]`,\n metadata: { ...metadata, draftTruncated: true },\n },\n }\n}\n\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value) ?? ''\n } catch {\n return String(value)\n }\n}\n\n/** Build the coalescing draft writer for one turn. */\nexport function createAssistantDraftWriter(options: AssistantDraftWriterOptions): AssistantDraftWriter {\n const log = options.log ?? (() => {})\n const baseIntervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS\n const backoffBytes = options.backoffBytes ?? DEFAULT_BACKOFF_BYTES\n const maxToolOutput = options.maxDraftToolOutputBytes ?? DEFAULT_MAX_DRAFT_TOOL_OUTPUT_BYTES\n\n let dirty = false\n let closed = false\n let inFlight: Promise<void> | undefined\n let lastWriteAt = 0\n let lastBlobBytes = 0\n let rowId: string | undefined\n let writes = 0\n\n /** Interval for the NEXT write, backed off by the size of the last blob\n * written — a turn accumulating a huge parts array rewrites it less often. */\n function currentIntervalMs(): number {\n if (lastBlobBytes >= backoffBytes * 10) return baseIntervalMs * 5\n if (lastBlobBytes >= backoffBytes) return Math.round(baseIntervalMs * 2.5)\n return baseIntervalMs\n }\n\n async function projectValues(snapshot: AssistantDraftSnapshot): Promise<AssistantRowValues> {\n const transform = options.transformText\n const content = transform ? await transform(snapshot.content) : snapshot.content\n let parts: ChatMessagePart[] | undefined\n if (snapshot.parts) {\n const capped = snapshot.parts.map((part) => capDraftToolOutput(part, maxToolOutput))\n const redacted = transform\n ? await Promise.all(\n capped.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 : capped\n parts = toChatMessageParts(redacted)\n }\n const usage = snapshot.usage ?? {}\n return {\n content,\n ...(parts && parts.length > 0 ? { parts } : {}),\n ...(snapshot.model ? { model: snapshot.model } : {}),\n ...(usage.inputTokens !== undefined ? { inputTokens: usage.inputTokens } : {}),\n ...(usage.outputTokens !== undefined ? { outputTokens: usage.outputTokens } : {}),\n ...(usage.reasoningTokens !== undefined ? { reasoningTokens: usage.reasoningTokens } : {}),\n ...(usage.cacheReadTokens !== undefined ? { cacheReadTokens: usage.cacheReadTokens } : {}),\n ...(usage.cacheWriteTokens !== undefined ? { cacheWriteTokens: usage.cacheWriteTokens } : {}),\n ...(usage.costUsd !== undefined ? { costUsd: usage.costUsd } : {}),\n }\n }\n\n /** Adopt the row a PREVIOUS attempt at this turn already inserted, if any.\n * This lookup — by the deterministic id, through the store's ordinary read\n * — is the whole crash-safety mechanism: no new state, no extra column, no\n * second index. */\n async function adoptRow(): Promise<string | undefined> {\n if (rowId) return rowId\n const existing = (await options.store.listMessages(options.threadId)).find(\n (message) => message.id === options.messageId,\n )\n if (existing) rowId = existing.id\n return rowId\n }\n\n /** Insert-or-patch one set of values onto this turn's single row. */\n async function writeOnce(values: AssistantRowValues): Promise<void> {\n if (await adoptRow()) {\n await options.store.updateMessage!(rowId!, values)\n writes += 1\n return\n }\n const inserted = await options.store.appendMessage({\n id: options.messageId,\n threadId: options.threadId,\n role: 'assistant',\n ...values,\n })\n // The `?? options.messageId` tail is valid HERE and only here: this writer\n // supplied the id, so a store that returns nothing still wrote that row.\n // A caller that did not assign an id has no such fallback — which is why\n // `rowIdOf` deliberately stops at the honest read.\n rowId = rowIdOf(inserted) ?? options.messageId\n writes += 1\n }\n\n function trigger(): void {\n if (closed) return\n if (!dirty) return\n // Single-flight: a write already in flight SUPPRESSES this trigger rather\n // than queueing behind it. `dirty` stays armed, so the next event after it\n // lands writes the newer snapshot — one write is never spent on state a\n // later one supersedes.\n if (inFlight) return\n const now = Date.now()\n // The first content event writes immediately (a late viewer sees a row at\n // once); the time floor governs every write after it.\n if (lastWriteAt !== 0 && now - lastWriteAt < currentIntervalMs()) return\n const snapshot = options.snapshot()\n if (!snapshot) return\n // Nothing to persist yet: an armed-but-empty snapshot (first event is a\n // tool call with no text and no parts) would insert a blank row.\n if (!snapshot.content && (!snapshot.parts || snapshot.parts.length === 0)) return\n dirty = false\n lastWriteAt = now\n inFlight = (async () => {\n try {\n const values = await projectValues(snapshot)\n lastBlobBytes = values.parts ? safeStringify(values.parts).length : 0\n await writeOnce(values)\n } catch (err) {\n // Best-effort by contract: a store outage degrades freshness for late\n // viewers, it does not fail a healthy turn.\n log('[chat-routes] incremental assistant persistence failed', {\n messageId: options.messageId,\n error: err instanceof Error ? err.message : String(err),\n })\n } finally {\n inFlight = undefined\n // A trigger that arrived while this write was in flight left `dirty`\n // armed with a NEWER snapshot. Re-run it here or that state would sit\n // unwritten until the next content event — and if this was the last\n // event of a quiet stretch, until the final write.\n if (dirty && !closed) trigger()\n }\n })()\n }\n\n return {\n notify(event) {\n if (closed) return\n if (isDraftContentEvent(event)) dirty = true\n trigger()\n },\n async close() {\n closed = true\n if (inFlight) await inFlight\n },\n async finalize(values) {\n closed = true\n if (inFlight) await inFlight\n await writeOnce(values)\n },\n rowId: () => rowId,\n async discard() {\n closed = true\n if (inFlight) await inFlight\n if (!options.store.deleteMessage) return\n try {\n if (!(await adoptRow())) return\n await options.store.deleteMessage(rowId!)\n rowId = undefined\n } catch (err) {\n log('[chat-routes] draft assistant row discard failed', {\n messageId: options.messageId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n },\n writeCount: () => writes,\n }\n}\n\n/** True when a store can support incremental persistence at all. Without\n * `updateMessage` a draft row could never be patched, so the caller keeps\n * today's exact single-write behavior. */\nexport function storeSupportsDraftPersistence(store: AssistantDraftStore): boolean {\n return typeof store.updateMessage === 'function'\n}\n\n/** The row id an `appendMessage` actually returned, or `null` when the store\n * returned nothing usable. `ChatTurnMessageStore.appendMessage` is typed\n * `Promise<unknown>` so a product adapter is free to resolve `void`; every\n * caller that wants to NAME the row it just wrote has to read defensively.\n *\n * Deliberately no fallback to a caller-assigned id — see `writeOnce`, which\n * adds its own. A caller that let the store mint the id has nothing to fall\n * back TO, and guessing one would report a row that may not exist. */\nexport function rowIdOf(inserted: unknown): string | null {\n const id = (inserted as { id?: unknown } | null | undefined)?.id\n return typeof id === 'string' && id ? id : null\n}\n\n/** The default deterministic assistant-row id for a turn. Readable on purpose\n * (an operator grepping a transcript row id finds the run), and stable across\n * re-entries because every input already is. */\nexport function assistantRowIdForTurn(turnKey: string): string {\n return `assistant:${turnKey}`\n}\n","/**\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 * 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 chain of length 1 costs nothing: one attempt, no extra call, no added\n * latency, byte-identical to no failover at all.\n */\n\nimport {\n isUpstreamUnavailable,\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 === '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 */\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}\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}\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}) => 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 /** Override the commit-point rule. Default {@link isCommittingSandboxEvent}. */\n isCommitting?: (event: unknown) => 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/**\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 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 (both behaviors inherited from `runWithModelFailover`).\n */\nexport function streamWithModelFailover(\n options: ModelFailoverStreamOptions,\n): ModelFailoverStreamHandle {\n const committing = options.isCommitting ?? isCommittingSandboxEvent\n const emptyTurnRetries = resolveEmptyTurnRetries(options.emptyTurnRetries)\n let serving: string | undefined\n let trail: ModelFailoverAttempt[] = []\n let fellBack = false\n let attemptIndex = 0\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 source = await options.open({ model, attempt: attemptIndex })\n const iterator = source[Symbol.asyncIterator]()\n const buffered: unknown[] = []\n\n for (;;) {\n // A THROWN failure propagates untouched: `runWithModelFailover` classifies\n // it (outage → next model, anything else → re-thrown to the caller).\n const next = await iterator.next()\n if (next.done) {\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 }, empty: true }\n }\n\n const event = next.value\n const failure = classifyTerminalFailure(event)\n if (failure?.outage) {\n await 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 buffered.push(event)\n // A terminal NON-outage failure surfaces exactly as it does today.\n if (failure) return { outcome: { committed: true, buffered, iterator }, empty: false }\n if (committing(event)) {\n return { outcome: { committed: true, buffered, iterator }, empty: isEmptyTerminalReceipt(event) }\n }\n }\n }\n\n const probe = async (model: string): Promise<AttemptOutcome> => {\n for (let retry = 0; ; retry += 1) {\n const pass = await drainOnce(model)\n if (!pass.empty || retry >= emptyTurnRetries) return pass.outcome\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 (iterator) await 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 try {\n const outcome = await 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 })\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 throw err\n }\n\n for (const event of handle.buffered) yield event\n\n const live = handle.iterator\n if (!live) return\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 await closeIterator(live, options.log)\n }\n })()\n\n return {\n events,\n servingModel: () => serving,\n attempts: () => trail,\n usedFallback: () => fellBack,\n }\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 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 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 /** 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 usageFromStepFinish(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\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\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} {\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 return { userMessage, failureNote }\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.emptyTurnRetries !== undefined ? { emptyTurnRetries: options.emptyTurnRetries } : {}),\n ...(options.onEmptyTurnRetry ? { onEmptyTurnRetry: options.onEmptyTurnRetry } : {}),\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 /** Tool ids already announced as `tool_call` / settled as `tool_result`. */\n const announcedTools = new Set<string>()\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 { type: 'usage', usage: { promptTokens, completionTokens } }\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 if (toolId && !announcedTools.has(toolId)) {\n announcedTools.add(toolId)\n yield {\n type: 'tool_call',\n call: { toolCallId: toolId, toolName, args: asRecord(state?.input) ?? {} },\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 usageFromStepFinish(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 { type: 'usage', usage: { promptTokens, completionTokens } }\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 === '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: 'sandbox.stream_failed',\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","/**\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 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 the result (the\n * cached / finished-server-side path). Omitted when the record only carries\n * 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 /** 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/text falls\n * back to it. Typically `() => box.findCompletedTurn(turnId)`. */\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 * Unset, a re-stream over a `running` buffer is still attempted but logged\n * as a possible-duplication hazard. */\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(final: DetachedTurnFinal | null): DetachedTurnResult {\n return {\n state: 'completed',\n text: final?.text ?? '',\n parts: final?.parts ?? [],\n usage: final?.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 // 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 (base: DetachedTurnResult): 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 = !producer && opts.persist\n ? (await opts.persist.store.listMessages(opts.persist.threadId)).find(\n (message) =>\n message.id === (opts.persist?.messageId ?? assistantRowIdForTurn(turnId)),\n )\n : 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 try {\n return (await opts.completedResult()) ?? null\n } catch (err) {\n opts.log?.('[chat-routes] runDetachedTurn completedResult lookup failed', { turnId, err: String(err) })\n return null\n }\n }\n\n let prior: string | null = null\n try {\n prior = await store.getStatus(turnId)\n } catch (err) {\n // A transient store blip must NOT silently fall through to a full re-stream\n // (which would duplicate a completed turn's buffer) — surface it.\n opts.log?.('[chat-routes] runDetachedTurn getStatus failed; treating as no prior', { turnId, err: String(err) })\n }\n\n if (prior === 'complete') return await settleRow(cachedResultFrom(await completed()))\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).catch((err) => {\n opts.log?.('[chat-routes] runDetachedTurn failed to settle a completed running turn', { turnId, err: String(err) })\n })\n return await settleRow(cachedResultFrom(final))\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 await opts.resetBuffer(turnId).catch((err) => {\n opts.log?.('[chat-routes] runDetachedTurn resetBuffer failed; re-stream may duplicate rows', { turnId, err: String(err) })\n })\n } else {\n opts.log?.('[chat-routes] runDetachedTurn re-streaming over a running buffer without resetBuffer; rows may duplicate', { turnId })\n }\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.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 if (!runError && !hasUsage(usage)) {\n const final = await completed()\n if (final?.usage) usage = { ...usage, ...final.usage }\n if (!text && final?.text) {\n return await settleRow({ state: 'completed', text: final.text, parts: final.parts ?? parts, usage, 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","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 * `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","/**\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 the injected\n * {@link WriteAttachmentFn} (gtm hard-wired its vault writer), the path strategy\n * is the injected `buildAttachmentPath` (neutral `uploads/agent/<date>/` default,\n * no domain bucket taxonomy baked), the MIME map is an injectable hook, and the\n * date segment reads an injectable clock. The idempotent `hash8(id ?? url ??\n * filename)` naming is preserved so re-promoting the same source part resolves\n * to the same path.\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 type { WriteAttachmentFn } from './attachment-store'\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]*)$/.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:([^;,]+)[;,]/.exec(url)\n return match ? match[1] : undefined\n}\n\nfunction basenameFromUrl(url: string | undefined): string | undefined {\n if (!url || url.startsWith('data:')) 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 = url.startsWith('file://') ? 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: ${err instanceof Error ? err.message : String(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: ${err instanceof Error ? err.message : String(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: ${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: ${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 (url.startsWith('data:')) return resolveDataUrlBytes(url, input.filename, input.maxBytes)\n\n const isSandboxPath = url.startsWith('file://') || url.startsWith('/')\n if (!isSandboxPath) return { succeeded: false, reason: `unsupported file URL scheme: ${url}` }\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\n/** Define options for promoting a part of an agent file within a specific session and scope */\nexport interface PromoteAgentFilePartOptions {\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 /** REQUIRED store writer — no default (the product owns its store). */\n writeAttachment: WriteAttachmentFn\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}\n\n/** Promote a part of an agent file with optional byte limits and MIME type detection */\nexport async function promoteAgentFilePart(options: PromoteAgentFilePartOptions): Promise<PromoteFilePartResult> {\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\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 const mediaType = options.raw.mediaType ?? options.raw.mime ?? dataUrlMime(options.raw.url) ?? sniffMime(filename)\n const kind = attachmentKindForMime(mediaType)\n const digest = await hash8(options.raw.id ?? options.raw.url ?? filename)\n const date = now().toISOString().split('T')[0] ?? ''\n const path = buildAttachmentPath({ filename, hash8: digest, date, mediaType, kind })\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<WriteAttachmentFn>>\n try {\n written = await options.writeAttachment(options.scopeId, path, resolved.bytes, {\n mediaType,\n name: filename,\n originalName: options.raw.filename ?? filename,\n size: resolved.bytes.byteLength,\n })\n } catch (err) {\n return { succeeded: false, filename, reason: err instanceof Error ? err.message : String(err) }\n }\n if (!written.ok) return { succeeded: false, filename, reason: written.reason }\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 atomic batch (every file is validated before any file is\n * written — a batch never partially lands), a content-sniffed type gate\n * (`checkAttachmentType` over `sniffBinary`'s magic-byte read, not the\n * extension or the browser-reported MIME), per-kind + aggregate byte caps,\n * and sanitized filenames. Storage is fully seamed through the injected\n * `WriteAttachmentFn` (`./attachment-store`) — no default store, the product\n * owns where bytes actually live (vault, object store, …) — and auth/rate\n * limiting is entirely the injected `authorize` 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 type { WriteAttachmentFn } from './attachment-store'\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/** Outcome of the injected `authorize` seam: auth + rate limiting +\n * scope resolution, all in one place so a 429 rides `{ok:false, response}`\n * exactly like a 401 does — this factory has no rate-limit opinion of its\n * own. `writeAttachment` lets a single request override the option-level\n * store (e.g. routing per-tenant), defaulting to `options.writeAttachment`\n * when absent. */\nexport type AttachmentUploadAuthorization =\n | { ok: true; scopeId: string; writeAttachment?: WriteAttachmentFn }\n | { ok: false; response: Response }\n\n/** Define options to authorize, write, and limit attachment uploads in a route */\nexport interface CreateAttachmentUploadRouteOptions {\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 /** Default store writer. `authorize` may override it per-request. */\n writeAttachment: WriteAttachmentFn\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 /** Ceiling on a text file's raw size. Default {@link MAX_TEXT_ATTACHMENT_BYTES}. */\n maxTextBytes?: number\n /** Aggregate raw-byte ceiling across the batch. 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`. Default\n * {@link ALLOWED_ATTACHMENT_SNIFFED_MIMES}. */\n allowedSniffedMimes?: ReadonlySet<string>\n /** Sanitized-name → store path. Default identity (the sanitized name IS\n * the path); gtm passes `vaultFolderForFileName`, a tenant product a\n * scope prefix. */\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 can't type.\n * Default {@link sniffMimeFromName}. */\n sniffMime?: (name: string) => string\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\n/** Resolve an attachment upload route handler with customizable limits and validation options */\nexport function createAttachmentUploadRoute(\n options: CreateAttachmentUploadRouteOptions,\n): (request: Request) => Promise<Response> {\n const maxCount = options.limits?.maxCount ?? ATTACHMENT_MAX_COUNT\n const maxBinaryBytes = options.limits?.maxBinaryBytes ?? MAX_BINARY_ATTACHMENT_BYTES\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\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 write = auth.writeAttachment ?? options.writeAttachment\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 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 seenPaths = 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 const limit = sniff.binary ? maxBinaryBytes : maxTextBytes\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 path = pathFor(name)\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 if (seenPaths.has(path)) {\n return attachmentUploadError(\n 400,\n 'attachment_duplicate_path',\n `attachments must not repeat a path within one upload: ${path}`,\n path,\n )\n }\n seenPaths.add(path)\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, 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 for (const input of prepared) {\n const written = await write(auth.scopeId, input.path, input.bytes, {\n mediaType: input.mediaType,\n name: input.name,\n originalName: input.originalName,\n size: input.size,\n })\n if (!written.ok) {\n return attachmentUploadError(413, 'attachment_write_failed', written.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 }\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 { 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 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","base64","inlinePart","cost"]}
|
|
1
|
+
{"version":3,"sources":["../../src/chat-routes/turn-routes.ts","../../src/chat-routes/draft-persistence.ts","../../src/chat-routes/model-failover-stream.ts","../../src/chat-routes/sandbox-turn-usage.ts","../../src/chat-routes/sandbox-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/resolve-attachments.ts","../../src/chat-routes/promote-file-part.ts","../../src/chat-routes/attachment-upload.ts","../../src/chat-routes/dispatch-parts.ts"],"sourcesContent":["/**\n * `createChatTurnRoutes` — the assembled server chat vertical (issue #188\n * Phase 1). One factory composing the pieces every product re-wired by hand:\n *\n * body parse/validate → `/web` `parseJsonObjectBody` + `./wire`\n * turn identity → `/stream` `resolveChatTurn` + agent-runtime `/durable`\n * `deriveExecutionId`\n * producer → injected seam (sandbox lane via\n * `createSandboxChatProducer`; router lane is the\n * product's own `ChatTurnProducer`)\n * turn engine → agent-runtime `/durable` `handleChatTurn` (verbatim)\n * durability → `/stream` turn-buffer tap, wired BY DEFAULT\n * (tee + drain keeps the turn running after a\n * client drop; replay serves the buffered tail)\n * persistence → injected `/chat-store`-shaped store\n * (user row on send, assistant row on completion)\n * interactions answer → `/interactions` `createInteractionAnswerRoute`\n *\n * Handlers are web-standard `Request → Response` (Workers, Node 18+, Deno) —\n * no router import. Auth/access is one injected `authorize` seam, composable\n * with `/app-auth` guards but not coupled to them.\n *\n * Optional product seams let a complex turn-orchestrator compose the vertical\n * instead of hand-rolling a generator — each omittable to the exact behavior\n * above: `turnLock` (single-flight acquire/release around the turn),\n * `contextGate` (pre-producer domain-readiness short-circuit), `beforeTurn`\n * (observe + augment the producer input), `lifecycle` (deterministic\n * start/complete/error telemetry), `heartbeat` (keepalive during silent\n * producer waits), and `onRawEvent` (the raw producer events, for telemetry) —\n * plus the `authorize` result's `insertUserMessage` flag (suppress the user row\n * for a product-dispatched turn). `handleChatTurn` stays the engine — the seams\n * only wrap its input, its producer stream, and its settle.\n *\n * Seam stability: all of the above are STABLE and safe to depend on. They\n * graduated in #227 against the bar this package holds itself to — a seam is\n * provisional until two INDEPENDENT consumers exercise it, because one\n * consumer's shape is indistinguishable from that consumer's assumptions.\n * `turnLock` cleared it with `/turn-stream`'s shared DO adapter (#221);\n * `contextGate`, `beforeTurn`, `onRawEvent` and `insertUserMessage` each\n * cleared it with two product verticals reading them differently — and the\n * review found no leaked assumptions to fix, only `ChatRouteEvent` to export.\n *\n * They stay FLAT top-level options (not grouped under a `hooks` object): that\n * grouping would break every shipped consumer's call for no mechanism gain, and\n * this package's exports are additive-only. For the same reason `onRawEvent`\n * keeps its two-argument `(event, context)` signature rather than being\n * normalized to the single-args shape the other seams take.\n */\n\nimport {\n deriveExecutionId,\n handleChatTurn,\n type ChatTurnIdentity,\n type ChatTurnProducer,\n} from '@tangle-network/agent-runtime/durable'\nimport { mentionInputToPart, toChatMessageParts, type ChatMessagePart } from '../chat-store/parts'\nimport {\n assistantRowIdForTurn,\n createAssistantDraftWriter,\n rowIdOf,\n storeSupportsDraftPersistence,\n type AssistantDraftStore,\n type AssistantDraftWriter,\n type DraftPersistenceTuning,\n} from './draft-persistence'\nimport {\n createInteractionAnswerRoute,\n type InteractionAnswerRoute,\n type InteractionAnswerRouteOptions,\n} from '../interactions/route'\nimport {\n coalesceDeltas,\n createBufferedTurnTap,\n normalizeClientTurnId,\n replayTurnEvents,\n resolveChatTurn,\n stampReplaySeq,\n type PersistedChatMessageForTurn,\n type TurnEventStore,\n} from '../stream/index'\nimport type { ModelFailoverAttempt } from '../model-resolution/failover'\nimport { parseJsonObjectBody } from '../web/index'\nimport {\n assertPromptPartsWithinCap,\n ChatTurnInputError,\n parseChatTurnParts,\n parseFileMentions,\n type ChatTurnFilePartInput,\n type ChatTurnPartInput,\n type ChatTurnRequestPayload,\n type FileMention,\n} from './wire'\n\n// ── seams ───────────────────────────────────────────────────────────────────\n\n/** Usage receipt persisted onto the assistant message (the flattened\n * `step-finish` shape `/chat-store`'s columns mirror). */\nexport interface ChatTurnUsage {\n inputTokens?: number\n outputTokens?: number\n reasoningTokens?: number\n cacheReadTokens?: number\n cacheWriteTokens?: number\n costUsd?: number\n}\n\n/** What the route persists — a structural subset of `/chat-store`'s\n * `ChatStore`, so `createChatStore(db, tables)` satisfies it directly and a\n * product with its own persistence adapts without importing drizzle. */\nexport interface ChatTurnMessageStore {\n listMessages(threadId: string): Promise<Array<{\n id: string\n role: 'user' | 'assistant' | 'system' | 'tool'\n content: string\n parts?: ChatMessagePart[] | null\n }>>\n appendMessage(input: {\n /** Caller-assigned row id. Honored by `/chat-store`'s `createChatStore`;\n * a product store free to ignore it (the writer then adopts whatever id\n * the insert returns). Set only by incremental persistence. */\n id?: string\n threadId: string\n role: 'user' | 'assistant'\n content: string\n parts?: ChatMessagePart[]\n model?: string | null\n requestedModel?: string | null\n servedModel?: string | null\n servedProvider?: string | null\n servedSource?: string | null\n inputTokens?: number | null\n outputTokens?: number | null\n reasoningTokens?: number | null\n cacheReadTokens?: number | null\n cacheWriteTokens?: number | null\n costUsd?: number | null\n }): Promise<unknown>\n /** Patch an existing row. Its PRESENCE is what enables incremental assistant\n * persistence — a store without it keeps today's exact single-write-on-\n * completion behavior, which is what makes this additive. */\n updateMessage?(id: string, patch: {\n content?: string\n parts?: ChatMessagePart[]\n model?: string | null\n requestedModel?: string | null\n servedModel?: string | null\n servedProvider?: string | null\n servedSource?: string | null\n inputTokens?: number | null\n outputTokens?: number | null\n reasoningTokens?: number | null\n cacheReadTokens?: number | null\n cacheWriteTokens?: number | null\n costUsd?: number | null\n }): Promise<unknown>\n /** Remove a row. Used to retract a draft assistant row for a turn that ended\n * producing nothing, so the empty-turn case still leaves no row at all. */\n deleteMessage?(id: string): Promise<unknown>\n}\n\n/** `ChatTurnProducer` plus the persisted projection the assembly reads after\n * drain. `createSandboxChatProducer` returns this; a router-lane producer\n * may omit the optional members (finalText persists as a single text part). */\nexport interface ChatTurnRouteProducer extends ChatTurnProducer {\n assistantParts?(): Array<Record<string, unknown>>\n /** MID-STREAM snapshot of the same projection, safe to call while the turn\n * is running: no dangling-tool terminalization, no pending-ask settlement\n * (see `/stream`'s `draftAssistantParts`). Read by incremental persistence.\n * A producer that omits it still drafts — the scalar text — but persists no\n * parts until the turn completes. */\n draftParts?(): Array<Record<string, unknown>>\n usage?(): ChatTurnUsage\n /** The model that SERVED the turn. With failover wired this is the model that\n * actually answered, which is not necessarily the one the caller preferred —\n * read it after the stream drains, never before. */\n model?: string\n /** Model-failover attribution, when the producer supports it. Reported onto\n * the usage/billing receipt so a downgrade is never silent. */\n modelFailover?(): ChatTurnModelFailover\n /** Requested-versus-served attribution reported by the sandbox sidecar.\n * `echoReceived` distinguishes a missing echo from a partial echo. */\n modelAttribution?(): ChatTurnModelAttribution\n}\n\n/** Which model served, and what it took to get there. */\nexport interface ChatTurnModelFailover {\n /** The model that served the turn. */\n model?: string\n /** Every model tried, in order, with the reason each was abandoned. */\n attempts: ModelFailoverAttempt[]\n /** True when the preferred model did not serve. */\n usedFallback: boolean\n}\n\n/** Requested and effective model attribution for one sandbox turn. */\nexport interface ChatTurnModelAttribution {\n /** The model explicitly requested by the caller, before shell failover. */\n requestedModel?: string\n /** The model the downstream sandbox reports actually served the turn. */\n servedModel?: string\n /** The provider that served the turn, when echoed by the sandbox. */\n servedProvider?: string\n /** How the sandbox selected the served model. */\n servedSource?: 'request' | 'environment' | 'profile'\n /** True when a structurally valid effective-backend echo was observed. */\n echoReceived: boolean\n}\n\n/** Resolve authorization status and context for a chat turn including tenant and user identification */\nexport type ChatTurnAuthorization<TContext> =\n | {\n ok: true\n tenantId: string\n userId: string\n context: TContext\n /** When `false`, skip the `role:'user'` message insert for this turn — for\n * a product-dispatched / synthetic turn (e.g. a follow-up the product\n * raised itself) that must not surface a new user row. Composes with —\n * never overrides — the engine's retry-dedup: `authorize` runs before\n * turn identity is resolved, so it cannot tell a retry from a fresh turn;\n * a turn already deduped stays deduped. Omit / `true` → today's behavior.\n *\n * Settled shape (#227). The validated case in both consumers is the same:\n * a durable plan-approval follow-up re-entering an execution the decision\n * route already enqueued — a decision, not a typed message, so it must\n * not surface a user bubble. Suppressing the insert also propagates:\n * `ChatTurnProduceArgs.userMessageId` is `null` for the rest of the turn\n * when there is no row to reuse, so a seam anchoring to the user row must\n * handle that arm rather than assume a string. */\n insertUserMessage?: boolean\n }\n | { ok: false; response: Response }\n\n/** Define arguments required to authorize a chat turn based on intent and request details */\nexport interface ChatTurnAuthorizeArgs {\n request: Request\n intent: 'turn' | 'replay' | 'running'\n /** Parsed, validated POST body (turn intent only). */\n body?: ChatTurnRequestPayload\n /** The buffered turn id being replayed (replay intent only). */\n turnId?: string\n /** The thread whose running turns are being discovered (running intent only). */\n threadId?: string\n}\n\n/** Define the arguments required to produce a chat turn with context and messaging details */\nexport interface ChatTurnProduceArgs<TContext> {\n request: Request\n body: ChatTurnRequestPayload\n identity: ChatTurnIdentity\n context: TContext\n /** The message to send: plain text, or parts when the client attached\n * files (a text part is prepended from `content` when present). */\n prompt: string | ChatTurnPartInput[]\n /** Stable id for cross-process reconnect (`deriveExecutionId`). */\n executionId: string\n /** The turn-buffer id announced to the client for replay. */\n turnStreamId: string\n priorMessages: PersistedChatMessageForTurn[]\n /** The durable `role:'user'` row this turn is anchored to — the row the\n * factory just inserted, or the one retry-dedup REUSED. Products anchor\n * optimistic-bubble swaps, retry targeting, and stop-polling to it, and it\n * is the only way to name a REUSED row (which `priorMessages` excludes).\n *\n * Three states, all meaningful:\n * - `undefined` — not resolved yet. `turnLock.acquire` is the one seam that\n * sees this: it runs before any side effect by contract, so the row does\n * not exist when it reads the args.\n * - `null` — resolved, no row: `authorize` returned `insertUserMessage:\n * false` on a turn with nothing to reuse, or the store's `appendMessage`\n * resolved without a usable id.\n * - a string — the row id, for `contextGate`, `beforeTurn`, and `produce`. */\n userMessageId?: string | null\n}\n\n/** One event as it crosses the route: the producer's own vocabulary, or an\n * injected keepalive. Same shape the engine forwards verbatim.\n *\n * Public because it IS the vocabulary of two seams — `onRawEvent`'s parameter\n * and `heartbeat.event`'s return. Exported so a product can declare a\n * standalone handler (`function onRawEvent(e: ChatRouteEvent, ctx: T)`) rather\n * than depending on contextual typing from an inline object literal. */\nexport type ChatRouteEvent = { type: string; data?: Record<string, unknown> }\n\n/** Best-effort human-readable cause from a terminal `error` /\n * `session.run.failed` event's `data`. */\nfunction failureReasonOf(data: Record<string, unknown> | undefined): string | undefined {\n if (!data) return undefined\n const message = data.message ?? data.error ?? data.reason\n if (typeof message === 'string' && message.length > 0) return message\n return undefined\n}\n\n/** Keepalive emitted while the producer is quiet (long tool calls, first-token\n * wait) so client watchdogs stay re-armed. One is emitted each time\n * `intervalMs` elapses with no producer event; the window resets on every real\n * event, so a chatty producer never triggers one. The product owns the event\n * shape (`type` + `data`). Omit → no keepalives (today's behavior). */\nexport interface ChatTurnHeartbeat {\n intervalMs: number\n event(info: { elapsedMs: number; tick: number }): ChatRouteEvent\n}\n\n/** Patch a `beforeTurn` hook returns to augment the producer's input. Omitted\n * fields keep the route-assembled value; the product's `produce` still owns\n * the system prompt. */\nexport interface ChatTurnInputPatch {\n prompt?: string | ChatTurnPartInput[]\n priorMessages?: PersistedChatMessageForTurn[]\n}\n\n/** Pre-turn readiness verdict — proceed, or short-circuit with the product's\n * own `Response` (e.g. a canned assistant reply asking for missing context).\n * Distinct from `authorize`: this gates domain readiness, not access. */\nexport type ChatTurnGateResult =\n | { proceed: true }\n | { proceed: false; response: Response }\n\n/** Single-flight lock verdict — acquired (with an opaque handle passed back to\n * `release`), or already held (short-circuit with the product's 409-style\n * `Response`). */\nexport type ChatTurnLockResult =\n | { acquired: true; handle?: unknown }\n | { acquired: false; response: Response }\n\n/** Async acquire/release wrapped around the turn. `acquire` runs before any\n * side effect; `release` runs once when the turn settles — including on a\n * short-circuit or a throw. */\nexport interface ChatTurnLock<TContext> {\n acquire(args: ChatTurnProduceArgs<TContext>): ChatTurnLockResult | Promise<ChatTurnLockResult>\n release(handle: unknown): void | Promise<void>\n}\n\ninterface ChatTurnLifecycleBase<TContext> {\n identity: ChatTurnIdentity\n executionId: string\n turnStreamId: string\n context: TContext\n}\n/** Define lifecycle start event with context and timestamp for a chat turn */\nexport interface ChatTurnLifecycleStart<TContext> extends ChatTurnLifecycleBase<TContext> {\n startedAt: number\n}\n/** Define the structure representing the completion state of a chat turn lifecycle with usage data */\nexport interface ChatTurnLifecycleComplete<TContext> extends ChatTurnLifecycleBase<TContext> {\n finalText: string\n usage: ChatTurnUsage\n durationMs: number\n /** The model that SERVED the turn — the fallback's id when failover moved it.\n * `usage` is that model's, so telemetry that splits cost or quality by model\n * must key on this and not on the requested one. */\n model?: string\n /** Requested-versus-served attribution. A difference is detectable from\n * this receipt and from the persisted assistant row independently. */\n requestedModel?: string\n servedModel?: string\n servedProvider?: string\n servedSource?: 'request' | 'environment' | 'profile'\n /** Attribution for a downgrade: which models were tried and why each failed.\n * `undefined` when the producer reports no failover support. */\n modelFailover?: ChatTurnModelFailover\n /** The durable `role:'assistant'` row this turn wrote, or `null` when it\n * wrote none (an empty turn leaves no row — a draft started mid-stream is\n * retracted). The detached lane surfaces the same id as\n * `DetachedTurnResult.messageId`; one contract, two lanes. */\n assistantMessageId: string | null\n /** Set when `contextGate` answered this turn and the producer never ran.\n *\n * Present so telemetry can tell \"the model produced nothing\" apart from \"the\n * model was never asked\" — they look identical here (`finalText: ''`,\n * empty `usage`) and mean opposite things. A gated turn writes no assistant\n * row, so `assistantMessageId` is `null`, and the billing/persistence\n * `onTurnComplete` is deliberately NOT fired for it. */\n gated?: true\n}\n/** Represent an error occurring during a chat turn lifecycle with context and duration information */\nexport interface ChatTurnLifecycleError<TContext> extends ChatTurnLifecycleBase<TContext> {\n error: unknown\n durationMs: number\n}\n\n/** Deterministic run telemetry: `onTurnStart` fires before the producer runs;\n * exactly one of `onTurnComplete` / `onTurnError` fires after the turn\n * settles, always after `onTurnStart`. Failure is derived from the turn's own\n * `error` / `session.run.failed` events (or a drain throw), not the engine's\n * lifecycle envelope. Hook errors are swallowed — telemetry never fails a\n * turn. */\nexport interface ChatTurnLifecycle<TContext> {\n onTurnStart?(info: ChatTurnLifecycleStart<TContext>): void | Promise<void>\n onTurnComplete?(info: ChatTurnLifecycleComplete<TContext>): void | Promise<void>\n onTurnError?(info: ChatTurnLifecycleError<TContext>): void | Promise<void>\n}\n\n/** What a settled turn reports to `onTurnComplete` — the product's\n * post-processing seam (billing, titles, audit). */\nexport interface ChatTurnCompleteInput<TContext> {\n identity: ChatTurnIdentity\n finalText: string\n context: TContext\n failed: boolean\n failureReason?: string\n /** The model that SERVED this turn. With failover wired it may differ from\n * the requested one, so a product that bills or scores per model MUST read\n * it here rather than assuming the model it asked for. */\n model?: string\n /** Requested-versus-served attribution. A difference is detectable from\n * this receipt and from the persisted assistant row independently. */\n requestedModel?: string\n servedModel?: string\n servedProvider?: string\n servedSource?: 'request' | 'environment' | 'profile'\n /** Present when the producer supports failover: the full attempt trail, and\n * `usedFallback` — the flag that makes a silent downgrade impossible. */\n modelFailover?: ChatTurnModelFailover\n /** The durable `role:'assistant'` row this turn wrote, or `null` when it\n * wrote none (an empty turn leaves no row).\n *\n * Populated even when `failed` is true — a terminal error event still\n * persists whatever partial answer arrived, and that pairing is exactly\n * what lets a product render an error row against a REAL message instead of\n * hunting for the newest row in the thread. The detached lane surfaces the\n * same id as `DetachedTurnResult.messageId`. */\n assistantMessageId: string | null\n}\n\n/** Define options to configure chat turn routes including authorization, storage, and event buffering */\nexport interface CreateChatTurnRoutesOptions<TContext = void> {\n /** Names the product in `deriveExecutionId` so retries land on the same\n * substrate execution. */\n projectId: string\n /** Authenticate + authorize the caller for a turn or a replay. The only\n * product-supplied access step: session auth, thread/workspace access,\n * seat/balance gates, rate limits all live here. */\n authorize(args: ChatTurnAuthorizeArgs): Promise<ChatTurnAuthorization<TContext>>\n /** Thread/message persistence (`/chat-store`'s store or a product adapter). */\n store: ChatTurnMessageStore\n /** Turn-event buffer (`createD1TurnEventStore(env.DB)` or `/turn-stream`'s\n * `createDurableObjectTurnEventStore(env.TURN_STREAM_DO)` in production,\n * `createMemoryTurnEventStore()` in tests). Wired by default — every turn\n * is buffered and replayable. */\n turnStore: TurnEventStore\n /** Build the turn's event stream. Sandbox lane: `streamSandboxPrompt(...)`\n * wrapped in `createSandboxChatProducer`. Router/openai-compat lane: the\n * product's own producer. May be async (box resolution). */\n produce(args: ChatTurnProduceArgs<TContext>): ChatTurnRouteProducer | Promise<ChatTurnRouteProducer>\n /** Single-flight lock acquired before any side effect and released once when\n * the turn settles (including short-circuit/throw). `/turn-stream`'s\n * `createDurableTurnLock` is the shared DO-backed implementation. Omit →\n * no lock. */\n turnLock?: ChatTurnLock<TContext>\n /** Pre-turn readiness gate that can short-circuit with a product `Response`\n * before the producer runs (the user row is already persisted). Runs after\n * `turnLock.acquire`, before `beforeTurn`. Omit → always proceed.\n *\n * Settled shape (#227). What a consumer may depend on:\n * - `args.userMessageId` is RESOLVED here — a string, or `null` when the\n * insert was suppressed with nothing to reuse. (`turnLock.acquire` is the\n * only seam that sees it `undefined`.)\n * - `{proceed:false}` releases the lock and returns the product's `Response`\n * verbatim: `beforeTurn` and `produce` never run, no assistant row is\n * written, and the user row already inserted is KEPT — a real user turn\n * whose assistant side is the gate's own response.\n * - Always returning `{proceed:true}` is supported, not a misuse: the seam\n * doubles as the one place that runs after the user row exists and before\n * the producer, which is where per-turn analytics and readiness\n * precomputation belong. A gate that never gates is a valid consumer. */\n contextGate?(args: ChatTurnProduceArgs<TContext>): ChatTurnGateResult | Promise<ChatTurnGateResult>\n /** Observe the assembled producer input and optionally augment it (rewrite\n * the prompt / prior messages) before the producer runs. Omit → no change.\n *\n * Settled shape (#227). BOTH return arms are contract:\n * - a `ChatTurnInputPatch` shallow-merges over the route-assembled args, so\n * an omitted field keeps the route's value;\n * - `void` means \"no patch\" — and mutating `args.context` in place is the\n * supported way to thread request-scoped state forward to `produce` /\n * `lifecycle` / `onTurnComplete`, which all receive the same object.\n *\n * A throw propagates (the turn fails with the lock released). It runs BEFORE\n * `lifecycle.onTurnStart`, so a throw here fires no terminal lifecycle hook —\n * the span never opened. Telemetry for a failure in this seam belongs in the\n * seam, not in `lifecycle`. */\n beforeTurn?(args: ChatTurnProduceArgs<TContext>): ChatTurnInputPatch | void | Promise<ChatTurnInputPatch | void>\n /** Deterministic run telemetry (start / complete / error) with identity and\n * timing. Omit → no telemetry. */\n lifecycle?: ChatTurnLifecycle<TContext>\n /** Keepalive injected while the producer is quiet. Omit → no keepalives. */\n heartbeat?: ChatTurnHeartbeat\n /** Observe each event the producer emits, before the engine frames it and\n * before any heartbeat injection (the raw sidecar-producer events, for\n * telemetry). Never alters the stream; errors are swallowed. Distinct from\n * `onEvent`, which sees the engine-framed stream incl. lifecycle envelopes.\n *\n * Settled shape (#227). What a consumer may depend on:\n * - it sees EXACTLY the producer's own events — no engine lifecycle\n * envelopes, and no injected keepalives (`heartbeat` wraps the stream\n * downstream of this tap, so a synthetic event never reaches a trace);\n * - a throw is caught and logged, never surfaced: a broken telemetry sink\n * cannot fail a turn or truncate the client's stream;\n * - it is an OBSERVER — the return value is ignored, and the event object\n * continues downstream. Mutating it mutates the stream; don't.\n *\n * Takes `(event, context)` rather than one args object, matching both\n * shipped consumers; see the module header on why it stays that way. */\n onRawEvent?(event: ChatRouteEvent, context: TContext): void | Promise<void>\n /** Pre-persist transform of the final text (e.g. `/redact`'s `redactPII`).\n * Live stream is never altered. */\n transformFinalText?(text: string): string | Promise<string>\n /** Incremental persistence of the assistant row WHILE the turn streams\n * (`./draft-persistence`), so a viewer arriving mid-run is served from\n * durable storage instead of the streaming gateway's hot event buffer.\n *\n * This is what lets the gateway keep that hot buffer SHORT at scale: its\n * Redis footprint is one sorted set per session refreshed on every push, so\n * memory grows LINEARLY with `ttl x concurrent sessions`. Stretching the\n * TTL to cover late viewers buys memory proportional to the increase and\n * still serves nothing past the new horizon. The buffer stays a reconnect\n * window; durable storage — kept at most one cadence interval stale by this\n * option — is the history tier.\n *\n * Enabled by DEFAULT whenever `store.updateMessage` exists (every\n * `/chat-store` consumer); a store without it keeps today's exact\n * single-write behavior. Pass `false` to opt out, or an object to tune the\n * cadence. Wiring it against a store that cannot patch rows throws at route\n * construction rather than silently no-op'ing. */\n incrementalPersistence?: false | DraftPersistenceTuning\n /** Deterministic durable row id for this turn's assistant message. Default\n * `assistant:<executionId>` — already stable across retries because\n * `deriveExecutionId` is. Override only if the product's message ids have a\n * format constraint; it MUST stay deterministic per turn or a re-entered\n * turn will duplicate its row instead of converging. */\n draftMessageId?(args: { identity: ChatTurnIdentity; executionId: string; threadId: string }): string\n /** Post-processing after a turn settles (billing, titles, audit). Fires with\n * `failed:true` + `failureReason` when the turn carried a terminal error\n * event (model 402 / rate-limit / server error) instead of a clean\n * completion, so products skip the deduct and render an error row rather\n * than billing an empty turn and marking it done. A turn that THROWS never\n * reaches this hook (the engine skips it on a producer throw). Errors are\n * swallowed by the engine — they never fail a streamed turn. */\n onTurnComplete?(input: ChatTurnCompleteInput<TContext>): Promise<void>\n /** Per-event side channel (product broadcast). The turn-buffer tap is\n * already wired; this runs in addition. */\n onEvent?(event: { type: string; data?: Record<string, unknown> }, context: TContext): void | Promise<void>\n /** Trace flush handed to `waitUntil` (OTLP export). */\n traceFlush?(context: TContext): Promise<void>\n /** Compose the interaction-answer endpoints (`/interactions`). Omit when the\n * product has no sidecar ask channel. */\n interactions?: InteractionAnswerRouteOptions\n /** Byte budget for inline prompt parts. Default `INLINE_PARTS_MAX_BYTES`. */\n maxInlinePartBytes?: number\n /** Per-flush coalescer for the turn buffer. Default `coalesceDeltas` (this\n * assembly streams the client vocabulary's `{type:'text'|'reasoning',\n * text}` lines, which it merges). A producer streaming raw\n * `message.part.updated` events passes `coalesceChatStreamEvents`. */\n coalesceTurnEvents?: (events: unknown[]) => unknown[]\n replay?: { pollMs?: number; timeoutMs?: number }\n log?: (message: string, meta?: Record<string, unknown>) => void\n}\n\n/** Define routes to run, replay, and list running chat turns with streaming and reconnect support */\nexport interface ChatTurnRoutes {\n /** POST — run one turn, streaming NDJSON. First line is\n * `{type:'turn', turnId}` (the replay handle); the rest is the engine's\n * event protocol. Pass the platform's `waitUntil` so the turn keeps\n * running (and buffering) after a client disconnect. */\n turn(request: Request, ctx?: { waitUntil?(p: Promise<unknown>): void }): Promise<Response>\n /** GET — replay a buffered turn from `?fromSeq=` (0 = everything), then\n * follow it live until it completes. */\n replay(request: Request, params: { turnId: string }): Promise<Response>\n /** GET `?threadId=` — the reconnect-discovery endpoint: the turn ids still\n * running on a thread, so a client that reloaded mid-turn can re-attach to\n * the live stream via {@link replay} instead of losing it. Returns `[]` when\n * the turn store cannot enumerate running turns (`listRunning` unimplemented). */\n running(request: Request): Promise<Response>\n /** list/answer endpoints from `/interactions`; null when not configured. */\n interactions: InteractionAnswerRoute | null\n}\n\n// ── body validation ────────────────────────────────────────────────────────\n\nfunction errorResponse(err: ChatTurnInputError): Response {\n return Response.json({ code: err.code, error: err.message }, { status: err.status })\n}\n\ninterface ParsedTurnBody {\n payload: ChatTurnRequestPayload\n content: string\n fileParts: ChatTurnFilePartInput[]\n mentions: FileMention[]\n turnId: string | undefined\n}\n\nfunction validateTurnBody(body: Record<string, unknown>, maxInlinePartBytes: number | undefined): ParsedTurnBody {\n const threadId = typeof body.threadId === 'string' ? body.threadId.trim() : ''\n if (!threadId) throw new ChatTurnInputError('Missing threadId')\n const rawContent = body.content ?? body.message ?? ''\n if (typeof rawContent !== 'string') throw new ChatTurnInputError('content must be a string')\n const content = rawContent.trim()\n const fileParts = parseChatTurnParts(body.parts)\n // Path references, not bytes — validated for traversal/charset/count, never\n // counted against the inline-parts byte budget below.\n const mentions = parseFileMentions(body.mentions)\n // A mention is a turn's whole payload often enough to count: \"@chart.png\"\n // with no prose is a real ask, and the pointer block the mentions produce is\n // prompt content the model reads.\n const hasAttachments = Array.isArray(body.attachments) && body.attachments.length > 0\n if (!content && fileParts.length === 0 && mentions.length === 0 && !hasAttachments) {\n throw new ChatTurnInputError('Missing content (send text, parts, mentions, attachments, or any combination)')\n }\n assertPromptPartsWithinCap(fileParts, maxInlinePartBytes)\n let turnId: string | undefined\n try {\n turnId = normalizeClientTurnId(body.turnId)\n } catch (err) {\n throw new ChatTurnInputError(err instanceof Error ? err.message : 'Invalid turnId')\n }\n return {\n // The VALIDATED, deduped mention list replaces the raw one on the payload,\n // so every downstream seam (`authorize`, `contextGate`, `beforeTurn`,\n // `produce`) reads checked paths and never the request's own.\n payload: { ...body, threadId, content, mentions } as ChatTurnRequestPayload,\n content,\n fileParts,\n mentions,\n turnId,\n }\n}\n\n/** File parts persist onto the user message verbatim — the wire shape is the\n * persisted `ChatFilePart`/`ChatImagePart` vocabulary already. Mentions get\n * the one mapping step their own vocabulary needs. The typed projection is\n * `/chat-store`'s (same boundary as the assistant hop).\n *\n * Mentions persist as parts rather than being folded into the prompt because\n * the prompt is not readable back: a retry rebuilds the turn from the stored\n * row, and a transcript draws its pills from it. Turning them INTO prompt\n * text stays the product's job — only the product knows how to resolve a\n * workspace-relative path to an in-box one (`fileMentionsToParts`'\n * `resolvePath` seam), so the route never dispatches them itself. */\nfunction userPartsWithFiles(\n userParts: Array<Record<string, unknown>>,\n fileParts: ChatTurnFilePartInput[],\n mentions: FileMention[],\n): ChatMessagePart[] {\n return toChatMessageParts([\n ...userParts,\n ...fileParts.map((part) => ({ ...part })),\n ...mentions.map((mention) => ({ ...mentionInputToPart(mention) })),\n ])\n}\n\n// ── producer-stream wrappers (heartbeat + raw tap) ───────────────────────────\n\n/** Fire `onRawEvent` for each producer event, before the engine frames it.\n * Best-effort — a telemetry throw is logged, never propagated. */\nasync function* tapRawEvents(\n source: AsyncIterable<ChatRouteEvent>,\n onRawEvent: (event: ChatRouteEvent) => void | Promise<void>,\n log: (message: string, meta?: Record<string, unknown>) => void,\n): AsyncGenerator<ChatRouteEvent, void, unknown> {\n for await (const event of source) {\n try {\n await onRawEvent(event)\n } catch (err) {\n log('[chat-routes] onRawEvent failed', { error: err instanceof Error ? err.message : String(err) })\n }\n yield event\n }\n}\n\n/** Inject a keepalive whenever `intervalMs` elapses with no source event. The\n * silent window (elapsed + tick) resets on every real event, so a producer\n * that keeps emitting never triggers a heartbeat. Closes the source on early\n * return, matching a `for await` over it. */\nasync function* withStreamHeartbeat(\n source: AsyncIterable<ChatRouteEvent>,\n intervalMs: number,\n makeEvent: (info: { elapsedMs: number; tick: number }) => ChatRouteEvent,\n): AsyncGenerator<ChatRouteEvent, void, unknown> {\n const iterator = source[Symbol.asyncIterator]()\n try {\n let pending = iterator.next()\n let windowStart = Date.now()\n let tick = 0\n for (;;) {\n let timer: ReturnType<typeof setTimeout> | undefined\n let winner: 'event' | 'heartbeat'\n try {\n const heartbeat = new Promise<'heartbeat'>((resolve) => {\n timer = setTimeout(() => resolve('heartbeat'), intervalMs)\n })\n winner = await Promise.race([pending.then(() => 'event' as const), heartbeat])\n } finally {\n // Clear the pending timer on EVERY exit — including a `pending`\n // rejection — so a rejected source never orphans a setTimeout that\n // keeps the runtime alive for up to `intervalMs`.\n if (timer !== undefined) clearTimeout(timer)\n }\n if (winner === 'heartbeat') {\n tick += 1\n yield makeEvent({ elapsedMs: Date.now() - windowStart, tick })\n continue\n }\n const result = await pending\n if (result.done) return\n yield result.value\n pending = iterator.next()\n windowStart = Date.now()\n tick = 0\n }\n } finally {\n await iterator.return?.()\n }\n}\n\n// ── the factory ────────────────────────────────────────────────────────────\n\n/** Build chat turn routes to handle and validate incoming chat requests with optional logging */\nexport function createChatTurnRoutes<TContext = void>(\n options: CreateChatTurnRoutesOptions<TContext>,\n): ChatTurnRoutes {\n const log = options.log ?? ((message, meta) => console.error(message, meta ?? ''))\n\n // Incremental persistence is on whenever the store can patch a row. An\n // EXPLICIT opt-in against a store that cannot is a wiring bug, so it fails\n // here — at construction — instead of silently degrading every turn.\n const draftStore: AssistantDraftStore = options.store\n if (options.incrementalPersistence && !storeSupportsDraftPersistence(draftStore)) {\n throw new Error(\n 'incrementalPersistence requires a store with updateMessage() — `/chat-store`\\'s createChatStore has it; a product store must implement it or omit the option',\n )\n }\n const draftTuning: DraftPersistenceTuning | null =\n options.incrementalPersistence === false || !storeSupportsDraftPersistence(draftStore)\n ? null\n : (options.incrementalPersistence ?? {})\n\n async function turn(request: Request, ctx?: { waitUntil?(p: Promise<unknown>): void }): Promise<Response> {\n const [rawBody, badBody] = await parseJsonObjectBody(request)\n if (badBody) return badBody\n\n let parsed: ParsedTurnBody\n try {\n parsed = validateTurnBody(rawBody, options.maxInlinePartBytes)\n } catch (err) {\n if (err instanceof ChatTurnInputError) return errorResponse(err)\n throw err\n }\n const { payload, content, fileParts, mentions, turnId } = parsed\n\n const auth = await options.authorize({ request, intent: 'turn', body: payload })\n if (!auth.ok) return auth.response\n const { tenantId, userId, context } = auth\n\n // Turn identity: reuse the just-persisted user row on a retry (same\n // turnId or identical trailing content) instead of double-inserting.\n const existingMessages = (await options.store.listMessages(payload.threadId)).map((m) => ({\n id: m.id,\n role: m.role,\n content: m.content,\n parts: (m.parts ?? null) as PersistedChatMessageForTurn['parts'],\n }))\n // An assistant row already trailing the thread is either this turn's\n // in-flight draft (retry — walk past it) or a settled answer (a genuine\n // repeat — new turn). The turn buffer's running index is the discriminator;\n // it is only consulted in the one case that can be ambiguous, so the common\n // path costs nothing.\n let hasRunningTurn = false\n if (draftTuning && existingMessages.at(-1)?.role === 'assistant') {\n try {\n hasRunningTurn = ((await options.turnStore.listRunning?.(payload.threadId)) ?? []).length > 0\n } catch (err) {\n log('[chat-routes] listRunning probe failed; treating the thread as idle', {\n threadId: payload.threadId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n const chatTurn = resolveChatTurn({ existingMessages, userContent: content, turnId, hasRunningTurn })\n\n const identity: ChatTurnIdentity = {\n tenantId,\n sessionId: payload.threadId,\n userId,\n turnIndex: chatTurn.turnIndex,\n }\n const executionId = deriveExecutionId({\n projectId: options.projectId,\n sessionId: payload.threadId,\n turnIndex: chatTurn.turnIndex,\n })\n const turnStreamId = crypto.randomUUID()\n\n const prompt: string | ChatTurnPartInput[] =\n fileParts.length === 0\n ? content\n : content\n ? [{ type: 'text', text: content }, ...fileParts]\n : [...fileParts]\n\n // The producer input every pre-turn seam reads. Replaced — never mutated\n // in place — as later steps resolve more of it (`userMessageId` after the\n // insert, `beforeTurn`'s patch after that), so a seam that captured the\n // object never sees it change underneath. `produce` reads the last\n // version because the engine defers its first pull.\n let produceArgs: ChatTurnProduceArgs<TContext> = {\n request,\n body: payload,\n identity,\n context,\n prompt,\n executionId,\n turnStreamId,\n priorMessages: chatTurn.priorMessages,\n }\n\n // Single-flight lock: acquire before any side effect. `release` runs\n // exactly once — in the drain's `finally` on a normal turn, or right here\n // on a short-circuit / throw.\n let lockAcquired = false\n let lockHandle: unknown\n let lockReleased = false\n const releaseLock = async (): Promise<void> => {\n if (!lockAcquired || lockReleased) return\n lockReleased = true\n try {\n await options.turnLock!.release(lockHandle)\n } catch (err) {\n log('[chat-routes] turnLock.release failed', {\n turnId: turnStreamId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n if (options.turnLock) {\n const acquired = await options.turnLock.acquire(produceArgs)\n if (!acquired.acquired) return acquired.response\n lockAcquired = true\n lockHandle = acquired.handle\n }\n\n // Turn state, hoisted so the pre-stream `catch` can settle the lifecycle\n // (fire `onTurnError`, close the span) even when a seam throws\n // synchronously before the drain — the drain would otherwise be the only\n // path that runs the terminal hook.\n let producer: ChatTurnRouteProducer | undefined\n let draft: AssistantDraftWriter | undefined\n // Set once persistence settles; `undefined` means it never ran.\n let assistantMessageId: string | null | undefined\n /** The assistant row this turn ended with. Falls back to the draft writer\n * so a turn whose producer THREW — skipping `persistAssistantMessage`\n * entirely — still names the partial row it left behind. */\n const assistantRowId = (): string | null => assistantMessageId ?? draft?.rowId() ?? null\n let runFailed = false\n // Data of the event that marked the run failed — handed to `onTurnError`\n // when no drain throw supplies a richer cause.\n let lastFailureData: Record<string, unknown> | undefined\n let turnStartedAtMs = 0\n let turnStarted = false\n let lifecycleSettled = false\n // Set when `contextGate` answered the turn without running the producer.\n let gatedTurn = false\n\n // Exactly one terminal lifecycle hook, after the turn settles (idempotent).\n // Failure is this route's own verdict (`runFailed` from error/failed\n // events, or a drain/sync throw), not the engine's envelope.\n const fireTerminalLifecycle = async (failed: boolean, terminalError: unknown): Promise<void> => {\n if (lifecycleSettled) return\n lifecycleSettled = true\n const lifecycle = options.lifecycle\n if (!lifecycle) return\n const durationMs = Date.now() - turnStartedAtMs\n try {\n if (failed) {\n await lifecycle.onTurnError?.({\n identity, executionId, turnStreamId, context, durationMs,\n error: terminalError ?? lastFailureData ?? new Error('chat turn failed'),\n })\n } else {\n const failoverInfo = producer?.modelFailover?.()\n const attribution = producer?.modelAttribution?.()\n await lifecycle.onTurnComplete?.({\n identity, executionId, turnStreamId, context, durationMs,\n finalText: producer?.finalText() ?? '',\n usage: producer?.usage?.() ?? {},\n assistantMessageId: assistantRowId(),\n ...(gatedTurn ? { gated: true } : {}),\n ...(producer?.model ? { model: producer.model } : {}),\n ...(attribution?.requestedModel ? { requestedModel: attribution.requestedModel } : {}),\n ...(attribution?.servedModel ? { servedModel: attribution.servedModel } : {}),\n ...(attribution?.servedProvider ? { servedProvider: attribution.servedProvider } : {}),\n ...(attribution?.servedSource ? { servedSource: attribution.servedSource } : {}),\n ...(failoverInfo ? { modelFailover: failoverInfo } : {}),\n })\n }\n } catch (err) {\n log('[chat-routes] lifecycle terminal hook failed', {\n turnId: turnStreamId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n try {\n // The product (via `authorize`) may suppress the user-row insert for a\n // dispatched/synthetic turn. AND-composition: it can only subtract, never\n // resurrect a turn the engine already deduped as a retry.\n const insertUserMessage = chatTurn.shouldInsertUserMessage && (auth.insertUserMessage ?? true)\n // Name the row this turn is anchored to. Insert and reuse are mutually\n // exclusive (`shouldInsertUserMessage` is false exactly when a reusable\n // row was found), so one source or the other — never both, never a\n // guess. Products used to re-derive this by decorating the store and\n // falling back to the newest row, which mis-resolves under sub-second\n // same-thread turns.\n let userMessageId: string | null = chatTurn.reusedUserMessageId ?? null\n if (insertUserMessage) {\n userMessageId = rowIdOf(\n await options.store.appendMessage({\n threadId: payload.threadId,\n role: 'user',\n content,\n parts: userPartsWithFiles(chatTurn.userParts, fileParts, mentions),\n }),\n )\n }\n produceArgs = { ...produceArgs, userMessageId }\n\n // Domain-readiness gate: may short-circuit with the product's own\n // response before the producer runs. The user row above is kept (a real\n // user turn); the gate's response is the assistant side of it.\n if (options.contextGate) {\n const gate = await options.contextGate(produceArgs)\n if (!gate.proceed) {\n await releaseLock()\n // A gated turn is still a TURN, and until now it was the one answer\n // path that fired no lifecycle hook at all — `turnStarted` is set\n // further down, so an early return here left telemetry with no record\n // that anything happened. A product whose gate answers everything\n // therefore looks IDLE rather than broken, which is indistinguishable\n // from healthy on every dashboard.\n //\n // Only the LIFECYCLE hooks fire (telemetry, errors swallowed); the\n // billing/persistence `onTurnComplete` deliberately does not, because\n // no model ran and no assistant row was written. `gated` marks the\n // completion so a consumer counts it without mistaking it for a model\n // turn that produced nothing.\n gatedTurn = true\n turnStartedAtMs = Date.now()\n if (options.lifecycle?.onTurnStart) {\n try {\n await options.lifecycle.onTurnStart({\n identity, executionId, turnStreamId, context, startedAt: turnStartedAtMs,\n })\n } catch (err) {\n log('[chat-routes] lifecycle.onTurnStart failed', {\n turnId: turnStreamId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n await fireTerminalLifecycle(false, undefined)\n return gate.response\n }\n }\n\n // Observe + optionally augment the assembled producer input.\n if (options.beforeTurn) {\n const patch = await options.beforeTurn(produceArgs)\n if (patch) produceArgs = { ...produceArgs, ...patch }\n }\n\n // Durability tap: every engine event buffers (coalesced) so a dropped\n // client replays the tail. Live delivery rides the Response body, not the\n // tap, so `write` is intentionally absent.\n const tap = createBufferedTurnTap({\n store: options.turnStore,\n turnId: turnStreamId,\n scopeId: payload.threadId,\n coalesce: options.coalesceTurnEvents ?? coalesceDeltas,\n })\n const turnMarker = { type: 'turn', turnId: turnStreamId }\n await tap.onEvent(turnMarker)\n\n // Durable projection: the turn buffer above serves LIVE reconnect; this\n // keeps the persisted transcript at most one cadence interval behind, so\n // a viewer arriving after the buffer's (deliberately short) hot window\n // reads the in-flight answer from storage instead of an empty thread.\n // Row identity is the turn's own `executionId`, so a re-entered turn\n // patches the row a previous attempt started instead of duplicating it.\n if (draftTuning) {\n draft = createAssistantDraftWriter({\n ...draftTuning,\n store: draftStore,\n threadId: payload.threadId,\n messageId: options.draftMessageId\n ? options.draftMessageId({ identity, executionId, threadId: payload.threadId })\n : assistantRowIdForTurn(executionId),\n snapshot: () => {\n if (!producer) return null\n return {\n content: producer.finalText(),\n ...(producer.draftParts ? { parts: producer.draftParts() } : {}),\n ...(producer.usage ? { usage: producer.usage() } : {}),\n ...(producer.model ? { model: producer.model } : {}),\n }\n },\n ...(options.transformFinalText ? { transformText: options.transformFinalText } : {}),\n log,\n })\n }\n\n turnStartedAtMs = Date.now()\n turnStarted = true\n if (options.lifecycle?.onTurnStart) {\n try {\n await options.lifecycle.onTurnStart({\n identity, executionId, turnStreamId, context, startedAt: turnStartedAtMs,\n })\n } catch (err) {\n log('[chat-routes] lifecycle.onTurnStart failed', {\n turnId: turnStreamId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n const result = handleChatTurn({\n identity,\n waitUntil: ctx?.waitUntil,\n log,\n hooks: {\n // The engine wants a synchronous producer; box resolution is async —\n // defer it into the generator's first pull.\n produce: () => ({\n stream: (async function* () {\n producer = await options.produce(produceArgs)\n let source: AsyncIterable<ChatRouteEvent> = producer.stream\n if (options.onRawEvent) {\n source = tapRawEvents(source, (event) => options.onRawEvent!(event, context), log)\n }\n if (options.heartbeat) {\n source = withStreamHeartbeat(source, options.heartbeat.intervalMs, options.heartbeat.event)\n }\n for await (const event of source) yield event\n })(),\n finalText: () => producer?.finalText() ?? '',\n }),\n onEvent: async (event) => {\n if (event.type === 'session.run.failed' || event.type === 'error') {\n runFailed = true\n lastFailureData = event.data\n }\n await tap.onEvent(event)\n // Synchronous bookkeeping; the write it may start is fire-and-\n // forget, so store latency never back-pressures the live stream.\n draft?.notify(event)\n if (options.onEvent) await options.onEvent(event, context)\n },\n ...(options.transformFinalText ? { transformFinalText: options.transformFinalText } : {}),\n persistAssistantMessage: async ({ finalText }) => {\n // The typed boundary: stream-normalizer records → stored vocabulary\n // (validating projection owned by /chat-store — no cast here). The\n // scalar `finalText` arrives already transformed by the engine; the\n // producer's text PARTS are raw, so the same transform must run over\n // each text segment before persistence or a redaction (legal PII)\n // leaks at rest through message.parts.\n const rawParts = producer?.assistantParts ? producer.assistantParts() : undefined\n const projected =\n rawParts && options.transformFinalText\n ? await Promise.all(\n rawParts.map(async (part) =>\n String((part as { type?: unknown }).type ?? '') === 'text'\n ? { ...part, text: await options.transformFinalText!(String((part as { text?: unknown }).text ?? '')) }\n : part,\n ),\n )\n : rawParts\n const parts = projected ? toChatMessageParts(projected) : undefined\n if (!finalText.trim() && (!parts || parts.length === 0)) {\n // Empty turn: today this leaves no assistant row at all, so a\n // draft row started earlier is retracted, not left behind.\n await draft?.discard()\n // Read the writer rather than assuming `null`: `discard` is a\n // no-op on a store without `deleteMessage` (drafting needs only\n // `updateMessage`), and there the draft row genuinely survives.\n assistantMessageId = draft?.rowId() ?? null\n return\n }\n const usage = producer?.usage?.() ?? {}\n const attribution = producer?.modelAttribution?.()\n const values = {\n content: finalText,\n ...(parts && parts.length > 0 ? { parts } : {}),\n ...(producer?.model ? { model: producer.model } : {}),\n ...(attribution?.requestedModel ? { requestedModel: attribution.requestedModel } : {}),\n ...(attribution?.servedModel ? { servedModel: attribution.servedModel } : {}),\n ...(attribution?.servedProvider ? { servedProvider: attribution.servedProvider } : {}),\n ...(attribution?.servedSource ? { servedSource: attribution.servedSource } : {}),\n ...(usage.inputTokens !== undefined ? { inputTokens: usage.inputTokens } : {}),\n ...(usage.outputTokens !== undefined ? { outputTokens: usage.outputTokens } : {}),\n ...(usage.reasoningTokens !== undefined ? { reasoningTokens: usage.reasoningTokens } : {}),\n ...(usage.cacheReadTokens !== undefined ? { cacheReadTokens: usage.cacheReadTokens } : {}),\n ...(usage.cacheWriteTokens !== undefined ? { cacheWriteTokens: usage.cacheWriteTokens } : {}),\n ...(usage.costUsd !== undefined ? { costUsd: usage.costUsd } : {}),\n }\n // ONE row per turn either way. With drafting, `finalize` settles\n // any in-flight draft and then insert-or-patches the same\n // deterministic id — so the row this turn ends with is identical to\n // the one the single-write path produces. Without it, today's\n // append is untouched.\n if (draft) {\n await draft.finalize(values)\n assistantMessageId = draft.rowId() ?? null\n return\n }\n assistantMessageId = rowIdOf(\n await options.store.appendMessage({\n threadId: payload.threadId,\n role: 'assistant',\n ...values,\n }),\n )\n },\n ...(options.onTurnComplete\n ? {\n // Wired into the engine's completion hook, which fires only when\n // the stream ended without throwing. A terminal error EVENT\n // (not a throw) still lands here — so surface `runFailed` so the\n // product skips billing an errored turn instead of marking it\n // complete with empty text.\n onTurnComplete: ({ identity: turnIdentity, finalText }: { identity: ChatTurnIdentity; finalText: string }) => {\n // Read AFTER the drain, so the model reported to billing is\n // the one that actually served — a fallback included.\n const failoverInfo = producer?.modelFailover?.()\n const attribution = producer?.modelAttribution?.()\n return options.onTurnComplete!({\n identity: turnIdentity,\n finalText,\n context,\n failed: runFailed,\n assistantMessageId: assistantRowId(),\n ...(runFailed ? { failureReason: failureReasonOf(lastFailureData) } : {}),\n ...(producer?.model ? { model: producer.model } : {}),\n ...(attribution?.requestedModel ? { requestedModel: attribution.requestedModel } : {}),\n ...(attribution?.servedModel ? { servedModel: attribution.servedModel } : {}),\n ...(attribution?.servedProvider ? { servedProvider: attribution.servedProvider } : {}),\n ...(attribution?.servedSource ? { servedSource: attribution.servedSource } : {}),\n ...(failoverInfo ? { modelFailover: failoverInfo } : {}),\n })\n },\n }\n : {}),\n ...(options.traceFlush ? { traceFlush: () => options.traceFlush!(context) } : {}),\n },\n })\n\n // Tee: one branch to the live client, one drained under waitUntil so the\n // turn (and its buffering via onEvent) runs to completion after a client\n // drop — the engine body executes as it is pulled.\n const [clientBody, drainBody] = result.body.tee()\n const drained = (async () => {\n const reader = drainBody.getReader()\n let drainError: unknown\n try {\n for (;;) {\n const { done } = await reader.read()\n if (done) break\n }\n } catch (err) {\n drainError = err\n log('[chat-routes] turn drain failed', {\n turnId: turnStreamId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n const failed = runFailed || drainError !== undefined\n // Settle any in-flight draft INSIDE the waitUntil-tracked drain. On the\n // path where the producer throws, `persistAssistantMessage` never runs\n // to close the writer, and an unawaited write would race the isolate's\n // teardown — leaving a torn row instead of a clean partial one for a\n // re-entered turn to adopt.\n await draft?.close()\n try {\n await tap.done(failed ? 'error' : 'complete')\n } catch (err) {\n log('[chat-routes] turn buffer finalize failed', {\n turnId: turnStreamId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n await fireTerminalLifecycle(failed, drainError)\n await releaseLock()\n })()\n if (ctx?.waitUntil) ctx.waitUntil(drained)\n else void drained.catch(() => {})\n\n // Announce the replay handle before the engine's first event.\n const encoder = new TextEncoder()\n const marker = new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(encoder.encode(`${JSON.stringify(turnMarker)}\\n`))\n controller.close()\n },\n })\n const body = concatStreams([marker, clientBody])\n\n return new Response(body, {\n headers: {\n 'Content-Type': result.contentType,\n 'Cache-Control': 'no-cache',\n },\n })\n } catch (err) {\n // A throw before the turn began streaming (user-insert, gate, beforeTurn,\n // lifecycle-start, tap setup, engine construction, tee). If the turn had\n // already started, settle the lifecycle with `onTurnError` (close the\n // span) — the drain never ran to do it. Then release the lock, propagate.\n if (turnStarted) await fireTerminalLifecycle(true, err)\n await releaseLock()\n throw err\n }\n }\n\n async function replay(request: Request, params: { turnId: string }): Promise<Response> {\n const turnId = params.turnId?.trim()\n if (!turnId) return Response.json({ error: 'Missing turnId' }, { status: 400 })\n const auth = await options.authorize({ request, intent: 'replay', turnId })\n if (!auth.ok) return auth.response\n\n const fromSeqRaw = new URL(request.url).searchParams.get('fromSeq')\n const fromSeq = fromSeqRaw ? Math.max(0, Math.trunc(Number(fromSeqRaw)) || 0) : 0\n\n const encoder = new TextEncoder()\n const events = replayTurnEvents({\n store: options.turnStore,\n turnId,\n fromSeq,\n ...(options.replay?.pollMs !== undefined ? { pollMs: options.replay.pollMs } : {}),\n ...(options.replay?.timeoutMs !== undefined ? { timeoutMs: options.replay.timeoutMs } : {}),\n })\n const body = new ReadableStream<Uint8Array>({\n async pull(controller) {\n const { done, value } = await events.next()\n if (done) {\n controller.close()\n return\n }\n // Stamp the buffer ordinal so a reconnecting client resumes from\n // `?fromSeq=<lastSeq>` instead of refetching the turn from 0 and\n // re-applying every delta onto already-rendered state.\n controller.enqueue(encoder.encode(`${stampReplaySeq(value)}\\n`))\n },\n cancel() {\n void events.return(undefined)\n },\n })\n return new Response(body, {\n headers: {\n 'Content-Type': 'application/x-ndjson',\n 'Cache-Control': 'no-cache',\n },\n })\n }\n\n async function running(request: Request): Promise<Response> {\n const threadId = new URL(request.url).searchParams.get('threadId')?.trim()\n if (!threadId) return Response.json({ error: 'Missing threadId' }, { status: 400 })\n const auth = await options.authorize({ request, intent: 'running', threadId })\n if (!auth.ok) return auth.response\n // `listRunning` is optional on the store; a store that cannot enumerate\n // running turns simply reports none — the client falls back to the persisted\n // transcript, never to a hang.\n const ids = (await options.turnStore.listRunning?.(threadId)) ?? []\n return Response.json({ running: ids })\n }\n\n return {\n turn,\n replay,\n running,\n interactions: options.interactions ? createInteractionAnswerRoute(options.interactions) : null,\n }\n}\n\n/** Sequential concat of byte streams (marker line, then the engine body). */\nfunction concatStreams(streams: ReadableStream<Uint8Array>[]): ReadableStream<Uint8Array> {\n let index = 0\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n for (;;) {\n if (!reader) {\n const next = streams[index++]\n if (!next) {\n controller.close()\n return\n }\n reader = next.getReader()\n }\n const { done, value } = await reader.read()\n if (done) {\n reader = null\n continue\n }\n controller.enqueue(value)\n return\n }\n },\n async cancel(reason) {\n await reader?.cancel(reason)\n for (const stream of streams.slice(index)) await stream.cancel(reason)\n },\n })\n}\n","/**\n * Incremental (\"draft\") persistence of the assistant row WHILE a turn streams.\n *\n * Why this exists — the scale argument, not a convenience:\n *\n * A live turn is readable from two places. The hot path is the session\n * gateway's in-memory/Redis event buffer, which exists so a viewer survives a\n * network blip: it is keyed one sorted set per session, refreshed on every\n * push, and expires on a TTL. Its memory cost is `arrival_rate x TTL x\n * bytes_per_session` — strictly LINEAR in the TTL. Stretching that TTL to\n * cover \"a viewer who opens the tab later\" is a category error: it buys memory\n * proportional to the increase and still serves nothing to a viewer who\n * arrives past the new horizon.\n *\n * So the hot buffer must stay SHORT (live delivery + reconnect only), and\n * durable storage must serve history. That only works if durable storage\n * actually HAS the in-flight turn — which, before this module, it did not: the\n * assistant row was written once, after the stream drained. A viewer arriving\n * mid-turn past the hot window read an empty transcript.\n *\n * This module closes that gap: the assistant row is inserted early and patched\n * on a coalesced cadence, so the durable transcript is at most one interval\n * (default 2 s) behind the live stream and the hot buffer never has to be the\n * history tier.\n *\n * Mechanism only. It owns no vocabulary: the caller supplies the snapshot\n * function, the store, and the deterministic row id.\n *\n * Four properties the cadence guarantees:\n * - **Time-floored.** At most one write per `intervalMs`, never one per token.\n * - **Dirty-gated.** Only content-bearing events arm a write; heartbeats,\n * status pings, and lifecycle envelopes never do.\n * - **Single-flight.** A write already in flight suppresses the next trigger\n * instead of queueing; the final write is authoritative regardless of how\n * many drafts landed.\n * - **Best-effort.** A store failure is logged and swallowed — a durability\n * optimization must never kill a healthy stream (the same rule\n * `withDurableChatProjection` already states).\n */\n\nimport { toChatMessageParts, type ChatMessagePart } from '../chat-store/parts'\nimport type { ChatTurnUsage } from './turn-routes'\n\n/** Message row shape the writer reads back when re-entering a turn. */\nexport interface DraftStoredMessage {\n id: string\n role: 'user' | 'assistant' | 'system' | 'tool'\n content: string\n parts?: ChatMessagePart[] | null\n model?: string | null\n requestedModel?: string | null\n servedModel?: string | null\n servedProvider?: string | null\n servedSource?: string | null\n}\n\n/** Values written to the assistant row — the intersection of the append and\n * patch shapes, so one snapshot serves both. */\nexport interface AssistantRowValues {\n content: string\n parts?: ChatMessagePart[]\n model?: string | null\n requestedModel?: string | null\n servedModel?: string | null\n servedProvider?: string | null\n servedSource?: string | null\n inputTokens?: number | null\n outputTokens?: number | null\n reasoningTokens?: number | null\n cacheReadTokens?: number | null\n cacheWriteTokens?: number | null\n costUsd?: number | null\n}\n\n/** The store capability incremental persistence needs on top of\n * `appendMessage`. A store without `updateMessage` cannot patch a row, so it\n * cannot draft at all — the caller keeps today's single-write behavior. */\nexport interface AssistantDraftStore {\n listMessages(threadId: string): Promise<DraftStoredMessage[]>\n appendMessage(input: AssistantRowValues & {\n id?: string\n threadId: string\n role: 'user' | 'assistant'\n }): Promise<unknown>\n updateMessage?(id: string, patch: AssistantRowValues): Promise<unknown>\n deleteMessage?(id: string): Promise<unknown>\n}\n\n/** Live snapshot of the assistant body, taken from the producer's own\n * accumulators. `parts` is the DRAFT projection (`draftParts()`), never the\n * finalized one — see `draftAssistantParts`. */\nexport interface AssistantDraftSnapshot {\n content: string\n parts?: Array<Record<string, unknown>>\n usage?: ChatTurnUsage\n model?: string\n}\n\n/** Product-tunable cadence. Defaults are stated on each field; a product with\n * a chattier or heavier workload moves them without forking the writer. */\nexport interface DraftPersistenceTuning {\n /** Minimum wall-clock gap between draft writes, in ms. Default 2000.\n *\n * Justification for 2 s, from a measured tool-heavy production run (517\n * stream events over ~90 s wall = ~5.7 events/s): a 2 s floor with the\n * dirty gate turns 517 candidate writes into <= 45, while leaving the\n * durable row at most 2 s stale — an order of magnitude below the time it\n * takes a viewer to open a tab and render, so a late viewer never perceives\n * the lag. Fleet arithmetic at 10k concurrent 60 s runs: <= 30 updates per\n * run x 167 run-starts/s = ~334 row-updates/s spread over per-tenant\n * shards. Lower it and write amplification grows with no perceptible\n * freshness gain; raise it past ~5 s and a late viewer starts seeing a\n * visibly truncated answer. */\n intervalMs?: number\n /** Serialized-parts size (bytes) past which the interval backs off, so a\n * turn accumulating a megabyte-scale `parts` blob does not rewrite it every\n * interval. Default 262144 (256 KiB) -> interval x 2.5; ten times that ->\n * interval x 5. The final write is never throttled. */\n backoffBytes?: number\n /** Per-tool-part output cap applied to DRAFTS ONLY (bytes). A tool returning\n * a large blob would otherwise be rewritten in full on every draft. The\n * final write always carries the untruncated value. Default 32768 (32 KiB);\n * 0 disables truncation. */\n maxDraftToolOutputBytes?: number\n}\n\n/** Define the inputs required to construct an assistant draft writer */\nexport interface AssistantDraftWriterOptions extends DraftPersistenceTuning {\n store: AssistantDraftStore\n threadId: string\n /** DETERMINISTIC row id for this turn's assistant message — the whole\n * idempotency mechanism. Derived from the turn's existing identity\n * (`deriveExecutionId` in the interactive lane, the turn id in the detached\n * lane), so a re-entered turn addresses the SAME row: the writer looks the\n * id up before its first insert and patches what it finds. */\n messageId: string\n /** Read the producer's live accumulators. Returns null before the producer\n * is resolved (the assembly defers box resolution into the first pull). */\n snapshot(): AssistantDraftSnapshot | null\n /** Pre-persist text transform (`/redact`'s `redactPII`). Applied to the\n * draft's scalar content AND every draft text part — parity with the final\n * write, or incremental persistence would re-open the at-rest PII leak that\n * transform closed, just seconds earlier and on every turn. */\n transformText?(text: string): string | Promise<string>\n log?: (message: string, meta?: Record<string, unknown>) => void\n}\n\n/** Coalescing writer that keeps one durable assistant row in step with a\n * streaming turn. Created per turn; not reusable. */\nexport interface AssistantDraftWriter {\n /** Arm/trigger a draft write from one engine event. Synchronous by design —\n * the write itself is fire-and-forget so the stream is never blocked on\n * store latency. */\n notify(event: { type?: unknown }): void\n /** Stop drafting and settle any in-flight write. Called before the final\n * write so a late draft can never clobber the authoritative row. */\n close(): Promise<void>\n /** Write the AUTHORITATIVE completion values onto this turn's row —\n * insert-or-patch under the same deterministic id, un-throttled and\n * un-truncated. Errors propagate: the final write is the one that must not\n * fail silently. Implies {@link close}. */\n finalize(values: AssistantRowValues): Promise<void>\n /** The durable row this turn is writing, once one exists. */\n rowId(): string | undefined\n /** Retract the row for a turn that produced nothing (mirrors the final\n * write's empty-turn skip, which leaves no row at all today). Also retracts\n * a row a PREVIOUS attempt left behind, so a re-entered turn that ends\n * empty converges on \"no row\" rather than a stale partial. */\n discard(): Promise<void>\n /** Diagnostics: how many draft writes actually reached the store. */\n writeCount(): number\n}\n\n/** Event types that carry assistant content and therefore arm a draft write.\n * An allowlist on purpose: an unknown type (a product heartbeat, a bespoke\n * passthrough) must NEVER arm a write, or a silent producer would rewrite the\n * same row forever. */\nconst CONTENT_EVENT_TYPES: ReadonlySet<string> = new Set([\n 'text',\n 'reasoning',\n 'tool_call',\n 'tool_result',\n 'usage',\n 'notice',\n 'error',\n 'file',\n 'interaction',\n 'interaction.cancel',\n 'plan.submitted',\n 'message.part.updated',\n])\n\nconst DEFAULT_INTERVAL_MS = 2000\nconst DEFAULT_BACKOFF_BYTES = 262144\nconst DEFAULT_MAX_DRAFT_TOOL_OUTPUT_BYTES = 32768\n\n/** True when this event should arm a draft write. */\nexport function isDraftContentEvent(event: { type?: unknown }): boolean {\n return typeof event?.type === 'string' && CONTENT_EVENT_TYPES.has(event.type)\n}\n\n/** Caps one tool part's `output` for a DRAFT write. The value is replaced by a\n * truncated string plus a marker, so a reader can tell a clipped draft from a\n * real short output; the final write restores the untruncated value. */\nfunction capDraftToolOutput(part: Record<string, unknown>, maxBytes: number): Record<string, unknown> {\n if (maxBytes <= 0) return part\n if (String(part.type ?? '') !== 'tool') return part\n const state = part.state\n if (!state || typeof state !== 'object') return part\n const record = state as Record<string, unknown>\n const output = record.output\n if (output === undefined || output === null) return part\n const serialized = typeof output === 'string' ? output : safeStringify(output)\n if (serialized.length <= maxBytes) return part\n const metadata = (record.metadata && typeof record.metadata === 'object' ? record.metadata : {}) as Record<string, unknown>\n return {\n ...part,\n state: {\n ...record,\n output: `${serialized.slice(0, maxBytes)}…[draft-truncated ${serialized.length - maxBytes} chars]`,\n metadata: { ...metadata, draftTruncated: true },\n },\n }\n}\n\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value) ?? ''\n } catch {\n return String(value)\n }\n}\n\n/** Build the coalescing draft writer for one turn. */\nexport function createAssistantDraftWriter(options: AssistantDraftWriterOptions): AssistantDraftWriter {\n const log = options.log ?? (() => {})\n const baseIntervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS\n const backoffBytes = options.backoffBytes ?? DEFAULT_BACKOFF_BYTES\n const maxToolOutput = options.maxDraftToolOutputBytes ?? DEFAULT_MAX_DRAFT_TOOL_OUTPUT_BYTES\n\n let dirty = false\n let closed = false\n let inFlight: Promise<void> | undefined\n let lastWriteAt = 0\n let lastBlobBytes = 0\n let rowId: string | undefined\n let writes = 0\n\n /** Interval for the NEXT write, backed off by the size of the last blob\n * written — a turn accumulating a huge parts array rewrites it less often. */\n function currentIntervalMs(): number {\n if (lastBlobBytes >= backoffBytes * 10) return baseIntervalMs * 5\n if (lastBlobBytes >= backoffBytes) return Math.round(baseIntervalMs * 2.5)\n return baseIntervalMs\n }\n\n async function projectValues(snapshot: AssistantDraftSnapshot): Promise<AssistantRowValues> {\n const transform = options.transformText\n const content = transform ? await transform(snapshot.content) : snapshot.content\n let parts: ChatMessagePart[] | undefined\n if (snapshot.parts) {\n const capped = snapshot.parts.map((part) => capDraftToolOutput(part, maxToolOutput))\n const redacted = transform\n ? await Promise.all(\n capped.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 : capped\n parts = toChatMessageParts(redacted)\n }\n const usage = snapshot.usage ?? {}\n return {\n content,\n ...(parts && parts.length > 0 ? { parts } : {}),\n ...(snapshot.model ? { model: snapshot.model } : {}),\n ...(usage.inputTokens !== undefined ? { inputTokens: usage.inputTokens } : {}),\n ...(usage.outputTokens !== undefined ? { outputTokens: usage.outputTokens } : {}),\n ...(usage.reasoningTokens !== undefined ? { reasoningTokens: usage.reasoningTokens } : {}),\n ...(usage.cacheReadTokens !== undefined ? { cacheReadTokens: usage.cacheReadTokens } : {}),\n ...(usage.cacheWriteTokens !== undefined ? { cacheWriteTokens: usage.cacheWriteTokens } : {}),\n ...(usage.costUsd !== undefined ? { costUsd: usage.costUsd } : {}),\n }\n }\n\n /** Adopt the row a PREVIOUS attempt at this turn already inserted, if any.\n * This lookup — by the deterministic id, through the store's ordinary read\n * — is the whole crash-safety mechanism: no new state, no extra column, no\n * second index. */\n async function adoptRow(): Promise<string | undefined> {\n if (rowId) return rowId\n const existing = (await options.store.listMessages(options.threadId)).find(\n (message) => message.id === options.messageId,\n )\n if (existing) rowId = existing.id\n return rowId\n }\n\n /** Insert-or-patch one set of values onto this turn's single row. */\n async function writeOnce(values: AssistantRowValues): Promise<void> {\n if (await adoptRow()) {\n await options.store.updateMessage!(rowId!, values)\n writes += 1\n return\n }\n const inserted = await options.store.appendMessage({\n id: options.messageId,\n threadId: options.threadId,\n role: 'assistant',\n ...values,\n })\n // The `?? options.messageId` tail is valid HERE and only here: this writer\n // supplied the id, so a store that returns nothing still wrote that row.\n // A caller that did not assign an id has no such fallback — which is why\n // `rowIdOf` deliberately stops at the honest read.\n rowId = rowIdOf(inserted) ?? options.messageId\n writes += 1\n }\n\n function trigger(): void {\n if (closed) return\n if (!dirty) return\n // Single-flight: a write already in flight SUPPRESSES this trigger rather\n // than queueing behind it. `dirty` stays armed, so the next event after it\n // lands writes the newer snapshot — one write is never spent on state a\n // later one supersedes.\n if (inFlight) return\n const now = Date.now()\n // The first content event writes immediately (a late viewer sees a row at\n // once); the time floor governs every write after it.\n if (lastWriteAt !== 0 && now - lastWriteAt < currentIntervalMs()) return\n const snapshot = options.snapshot()\n if (!snapshot) return\n // Nothing to persist yet: an armed-but-empty snapshot (first event is a\n // tool call with no text and no parts) would insert a blank row.\n if (!snapshot.content && (!snapshot.parts || snapshot.parts.length === 0)) return\n dirty = false\n lastWriteAt = now\n inFlight = (async () => {\n try {\n const values = await projectValues(snapshot)\n lastBlobBytes = values.parts ? safeStringify(values.parts).length : 0\n await writeOnce(values)\n } catch (err) {\n // Best-effort by contract: a store outage degrades freshness for late\n // viewers, it does not fail a healthy turn.\n log('[chat-routes] incremental assistant persistence failed', {\n messageId: options.messageId,\n error: err instanceof Error ? err.message : String(err),\n })\n } finally {\n inFlight = undefined\n // A trigger that arrived while this write was in flight left `dirty`\n // armed with a NEWER snapshot. Re-run it here or that state would sit\n // unwritten until the next content event — and if this was the last\n // event of a quiet stretch, until the final write.\n if (dirty && !closed) trigger()\n }\n })()\n }\n\n return {\n notify(event) {\n if (closed) return\n if (isDraftContentEvent(event)) dirty = true\n trigger()\n },\n async close() {\n closed = true\n if (inFlight) await inFlight\n },\n async finalize(values) {\n closed = true\n if (inFlight) await inFlight\n await writeOnce(values)\n },\n rowId: () => rowId,\n async discard() {\n closed = true\n if (inFlight) await inFlight\n if (!options.store.deleteMessage) return\n try {\n if (!(await adoptRow())) return\n await options.store.deleteMessage(rowId!)\n rowId = undefined\n } catch (err) {\n log('[chat-routes] draft assistant row discard failed', {\n messageId: options.messageId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n },\n writeCount: () => writes,\n }\n}\n\n/** True when a store can support incremental persistence at all. Without\n * `updateMessage` a draft row could never be patched, so the caller keeps\n * today's exact single-write behavior. */\nexport function storeSupportsDraftPersistence(store: AssistantDraftStore): boolean {\n return typeof store.updateMessage === 'function'\n}\n\n/** The row id an `appendMessage` actually returned, or `null` when the store\n * returned nothing usable. `ChatTurnMessageStore.appendMessage` is typed\n * `Promise<unknown>` so a product adapter is free to resolve `void`; every\n * caller that wants to NAME the row it just wrote has to read defensively.\n *\n * Deliberately no fallback to a caller-assigned id — see `writeOnce`, which\n * adds its own. A caller that let the store mint the id has nothing to fall\n * back TO, and guessing one would report a row that may not exist. */\nexport function rowIdOf(inserted: unknown): string | null {\n const id = (inserted as { id?: unknown } | null | undefined)?.id\n return typeof id === 'string' && id ? id : null\n}\n\n/** The default deterministic assistant-row id for a turn. Readable on purpose\n * (an operator grepping a transcript row id finds the run), and stable across\n * re-entries because every input already is. */\nexport function assistantRowIdForTurn(turnKey: string): string {\n return `assistant:${turnKey}`\n}\n","/**\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 * 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 chain of length 1 costs nothing: one attempt, no extra call, no added\n * latency, byte-identical to no failover at all.\n */\n\nimport {\n isUpstreamUnavailable,\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 === '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 */\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}\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}\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}) => 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 /** Override the commit-point rule. Default {@link isCommittingSandboxEvent}. */\n isCommitting?: (event: unknown) => 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/**\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 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 (both behaviors inherited from `runWithModelFailover`).\n */\nexport function streamWithModelFailover(\n options: ModelFailoverStreamOptions,\n): ModelFailoverStreamHandle {\n const committing = options.isCommitting ?? isCommittingSandboxEvent\n const emptyTurnRetries = resolveEmptyTurnRetries(options.emptyTurnRetries)\n let serving: string | undefined\n let trail: ModelFailoverAttempt[] = []\n let fellBack = false\n let attemptIndex = 0\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 source = await options.open({ model, attempt: attemptIndex })\n const iterator = source[Symbol.asyncIterator]()\n const buffered: unknown[] = []\n\n for (;;) {\n // A THROWN failure propagates untouched: `runWithModelFailover` classifies\n // it (outage → next model, anything else → re-thrown to the caller).\n const next = await iterator.next()\n if (next.done) {\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 }, empty: true }\n }\n\n const event = next.value\n const failure = classifyTerminalFailure(event)\n if (failure?.outage) {\n await 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 buffered.push(event)\n // A terminal NON-outage failure surfaces exactly as it does today.\n if (failure) return { outcome: { committed: true, buffered, iterator }, empty: false }\n if (committing(event)) {\n return { outcome: { committed: true, buffered, iterator }, empty: isEmptyTerminalReceipt(event) }\n }\n }\n }\n\n const probe = async (model: string): Promise<AttemptOutcome> => {\n for (let retry = 0; ; retry += 1) {\n const pass = await drainOnce(model)\n if (!pass.empty || retry >= emptyTurnRetries) return pass.outcome\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 (iterator) await 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 try {\n const outcome = await 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 })\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 throw err\n }\n\n for (const event of handle.buffered) yield event\n\n const live = handle.iterator\n if (!live) return\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 await closeIterator(live, options.log)\n }\n })()\n\n return {\n events,\n servingModel: () => serving,\n attempts: () => trail,\n usedFallback: () => fellBack,\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 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 /** 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\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} {\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 return { userMessage, failureNote }\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.emptyTurnRetries !== undefined ? { emptyTurnRetries: options.emptyTurnRetries } : {}),\n ...(options.onEmptyTurnRetry ? { onEmptyTurnRetry: options.onEmptyTurnRetry } : {}),\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 /** Tool ids already announced as `tool_call` / settled as `tool_result`. */\n const announcedTools = new Set<string>()\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 { type: 'usage', usage: { promptTokens, completionTokens } }\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 if (toolId && !announcedTools.has(toolId)) {\n announcedTools.add(toolId)\n yield {\n type: 'tool_call',\n call: { toolCallId: toolId, toolName, args: asRecord(state?.input) ?? {} },\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 { type: 'usage', usage: { promptTokens, completionTokens } }\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 === '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: 'sandbox.stream_failed',\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","/**\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 /** 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 * Unset, a re-stream over a `running` buffer is still attempted but logged\n * as a possible-duplication hazard. */\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 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: final?.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 try {\n return (await opts.completedResult()) ?? null\n } catch (err) {\n opts.log?.('[chat-routes] runDetachedTurn completedResult lookup failed', { turnId, err: String(err) })\n return null\n }\n }\n\n let prior: string | null = null\n try {\n prior = await store.getStatus(turnId)\n } catch (err) {\n // A transient store blip must NOT silently fall through to a full re-stream\n // (which would duplicate a completed turn's buffer) — surface it.\n opts.log?.('[chat-routes] runDetachedTurn getStatus failed; treating as no prior', { turnId, err: String(err) })\n }\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).catch((err) => {\n opts.log?.('[chat-routes] runDetachedTurn failed to settle a completed running turn', { turnId, err: String(err) })\n })\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 await opts.resetBuffer(turnId).catch((err) => {\n opts.log?.('[chat-routes] runDetachedTurn resetBuffer failed; re-stream may duplicate rows', { turnId, err: String(err) })\n })\n } else {\n opts.log?.('[chat-routes] runDetachedTurn re-streaming over a running buffer without resetBuffer; rows may duplicate', { turnId })\n }\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.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. The session-level result is consulted only\n * when the matching completed assistant message is still the latest message;\n * otherwise that result could belong to a newer turn on the same session.\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) return null\n\n let result = cached ? asRecord(cached.result) : undefined\n const latestConversationMessage = messages\n .filter((candidate) => candidate.role === 'user' || candidate.role === 'assistant')\n .at(-1)\n if (!result && message && latestConversationMessage === message) {\n try {\n result = asRecord(await session.result())\n } catch (error) {\n log?.('[chat-routes] completed Sandbox session result lookup failed', {\n turnId,\n sessionId,\n error: String(error),\n })\n }\n }\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 * `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","/**\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 the injected\n * {@link WriteAttachmentFn} (gtm hard-wired its vault writer), the path strategy\n * is the injected `buildAttachmentPath` (neutral `uploads/agent/<date>/` default,\n * no domain bucket taxonomy baked), the MIME map is an injectable hook, and the\n * date segment reads an injectable clock. The idempotent `hash8(id ?? url ??\n * filename)` naming is preserved so re-promoting the same source part resolves\n * to the same path.\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 type { WriteAttachmentFn } from './attachment-store'\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]*)$/.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:([^;,]+)[;,]/.exec(url)\n return match ? match[1] : undefined\n}\n\nfunction basenameFromUrl(url: string | undefined): string | undefined {\n if (!url || url.startsWith('data:')) 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 = url.startsWith('file://') ? 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: ${err instanceof Error ? err.message : String(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: ${err instanceof Error ? err.message : String(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: ${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: ${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 (url.startsWith('data:')) return resolveDataUrlBytes(url, input.filename, input.maxBytes)\n\n const isSandboxPath = url.startsWith('file://') || url.startsWith('/')\n if (!isSandboxPath) return { succeeded: false, reason: `unsupported file URL scheme: ${url}` }\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\n/** Define options for promoting a part of an agent file within a specific session and scope */\nexport interface PromoteAgentFilePartOptions {\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 /** REQUIRED store writer — no default (the product owns its store). */\n writeAttachment: WriteAttachmentFn\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}\n\n/** Promote a part of an agent file with optional byte limits and MIME type detection */\nexport async function promoteAgentFilePart(options: PromoteAgentFilePartOptions): Promise<PromoteFilePartResult> {\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\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 const mediaType = options.raw.mediaType ?? options.raw.mime ?? dataUrlMime(options.raw.url) ?? sniffMime(filename)\n const kind = attachmentKindForMime(mediaType)\n const digest = await hash8(options.raw.id ?? options.raw.url ?? filename)\n const date = now().toISOString().split('T')[0] ?? ''\n const path = buildAttachmentPath({ filename, hash8: digest, date, mediaType, kind })\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<WriteAttachmentFn>>\n try {\n written = await options.writeAttachment(options.scopeId, path, resolved.bytes, {\n mediaType,\n name: filename,\n originalName: options.raw.filename ?? filename,\n size: resolved.bytes.byteLength,\n })\n } catch (err) {\n return { succeeded: false, filename, reason: err instanceof Error ? err.message : String(err) }\n }\n if (!written.ok) return { succeeded: false, filename, reason: written.reason }\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 atomic batch (every file is validated before any file is\n * written — a batch never partially lands), a content-sniffed type gate\n * (`checkAttachmentType` over `sniffBinary`'s magic-byte read, not the\n * extension or the browser-reported MIME), per-kind + aggregate byte caps,\n * and sanitized filenames. Storage is fully seamed through the injected\n * `WriteAttachmentFn` (`./attachment-store`) — no default store, the product\n * owns where bytes actually live (vault, object store, …) — and auth/rate\n * limiting is entirely the injected `authorize` 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 type { WriteAttachmentFn } from './attachment-store'\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/** Outcome of the injected `authorize` seam: auth + rate limiting +\n * scope resolution, all in one place so a 429 rides `{ok:false, response}`\n * exactly like a 401 does — this factory has no rate-limit opinion of its\n * own. `writeAttachment` lets a single request override the option-level\n * store (e.g. routing per-tenant), defaulting to `options.writeAttachment`\n * when absent. */\nexport type AttachmentUploadAuthorization =\n | { ok: true; scopeId: string; writeAttachment?: WriteAttachmentFn }\n | { ok: false; response: Response }\n\n/** Define options to authorize, write, and limit attachment uploads in a route */\nexport interface CreateAttachmentUploadRouteOptions {\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 /** Default store writer. `authorize` may override it per-request. */\n writeAttachment: WriteAttachmentFn\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 /** Ceiling on a text file's raw size. Default {@link MAX_TEXT_ATTACHMENT_BYTES}. */\n maxTextBytes?: number\n /** Aggregate raw-byte ceiling across the batch. 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`. Default\n * {@link ALLOWED_ATTACHMENT_SNIFFED_MIMES}. */\n allowedSniffedMimes?: ReadonlySet<string>\n /** Sanitized-name → store path. Default identity (the sanitized name IS\n * the path); gtm passes `vaultFolderForFileName`, a tenant product a\n * scope prefix. */\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 can't type.\n * Default {@link sniffMimeFromName}. */\n sniffMime?: (name: string) => string\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\n/** Resolve an attachment upload route handler with customizable limits and validation options */\nexport function createAttachmentUploadRoute(\n options: CreateAttachmentUploadRouteOptions,\n): (request: Request) => Promise<Response> {\n const maxCount = options.limits?.maxCount ?? ATTACHMENT_MAX_COUNT\n const maxBinaryBytes = options.limits?.maxBinaryBytes ?? MAX_BINARY_ATTACHMENT_BYTES\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\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 write = auth.writeAttachment ?? options.writeAttachment\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 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 seenPaths = 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 const limit = sniff.binary ? maxBinaryBytes : maxTextBytes\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 path = pathFor(name)\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 if (seenPaths.has(path)) {\n return attachmentUploadError(\n 400,\n 'attachment_duplicate_path',\n `attachments must not repeat a path within one upload: ${path}`,\n path,\n )\n }\n seenPaths.add(path)\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, 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 for (const input of prepared) {\n const written = await write(auth.scopeId, input.path, input.bytes, {\n mediaType: input.mediaType,\n name: input.name,\n originalName: input.originalName,\n size: input.size,\n })\n if (!written.ok) {\n return attachmentUploadError(413, 'attachment_write_failed', written.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 }\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 { 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 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","inlinePart","cost"]}
|