@tangle-network/agent-app 0.60.191 → 0.60.193
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-auth/index.js +3 -3
- package/dist/assistant/index.js +8 -8
- package/dist/chat-react/index.js +2 -2
- package/dist/chat-routes/attachment-upload.d.ts +1 -1
- package/dist/chat-routes/attachment-validation.d.ts +50 -1
- package/dist/chat-routes/index.js +16 -82
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-routes/model-failover-stream.d.ts +8 -0
- package/dist/chat-routes/resolve-attachments.d.ts +1 -25
- package/dist/chat-routes/sandbox-producer.d.ts +12 -0
- package/dist/chat-routes/turn-routes.d.ts +14 -0
- package/dist/chat-store/index.js +2 -2
- package/dist/{chunk-MSAGTFN4.js → chunk-6RFNLZCK.js} +2 -2
- package/dist/{chunk-TNWDYAFX.js → chunk-E6UTFXKQ.js} +4 -4
- package/dist/{chunk-Y2SOQOJE.js → chunk-KNTRS7OZ.js} +2 -2
- package/dist/{chunk-QAF7ZTO3.js → chunk-L5JLOC2F.js} +4 -4
- package/dist/{chunk-WTC6AOPL.js → chunk-PBQYVAB6.js} +82 -2
- package/dist/chunk-PBQYVAB6.js.map +1 -0
- package/dist/chunk-XELWWXJJ.js +101 -0
- package/dist/chunk-XELWWXJJ.js.map +1 -0
- package/dist/{chunk-J26IXWFK.js → chunk-XP77DJEF.js} +17 -2
- package/dist/chunk-XP77DJEF.js.map +1 -0
- package/dist/{chunk-63EKFZOE.js → chunk-ZLX4BA27.js} +15 -1
- package/dist/chunk-ZLX4BA27.js.map +1 -0
- package/dist/model-resolution/failover.d.ts +16 -2
- package/dist/model-resolution/index.d.ts +42 -0
- package/dist/model-resolution/index.js +30 -5
- package/dist/model-resolution/index.js.map +1 -1
- package/dist/operator/chat-adapter.d.ts +9 -0
- package/dist/operator/contract.d.ts +11 -1
- package/dist/operator/index.js +18 -4
- package/dist/operator/index.js.map +1 -1
- package/dist/operator/server.d.ts +7 -0
- package/dist/policy-filters/index.d.ts +166 -0
- package/dist/policy-filters/index.js +23 -0
- package/dist/policy-filters/index.js.map +1 -0
- package/dist/public-consultation/index.d.ts +1 -1
- package/dist/public-consultation/index.js +4 -3
- package/dist/public-consultation/index.js.map +1 -1
- package/dist/sandbox/index.js +2 -2
- package/dist/settings-react/index.js +1 -1
- package/dist/tools/index.js +2 -2
- package/dist/web-react/index.js +12 -12
- package/package.json +6 -1
- package/dist/chunk-63EKFZOE.js.map +0 -1
- package/dist/chunk-J26IXWFK.js.map +0 -1
- package/dist/chunk-WTC6AOPL.js.map +0 -1
- /package/dist/{chunk-MSAGTFN4.js.map → chunk-6RFNLZCK.js.map} +0 -0
- /package/dist/{chunk-TNWDYAFX.js.map → chunk-E6UTFXKQ.js.map} +0 -0
- /package/dist/{chunk-Y2SOQOJE.js.map → chunk-KNTRS7OZ.js.map} +0 -0
- /package/dist/{chunk-QAF7ZTO3.js.map → chunk-L5JLOC2F.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/chat-routes/draft-persistence.ts","../src/chat-routes/turn-routes.ts","../src/chat-routes/gateway-adapter.ts"],"sourcesContent":["/**\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 extends Pick<AssistantRowValues, keyof ChatTurnUsage> {\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 * `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 * `prepareTurn` (post-admission context work, run inside a progress-first\n * stream) is provisional: one consumer so far.\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 BufferedTurnTap,\n type PersistedChatMessageForTurn,\n type TurnEventStore,\n} from '../stream/index'\nimport type { ModelFailoverAttempt } from '../model-resolution/failover'\nimport { parseJsonObjectBody } from '../web/index'\nimport {\n buildTurnPhaseEvent,\n TURN_INTENT_EVENT,\n TURN_PROGRESS_FIRST,\n TURN_PROGRESS_HEADER,\n TURN_ROUTE_PHASE_MESSAGES,\n turnResponseEventFor,\n type TurnIntentEvent,\n type TurnRoutePhase,\n} from './turn-progress'\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 /** Actual backend result/done was observed; transport EOF/error alone is not completion. */\n terminalObserved?(): boolean\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/** What `prepareTurn` receives: the admitted turn, before the conversation loads. */\nexport interface ChatTurnPrepareArgs<TContext> {\n request: Request\n /** Parsed, validated POST body. */\n body: ChatTurnRequestPayload\n tenantId: string\n userId: string\n threadId: string\n /** The context `authorize` returned; mutate it in place to finish it. */\n context: TContext\n /** Name the stage this work is in. On a progress-first stream it reaches the\n * client as a `session.run.phase` event; otherwise it is a no-op. */\n progress(phase: string, message: string): void\n}\n\n/** `prepareTurn`'s verdict: proceed, or answer with the product's response. */\nexport type ChatTurnPrepareResult = { ok: true } | { ok: false; response: Response }\n\n/** Trusted per-turn limits supplied by a server adapter, never by the body. */\nexport interface ChatTurnExecutionLimits {\n maxInputTokens?: number\n maxOutputTokens?: number\n maxReasoningTokens?: number\n maxToolTokens?: number\n maxToolCalls?: number\n maxProviderCostUsd?: number\n}\n\n/** Server-only context for one turn request. */\nexport interface ChatTurnRequestContext {\n waitUntil?(p: Promise<unknown>): void\n /** Abort this turn when its HTTP client disconnects. Browser callers omit it. */\n cancelOnDisconnect?: boolean\n /** Limits already authenticated by the server adapter. */\n executionLimits?: ChatTurnExecutionLimits\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 turn's intent line (`intentLine`), or null when it was off, slow or\n * failed. Never rejects. A product puts it in the harness turn's context so\n * the agent knows what the founder was told it would do. */\n intentLine?: Promise<string | null>\n /** Server-authenticated limits for this turn. Never copied from the body. */\n executionLimits?: ChatTurnExecutionLimits\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 * Transfer completion ownership to a product-owned durable runner. The\n * product MUST first register that runner durably, then await this callback\n * before dispatching native execution. Once it resolves, this route remains\n * a live event projector only: it no longer writes or settles the assistant\n * transcript, lifecycle hooks, lock, or replay-buffer status.\n *\n * Optional for source compatibility with synthetic producers. Every\n * `createChatTurnRoutes` invocation supplies it before `produce` runs.\n */\n handoffCompletion?: () => Promise<void>\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/** The typed reason a terminal `error` / `session.run.failed` event's `data`\n * names, e.g. `sandbox.stream_failed` or an upstream code. */\nfunction failureCodeOf(data: Record<string, unknown> | undefined): string | undefined {\n const code = data?.code\n return typeof code === 'string' && code.length > 0 ? code : 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, short-circuit, or hand off to a\n * product-owned durable turn. Distinct from `authorize`: this gates domain\n * readiness, not access. */\nexport type ChatTurnGateResult =\n | { proceed: true }\n | { proceed: false; response: Response }\n /** The product has accepted the turn and owns execution and lifecycle after\n * the route releases its lock. */\n | { proceed: 'handoff'; 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 /** Bring up whatever `acquire` will talk to, as soon as the turn is admitted\n * and while `prepareTurn` and the history read run. Never awaited; a failure\n * is logged and the turn goes on to `acquire` as usual. */\n warm?(target: { tenantId: string; userId: string; threadId: string; context: TContext }): void | Promise<void>\n}\n\ninterface ChatTurnLifecycleBase<TContext> {\n identity: ChatTurnIdentity\n executionId: string\n turnStreamId: string\n context: TContext\n}\n/** When the turn's first visible word streamed, measured from `onTurnStart`. Absent when no word streamed. */\ninterface ChatTurnLifecycleTiming {\n firstTextMs?: number\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>, ChatTurnLifecycleTiming {\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>, ChatTurnLifecycleTiming {\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 /** Typed reason of a failed turn, when its terminal error event names one. */\n failureCode?: 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 /** Finish the turn's context after `authorize` has admitted the caller and\n * before the conversation loads or the lock is taken: resolve the profile,\n * the model, anything that reads or writes product state. Keep `authorize`\n * to admission (identity, access, rate limits, input checks) so a refusal\n * stays fast and no write ever runs for a caller who is not admitted; on a\n * progress-first stream this work runs inside the stream and can name its\n * stages through `progress`. A refusal here is answered like a held lock:\n * a plain response without the progress header, a `turn.response` event\n * with it. Omit → nothing to prepare. */\n prepareTurn?(args: ChatTurnPrepareArgs<TContext>): ChatTurnPrepareResult | Promise<ChatTurnPrepareResult>\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 * - `{proceed:'handoff'}` releases the route-acquired lock, returns the\n * product's accepted `Response`, and transfers execution and lifecycle\n * ownership to the product's durable owner. The route runs no producer\n * or lifecycle hook.\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 /** Text for the route's own stages on a progress-first stream (`./turn-progress`).\n * Omitted stages keep `TURN_ROUTE_PHASE_MESSAGES`. */\n progressMessages?: Partial<Record<TurnRoutePhase, string>>\n /** Write and stream the turn's intent line ({@link ChatTurnIntentLine}). Omit → no line. */\n intentLine?: ChatTurnIntentLine<TContext>\n log?: (message: string, meta?: Record<string, unknown>) => void\n}\n\n/** What `intentLine.generate` receives: the admitted turn's message and the thread's last few turns. */\nexport interface ChatTurnIntentLineArgs<TContext> {\n content: string\n /** The thread's last turns before this one, oldest first (at most {@link INTENT_LINE_HISTORY_MESSAGES}). */\n history: ReadonlyArray<{ role: 'user' | 'assistant' | 'system' | 'tool'; content: string }>\n tenantId: string\n userId: string\n threadId: string\n context: TContext\n /** Aborted when the line is no longer wanted (its deadline passed). */\n signal: AbortSignal\n}\n\n/**\n * The intent line: one plain sentence, written by a fast model call the moment\n * a turn is admitted, saying what the agent is about to do. It reaches a\n * progress-first client as a {@link TURN_INTENT_EVENT} while the harness boots,\n * and the turn's first answer text replaces it. It narrates intent and must\n * never answer or act. It fails open: a line later than `timeoutMs`, an error,\n * or an empty answer shows nothing.\n */\nexport interface ChatTurnIntentLine<TContext> {\n generate(args: ChatTurnIntentLineArgs<TContext>): Promise<string | null>\n /** Longest wait for the line. Default {@link INTENT_LINE_TIMEOUT_MS}. */\n timeoutMs?: number\n}\n\n/** A line slower than this shows nothing. */\nexport const INTENT_LINE_TIMEOUT_MS = 1_500\n/** Turns of the thread the line is written from. */\nexport const INTENT_LINE_HISTORY_MESSAGES = 6\n/** Longest line kept; anything longer is cut. */\nexport const INTENT_LINE_MAX_CHARS = 240\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. A request with\n * `TURN_PROGRESS_HEADER` gets a progress-first stream once it is\n * authorized (`./turn-progress`). */\n turn(request: Request, ctx?: ChatTurnRequestContext): 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 const retryAttemptId = body.retryAttemptId\n if (retryAttemptId !== undefined && (\n typeof retryAttemptId !== 'string' || !/^[A-Za-z0-9_-]{1,64}$/.test(retryAttemptId) || !turnId\n )) throw new ChatTurnInputError('retryAttemptId requires turnId and 1–64 letters, digits, underscores, or hyphens')\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/** A word the user sees: a producer `text` event, or a sandbox text part delta. */\nfunction isVisibleText(event: ChatRouteEvent): boolean {\n const value = event as ChatRouteEvent & { text?: unknown }\n if (event.type === 'text') return typeof value.text === 'string' ? value.text.trim().length > 0 : true\n if (event.type !== 'message.part.updated') return false\n const part = event.data?.part as { type?: unknown } | undefined\n const delta = event.data?.delta\n return part?.type === 'text' && typeof delta === 'string' && delta.trim().length > 0\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 const progressMessages: Record<TurnRoutePhase, string> = { ...TURN_ROUTE_PHASE_MESSAGES, ...options.progressMessages }\n\n /** A turn whose body parsed and whose caller `authorize` admitted. */\n type AdmittedTurn = { parsed: ParsedTurnBody; auth: Extract<ChatTurnAuthorization<TContext>, { ok: true }> }\n\n /** Start the admitted turn's intent line; resolves to the line, or null. Never rejects. */\n function startIntentLine(admitted: AdmittedTurn): Promise<string | null> | undefined {\n const intentLine = options.intentLine\n if (!intentLine) return undefined\n const { parsed, auth } = admitted\n const threadId = parsed.payload.threadId\n const controller = new AbortController()\n let timer: ReturnType<typeof setTimeout> | undefined\n const deadline = new Promise<null>((resolve) => {\n timer = setTimeout(() => {\n controller.abort()\n resolve(null)\n }, intentLine.timeoutMs ?? INTENT_LINE_TIMEOUT_MS)\n })\n const generated = (async () => {\n const history = (await options.store.listMessages(threadId))\n .slice(-INTENT_LINE_HISTORY_MESSAGES)\n .map((message) => ({ role: message.role, content: message.content }))\n const text = await intentLine.generate({\n content: parsed.content,\n history,\n tenantId: auth.tenantId,\n userId: auth.userId,\n threadId,\n context: auth.context,\n signal: controller.signal,\n })\n const line = typeof text === 'string' ? text.trim().replace(/\\s+/g, ' ') : ''\n return line ? line.slice(0, INTENT_LINE_MAX_CHARS) : null\n })().catch((err) => {\n if (!controller.signal.aborted) {\n log('[chat-routes] intent line failed; showing none', { error: err instanceof Error ? err.message : String(err) })\n }\n return null\n })\n return Promise.race([generated, deadline]).finally(() => clearTimeout(timer))\n }\n /** Emits one stage on a progress-first stream. */\n type TurnProgress = (phase: string, message: string) => void\n\n async function turn(request: Request, ctx?: ChatTurnRequestContext): Promise<Response> {\n // Parsing and authorization finish before any response opens, for both\n // shapes: a refusal is always a plain HTTP response that reveals nothing\n // beyond what it says, and nothing runs for a caller who is not admitted.\n const admitted = await admitTurn(request)\n if (!admitted.ok) return admitted.response\n if (request.headers.get(TURN_PROGRESS_HEADER) !== TURN_PROGRESS_FIRST) return runTurn(request, ctx, admitted)\n return progressFirstTurn(request, ctx, admitted)\n }\n\n /** Parse and validate the body, then authorize the caller. */\n async function admitTurn(request: Request): Promise<({ ok: true } & AdmittedTurn) | { ok: false; response: Response }> {\n const [rawBody, badBody] = await parseJsonObjectBody(request)\n if (badBody) return { ok: false, response: badBody }\n let parsed: ParsedTurnBody\n try {\n parsed = validateTurnBody(rawBody, options.maxInlinePartBytes)\n } catch (err) {\n if (err instanceof ChatTurnInputError) return { ok: false, response: errorResponse(err) }\n throw err\n }\n const auth = await options.authorize({ request, intent: 'turn', body: parsed.payload })\n if (!auth.ok) return { ok: false, response: auth.response }\n return { ok: true, parsed, auth }\n }\n\n /**\n * Open the response stream as soon as the caller is admitted, so the client\n * shows the turn's real stage while the conversation loads, the lock is\n * taken and the product's pre-turn work runs. The turn runs exactly as\n * {@link runTurn} does; its stream is piped through, and any other response\n * it returns after admission (a held lock, a gate's answer, a handoff, a\n * failure) becomes one `turn.response` event that `settleTurnResponse` turns\n * back into that response on the client.\n */\n function progressFirstTurn(request: Request, ctx: ChatTurnRequestContext | undefined, admitted: AdmittedTurn): Response {\n const receivedAt = Date.now()\n const encoder = new TextEncoder()\n const { readable, writable } = new TransformStream<Uint8Array, Uint8Array>()\n const writer = writable.getWriter()\n let clientGone = false\n const write = async (chunk: Uint8Array): Promise<void> => {\n if (clientGone) return\n try {\n await writer.write(chunk)\n } catch {\n clientGone = true\n }\n }\n const writeEvent = (event: ChatRouteEvent): Promise<void> => write(encoder.encode(`${JSON.stringify(event)}\\n`))\n const progress: TurnProgress = (phase, message) => {\n void writeEvent(buildTurnPhaseEvent(phase, message, { sinceRequestMs: Date.now() - receivedAt }))\n }\n progress('accepted', progressMessages.accepted)\n\n // The intent line reaches the client between whole NDJSON lines: before the\n // turn's own stream, or at a line boundary of it (its chunks can split lines).\n const intent = startIntentLine(admitted)\n let piping = false\n let atLineStart = true\n let pendingIntent: TurnIntentEvent | null = null\n if (intent) {\n void intent.then((text) => {\n if (!text || clientGone) return\n const event: TurnIntentEvent = { type: TURN_INTENT_EVENT, data: { text, sinceRequestMs: Date.now() - receivedAt } }\n if (!piping || atLineStart) void writeEvent(event)\n else pendingIntent = event\n })\n }\n\n const pump = (async () => {\n let inner: Response\n try {\n inner = await runTurn(request, ctx, admitted, progress, intent)\n } catch (err) {\n log('[chat-routes] turn failed before it streamed', { error: err instanceof Error ? err.message : String(err) })\n inner = Response.json({ error: 'The turn failed before it started. Try again.' }, { status: 500 })\n }\n if (!inner.body || !inner.headers.get('x-turn-id')) {\n await writeEvent(await turnResponseEventFor(inner))\n } else {\n const reader = inner.body.getReader()\n piping = true\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n atLineStart = value.length > 0 && value[value.length - 1] === 0x0a\n await write(value)\n if (atLineStart && pendingIntent) {\n const event = pendingIntent\n pendingIntent = null\n await writeEvent(event)\n }\n if (clientGone) {\n // The turn keeps running under its own drain; only this view stops.\n await reader.cancel().catch(() => {})\n break\n }\n }\n } catch (err) {\n log('[chat-routes] progress-first pipe failed', { error: err instanceof Error ? err.message : String(err) })\n }\n }\n if (!clientGone) await writer.close().catch(() => {})\n })()\n if (ctx?.waitUntil) ctx.waitUntil(pump)\n else void pump.catch(() => {})\n\n return new Response(readable, {\n headers: {\n 'Content-Type': 'application/x-ndjson',\n 'Cache-Control': 'no-cache',\n [TURN_PROGRESS_HEADER]: TURN_PROGRESS_FIRST,\n },\n })\n }\n\n async function runTurn(\n request: Request,\n ctx: ChatTurnRequestContext | undefined,\n admitted: AdmittedTurn,\n progress?: TurnProgress,\n startedIntent?: Promise<string | null>,\n ): Promise<Response> {\n const { parsed, auth } = admitted\n // A progress-first turn started its line at admission; any other starts it here.\n const intentLine = startedIntent ?? startIntentLine(admitted)\n const { payload, content, fileParts, mentions, turnId } = parsed\n const { tenantId, userId, context } = auth\n\n if (options.turnLock?.warm) {\n // Overlaps the lock's first-use cost with prepareTurn and the history read.\n try {\n void Promise.resolve(options.turnLock.warm({ tenantId, userId, threadId: payload.threadId, context })).catch((err) => {\n log('[chat-routes] turnLock.warm failed', { error: err instanceof Error ? err.message : String(err) })\n })\n } catch (err) {\n log('[chat-routes] turnLock.warm failed', { error: err instanceof Error ? err.message : String(err) })\n }\n }\n\n if (options.prepareTurn) {\n const prepared = await options.prepareTurn({\n request,\n body: payload,\n tenantId,\n userId,\n threadId: payload.threadId,\n context,\n progress: (phase, message) => progress?.(phase, message),\n })\n if (!prepared.ok) return prepared.response\n }\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 readHistory = async () => (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 const existingMessages = await readHistory()\n // Lock acquisition is async and follows identity derivation. A previous\n // owner can settle in that gap; the new owner must not dispatch an identity\n // or prompt derived from the old history. Capture values now, including\n // parts, rather than retaining mutable message references across the lock.\n const historyBeforeLock = options.turnLock ? JSON.stringify(existingMessages) : undefined\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 turnExecutionId = deriveExecutionId({\n projectId: options.projectId,\n sessionId: payload.threadId,\n turnIndex: chatTurn.turnIndex,\n })\n // Message identity preserves the original inputs; an explicit retry starts\n // another native execution with its own immutable completion receipt.\n const executionId = payload.retryAttemptId\n ? `${turnExecutionId}:retry:${payload.retryAttemptId}`\n : turnExecutionId\n if (executionId.length > 256) return errorResponse(new ChatTurnInputError('Retry execution id exceeds 256 bytes'))\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 // A product calls this only after it has durably registered the external\n // completion owner and before it dispatches native execution. Keeping the\n // public field optional preserves existing synthetic producers; this\n // assembled route always supplies it to the real `produce` invocation.\n let draft: AssistantDraftWriter | undefined\n let tap: BufferedTurnTap | undefined\n let completionHandoff = false\n let completionHandoffPromise: Promise<void> | undefined\n const handoffCompletion = (): Promise<void> => {\n if (!completionHandoffPromise) {\n completionHandoffPromise = (async () => {\n // Switch first: an event arriving while the prior draft write closes\n // must stay live-only and cannot arm another draft write or renewal.\n completionHandoff = true\n try {\n await tap?.detach()\n } catch (err) {\n log('[chat-routes] turn buffer detach failed', {\n turnId: turnStreamId,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n // `close` waits for the draft writer's in-flight write. The durable\n // owner can therefore safely replace that draft after this resolves.\n await draft?.close()\n })()\n }\n return completionHandoffPromise\n }\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 handoffCompletion,\n ...(intentLine ? { intentLine } : {}),\n ...(ctx?.executionLimits ? { executionLimits: ctx.executionLimits } : {}),\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 // `handoffCompletion` transfers the route-acquired lock to the durable\n // owner too. Keep the legacy contextGate `{proceed:'handoff'}` path\n // unchanged when that callback was never invoked.\n if (completionHandoff || !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 progress?.('preparing', progressMessages.preparing)\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 // 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 // The first visible word, the number a rollout's first-words kill line reads.\n let firstTextAtMs: number | undefined\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 const timing = firstTextAtMs === undefined ? {} : { firstTextMs: firstTextAtMs - turnStartedAtMs }\n try {\n if (failed) {\n await lifecycle.onTurnError?.({\n identity, executionId, turnStreamId, context, durationMs, ...timing,\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, ...timing,\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 if (lockAcquired && JSON.stringify(await readHistory()) !== historyBeforeLock) {\n await releaseLock()\n return Response.json({\n code: 'chat_history_changed',\n error: 'The conversation changed while this turn was acquiring its lock. Refresh the conversation before starting new work.',\n }, { status: 409 })\n }\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 === 'handoff') {\n await releaseLock()\n return gate.response\n }\n if (!gate.proceed) {\n await releaseLock()\n if (completionHandoff) return gate.response\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 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 if (completionHandoff) await tap.detach()\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 // `contextGate` / `beforeTurn` may have handed ownership off before\n // the writer existed. Close this late-created writer immediately so it\n // cannot receive a later live event.\n if (completionHandoff) await draft.close()\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 (ctx?.cancelOnDisconnect) {\n source = abortOnSignal(source, request.signal)\n }\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 (firstTextAtMs === undefined && isVisibleText(event)) firstTextAtMs = Date.now()\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 if (!completionHandoff) draft?.notify(event)\n if (options.onEvent) await options.onEvent(event, context)\n },\n ...(options.transformFinalText ? { transformFinalText: options.transformFinalText } : {}),\n persistAssistantMessage: async ({ finalText }) => {\n if (completionHandoff) return\n if (ctx?.cancelOnDisconnect && request.signal.aborted) {\n await draft?.discard()\n assistantMessageId = null\n return\n }\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: async ({ identity: turnIdentity, finalText }: { identity: ChatTurnIdentity; finalText: string }) => {\n if (completionHandoff) return\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 ...(runFailed && failureCodeOf(lastFailureData) ? { failureCode: failureCodeOf(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 if (completionHandoff) {\n // The external owner settles transcript, lifecycle, lock and replay\n // status. Flush only the post-handoff live projection; EOF is an\n // observation stop, never a terminal verdict for that owner.\n await tap.detach()\n return\n }\n if (ctx?.cancelOnDisconnect && request.signal.aborted) {\n await draft?.discard()\n } else {\n await draft?.close()\n }\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 in headers and body 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 'X-Turn-Id': turnStreamId,\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 (!completionHandoff) {\n if (turnStarted) await fireTerminalLifecycle(true, err)\n await releaseLock()\n }\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/**\n * Stop a producer pull as soon as an opted-in caller disconnects.\n * `AsyncIterator.return()` alone cannot interrupt a pending `next()` call, so\n * race each pull with the request signal and close the source in `finally`.\n */\nasync function* abortOnSignal<T>(\n source: AsyncIterable<T>,\n signal: AbortSignal,\n): AsyncGenerator<T, void, unknown> {\n const iterator = source[Symbol.asyncIterator]()\n try {\n for (;;) {\n const next = await nextWithAbort(iterator, signal)\n if (next.done) return\n yield next.value\n }\n } finally {\n await closeIteratorAfterAbort(iterator)\n }\n}\n\nfunction nextWithAbort<T>(\n iterator: AsyncIterator<T>,\n signal: AbortSignal,\n): Promise<IteratorResult<T>> {\n if (signal.aborted) return Promise.reject(new Error('chat turn cancelled'))\n return new Promise((resolve, reject) => {\n let settled = false\n const cleanup = () => signal.removeEventListener('abort', onAbort)\n const onAbort = () => {\n if (settled) return\n settled = true\n cleanup()\n reject(new Error('chat turn cancelled'))\n }\n signal.addEventListener('abort', onAbort, { once: true })\n if (signal.aborted) {\n onAbort()\n return\n }\n Promise.resolve()\n .then(() => iterator.next())\n .then(\n (result) => {\n if (settled) return\n settled = true\n cleanup()\n resolve(result)\n },\n (error: unknown) => {\n if (settled) return\n settled = true\n cleanup()\n reject(error)\n },\n )\n })\n}\n\nconst ABORT_CLEANUP_TIMEOUT_MS = 50\n\nasync function closeIteratorAfterAbort<T>(iterator: AsyncIterator<T>): Promise<void> {\n let closing: PromiseLike<unknown> | undefined\n try {\n const result = iterator.return?.()\n if (result) closing = Promise.resolve(result)\n } catch {\n return\n }\n if (!closing) return\n let timeout: ReturnType<typeof setTimeout> | undefined\n try {\n await Promise.race([\n Promise.resolve(closing).catch(() => undefined),\n new Promise<void>((resolve) => {\n timeout = setTimeout(resolve, ABORT_CLEANUP_TIMEOUT_MS)\n }),\n ])\n } finally {\n if (timeout !== undefined) clearTimeout(timeout)\n }\n}\n","import type { SandboxStreamEvent } from '../sandbox/index'\nimport type { ChatTurnExecutionLimits, ChatTurnRoutes } from './turn-routes'\nimport { chatTurnRequestInit, type ChatTurnRequestPayload } from './wire'\n\nexport interface StreamChatRouteAsSandboxOptions {\n routes: ChatTurnRoutes\n request: Request\n payload: ChatTurnRequestPayload\n waitUntil?: (promise: Promise<unknown>) => void\n /** Stop forwarding when the API client disconnects. The persisted turn continues. */\n signal?: AbortSignal\n /** Limits authenticated by the gateway and forwarded to the producer. */\n executionLimits?: ChatTurnExecutionLimits\n}\n\ninterface ParsedChatRouteEvent {\n type?: string\n data?: Record<string, unknown>\n text?: string\n usage?: Record<string, unknown>\n toolName?: string\n}\n\ntype GatewayUsageUpdate = NonNullable<NonNullable<SandboxStreamEvent['data']>['usage']>\n\n/**\n * Drive the normal persisted chat route and expose its events to an\n * agent-gateway sandbox adapter. The route remains the only turn owner: auth,\n * locking, live delivery, transcript writes, and completion hooks run once.\n */\nexport async function* streamChatRouteAsSandboxEvents(\n options: StreamChatRouteAsSandboxOptions,\n): AsyncGenerator<SandboxStreamEvent> {\n const parentSignal = options.signal ?? options.request.signal\n if (parentSignal.aborted) return\n let reader: ReadableStreamDefaultReader<Uint8Array> | undefined\n const stopForwarding = () => void reader?.cancel()\n parentSignal.addEventListener('abort', stopForwarding, { once: true })\n\n const request = new Request(options.request.url, {\n ...chatTurnRequestInit(options.payload),\n headers: options.request.headers,\n })\n request.headers.set('Content-Type', 'application/json')\n\n let complete = false\n try {\n const response = await options.routes.turn(\n request,\n {\n ...(options.waitUntil ? { waitUntil: options.waitUntil } : {}),\n ...(options.executionLimits ? { executionLimits: options.executionLimits } : {}),\n },\n )\n if (!response.ok) {\n await response.body?.cancel().catch(() => undefined)\n throw new Error(`chat route rejected the gateway turn (${response.status})`)\n }\n if (!response.body) throw new Error('chat route returned no stream')\n\n reader = response.body.getReader()\n if (parentSignal.aborted) {\n await reader.cancel().catch(() => undefined)\n return\n }\n const decoder = new TextDecoder()\n let buffer = ''\n for (;;) {\n const chunk = await reader.read()\n if (chunk.done) {\n complete = true\n buffer += decoder.decode()\n const event = toSandboxEvent(parseEvent(buffer))\n if (event) yield event\n break\n }\n buffer += decoder.decode(chunk.value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n for (const line of lines) {\n const event = toSandboxEvent(parseEvent(line))\n if (event) yield event\n }\n }\n } finally {\n if (!complete) await reader?.cancel().catch(() => undefined)\n reader?.releaseLock()\n parentSignal.removeEventListener('abort', stopForwarding)\n }\n}\n\nfunction parseEvent(line: string): ParsedChatRouteEvent | null {\n if (!line.trim()) return null\n let value: unknown\n try {\n value = JSON.parse(line)\n } catch {\n throw new Error('chat route returned invalid NDJSON')\n }\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error('chat route returned a non-object event')\n }\n const event = value as Record<string, unknown>\n if (event.type !== undefined && typeof event.type !== 'string') {\n throw new Error('chat route returned an invalid event type')\n }\n const data = event.data\n if (data !== undefined && (!data || typeof data !== 'object' || Array.isArray(data))) {\n throw new Error('chat route returned invalid event data')\n }\n const dataUsage = data && (data as Record<string, unknown>).usage\n if (dataUsage !== undefined && (!dataUsage || typeof dataUsage !== 'object' || Array.isArray(dataUsage))) {\n throw new Error('chat route returned invalid data.usage')\n }\n if (event.text !== undefined && typeof event.text !== 'string') {\n throw new Error('chat route returned an invalid text event')\n }\n const usage = event.usage\n if (usage !== undefined && (!usage || typeof usage !== 'object' || Array.isArray(usage))) {\n throw new Error('chat route returned invalid usage')\n }\n if (event.toolName !== undefined && typeof event.toolName !== 'string') {\n throw new Error('chat route returned an invalid tool name')\n }\n return {\n ...(typeof event.type === 'string' ? { type: event.type } : {}),\n ...(data ? { data: data as SandboxStreamEvent['data'] } : {}),\n ...(typeof event.text === 'string' ? { text: event.text } : {}),\n ...(usage ? { usage: usage as Record<string, unknown> } : {}),\n ...(typeof event.toolName === 'string' ? { toolName: event.toolName } : {}),\n }\n}\n\nfunction isNonNegativeSafeInteger(value: unknown): value is number {\n return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0\n}\n\nfunction aliasedSafeInteger(\n usage: Record<string, unknown>,\n field: string,\n alias?: string,\n): number | undefined {\n const value = usage[field]\n const aliasValue = alias ? usage[alias] : undefined\n for (const candidate of [value, aliasValue]) {\n if (candidate !== undefined && !isNonNegativeSafeInteger(candidate)) {\n throw new Error(`chat route returned invalid usage.${field}`)\n }\n }\n if (value !== undefined && aliasValue !== undefined && value !== aliasValue) {\n throw new Error(`chat route returned conflicting usage.${field}`)\n }\n return (value ?? aliasValue) as number | undefined\n}\n\nfunction aliasedNonNegativeNumber(\n usage: Record<string, unknown>,\n field: string,\n alias?: string,\n): number | undefined {\n const value = usage[field]\n const aliasValue = alias ? usage[alias] : undefined\n for (const candidate of [value, aliasValue]) {\n if (\n candidate !== undefined &&\n (typeof candidate !== 'number' || !Number.isFinite(candidate) || candidate < 0)\n ) {\n throw new Error(`chat route returned invalid usage.${field}`)\n }\n }\n if (value !== undefined && aliasValue !== undefined && value !== aliasValue) {\n throw new Error(`chat route returned conflicting usage.${field}`)\n }\n return (value ?? aliasValue) as number | undefined\n}\n\n/** Preserve each measured field without inventing the fields the route lacks. */\nfunction providerUsageUpdate(value: unknown): GatewayUsageUpdate | null {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return null\n const source = value as Record<string, unknown>\n const usage: GatewayUsageUpdate = {}\n const inputTokens = aliasedSafeInteger(source, 'inputTokens', 'promptTokens')\n const outputTokens = aliasedSafeInteger(source, 'outputTokens', 'completionTokens')\n const reasoningTokens = aliasedSafeInteger(source, 'reasoningTokens')\n const toolTokens = aliasedSafeInteger(source, 'toolTokens')\n const toolCallCount = aliasedSafeInteger(source, 'toolCallCount')\n const providerCostUsd = aliasedNonNegativeNumber(source, 'providerCostUsd', 'costUsd')\n if (inputTokens !== undefined) usage.inputTokens = inputTokens\n if (outputTokens !== undefined) usage.outputTokens = outputTokens\n if (reasoningTokens !== undefined) usage.reasoningTokens = reasoningTokens\n if (toolTokens !== undefined) usage.toolTokens = toolTokens\n if (toolCallCount !== undefined) usage.toolCallCount = toolCallCount\n if (providerCostUsd !== undefined) usage.providerCostUsd = providerCostUsd\n if (source.budgetEnforced !== undefined) {\n if (typeof source.budgetEnforced !== 'boolean') {\n throw new Error('chat route returned invalid usage.budgetEnforced')\n }\n usage.budgetEnforced = source.budgetEnforced\n }\n return Object.keys(usage).length > 0 ? usage : null\n}\n\n/** Translate the app route's producer vocabulary to the gateway stream shape. */\nfunction toSandboxEvent(event: ParsedChatRouteEvent | null): SandboxStreamEvent | null {\n if (!event) return null\n\n // `createChatTurnRoutes` emits flattened text events. The gateway's adapter\n // contract uses the sandbox's canonical part-update shape instead.\n if (event.type === 'text') {\n if (event.text === undefined) throw new Error('chat route text event had no text')\n return {\n type: 'message.part.updated',\n data: { part: { type: 'text' }, delta: event.text },\n }\n }\n\n // Reasoning is persisted by the app route but is not public API output.\n // Dropping it also prevents internal thought text from crossing the paid\n // gateway boundary.\n if (event.type === 'reasoning') return null\n\n // Preserve measured fields even when the app route cannot prove a complete\n // provider-enforced receipt. The gateway fills only absent legacy fields and\n // marks that completed receipt as not provider-enforced.\n if (event.type === 'usage' || event.usage || event.data?.usage) {\n const usage = providerUsageUpdate(event.usage ?? event.data?.usage)\n if (!usage) return null\n return { type: 'usage', data: { usage } }\n }\n\n // A producer can announce the same tool call twice while its arguments fill\n // in. The terminal tool result occurs once, so count that event instead.\n if (event.type === 'tool_result') {\n if (!event.toolName) throw new Error('chat route tool result had no tool name')\n return { type: 'tool_result', data: { tool: { name: event.toolName } } }\n }\n\n if (event.type === 'input-required' && !event.data?.inputRequired) {\n const prompt = typeof event.data?.prompt === 'string' ? event.data.prompt : undefined\n return {\n type: 'input-required',\n data: { inputRequired: prompt ? { prompt } : {} },\n }\n }\n\n return {\n ...(event.type ? { type: event.type } : {}),\n ...(event.data ? { data: event.data as SandboxStreamEvent['data'] } : {}),\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiLA,IAAM,sBAA2C,oBAAI,IAAI;AAAA,EACvD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,sBAAsB;AAC5B,IAAM,wBAAwB;AAC9B,IAAM,sCAAsC;AAGrC,SAAS,oBAAoB,OAAoC;AACtE,SAAO,OAAO,OAAO,SAAS,YAAY,oBAAoB,IAAI,MAAM,IAAI;AAC9E;AAKA,SAAS,mBAAmB,MAA+B,UAA2C;AACpG,MAAI,YAAY,EAAG,QAAO;AAC1B,MAAI,OAAO,KAAK,QAAQ,EAAE,MAAM,OAAQ,QAAO;AAC/C,QAAM,QAAQ,KAAK;AACnB,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,SAAS;AACf,QAAM,SAAS,OAAO;AACtB,MAAI,WAAW,UAAa,WAAW,KAAM,QAAO;AACpD,QAAM,aAAa,OAAO,WAAW,WAAW,SAAS,cAAc,MAAM;AAC7E,MAAI,WAAW,UAAU,SAAU,QAAO;AAC1C,QAAM,WAAY,OAAO,YAAY,OAAO,OAAO,aAAa,WAAW,OAAO,WAAW,CAAC;AAC9F,SAAO;AAAA,IACL,GAAG;AAAA,IACH,OAAO;AAAA,MACL,GAAG;AAAA,MACH,QAAQ,GAAG,WAAW,MAAM,GAAG,QAAQ,CAAC,0BAAqB,WAAW,SAAS,QAAQ;AAAA,MACzF,UAAU,EAAE,GAAG,UAAU,gBAAgB,KAAK;AAAA,IAChD;AAAA,EACF;AACF;AAEA,SAAS,cAAc,OAAwB;AAC7C,MAAI;AACF,WAAO,KAAK,UAAU,KAAK,KAAK;AAAA,EAClC,QAAQ;AACN,WAAO,OAAO,KAAK;AAAA,EACrB;AACF;AAGO,SAAS,2BAA2B,SAA4D;AACrG,QAAM,MAAM,QAAQ,QAAQ,MAAM;AAAA,EAAC;AACnC,QAAM,iBAAiB,QAAQ,cAAc;AAC7C,QAAM,eAAe,QAAQ,gBAAgB;AAC7C,QAAM,gBAAgB,QAAQ,2BAA2B;AAEzD,MAAI,QAAQ;AACZ,MAAI,SAAS;AACb,MAAI;AACJ,MAAI,cAAc;AAClB,MAAI,gBAAgB;AACpB,MAAI;AACJ,MAAI,SAAS;AAIb,WAAS,oBAA4B;AACnC,QAAI,iBAAiB,eAAe,GAAI,QAAO,iBAAiB;AAChE,QAAI,iBAAiB,aAAc,QAAO,KAAK,MAAM,iBAAiB,GAAG;AACzE,WAAO;AAAA,EACT;AAEA,iBAAe,cAAc,UAA+D;AAC1F,UAAM,YAAY,QAAQ;AAC1B,UAAM,UAAU,YAAY,MAAM,UAAU,SAAS,OAAO,IAAI,SAAS;AACzE,QAAI;AACJ,QAAI,SAAS,OAAO;AAClB,YAAM,SAAS,SAAS,MAAM,IAAI,CAAC,SAAS,mBAAmB,MAAM,aAAa,CAAC;AACnF,YAAM,WAAW,YACb,MAAM,QAAQ;AAAA,QACZ,OAAO;AAAA,UAAI,OAAO,SAChB,OAAQ,KAA4B,QAAQ,EAAE,MAAM,SAChD,EAAE,GAAG,MAAM,MAAM,MAAM,UAAU,OAAQ,KAA4B,QAAQ,EAAE,CAAC,EAAE,IAClF;AAAA,QACN;AAAA,MACF,IACA;AACJ,cAAQ,mBAAmB,QAAQ;AAAA,IACrC;AACA,UAAM,QAAQ,SAAS,SAAS,CAAC;AACjC,WAAO;AAAA,MACL;AAAA,MACA,GAAI,SAAS,MAAM,SAAS,IAAI,EAAE,MAAM,IAAI,CAAC;AAAA,MAC7C,GAAI,SAAS,QAAQ,EAAE,OAAO,SAAS,MAAM,IAAI,CAAC;AAAA,MAClD,GAAI,MAAM,gBAAgB,SAAY,EAAE,aAAa,MAAM,YAAY,IAAI,CAAC;AAAA,MAC5E,GAAI,MAAM,iBAAiB,SAAY,EAAE,cAAc,MAAM,aAAa,IAAI,CAAC;AAAA,MAC/E,GAAI,MAAM,oBAAoB,SAAY,EAAE,iBAAiB,MAAM,gBAAgB,IAAI,CAAC;AAAA,MACxF,GAAI,MAAM,oBAAoB,SAAY,EAAE,iBAAiB,MAAM,gBAAgB,IAAI,CAAC;AAAA,MACxF,GAAI,MAAM,qBAAqB,SAAY,EAAE,kBAAkB,MAAM,iBAAiB,IAAI,CAAC;AAAA,MAC3F,GAAI,MAAM,YAAY,SAAY,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;AAAA,IAClE;AAAA,EACF;AAMA,iBAAe,WAAwC;AACrD,QAAI,MAAO,QAAO;AAClB,UAAM,YAAY,MAAM,QAAQ,MAAM,aAAa,QAAQ,QAAQ,GAAG;AAAA,MACpE,CAAC,YAAY,QAAQ,OAAO,QAAQ;AAAA,IACtC;AACA,QAAI,SAAU,SAAQ,SAAS;AAC/B,WAAO;AAAA,EACT;AAGA,iBAAe,UAAU,QAA2C;AAClE,QAAI,MAAM,SAAS,GAAG;AACpB,YAAM,QAAQ,MAAM,cAAe,OAAQ,MAAM;AACjD,gBAAU;AACV;AAAA,IACF;AACA,UAAM,WAAW,MAAM,QAAQ,MAAM,cAAc;AAAA,MACjD,IAAI,QAAQ;AAAA,MACZ,UAAU,QAAQ;AAAA,MAClB,MAAM;AAAA,MACN,GAAG;AAAA,IACL,CAAC;AAKD,YAAQ,QAAQ,QAAQ,KAAK,QAAQ;AACrC,cAAU;AAAA,EACZ;AAEA,WAAS,UAAgB;AACvB,QAAI,OAAQ;AACZ,QAAI,CAAC,MAAO;AAKZ,QAAI,SAAU;AACd,UAAM,MAAM,KAAK,IAAI;AAGrB,QAAI,gBAAgB,KAAK,MAAM,cAAc,kBAAkB,EAAG;AAClE,UAAM,WAAW,QAAQ,SAAS;AAClC,QAAI,CAAC,SAAU;AAGf,QAAI,CAAC,SAAS,YAAY,CAAC,SAAS,SAAS,SAAS,MAAM,WAAW,GAAI;AAC3E,YAAQ;AACR,kBAAc;AACd,gBAAY,YAAY;AACtB,UAAI;AACF,cAAM,SAAS,MAAM,cAAc,QAAQ;AAC3C,wBAAgB,OAAO,QAAQ,cAAc,OAAO,KAAK,EAAE,SAAS;AACpE,cAAM,UAAU,MAAM;AAAA,MACxB,SAAS,KAAK;AAGZ,YAAI,0DAA0D;AAAA,UAC5D,WAAW,QAAQ;AAAA,UACnB,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD,CAAC;AAAA,MACH,UAAE;AACA,mBAAW;AAKX,YAAI,SAAS,CAAC,OAAQ,SAAQ;AAAA,MAChC;AAAA,IACF,GAAG;AAAA,EACL;AAEA,SAAO;AAAA,IACL,OAAO,OAAO;AACZ,UAAI,OAAQ;AACZ,UAAI,oBAAoB,KAAK,EAAG,SAAQ;AACxC,cAAQ;AAAA,IACV;AAAA,IACA,MAAM,QAAQ;AACZ,eAAS;AACT,UAAI,SAAU,OAAM;AAAA,IACtB;AAAA,IACA,MAAM,SAAS,QAAQ;AACrB,eAAS;AACT,UAAI,SAAU,OAAM;AACpB,YAAM,UAAU,MAAM;AAAA,IACxB;AAAA,IACA,OAAO,MAAM;AAAA,IACb,MAAM,UAAU;AACd,eAAS;AACT,UAAI,SAAU,OAAM;AACpB,UAAI,CAAC,QAAQ,MAAM,cAAe;AAClC,UAAI;AACF,YAAI,CAAE,MAAM,SAAS,EAAI;AACzB,cAAM,QAAQ,MAAM,cAAc,KAAM;AACxC,gBAAQ;AAAA,MACV,SAAS,KAAK;AACZ,YAAI,oDAAoD;AAAA,UACtD,WAAW,QAAQ;AAAA,UACnB,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IACA,YAAY,MAAM;AAAA,EACpB;AACF;AAKO,SAAS,8BAA8B,OAAqC;AACjF,SAAO,OAAO,MAAM,kBAAkB;AACxC;AAUO,SAAS,QAAQ,UAAkC;AACxD,QAAM,KAAM,UAAkD;AAC9D,SAAO,OAAO,OAAO,YAAY,KAAK,KAAK;AAC7C;AAKO,SAAS,sBAAsB,SAAyB;AAC7D,SAAO,aAAa,OAAO;AAC7B;;;ACpXA;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AA2SP,SAAS,gBAAgB,MAA+D;AACtF,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,UAAU,KAAK,WAAW,KAAK,SAAS,KAAK;AACnD,MAAI,OAAO,YAAY,YAAY,QAAQ,SAAS,EAAG,QAAO;AAC9D,SAAO;AACT;AAIA,SAAS,cAAc,MAA+D;AACpF,QAAM,OAAO,MAAM;AACnB,SAAO,OAAO,SAAS,YAAY,KAAK,SAAS,IAAI,OAAO;AAC9D;AAuUO,IAAM,yBAAyB;AAE/B,IAAM,+BAA+B;AAErC,IAAM,wBAAwB;AAyBrC,SAAS,cAAc,KAAmC;AACxD,SAAO,SAAS,KAAK,EAAE,MAAM,IAAI,MAAM,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AACrF;AAUA,SAAS,iBAAiB,MAA+B,oBAAwD;AAC/G,QAAM,WAAW,OAAO,KAAK,aAAa,WAAW,KAAK,SAAS,KAAK,IAAI;AAC5E,MAAI,CAAC,SAAU,OAAM,IAAI,mBAAmB,kBAAkB;AAC9D,QAAM,aAAa,KAAK,WAAW,KAAK,WAAW;AACnD,MAAI,OAAO,eAAe,SAAU,OAAM,IAAI,mBAAmB,0BAA0B;AAC3F,QAAM,UAAU,WAAW,KAAK;AAChC,QAAM,YAAY,mBAAmB,KAAK,KAAK;AAG/C,QAAM,WAAW,kBAAkB,KAAK,QAAQ;AAIhD,QAAM,iBAAiB,MAAM,QAAQ,KAAK,WAAW,KAAK,KAAK,YAAY,SAAS;AACpF,MAAI,CAAC,WAAW,UAAU,WAAW,KAAK,SAAS,WAAW,KAAK,CAAC,gBAAgB;AAClF,UAAM,IAAI,mBAAmB,+EAA+E;AAAA,EAC9G;AACA,6BAA2B,WAAW,kBAAkB;AACxD,MAAI;AACJ,MAAI;AACF,aAAS,sBAAsB,KAAK,MAAM;AAAA,EAC5C,SAAS,KAAK;AACZ,UAAM,IAAI,mBAAmB,eAAe,QAAQ,IAAI,UAAU,gBAAgB;AAAA,EACpF;AACA,QAAM,iBAAiB,KAAK;AAC5B,MAAI,mBAAmB,WACrB,OAAO,mBAAmB,YAAY,CAAC,wBAAwB,KAAK,cAAc,KAAK,CAAC,QACvF,OAAM,IAAI,mBAAmB,uFAAkF;AAClH,SAAO;AAAA;AAAA;AAAA;AAAA,IAIL,SAAS,EAAE,GAAG,MAAM,UAAU,SAAS,SAAS;AAAA,IAChD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAaA,SAAS,mBACP,WACA,WACA,UACmB;AACnB,SAAO,mBAAmB;AAAA,IACxB,GAAG;AAAA,IACH,GAAG,UAAU,IAAI,CAAC,UAAU,EAAE,GAAG,KAAK,EAAE;AAAA,IACxC,GAAG,SAAS,IAAI,CAAC,aAAa,EAAE,GAAG,mBAAmB,OAAO,EAAE,EAAE;AAAA,EACnE,CAAC;AACH;AAGA,SAAS,cAAc,OAAgC;AACrD,QAAM,QAAQ;AACd,MAAI,MAAM,SAAS,OAAQ,QAAO,OAAO,MAAM,SAAS,WAAW,MAAM,KAAK,KAAK,EAAE,SAAS,IAAI;AAClG,MAAI,MAAM,SAAS,uBAAwB,QAAO;AAClD,QAAM,OAAO,MAAM,MAAM;AACzB,QAAM,QAAQ,MAAM,MAAM;AAC1B,SAAO,MAAM,SAAS,UAAU,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS;AACrF;AAMA,gBAAgB,aACd,QACA,YACA,KAC+C;AAC/C,mBAAiB,SAAS,QAAQ;AAChC,QAAI;AACF,YAAM,WAAW,KAAK;AAAA,IACxB,SAAS,KAAK;AACZ,UAAI,mCAAmC,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,CAAC;AAAA,IACpG;AACA,UAAM;AAAA,EACR;AACF;AAMA,gBAAgB,oBACd,QACA,YACA,WAC+C;AAC/C,QAAM,WAAW,OAAO,OAAO,aAAa,EAAE;AAC9C,MAAI;AACF,QAAI,UAAU,SAAS,KAAK;AAC5B,QAAI,cAAc,KAAK,IAAI;AAC3B,QAAI,OAAO;AACX,eAAS;AACP,UAAI;AACJ,UAAI;AACJ,UAAI;AACF,cAAM,YAAY,IAAI,QAAqB,CAAC,YAAY;AACtD,kBAAQ,WAAW,MAAM,QAAQ,WAAW,GAAG,UAAU;AAAA,QAC3D,CAAC;AACD,iBAAS,MAAM,QAAQ,KAAK,CAAC,QAAQ,KAAK,MAAM,OAAgB,GAAG,SAAS,CAAC;AAAA,MAC/E,UAAE;AAIA,YAAI,UAAU,OAAW,cAAa,KAAK;AAAA,MAC7C;AACA,UAAI,WAAW,aAAa;AAC1B,gBAAQ;AACR,cAAM,UAAU,EAAE,WAAW,KAAK,IAAI,IAAI,aAAa,KAAK,CAAC;AAC7D;AAAA,MACF;AACA,YAAM,SAAS,MAAM;AACrB,UAAI,OAAO,KAAM;AACjB,YAAM,OAAO;AACb,gBAAU,SAAS,KAAK;AACxB,oBAAc,KAAK,IAAI;AACvB,aAAO;AAAA,IACT;AAAA,EACF,UAAE;AACA,UAAM,SAAS,SAAS;AAAA,EAC1B;AACF;AAKO,SAAS,qBACd,SACgB;AAChB,QAAM,MAAM,QAAQ,QAAQ,CAAC,SAAS,SAAS,QAAQ,MAAM,SAAS,QAAQ,EAAE;AAKhF,QAAM,aAAkC,QAAQ;AAChD,MAAI,QAAQ,0BAA0B,CAAC,8BAA8B,UAAU,GAAG;AAChF,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,cACJ,QAAQ,2BAA2B,SAAS,CAAC,8BAA8B,UAAU,IACjF,OACC,QAAQ,0BAA0B,CAAC;AAE1C,QAAM,mBAAmD,EAAE,GAAG,2BAA2B,GAAG,QAAQ,iBAAiB;AAMrH,WAAS,gBAAgB,UAA4D;AACnF,UAAM,aAAa,QAAQ;AAC3B,QAAI,CAAC,WAAY,QAAO;AACxB,UAAM,EAAE,QAAQ,KAAK,IAAI;AACzB,UAAM,WAAW,OAAO,QAAQ;AAChC,UAAM,aAAa,IAAI,gBAAgB;AACvC,QAAI;AACJ,UAAM,WAAW,IAAI,QAAc,CAAC,YAAY;AAC9C,cAAQ,WAAW,MAAM;AACvB,mBAAW,MAAM;AACjB,gBAAQ,IAAI;AAAA,MACd,GAAG,WAAW,aAAa,sBAAsB;AAAA,IACnD,CAAC;AACD,UAAM,aAAa,YAAY;AAC7B,YAAM,WAAW,MAAM,QAAQ,MAAM,aAAa,QAAQ,GACvD,MAAM,CAAC,4BAA4B,EACnC,IAAI,CAAC,aAAa,EAAE,MAAM,QAAQ,MAAM,SAAS,QAAQ,QAAQ,EAAE;AACtE,YAAM,OAAO,MAAM,WAAW,SAAS;AAAA,QACrC,SAAS,OAAO;AAAA,QAChB;AAAA,QACA,UAAU,KAAK;AAAA,QACf,QAAQ,KAAK;AAAA,QACb;AAAA,QACA,SAAS,KAAK;AAAA,QACd,QAAQ,WAAW;AAAA,MACrB,CAAC;AACD,YAAM,OAAO,OAAO,SAAS,WAAW,KAAK,KAAK,EAAE,QAAQ,QAAQ,GAAG,IAAI;AAC3E,aAAO,OAAO,KAAK,MAAM,GAAG,qBAAqB,IAAI;AAAA,IACvD,GAAG,EAAE,MAAM,CAAC,QAAQ;AAClB,UAAI,CAAC,WAAW,OAAO,SAAS;AAC9B,YAAI,kDAAkD,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,CAAC;AAAA,MACnH;AACA,aAAO;AAAA,IACT,CAAC;AACD,WAAO,QAAQ,KAAK,CAAC,WAAW,QAAQ,CAAC,EAAE,QAAQ,MAAM,aAAa,KAAK,CAAC;AAAA,EAC9E;AAIA,iBAAe,KAAK,SAAkB,KAAiD;AAIrF,UAAM,WAAW,MAAM,UAAU,OAAO;AACxC,QAAI,CAAC,SAAS,GAAI,QAAO,SAAS;AAClC,QAAI,QAAQ,QAAQ,IAAI,oBAAoB,MAAM,oBAAqB,QAAO,QAAQ,SAAS,KAAK,QAAQ;AAC5G,WAAO,kBAAkB,SAAS,KAAK,QAAQ;AAAA,EACjD;AAGA,iBAAe,UAAU,SAA8F;AACrH,UAAM,CAAC,SAAS,OAAO,IAAI,MAAM,oBAAoB,OAAO;AAC5D,QAAI,QAAS,QAAO,EAAE,IAAI,OAAO,UAAU,QAAQ;AACnD,QAAI;AACJ,QAAI;AACF,eAAS,iBAAiB,SAAS,QAAQ,kBAAkB;AAAA,IAC/D,SAAS,KAAK;AACZ,UAAI,eAAe,mBAAoB,QAAO,EAAE,IAAI,OAAO,UAAU,cAAc,GAAG,EAAE;AACxF,YAAM;AAAA,IACR;AACA,UAAM,OAAO,MAAM,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,MAAM,OAAO,QAAQ,CAAC;AACtF,QAAI,CAAC,KAAK,GAAI,QAAO,EAAE,IAAI,OAAO,UAAU,KAAK,SAAS;AAC1D,WAAO,EAAE,IAAI,MAAM,QAAQ,KAAK;AAAA,EAClC;AAWA,WAAS,kBAAkB,SAAkB,KAAyC,UAAkC;AACtH,UAAM,aAAa,KAAK,IAAI;AAC5B,UAAM,UAAU,IAAI,YAAY;AAChC,UAAM,EAAE,UAAU,SAAS,IAAI,IAAI,gBAAwC;AAC3E,UAAM,SAAS,SAAS,UAAU;AAClC,QAAI,aAAa;AACjB,UAAM,QAAQ,OAAO,UAAqC;AACxD,UAAI,WAAY;AAChB,UAAI;AACF,cAAM,OAAO,MAAM,KAAK;AAAA,MAC1B,QAAQ;AACN,qBAAa;AAAA,MACf;AAAA,IACF;AACA,UAAM,aAAa,CAAC,UAAyC,MAAM,QAAQ,OAAO,GAAG,KAAK,UAAU,KAAK,CAAC;AAAA,CAAI,CAAC;AAC/G,UAAM,WAAyB,CAAC,OAAO,YAAY;AACjD,WAAK,WAAW,oBAAoB,OAAO,SAAS,EAAE,gBAAgB,KAAK,IAAI,IAAI,WAAW,CAAC,CAAC;AAAA,IAClG;AACA,aAAS,YAAY,iBAAiB,QAAQ;AAI9C,UAAM,SAAS,gBAAgB,QAAQ;AACvC,QAAI,SAAS;AACb,QAAI,cAAc;AAClB,QAAI,gBAAwC;AAC5C,QAAI,QAAQ;AACV,WAAK,OAAO,KAAK,CAAC,SAAS;AACzB,YAAI,CAAC,QAAQ,WAAY;AACzB,cAAM,QAAyB,EAAE,MAAM,mBAAmB,MAAM,EAAE,MAAM,gBAAgB,KAAK,IAAI,IAAI,WAAW,EAAE;AAClH,YAAI,CAAC,UAAU,YAAa,MAAK,WAAW,KAAK;AAAA,YAC5C,iBAAgB;AAAA,MACvB,CAAC;AAAA,IACH;AAEA,UAAM,QAAQ,YAAY;AACxB,UAAI;AACJ,UAAI;AACF,gBAAQ,MAAM,QAAQ,SAAS,KAAK,UAAU,UAAU,MAAM;AAAA,MAChE,SAAS,KAAK;AACZ,YAAI,gDAAgD,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,CAAC;AAC/G,gBAAQ,SAAS,KAAK,EAAE,OAAO,gDAAgD,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACnG;AACA,UAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,QAAQ,IAAI,WAAW,GAAG;AAClD,cAAM,WAAW,MAAM,qBAAqB,KAAK,CAAC;AAAA,MACpD,OAAO;AACL,cAAM,SAAS,MAAM,KAAK,UAAU;AACpC,iBAAS;AACT,YAAI;AACF,qBAAS;AACP,kBAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,gBAAI,KAAM;AACV,0BAAc,MAAM,SAAS,KAAK,MAAM,MAAM,SAAS,CAAC,MAAM;AAC9D,kBAAM,MAAM,KAAK;AACjB,gBAAI,eAAe,eAAe;AAChC,oBAAM,QAAQ;AACd,8BAAgB;AAChB,oBAAM,WAAW,KAAK;AAAA,YACxB;AACA,gBAAI,YAAY;AAEd,oBAAM,OAAO,OAAO,EAAE,MAAM,MAAM;AAAA,cAAC,CAAC;AACpC;AAAA,YACF;AAAA,UACF;AAAA,QACF,SAAS,KAAK;AACZ,cAAI,4CAA4C,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,CAAC;AAAA,QAC7G;AAAA,MACF;AACA,UAAI,CAAC,WAAY,OAAM,OAAO,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACtD,GAAG;AACH,QAAI,KAAK,UAAW,KAAI,UAAU,IAAI;AAAA,QACjC,MAAK,KAAK,MAAM,MAAM;AAAA,IAAC,CAAC;AAE7B,WAAO,IAAI,SAAS,UAAU;AAAA,MAC5B,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,QACjB,CAAC,oBAAoB,GAAG;AAAA,MAC1B;AAAA,IACF,CAAC;AAAA,EACH;AAEA,iBAAe,QACb,SACA,KACA,UACA,UACA,eACmB;AACnB,UAAM,EAAE,QAAQ,KAAK,IAAI;AAEzB,UAAM,aAAa,iBAAiB,gBAAgB,QAAQ;AAC5D,UAAM,EAAE,SAAS,SAAS,WAAW,UAAU,OAAO,IAAI;AAC1D,UAAM,EAAE,UAAU,QAAQ,QAAQ,IAAI;AAEtC,QAAI,QAAQ,UAAU,MAAM;AAE1B,UAAI;AACF,aAAK,QAAQ,QAAQ,QAAQ,SAAS,KAAK,EAAE,UAAU,QAAQ,UAAU,QAAQ,UAAU,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ;AACpH,cAAI,sCAAsC,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,CAAC;AAAA,QACvG,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,YAAI,sCAAsC,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,CAAC;AAAA,MACvG;AAAA,IACF;AAEA,QAAI,QAAQ,aAAa;AACvB,YAAM,WAAW,MAAM,QAAQ,YAAY;AAAA,QACzC;AAAA,QACA,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,UAAU,QAAQ;AAAA,QAClB;AAAA,QACA,UAAU,CAAC,OAAO,YAAY,WAAW,OAAO,OAAO;AAAA,MACzD,CAAC;AACD,UAAI,CAAC,SAAS,GAAI,QAAO,SAAS;AAAA,IACpC;AAIA,UAAM,cAAc,aAAa,MAAM,QAAQ,MAAM,aAAa,QAAQ,QAAQ,GAAG,IAAI,CAAC,OAAO;AAAA,MAC/F,IAAI,EAAE;AAAA,MACN,MAAM,EAAE;AAAA,MACR,SAAS,EAAE;AAAA,MACX,OAAQ,EAAE,SAAS;AAAA,IACrB,EAAE;AACF,UAAM,mBAAmB,MAAM,YAAY;AAK3C,UAAM,oBAAoB,QAAQ,WAAW,KAAK,UAAU,gBAAgB,IAAI;AAMhF,QAAI,iBAAiB;AACrB,QAAI,eAAe,iBAAiB,GAAG,EAAE,GAAG,SAAS,aAAa;AAChE,UAAI;AACF,0BAAmB,MAAM,QAAQ,UAAU,cAAc,QAAQ,QAAQ,KAAM,CAAC,GAAG,SAAS;AAAA,MAC9F,SAAS,KAAK;AACZ,YAAI,uEAAuE;AAAA,UACzE,UAAU,QAAQ;AAAA,UAClB,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD,CAAC;AAAA,MACH;AAAA,IACF;AACA,UAAM,WAAW,gBAAgB,EAAE,kBAAkB,aAAa,SAAS,QAAQ,eAAe,CAAC;AAEnG,UAAM,WAA6B;AAAA,MACjC;AAAA,MACA,WAAW,QAAQ;AAAA,MACnB;AAAA,MACA,WAAW,SAAS;AAAA,IACtB;AACA,UAAM,kBAAkB,kBAAkB;AAAA,MACxC,WAAW,QAAQ;AAAA,MACnB,WAAW,QAAQ;AAAA,MACnB,WAAW,SAAS;AAAA,IACtB,CAAC;AAGD,UAAM,cAAc,QAAQ,iBACxB,GAAG,eAAe,UAAU,QAAQ,cAAc,KAClD;AACJ,QAAI,YAAY,SAAS,IAAK,QAAO,cAAc,IAAI,mBAAmB,sCAAsC,CAAC;AACjH,UAAM,eAAe,OAAO,WAAW;AAEvC,UAAM,SACJ,UAAU,WAAW,IACjB,UACA,UACE,CAAC,EAAE,MAAM,QAAQ,MAAM,QAAQ,GAAG,GAAG,SAAS,IAC9C,CAAC,GAAG,SAAS;AAWrB,QAAI;AACJ,QAAI;AACJ,QAAI,oBAAoB;AACxB,QAAI;AACJ,UAAM,oBAAoB,MAAqB;AAC7C,UAAI,CAAC,0BAA0B;AAC7B,oCAA4B,YAAY;AAGtC,8BAAoB;AACpB,cAAI;AACF,kBAAM,KAAK,OAAO;AAAA,UACpB,SAAS,KAAK;AACZ,gBAAI,2CAA2C;AAAA,cAC7C,QAAQ;AAAA,cACR,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,YACxD,CAAC;AAAA,UACH;AAGA,gBAAM,OAAO,MAAM;AAAA,QACrB,GAAG;AAAA,MACL;AACA,aAAO;AAAA,IACT;AACA,QAAI,cAA6C;AAAA,MAC/C;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,eAAe,SAAS;AAAA,MACxB;AAAA,MACA,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,MACnC,GAAI,KAAK,kBAAkB,EAAE,iBAAiB,IAAI,gBAAgB,IAAI,CAAC;AAAA,IACzE;AAKA,QAAI,eAAe;AACnB,QAAI;AACJ,QAAI,eAAe;AACnB,UAAM,cAAc,YAA2B;AAI7C,UAAI,qBAAqB,CAAC,gBAAgB,aAAc;AACxD,qBAAe;AACf,UAAI;AACF,cAAM,QAAQ,SAAU,QAAQ,UAAU;AAAA,MAC5C,SAAS,KAAK;AACZ,YAAI,yCAAyC;AAAA,UAC3C,QAAQ;AAAA,UACR,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD,CAAC;AAAA,MACH;AAAA,IACF;AACA,QAAI,QAAQ,UAAU;AACpB,YAAM,WAAW,MAAM,QAAQ,SAAS,QAAQ,WAAW;AAC3D,UAAI,CAAC,SAAS,SAAU,QAAO,SAAS;AACxC,qBAAe;AACf,mBAAa,SAAS;AAAA,IACxB;AACA,eAAW,aAAa,iBAAiB,SAAS;AAMlD,QAAI;AAEJ,QAAI;AAIJ,UAAM,iBAAiB,MAAqB,sBAAsB,OAAO,MAAM,KAAK;AACpF,QAAI,YAAY;AAGhB,QAAI;AACJ,QAAI,kBAAkB;AAEtB,QAAI;AACJ,QAAI,cAAc;AAClB,QAAI,mBAAmB;AAEvB,QAAI,YAAY;AAKhB,UAAM,wBAAwB,OAAO,QAAiB,kBAA0C;AAC9F,UAAI,iBAAkB;AACtB,yBAAmB;AACnB,YAAM,YAAY,QAAQ;AAC1B,UAAI,CAAC,UAAW;AAChB,YAAM,aAAa,KAAK,IAAI,IAAI;AAChC,YAAM,SAAS,kBAAkB,SAAY,CAAC,IAAI,EAAE,aAAa,gBAAgB,gBAAgB;AACjG,UAAI;AACF,YAAI,QAAQ;AACV,gBAAM,UAAU,cAAc;AAAA,YAC5B;AAAA,YAAU;AAAA,YAAa;AAAA,YAAc;AAAA,YAAS;AAAA,YAAY,GAAG;AAAA,YAC7D,OAAO,iBAAiB,mBAAmB,IAAI,MAAM,kBAAkB;AAAA,UACzE,CAAC;AAAA,QACH,OAAO;AACL,gBAAM,eAAe,UAAU,gBAAgB;AAC/C,gBAAM,cAAc,UAAU,mBAAmB;AACjD,gBAAM,UAAU,iBAAiB;AAAA,YAC/B;AAAA,YAAU;AAAA,YAAa;AAAA,YAAc;AAAA,YAAS;AAAA,YAAY,GAAG;AAAA,YAC7D,WAAW,UAAU,UAAU,KAAK;AAAA,YACpC,OAAO,UAAU,QAAQ,KAAK,CAAC;AAAA,YAC/B,oBAAoB,eAAe;AAAA,YACnC,GAAI,YAAY,EAAE,OAAO,KAAK,IAAI,CAAC;AAAA,YACnC,GAAI,UAAU,QAAQ,EAAE,OAAO,SAAS,MAAM,IAAI,CAAC;AAAA,YACnD,GAAI,aAAa,iBAAiB,EAAE,gBAAgB,YAAY,eAAe,IAAI,CAAC;AAAA,YACpF,GAAI,aAAa,cAAc,EAAE,aAAa,YAAY,YAAY,IAAI,CAAC;AAAA,YAC3E,GAAI,aAAa,iBAAiB,EAAE,gBAAgB,YAAY,eAAe,IAAI,CAAC;AAAA,YACpF,GAAI,aAAa,eAAe,EAAE,cAAc,YAAY,aAAa,IAAI,CAAC;AAAA,YAC9E,GAAI,eAAe,EAAE,eAAe,aAAa,IAAI,CAAC;AAAA,UACxD,CAAC;AAAA,QACH;AAAA,MACF,SAAS,KAAK;AACZ,YAAI,gDAAgD;AAAA,UAClD,QAAQ;AAAA,UACR,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD,CAAC;AAAA,MACH;AAAA,IACF;AAEA,QAAI;AACF,UAAI,gBAAgB,KAAK,UAAU,MAAM,YAAY,CAAC,MAAM,mBAAmB;AAC7E,cAAM,YAAY;AAClB,eAAO,SAAS,KAAK;AAAA,UACnB,MAAM;AAAA,UACN,OAAO;AAAA,QACT,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MACpB;AAIA,YAAM,oBAAoB,SAAS,4BAA4B,KAAK,qBAAqB;AAOzF,UAAI,gBAA+B,SAAS,uBAAuB;AACnE,UAAI,mBAAmB;AACrB,wBAAgB;AAAA,UACd,MAAM,QAAQ,MAAM,cAAc;AAAA,YAChC,UAAU,QAAQ;AAAA,YAClB,MAAM;AAAA,YACN;AAAA,YACA,OAAO,mBAAmB,SAAS,WAAW,WAAW,QAAQ;AAAA,UACnE,CAAC;AAAA,QACH;AAAA,MACF;AACA,oBAAc,EAAE,GAAG,aAAa,cAAc;AAK9C,UAAI,QAAQ,aAAa;AACvB,cAAM,OAAO,MAAM,QAAQ,YAAY,WAAW;AAClD,YAAI,KAAK,YAAY,WAAW;AAC9B,gBAAM,YAAY;AAClB,iBAAO,KAAK;AAAA,QACd;AACA,YAAI,CAAC,KAAK,SAAS;AACjB,gBAAM,YAAY;AAClB,cAAI,kBAAmB,QAAO,KAAK;AAanC,sBAAY;AACZ,4BAAkB,KAAK,IAAI;AAC3B,cAAI,QAAQ,WAAW,aAAa;AAClC,gBAAI;AACF,oBAAM,QAAQ,UAAU,YAAY;AAAA,gBAClC;AAAA,gBAAU;AAAA,gBAAa;AAAA,gBAAc;AAAA,gBAAS,WAAW;AAAA,cAC3D,CAAC;AAAA,YACH,SAAS,KAAK;AACZ,kBAAI,8CAA8C;AAAA,gBAChD,QAAQ;AAAA,gBACR,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,cACxD,CAAC;AAAA,YACH;AAAA,UACF;AACA,gBAAM,sBAAsB,OAAO,MAAS;AAC5C,iBAAO,KAAK;AAAA,QACd;AAAA,MACF;AAGA,UAAI,QAAQ,YAAY;AACtB,cAAM,QAAQ,MAAM,QAAQ,WAAW,WAAW;AAClD,YAAI,MAAO,eAAc,EAAE,GAAG,aAAa,GAAG,MAAM;AAAA,MACtD;AAKA,YAAM,sBAAsB;AAAA,QAC1B,OAAO,QAAQ;AAAA,QACf,QAAQ;AAAA,QACR,SAAS,QAAQ;AAAA,QACjB,UAAU,QAAQ,sBAAsB;AAAA,MAC1C,CAAC;AACD,YAAM,aAAa,EAAE,MAAM,QAAQ,QAAQ,aAAa;AACxD,YAAM,IAAI,QAAQ,UAAU;AAC5B,UAAI,kBAAmB,OAAM,IAAI,OAAO;AAQxC,UAAI,aAAa;AACf,gBAAQ,2BAA2B;AAAA,UACjC,GAAG;AAAA,UACH,OAAO;AAAA,UACP,UAAU,QAAQ;AAAA,UAClB,WAAW,QAAQ,iBACf,QAAQ,eAAe,EAAE,UAAU,aAAa,UAAU,QAAQ,SAAS,CAAC,IAC5E,sBAAsB,WAAW;AAAA,UACrC,UAAU,MAAM;AACd,gBAAI,CAAC,SAAU,QAAO;AACtB,mBAAO;AAAA,cACL,SAAS,SAAS,UAAU;AAAA,cAC5B,GAAI,SAAS,aAAa,EAAE,OAAO,SAAS,WAAW,EAAE,IAAI,CAAC;AAAA,cAC9D,GAAI,SAAS,QAAQ,EAAE,OAAO,SAAS,MAAM,EAAE,IAAI,CAAC;AAAA,cACpD,GAAI,SAAS,QAAQ,EAAE,OAAO,SAAS,MAAM,IAAI,CAAC;AAAA,YACpD;AAAA,UACF;AAAA,UACA,GAAI,QAAQ,qBAAqB,EAAE,eAAe,QAAQ,mBAAmB,IAAI,CAAC;AAAA,UAClF;AAAA,QACF,CAAC;AAID,YAAI,kBAAmB,OAAM,MAAM,MAAM;AAAA,MAC3C;AAEA,wBAAkB,KAAK,IAAI;AAC3B,oBAAc;AACd,UAAI,QAAQ,WAAW,aAAa;AAClC,YAAI;AACF,gBAAM,QAAQ,UAAU,YAAY;AAAA,YAClC;AAAA,YAAU;AAAA,YAAa;AAAA,YAAc;AAAA,YAAS,WAAW;AAAA,UAC3D,CAAC;AAAA,QACH,SAAS,KAAK;AACZ,cAAI,8CAA8C;AAAA,YAChD,QAAQ;AAAA,YACR,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,UACxD,CAAC;AAAA,QACH;AAAA,MACF;AAEA,YAAM,SAAS,eAAe;AAAA,QAC5B;AAAA,QACA,WAAW,KAAK;AAAA,QAChB;AAAA,QACA,OAAO;AAAA;AAAA;AAAA,UAGL,SAAS,OAAO;AAAA,YACd,SAAS,mBAAmB;AAC1B,yBAAW,MAAM,QAAQ,QAAQ,WAAW;AAC5C,kBAAI,SAAwC,SAAS;AACrD,kBAAI,KAAK,oBAAoB;AAC3B,yBAAS,cAAc,QAAQ,QAAQ,MAAM;AAAA,cAC/C;AACA,kBAAI,QAAQ,YAAY;AACtB,yBAAS,aAAa,QAAQ,CAAC,UAAU,QAAQ,WAAY,OAAO,OAAO,GAAG,GAAG;AAAA,cACnF;AACA,kBAAI,QAAQ,WAAW;AACrB,yBAAS,oBAAoB,QAAQ,QAAQ,UAAU,YAAY,QAAQ,UAAU,KAAK;AAAA,cAC5F;AACA,+BAAiB,SAAS,OAAQ,OAAM;AAAA,YAC1C,GAAG;AAAA,YACH,WAAW,MAAM,UAAU,UAAU,KAAK;AAAA,UAC5C;AAAA,UACA,SAAS,OAAO,UAAU;AACxB,gBAAI,kBAAkB,UAAa,cAAc,KAAK,EAAG,iBAAgB,KAAK,IAAI;AAClF,gBAAI,MAAM,SAAS,wBAAwB,MAAM,SAAS,SAAS;AACjE,0BAAY;AACZ,gCAAkB,MAAM;AAAA,YAC1B;AACA,kBAAM,IAAK,QAAQ,KAAK;AAGxB,gBAAI,CAAC,kBAAmB,QAAO,OAAO,KAAK;AAC3C,gBAAI,QAAQ,QAAS,OAAM,QAAQ,QAAQ,OAAO,OAAO;AAAA,UAC3D;AAAA,UACA,GAAI,QAAQ,qBAAqB,EAAE,oBAAoB,QAAQ,mBAAmB,IAAI,CAAC;AAAA,UACvF,yBAAyB,OAAO,EAAE,UAAU,MAAM;AAChD,gBAAI,kBAAmB;AACvB,gBAAI,KAAK,sBAAsB,QAAQ,OAAO,SAAS;AACrD,oBAAM,OAAO,QAAQ;AACrB,mCAAqB;AACrB;AAAA,YACF;AAOA,kBAAM,WAAW,UAAU,iBAAiB,SAAS,eAAe,IAAI;AACxE,kBAAM,YACJ,YAAY,QAAQ,qBAChB,MAAM,QAAQ;AAAA,cACZ,SAAS;AAAA,gBAAI,OAAO,SAClB,OAAQ,KAA4B,QAAQ,EAAE,MAAM,SAChD,EAAE,GAAG,MAAM,MAAM,MAAM,QAAQ,mBAAoB,OAAQ,KAA4B,QAAQ,EAAE,CAAC,EAAE,IACpG;AAAA,cACN;AAAA,YACF,IACA;AACN,kBAAM,QAAQ,YAAY,mBAAmB,SAAS,IAAI;AAC1D,gBAAI,CAAC,UAAU,KAAK,MAAM,CAAC,SAAS,MAAM,WAAW,IAAI;AAGvD,oBAAM,OAAO,QAAQ;AAIrB,mCAAqB,OAAO,MAAM,KAAK;AACvC;AAAA,YACF;AACA,kBAAM,QAAQ,UAAU,QAAQ,KAAK,CAAC;AACtC,kBAAM,cAAc,UAAU,mBAAmB;AACjD,kBAAM,SAAS;AAAA,cACb,SAAS;AAAA,cACT,GAAI,SAAS,MAAM,SAAS,IAAI,EAAE,MAAM,IAAI,CAAC;AAAA,cAC7C,GAAI,UAAU,QAAQ,EAAE,OAAO,SAAS,MAAM,IAAI,CAAC;AAAA,cACnD,GAAI,aAAa,iBAAiB,EAAE,gBAAgB,YAAY,eAAe,IAAI,CAAC;AAAA,cACpF,GAAI,aAAa,cAAc,EAAE,aAAa,YAAY,YAAY,IAAI,CAAC;AAAA,cAC3E,GAAI,aAAa,iBAAiB,EAAE,gBAAgB,YAAY,eAAe,IAAI,CAAC;AAAA,cACpF,GAAI,aAAa,eAAe,EAAE,cAAc,YAAY,aAAa,IAAI,CAAC;AAAA,cAC9E,GAAI,MAAM,gBAAgB,SAAY,EAAE,aAAa,MAAM,YAAY,IAAI,CAAC;AAAA,cAC5E,GAAI,MAAM,iBAAiB,SAAY,EAAE,cAAc,MAAM,aAAa,IAAI,CAAC;AAAA,cAC/E,GAAI,MAAM,oBAAoB,SAAY,EAAE,iBAAiB,MAAM,gBAAgB,IAAI,CAAC;AAAA,cACxF,GAAI,MAAM,oBAAoB,SAAY,EAAE,iBAAiB,MAAM,gBAAgB,IAAI,CAAC;AAAA,cACxF,GAAI,MAAM,qBAAqB,SAAY,EAAE,kBAAkB,MAAM,iBAAiB,IAAI,CAAC;AAAA,cAC3F,GAAI,MAAM,YAAY,SAAY,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;AAAA,YAClE;AAMA,gBAAI,OAAO;AACT,oBAAM,MAAM,SAAS,MAAM;AAC3B,mCAAqB,MAAM,MAAM,KAAK;AACtC;AAAA,YACF;AACA,iCAAqB;AAAA,cACnB,MAAM,QAAQ,MAAM,cAAc;AAAA,gBAChC,UAAU,QAAQ;AAAA,gBAClB,MAAM;AAAA,gBACN,GAAG;AAAA,cACL,CAAC;AAAA,YACH;AAAA,UACF;AAAA,UACA,GAAI,QAAQ,iBACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAME,gBAAgB,OAAO,EAAE,UAAU,cAAc,UAAU,MAAyD;AAClH,kBAAI,kBAAmB;AAGvB,oBAAM,eAAe,UAAU,gBAAgB;AAC/C,oBAAM,cAAc,UAAU,mBAAmB;AACjD,qBAAO,QAAQ,eAAgB;AAAA,gBAC7B,UAAU;AAAA,gBACV;AAAA,gBACA;AAAA,gBACA,QAAQ;AAAA,gBACR,oBAAoB,eAAe;AAAA,gBACnC,GAAI,YAAY,EAAE,eAAe,gBAAgB,eAAe,EAAE,IAAI,CAAC;AAAA,gBACvE,GAAI,aAAa,cAAc,eAAe,IAAI,EAAE,aAAa,cAAc,eAAe,EAAE,IAAI,CAAC;AAAA,gBACrG,GAAI,UAAU,QAAQ,EAAE,OAAO,SAAS,MAAM,IAAI,CAAC;AAAA,gBACnD,GAAI,aAAa,iBAAiB,EAAE,gBAAgB,YAAY,eAAe,IAAI,CAAC;AAAA,gBACpF,GAAI,aAAa,cAAc,EAAE,aAAa,YAAY,YAAY,IAAI,CAAC;AAAA,gBAC3E,GAAI,aAAa,iBAAiB,EAAE,gBAAgB,YAAY,eAAe,IAAI,CAAC;AAAA,gBACpF,GAAI,aAAa,eAAe,EAAE,cAAc,YAAY,aAAa,IAAI,CAAC;AAAA,gBAC9E,GAAI,eAAe,EAAE,eAAe,aAAa,IAAI,CAAC;AAAA,cACxD,CAAC;AAAA,YACH;AAAA,UACF,IACA,CAAC;AAAA,UACL,GAAI,QAAQ,aAAa,EAAE,YAAY,MAAM,QAAQ,WAAY,OAAO,EAAE,IAAI,CAAC;AAAA,QACjF;AAAA,MACF,CAAC;AAKD,YAAM,CAAC,YAAY,SAAS,IAAI,OAAO,KAAK,IAAI;AAChD,YAAM,WAAW,YAAY;AAC3B,cAAM,SAAS,UAAU,UAAU;AACnC,YAAI;AACJ,YAAI;AACF,qBAAS;AACP,kBAAM,EAAE,KAAK,IAAI,MAAM,OAAO,KAAK;AACnC,gBAAI,KAAM;AAAA,UACZ;AAAA,QACF,SAAS,KAAK;AACZ,uBAAa;AACb,cAAI,mCAAmC;AAAA,YACrC,QAAQ;AAAA,YACR,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,UACxD,CAAC;AAAA,QACH;AACA,cAAM,SAAS,aAAa,eAAe;AAM3C,YAAI,mBAAmB;AAIrB,gBAAM,IAAI,OAAO;AACjB;AAAA,QACF;AACA,YAAI,KAAK,sBAAsB,QAAQ,OAAO,SAAS;AACrD,gBAAM,OAAO,QAAQ;AAAA,QACvB,OAAO;AACL,gBAAM,OAAO,MAAM;AAAA,QACrB;AACA,YAAI;AACF,gBAAM,IAAI,KAAK,SAAS,UAAU,UAAU;AAAA,QAC9C,SAAS,KAAK;AACZ,cAAI,6CAA6C;AAAA,YAC/C,QAAQ;AAAA,YACR,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,UACxD,CAAC;AAAA,QACH;AACA,cAAM,sBAAsB,QAAQ,UAAU;AAC9C,cAAM,YAAY;AAAA,MACpB,GAAG;AACH,UAAI,KAAK,UAAW,KAAI,UAAU,OAAO;AAAA,UACpC,MAAK,QAAQ,MAAM,MAAM;AAAA,MAAC,CAAC;AAGhC,YAAM,UAAU,IAAI,YAAY;AAChC,YAAM,SAAS,IAAI,eAA2B;AAAA,QAC5C,MAAM,YAAY;AAChB,qBAAW,QAAQ,QAAQ,OAAO,GAAG,KAAK,UAAU,UAAU,CAAC;AAAA,CAAI,CAAC;AACpE,qBAAW,MAAM;AAAA,QACnB;AAAA,MACF,CAAC;AACD,YAAM,OAAO,cAAc,CAAC,QAAQ,UAAU,CAAC;AAE/C,aAAO,IAAI,SAAS,MAAM;AAAA,QACxB,SAAS;AAAA,UACP,gBAAgB,OAAO;AAAA,UACvB,iBAAiB;AAAA,UACjB,aAAa;AAAA,QACf;AAAA,MACF,CAAC;AAAA,IACH,SAAS,KAAK;AAKZ,UAAI,CAAC,mBAAmB;AACtB,YAAI,YAAa,OAAM,sBAAsB,MAAM,GAAG;AACtD,cAAM,YAAY;AAAA,MACpB;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAEA,iBAAe,OAAO,SAAkB,QAA+C;AACrF,UAAM,SAAS,OAAO,QAAQ,KAAK;AACnC,QAAI,CAAC,OAAQ,QAAO,SAAS,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAC9E,UAAM,OAAO,MAAM,QAAQ,UAAU,EAAE,SAAS,QAAQ,UAAU,OAAO,CAAC;AAC1E,QAAI,CAAC,KAAK,GAAI,QAAO,KAAK;AAE1B,UAAM,aAAa,IAAI,IAAI,QAAQ,GAAG,EAAE,aAAa,IAAI,SAAS;AAClE,UAAM,UAAU,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,UAAU,CAAC,KAAK,CAAC,IAAI;AAEhF,UAAM,UAAU,IAAI,YAAY;AAChC,UAAM,SAAS,iBAAiB;AAAA,MAC9B,OAAO,QAAQ;AAAA,MACf;AAAA,MACA;AAAA,MACA,GAAI,QAAQ,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,OAAO,OAAO,IAAI,CAAC;AAAA,MAChF,GAAI,QAAQ,QAAQ,cAAc,SAAY,EAAE,WAAW,QAAQ,OAAO,UAAU,IAAI,CAAC;AAAA,IAC3F,CAAC;AACD,UAAM,OAAO,IAAI,eAA2B;AAAA,MAC1C,MAAM,KAAK,YAAY;AACrB,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,YAAI,MAAM;AACR,qBAAW,MAAM;AACjB;AAAA,QACF;AAIA,mBAAW,QAAQ,QAAQ,OAAO,GAAG,eAAe,KAAK,CAAC;AAAA,CAAI,CAAC;AAAA,MACjE;AAAA,MACA,SAAS;AACP,aAAK,OAAO,OAAO,MAAS;AAAA,MAC9B;AAAA,IACF,CAAC;AACD,WAAO,IAAI,SAAS,MAAM;AAAA,MACxB,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,MACnB;AAAA,IACF,CAAC;AAAA,EACH;AAEA,iBAAe,QAAQ,SAAqC;AAC1D,UAAM,WAAW,IAAI,IAAI,QAAQ,GAAG,EAAE,aAAa,IAAI,UAAU,GAAG,KAAK;AACzE,QAAI,CAAC,SAAU,QAAO,SAAS,KAAK,EAAE,OAAO,mBAAmB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAClF,UAAM,OAAO,MAAM,QAAQ,UAAU,EAAE,SAAS,QAAQ,WAAW,SAAS,CAAC;AAC7E,QAAI,CAAC,KAAK,GAAI,QAAO,KAAK;AAI1B,UAAM,MAAO,MAAM,QAAQ,UAAU,cAAc,QAAQ,KAAM,CAAC;AAClE,WAAO,SAAS,KAAK,EAAE,SAAS,IAAI,CAAC;AAAA,EACvC;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc,QAAQ,eAAe,6BAA6B,QAAQ,YAAY,IAAI;AAAA,EAC5F;AACF;AAGA,SAAS,cAAc,SAAmE;AACxF,MAAI,QAAQ;AACZ,MAAI,SAAyD;AAC7D,SAAO,IAAI,eAA2B;AAAA,IACpC,MAAM,KAAK,YAAY;AACrB,iBAAS;AACP,YAAI,CAAC,QAAQ;AACX,gBAAM,OAAO,QAAQ,OAAO;AAC5B,cAAI,CAAC,MAAM;AACT,uBAAW,MAAM;AACjB;AAAA,UACF;AACA,mBAAS,KAAK,UAAU;AAAA,QAC1B;AACA,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,YAAI,MAAM;AACR,mBAAS;AACT;AAAA,QACF;AACA,mBAAW,QAAQ,KAAK;AACxB;AAAA,MACF;AAAA,IACF;AAAA,IACA,MAAM,OAAO,QAAQ;AACnB,YAAM,QAAQ,OAAO,MAAM;AAC3B,iBAAW,UAAU,QAAQ,MAAM,KAAK,EAAG,OAAM,OAAO,OAAO,MAAM;AAAA,IACvE;AAAA,EACF,CAAC;AACH;AAOA,gBAAgB,cACd,QACA,QACkC;AAClC,QAAM,WAAW,OAAO,OAAO,aAAa,EAAE;AAC9C,MAAI;AACF,eAAS;AACP,YAAM,OAAO,MAAM,cAAc,UAAU,MAAM;AACjD,UAAI,KAAK,KAAM;AACf,YAAM,KAAK;AAAA,IACb;AAAA,EACF,UAAE;AACA,UAAM,wBAAwB,QAAQ;AAAA,EACxC;AACF;AAEA,SAAS,cACP,UACA,QAC4B;AAC5B,MAAI,OAAO,QAAS,QAAO,QAAQ,OAAO,IAAI,MAAM,qBAAqB,CAAC;AAC1E,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,QAAI,UAAU;AACd,UAAM,UAAU,MAAM,OAAO,oBAAoB,SAAS,OAAO;AACjE,UAAM,UAAU,MAAM;AACpB,UAAI,QAAS;AACb,gBAAU;AACV,cAAQ;AACR,aAAO,IAAI,MAAM,qBAAqB,CAAC;AAAA,IACzC;AACA,WAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AACxD,QAAI,OAAO,SAAS;AAClB,cAAQ;AACR;AAAA,IACF;AACA,YAAQ,QAAQ,EACb,KAAK,MAAM,SAAS,KAAK,CAAC,EAC1B;AAAA,MACC,CAAC,WAAW;AACV,YAAI,QAAS;AACb,kBAAU;AACV,gBAAQ;AACR,gBAAQ,MAAM;AAAA,MAChB;AAAA,MACA,CAAC,UAAmB;AAClB,YAAI,QAAS;AACb,kBAAU;AACV,gBAAQ;AACR,eAAO,KAAK;AAAA,MACd;AAAA,IACF;AAAA,EACJ,CAAC;AACH;AAEA,IAAM,2BAA2B;AAEjC,eAAe,wBAA2B,UAA2C;AACnF,MAAI;AACJ,MAAI;AACF,UAAM,SAAS,SAAS,SAAS;AACjC,QAAI,OAAQ,WAAU,QAAQ,QAAQ,MAAM;AAAA,EAC9C,QAAQ;AACN;AAAA,EACF;AACA,MAAI,CAAC,QAAS;AACd,MAAI;AACJ,MAAI;AACF,UAAM,QAAQ,KAAK;AAAA,MACjB,QAAQ,QAAQ,OAAO,EAAE,MAAM,MAAM,MAAS;AAAA,MAC9C,IAAI,QAAc,CAAC,YAAY;AAC7B,kBAAU,WAAW,SAAS,wBAAwB;AAAA,MACxD,CAAC;AAAA,IACH,CAAC;AAAA,EACH,UAAE;AACA,QAAI,YAAY,OAAW,cAAa,OAAO;AAAA,EACjD;AACF;;;ACvwDA,gBAAuB,+BACrB,SACoC;AACpC,QAAM,eAAe,QAAQ,UAAU,QAAQ,QAAQ;AACvD,MAAI,aAAa,QAAS;AAC1B,MAAI;AACJ,QAAM,iBAAiB,MAAM,KAAK,QAAQ,OAAO;AACjD,eAAa,iBAAiB,SAAS,gBAAgB,EAAE,MAAM,KAAK,CAAC;AAErE,QAAM,UAAU,IAAI,QAAQ,QAAQ,QAAQ,KAAK;AAAA,IAC/C,GAAG,oBAAoB,QAAQ,OAAO;AAAA,IACtC,SAAS,QAAQ,QAAQ;AAAA,EAC3B,CAAC;AACD,UAAQ,QAAQ,IAAI,gBAAgB,kBAAkB;AAEtD,MAAI,WAAW;AACf,MAAI;AACF,UAAM,WAAW,MAAM,QAAQ,OAAO;AAAA,MACpC;AAAA,MACA;AAAA,QACE,GAAI,QAAQ,YAAY,EAAE,WAAW,QAAQ,UAAU,IAAI,CAAC;AAAA,QAC5D,GAAI,QAAQ,kBAAkB,EAAE,iBAAiB,QAAQ,gBAAgB,IAAI,CAAC;AAAA,MAChF;AAAA,IACF;AACA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,SAAS,MAAM,OAAO,EAAE,MAAM,MAAM,MAAS;AACnD,YAAM,IAAI,MAAM,yCAAyC,SAAS,MAAM,GAAG;AAAA,IAC7E;AACA,QAAI,CAAC,SAAS,KAAM,OAAM,IAAI,MAAM,+BAA+B;AAEnE,aAAS,SAAS,KAAK,UAAU;AACjC,QAAI,aAAa,SAAS;AACxB,YAAM,OAAO,OAAO,EAAE,MAAM,MAAM,MAAS;AAC3C;AAAA,IACF;AACA,UAAM,UAAU,IAAI,YAAY;AAChC,QAAI,SAAS;AACb,eAAS;AACP,YAAM,QAAQ,MAAM,OAAO,KAAK;AAChC,UAAI,MAAM,MAAM;AACd,mBAAW;AACX,kBAAU,QAAQ,OAAO;AACzB,cAAM,QAAQ,eAAe,WAAW,MAAM,CAAC;AAC/C,YAAI,MAAO,OAAM;AACjB;AAAA,MACF;AACA,gBAAU,QAAQ,OAAO,MAAM,OAAO,EAAE,QAAQ,KAAK,CAAC;AACtD,YAAM,QAAQ,OAAO,MAAM,IAAI;AAC/B,eAAS,MAAM,IAAI,KAAK;AACxB,iBAAW,QAAQ,OAAO;AACxB,cAAM,QAAQ,eAAe,WAAW,IAAI,CAAC;AAC7C,YAAI,MAAO,OAAM;AAAA,MACnB;AAAA,IACF;AAAA,EACF,UAAE;AACA,QAAI,CAAC,SAAU,OAAM,QAAQ,OAAO,EAAE,MAAM,MAAM,MAAS;AAC3D,YAAQ,YAAY;AACpB,iBAAa,oBAAoB,SAAS,cAAc;AAAA,EAC1D;AACF;AAEA,SAAS,WAAW,MAA2C;AAC7D,MAAI,CAAC,KAAK,KAAK,EAAG,QAAO;AACzB,MAAI;AACJ,MAAI;AACF,YAAQ,KAAK,MAAM,IAAI;AAAA,EACzB,QAAQ;AACN,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AACA,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;AAC/D,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AACA,QAAM,QAAQ;AACd,MAAI,MAAM,SAAS,UAAa,OAAO,MAAM,SAAS,UAAU;AAC9D,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AACA,QAAM,OAAO,MAAM;AACnB,MAAI,SAAS,WAAc,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,IAAI;AACpF,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AACA,QAAM,YAAY,QAAS,KAAiC;AAC5D,MAAI,cAAc,WAAc,CAAC,aAAa,OAAO,cAAc,YAAY,MAAM,QAAQ,SAAS,IAAI;AACxG,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AACA,MAAI,MAAM,SAAS,UAAa,OAAO,MAAM,SAAS,UAAU;AAC9D,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AACA,QAAM,QAAQ,MAAM;AACpB,MAAI,UAAU,WAAc,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,IAAI;AACxF,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AACA,MAAI,MAAM,aAAa,UAAa,OAAO,MAAM,aAAa,UAAU;AACtE,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AACA,SAAO;AAAA,IACL,GAAI,OAAO,MAAM,SAAS,WAAW,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC;AAAA,IAC7D,GAAI,OAAO,EAAE,KAAyC,IAAI,CAAC;AAAA,IAC3D,GAAI,OAAO,MAAM,SAAS,WAAW,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC;AAAA,IAC7D,GAAI,QAAQ,EAAE,MAAwC,IAAI,CAAC;AAAA,IAC3D,GAAI,OAAO,MAAM,aAAa,WAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;AAAA,EAC3E;AACF;AAEA,SAAS,yBAAyB,OAAiC;AACjE,SAAO,OAAO,UAAU,YAAY,OAAO,cAAc,KAAK,KAAK,SAAS;AAC9E;AAEA,SAAS,mBACP,OACA,OACA,OACoB;AACpB,QAAM,QAAQ,MAAM,KAAK;AACzB,QAAM,aAAa,QAAQ,MAAM,KAAK,IAAI;AAC1C,aAAW,aAAa,CAAC,OAAO,UAAU,GAAG;AAC3C,QAAI,cAAc,UAAa,CAAC,yBAAyB,SAAS,GAAG;AACnE,YAAM,IAAI,MAAM,qCAAqC,KAAK,EAAE;AAAA,IAC9D;AAAA,EACF;AACA,MAAI,UAAU,UAAa,eAAe,UAAa,UAAU,YAAY;AAC3E,UAAM,IAAI,MAAM,yCAAyC,KAAK,EAAE;AAAA,EAClE;AACA,SAAQ,SAAS;AACnB;AAEA,SAAS,yBACP,OACA,OACA,OACoB;AACpB,QAAM,QAAQ,MAAM,KAAK;AACzB,QAAM,aAAa,QAAQ,MAAM,KAAK,IAAI;AAC1C,aAAW,aAAa,CAAC,OAAO,UAAU,GAAG;AAC3C,QACE,cAAc,WACb,OAAO,cAAc,YAAY,CAAC,OAAO,SAAS,SAAS,KAAK,YAAY,IAC7E;AACA,YAAM,IAAI,MAAM,qCAAqC,KAAK,EAAE;AAAA,IAC9D;AAAA,EACF;AACA,MAAI,UAAU,UAAa,eAAe,UAAa,UAAU,YAAY;AAC3E,UAAM,IAAI,MAAM,yCAAyC,KAAK,EAAE;AAAA,EAClE;AACA,SAAQ,SAAS;AACnB;AAGA,SAAS,oBAAoB,OAA2C;AACtE,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,EAAG,QAAO;AACxE,QAAM,SAAS;AACf,QAAM,QAA4B,CAAC;AACnC,QAAM,cAAc,mBAAmB,QAAQ,eAAe,cAAc;AAC5E,QAAM,eAAe,mBAAmB,QAAQ,gBAAgB,kBAAkB;AAClF,QAAM,kBAAkB,mBAAmB,QAAQ,iBAAiB;AACpE,QAAM,aAAa,mBAAmB,QAAQ,YAAY;AAC1D,QAAM,gBAAgB,mBAAmB,QAAQ,eAAe;AAChE,QAAM,kBAAkB,yBAAyB,QAAQ,mBAAmB,SAAS;AACrF,MAAI,gBAAgB,OAAW,OAAM,cAAc;AACnD,MAAI,iBAAiB,OAAW,OAAM,eAAe;AACrD,MAAI,oBAAoB,OAAW,OAAM,kBAAkB;AAC3D,MAAI,eAAe,OAAW,OAAM,aAAa;AACjD,MAAI,kBAAkB,OAAW,OAAM,gBAAgB;AACvD,MAAI,oBAAoB,OAAW,OAAM,kBAAkB;AAC3D,MAAI,OAAO,mBAAmB,QAAW;AACvC,QAAI,OAAO,OAAO,mBAAmB,WAAW;AAC9C,YAAM,IAAI,MAAM,kDAAkD;AAAA,IACpE;AACA,UAAM,iBAAiB,OAAO;AAAA,EAChC;AACA,SAAO,OAAO,KAAK,KAAK,EAAE,SAAS,IAAI,QAAQ;AACjD;AAGA,SAAS,eAAe,OAA+D;AACrF,MAAI,CAAC,MAAO,QAAO;AAInB,MAAI,MAAM,SAAS,QAAQ;AACzB,QAAI,MAAM,SAAS,OAAW,OAAM,IAAI,MAAM,mCAAmC;AACjF,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,GAAG,OAAO,MAAM,KAAK;AAAA,IACpD;AAAA,EACF;AAKA,MAAI,MAAM,SAAS,YAAa,QAAO;AAKvC,MAAI,MAAM,SAAS,WAAW,MAAM,SAAS,MAAM,MAAM,OAAO;AAC9D,UAAM,QAAQ,oBAAoB,MAAM,SAAS,MAAM,MAAM,KAAK;AAClE,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO,EAAE,MAAM,SAAS,MAAM,EAAE,MAAM,EAAE;AAAA,EAC1C;AAIA,MAAI,MAAM,SAAS,eAAe;AAChC,QAAI,CAAC,MAAM,SAAU,OAAM,IAAI,MAAM,yCAAyC;AAC9E,WAAO,EAAE,MAAM,eAAe,MAAM,EAAE,MAAM,EAAE,MAAM,MAAM,SAAS,EAAE,EAAE;AAAA,EACzE;AAEA,MAAI,MAAM,SAAS,oBAAoB,CAAC,MAAM,MAAM,eAAe;AACjE,UAAM,SAAS,OAAO,MAAM,MAAM,WAAW,WAAW,MAAM,KAAK,SAAS;AAC5E,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM,EAAE,eAAe,SAAS,EAAE,OAAO,IAAI,CAAC,EAAE;AAAA,IAClD;AAAA,EACF;AAEA,SAAO;AAAA,IACL,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC;AAAA,IACzC,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,KAAmC,IAAI,CAAC;AAAA,EACzE;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/chat-routes/binary-sniff.ts","../src/chat-routes/attachment-validation.ts"],"sourcesContent":["/**\n * Content-based binary/text classification, shared by the attachment upload\n * route (server) and the composer's client-side pre-validation (browser) —\n * both sides must agree on what counts as binary before a byte ever leaves\n * the client. Extension-based allowlists lie (a renamed `.docx`, a PNG saved\n * as `.txt`), so classification reads the actual bytes: a magic-byte table\n * for common binary formats first, then a UTF-8 decode attempt for\n * everything else. Three families need more than a fixed-offset signature and\n * get a reader of their own — ISO-BMFF brands (`sniffFtyp`), EBML containers\n * (`sniffEbml`), and OOXML Office packages, whose PKZIP container is shared\n * with every other `.zip` (`sniffOoxml`).\n *\n * Lifted near-verbatim from gtm-agent's `src/lib/binary-sniff.ts` (the\n * source PRs hardened this against real corruption/gate bugs: gtm#584,\n * gtm#592). Import-free by design — `/web-react` re-exports `/chat-routes`\n * modules into browser bundles (`tests/browser-safe-subpaths.test.ts` walks\n * the graph), so nothing here may reach a Node builtin or an engine package.\n */\n\nexport interface SniffResult {\n binary: boolean\n mime: string | null\n}\n\n/** Sniffed mime for a Word OOXML package (`.docx`). */\nexport const OOXML_WORD_MIME = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'\n\n/** Sniffed mime for an Excel OOXML package (`.xlsx`). */\nexport const OOXML_SPREADSHEET_MIME = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'\n\n/** Sniffed mime for a PowerPoint OOXML package (`.pptx`). */\nexport const OOXML_PRESENTATION_MIME = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'\n\n/** Sniffed mime for a macro-enabled Word package (`.docm`). Reported\n * separately from {@link OOXML_WORD_MIME} so a package carrying a VBA project\n * can never be admitted under the plain-document mime: a route that wants\n * macro-enabled files opts in explicitly through its allowed-mime seam. */\nexport const OOXML_WORD_MACRO_ENABLED_MIME = 'application/vnd.ms-word.document.macroEnabled.12'\n\n/** Sniffed mime for a macro-enabled Excel package (`.xlsm`). */\nexport const OOXML_SPREADSHEET_MACRO_ENABLED_MIME = 'application/vnd.ms-excel.sheet.macroEnabled.12'\n\n/** Sniffed mime for a macro-enabled PowerPoint package (`.pptm`). */\nexport const OOXML_PRESENTATION_MACRO_ENABLED_MIME = 'application/vnd.ms-powerpoint.presentation.macroEnabled.12'\n\nfunction bytesStartWith(bytes: Uint8Array, offset: number, signature: number[]): boolean {\n if (bytes.length < offset + signature.length) return false\n for (let i = 0; i < signature.length; i++) {\n if (bytes[offset + i] !== signature[i]) return false\n }\n return true\n}\n\nfunction asciiAt(bytes: Uint8Array, offset: number, text: string): boolean {\n if (bytes.length < offset + text.length) return false\n for (let i = 0; i < text.length; i++) {\n if (bytes[offset + i] !== text.charCodeAt(i)) return false\n }\n return true\n}\n\n/** RIFF containers (WebP, WAV) share the `RIFF....<TYPE>` header; the type\n * tag at byte offset 8 distinguishes them. */\nfunction sniffRiff(bytes: Uint8Array): string | null {\n if (!asciiAt(bytes, 0, 'RIFF')) return null\n if (asciiAt(bytes, 8, 'WEBP')) return 'image/webp'\n if (asciiAt(bytes, 8, 'WAVE')) return 'audio/wav'\n return null\n}\n\n/** MP4/MOV/AVIF/HEIC containers share an `ftyp` box at byte offset 4; the\n * major brand at offset 8 tells them apart within the shared ISO-BMFF\n * family. Brands outside this table (mp4, m4a, m4v, etc) fall back to\n * `video/mp4`, the family's most common member. */\nfunction sniffFtyp(bytes: Uint8Array): string | null {\n if (!asciiAt(bytes, 4, 'ftyp')) return null\n if (asciiAt(bytes, 8, 'qt ')) return 'video/quicktime'\n if (asciiAt(bytes, 8, 'avif') || asciiAt(bytes, 8, 'avis')) return 'image/avif'\n if (asciiAt(bytes, 8, 'heic') || asciiAt(bytes, 8, 'heix') || asciiAt(bytes, 8, 'hevc') || asciiAt(bytes, 8, 'hevx')) return 'image/heic'\n if (asciiAt(bytes, 8, 'mif1') || asciiAt(bytes, 8, 'msf1')) return 'image/heif'\n return 'video/mp4'\n}\n\nconst EBML_HEADER_ID = 0x1a45dfa3\nconst EBML_DOC_TYPE_ID = 0x4282\n/** A normal EBML header is only a few dozen bytes. This cap keeps a crafted\n * header from choosing how much untrusted input the DocType reader scans. */\nconst EBML_HEADER_SCAN_BYTES = 4 * 1024\n\ninterface EbmlVint {\n length: number\n value: number\n}\n\n/** Read an EBML variable-width integer. Element IDs retain their marker bit;\n * element sizes remove it. Values too large for exact JS integer arithmetic,\n * unknown-size sentinels, and truncated encodings are rejected. */\nfunction readEbmlVint(\n bytes: Uint8Array,\n offset: number,\n maxLength: number,\n stripMarker: boolean,\n): EbmlVint | null {\n const first = bytes[offset]\n if (first === undefined || first === 0) return null\n\n let marker = 0x80\n let length = 1\n while ((first & marker) === 0) {\n marker >>= 1\n length++\n }\n if (length > maxLength || offset + length > bytes.length) return null\n\n if (stripMarker) {\n let unknown = (first & (marker - 1)) === marker - 1\n for (let index = 1; index < length; index++) unknown &&= bytes[offset + index] === 0xff\n if (unknown) return null\n }\n\n let value = stripMarker ? first & (marker - 1) : first\n for (let index = 1; index < length; index++) {\n value = value * 0x100 + bytes[offset + index]!\n if (!Number.isSafeInteger(value)) return null\n }\n return { length, value }\n}\n\n/** WebM and Matroska share the EBML magic. Their header's bounded DocType\n * element is the distinguishing signal; other or malformed EBML containers\n * remain recognized only as unknown binary. */\nfunction sniffEbml(bytes: Uint8Array): string | null {\n const headerId = readEbmlVint(bytes, 0, 4, false)\n if (headerId?.value !== EBML_HEADER_ID) return null\n\n const headerSize = readEbmlVint(bytes, headerId.length, 8, true)\n if (!headerSize) return null\n const headerStart = headerId.length + headerSize.length\n const headerEnd = headerStart + headerSize.value\n if (!Number.isSafeInteger(headerEnd) || headerEnd > bytes.length) return null\n const scanEnd = Math.min(headerEnd, headerStart + EBML_HEADER_SCAN_BYTES)\n\n let cursor = headerStart\n while (cursor < scanEnd) {\n const id = readEbmlVint(bytes, cursor, 4, false)\n if (!id) return null\n const size = readEbmlVint(bytes, cursor + id.length, 8, true)\n if (!size) return null\n const valueStart = cursor + id.length + size.length\n const valueEnd = valueStart + size.value\n if (!Number.isSafeInteger(valueEnd) || valueEnd > headerEnd || valueEnd > scanEnd) return null\n\n if (id.value === EBML_DOC_TYPE_ID) {\n let docType = ''\n for (let index = valueStart; index < valueEnd; index++) {\n const byte = bytes[index]!\n if (byte > 0x7f) return null\n docType += String.fromCharCode(byte)\n }\n if (docType === 'webm') return 'video/webm'\n if (docType === 'matroska') return 'video/x-matroska'\n return null\n }\n cursor = valueEnd\n }\n return null\n}\n\n/** `BM` alone matches ordinary prose (\"BMW…\"), so require the BMP header's\n * reserved bytes (offsets 6-9), which the format mandates to be zero. */\nfunction sniffBmp(bytes: Uint8Array): boolean {\n return asciiAt(bytes, 0, 'BM')\n && bytes.length >= 10\n && bytes[6] === 0 && bytes[7] === 0 && bytes[8] === 0 && bytes[9] === 0\n}\n\n/** `ID3` alone matches ordinary prose (\"ID3 tags…\"), so require the ID3v2\n * header shape: a plausible version byte and sync-safe size bytes. */\nfunction sniffId3(bytes: Uint8Array): boolean {\n return asciiAt(bytes, 0, 'ID3')\n && bytes.length >= 10\n && bytes[3]! < 0x10\n && bytes[6]! < 0x80 && bytes[7]! < 0x80 && bytes[8]! < 0x80 && bytes[9]! < 0x80\n}\n\nconst ZIP_LOCAL_FILE_HEADER_SIGNATURE = 0x04034b50\nconst ZIP_CENTRAL_FILE_HEADER_SIGNATURE = 0x02014b50\nconst ZIP_END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50\n/** Fixed part of an end-of-central-directory record, before its comment. */\nconst ZIP_END_OF_CENTRAL_DIRECTORY_BYTES = 22\n/** Fixed part of a central-directory file header, before name/extra/comment. */\nconst ZIP_CENTRAL_FILE_HEADER_BYTES = 46\n/** Fixed part of a local file header, before name/extra. */\nconst ZIP_LOCAL_FILE_HEADER_BYTES = 30\n/** A zip's trailing comment is a u16 length, so the end-of-central-directory\n * record sits within this distance of the last byte. */\nconst ZIP_MAX_COMMENT_BYTES = 0xffff\n/** ZIP64 stores sentinels in the 16/32-bit fields and the real values in a\n * separate record. Nothing that large rides an attachment cap, so a sentinel\n * leaves the archive unclassified instead of half-parsed. */\nconst ZIP64_SENTINEL_U16 = 0xffff\nconst ZIP64_SENTINEL_U32 = 0xffffffff\n/** Deflate is the usual method; only method 0 (stored) can be read without an\n * inflater, and this module has no decompression (it runs in the browser\n * bundle and on the upload path, on untrusted bytes). */\nconst ZIP_METHOD_STORED = 0\n\n/** The OPC content-types part every OOXML package carries. */\nconst OPC_CONTENT_TYPES_PART = '[Content_Types].xml'\n\n/** The OPC package-relationships part. ECMA-376 Part 2 requires it in every\n * conforming package, so an archive that borrowed the Office part NAMES\n * without also carrying this one is not a package. Structural, like the\n * content-types part: both are read from the zip's own uncompressed metadata,\n * which is the only evidence available without an inflater. */\nconst OPC_PACKAGE_RELATIONSHIPS_PART = '_rels/.rels'\n\n/** Namespace a genuine content-types part declares. Only checkable when that\n * part is STORED uncompressed — Word, Excel and PowerPoint all deflate it —\n * so this narrows the crafted-archive surface where it applies and is silent\n * where it does not. */\nconst OPC_CONTENT_TYPES_NAMESPACE = 'http://schemas.openxmlformats.org/package/2006/content-types'\n\n/** Ceiling on how much of the content-types part is read when it happens to\n * be stored uncompressed — the genuine part is a few hundred bytes, and a\n * crafted archive must not be able to dictate how much text is scanned. */\nconst OPC_CONTENT_TYPES_SCAN_BYTES = 64 * 1024\n\ninterface OoxmlDeclaredMainPartType {\n /** The content type as it appears in the content-types part. */\n readonly declaredType: string\n /** Reported mime when the package carries no VBA project. */\n readonly mime: string\n /** Reported mime for the same family once a VBA project is present. */\n readonly macroEnabledMime: string\n /** True when `declaredType` is itself one of the macro-enabled types. */\n readonly macroEnabled: boolean\n}\n\n/** Content types a package DECLARES for its main part, in the content-types\n * part itself — the authoritative signal when it is readable, because it\n * distinguishes a document from a template or a macro-enabled sibling that\n * share the same part layout. Each row carries its macro-enabled sibling\n * because a declared type is only half the answer: the VBA-project scan can\n * override it, and a package that carries one is macro-enabled whatever it\n * declares. */\nconst OOXML_DECLARED_MAIN_PART_TYPES: ReadonlyArray<OoxmlDeclaredMainPartType> = [\n {\n declaredType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml',\n mime: OOXML_WORD_MIME,\n macroEnabledMime: OOXML_WORD_MACRO_ENABLED_MIME,\n macroEnabled: false,\n },\n {\n declaredType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml',\n mime: OOXML_SPREADSHEET_MIME,\n macroEnabledMime: OOXML_SPREADSHEET_MACRO_ENABLED_MIME,\n macroEnabled: false,\n },\n {\n declaredType: 'application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml',\n mime: OOXML_PRESENTATION_MIME,\n macroEnabledMime: OOXML_PRESENTATION_MACRO_ENABLED_MIME,\n macroEnabled: false,\n },\n {\n declaredType: 'application/vnd.ms-word.document.macroEnabled.main+xml',\n mime: OOXML_WORD_MACRO_ENABLED_MIME,\n macroEnabledMime: OOXML_WORD_MACRO_ENABLED_MIME,\n macroEnabled: true,\n },\n {\n declaredType: 'application/vnd.ms-excel.sheet.macroEnabled.main+xml',\n mime: OOXML_SPREADSHEET_MACRO_ENABLED_MIME,\n macroEnabledMime: OOXML_SPREADSHEET_MACRO_ENABLED_MIME,\n macroEnabled: true,\n },\n {\n declaredType: 'application/vnd.ms-powerpoint.presentation.macroEnabled.main+xml',\n mime: OOXML_PRESENTATION_MACRO_ENABLED_MIME,\n macroEnabledMime: OOXML_PRESENTATION_MACRO_ENABLED_MIME,\n macroEnabled: true,\n },\n]\n\n/** Main part of each Office format, as it appears in the package's entry\n * names — the structural signal used when the declared content types are\n * compressed (the common case: Word, Excel and PowerPoint all deflate the\n * content-types part). Entry names live in the zip's own uncompressed\n * metadata, so reading them needs no inflater.\n *\n * `directory` is the fallback for the packages that do not use the canonical\n * main-part name — a repaired or converted Word file declares\n * `word/document2.xml` — and it is only consulted for a package already\n * confirmed to carry the OPC content-types part, where a `word/` part means\n * a Word document and nothing else. The three directories never mix: an\n * embedded workbook inside a `.docx` lives under `word/embeddings/`. */\nconst OOXML_MAIN_PARTS: ReadonlyArray<{ part: string; directory: string; mime: string; macroEnabledMime: string }> = [\n { part: 'word/document.xml', directory: 'word/', mime: OOXML_WORD_MIME, macroEnabledMime: OOXML_WORD_MACRO_ENABLED_MIME },\n { part: 'xl/workbook.xml', directory: 'xl/', mime: OOXML_SPREADSHEET_MIME, macroEnabledMime: OOXML_SPREADSHEET_MACRO_ENABLED_MIME },\n { part: 'ppt/presentation.xml', directory: 'ppt/', mime: OOXML_PRESENTATION_MIME, macroEnabledMime: OOXML_PRESENTATION_MACRO_ENABLED_MIME },\n]\n\n/** A VBA project part means the package is macro-enabled whatever its\n * extension claims. Matched by suffix rather than by exact path: naming the\n * macro-enabled mime is the fail-closed answer (the default allow-list\n * refuses it), so a part this misses is the expensive direction. */\nconst OOXML_VBA_PROJECT_PART_SUFFIX = 'vbaProject.bin'\n\ninterface ZipEntry {\n name: string\n compressionMethod: number\n compressedBytes: number\n localHeaderOffset: number\n}\n\nfunction readU16Le(bytes: Uint8Array, offset: number): number {\n return bytes[offset]! | (bytes[offset + 1]! << 8)\n}\n\n/** Little-endian u32 as an unsigned JS number — the high byte is multiplied\n * rather than shifted, because `<< 24` would produce a negative value. */\nfunction readU32Le(bytes: Uint8Array, offset: number): number {\n return (bytes[offset]! | (bytes[offset + 1]! << 8) | (bytes[offset + 2]! << 16)) + bytes[offset + 3]! * 0x1000000\n}\n\n/** Entry names are raw bytes; only exact ASCII part names are compared here,\n * so a non-ASCII byte becomes a character that can never match one. */\nfunction asciiEntryName(bytes: Uint8Array, offset: number, length: number): string {\n let name = ''\n for (let i = 0; i < length; i++) {\n const byte = bytes[offset + i]!\n name += byte < 0x80 ? String.fromCharCode(byte) : '\\uFFFD'\n }\n return name\n}\n\n/** Scan back from the tail for the end-of-central-directory record. The\n * comment length must account for exactly the bytes that follow, or the\n * \"signature\" is compressed data that happens to spell `PK\\x05\\x06`. */\nfunction findZipEndOfCentralDirectory(bytes: Uint8Array): number | null {\n const lowest = Math.max(0, bytes.length - ZIP_END_OF_CENTRAL_DIRECTORY_BYTES - ZIP_MAX_COMMENT_BYTES)\n for (let offset = bytes.length - ZIP_END_OF_CENTRAL_DIRECTORY_BYTES; offset >= lowest; offset--) {\n if (readU32Le(bytes, offset) !== ZIP_END_OF_CENTRAL_DIRECTORY_SIGNATURE) continue\n if (offset + ZIP_END_OF_CENTRAL_DIRECTORY_BYTES + readU16Le(bytes, offset + 20) === bytes.length) return offset\n }\n return null\n}\n\n/** Read the archive's central directory — the authoritative index a zip\n * reader uses. Returns `null` for anything that is not a complete, in-bounds,\n * non-ZIP64 directory: a truncated or crafted archive is left unclassified\n * rather than guessed at from its first local header. */\nfunction readZipCentralDirectory(bytes: Uint8Array): ZipEntry[] | null {\n const end = findZipEndOfCentralDirectory(bytes)\n if (end === null) return null\n\n const entryCount = readU16Le(bytes, end + 10)\n const directoryBytes = readU32Le(bytes, end + 12)\n const directoryOffset = readU32Le(bytes, end + 16)\n if (entryCount === ZIP64_SENTINEL_U16) return null\n if (directoryBytes === ZIP64_SENTINEL_U32 || directoryOffset === ZIP64_SENTINEL_U32) return null\n if (directoryOffset + directoryBytes > bytes.length) return null\n\n const entries: ZipEntry[] = []\n let cursor = directoryOffset\n for (let i = 0; i < entryCount; i++) {\n if (cursor + ZIP_CENTRAL_FILE_HEADER_BYTES > bytes.length) return null\n if (readU32Le(bytes, cursor) !== ZIP_CENTRAL_FILE_HEADER_SIGNATURE) return null\n const nameLength = readU16Le(bytes, cursor + 28)\n const extraLength = readU16Le(bytes, cursor + 30)\n const commentLength = readU16Le(bytes, cursor + 32)\n if (cursor + ZIP_CENTRAL_FILE_HEADER_BYTES + nameLength > bytes.length) return null\n entries.push({\n name: asciiEntryName(bytes, cursor + ZIP_CENTRAL_FILE_HEADER_BYTES, nameLength),\n compressionMethod: readU16Le(bytes, cursor + 10),\n compressedBytes: readU32Le(bytes, cursor + 20),\n localHeaderOffset: readU32Le(bytes, cursor + 42),\n })\n cursor += ZIP_CENTRAL_FILE_HEADER_BYTES + nameLength + extraLength + commentLength\n }\n return entries\n}\n\n/** Text of an entry stored WITHOUT compression, or `null` for anything else.\n * The data offset is computed from the local header (its extra field may be\n * a different length than the central directory's copy). */\nfunction storedZipEntryText(bytes: Uint8Array, entry: ZipEntry): string | null {\n if (entry.compressionMethod !== ZIP_METHOD_STORED) return null\n const header = entry.localHeaderOffset\n if (header + ZIP_LOCAL_FILE_HEADER_BYTES > bytes.length) return null\n if (readU32Le(bytes, header) !== ZIP_LOCAL_FILE_HEADER_SIGNATURE) return null\n const start = header + ZIP_LOCAL_FILE_HEADER_BYTES + readU16Le(bytes, header + 26) + readU16Le(bytes, header + 28)\n const length = Math.min(entry.compressedBytes, OPC_CONTENT_TYPES_SCAN_BYTES)\n if (length === 0 || start + length > bytes.length) return null\n return new TextDecoder('utf-8').decode(bytes.subarray(start, start + length))\n}\n\n/**\n * Identify an OOXML (Office Open XML) package inside a PKZIP container, or\n * `null` for an archive that is not one.\n *\n * Every `.docx`/`.xlsx`/`.pptx` is a zip, so the zip signature alone says\n * nothing — matching on it would admit every `.zip` a user can produce. What\n * this reads is the archive's own uncompressed metadata: the central\n * directory's entry names, plus the content-types part's text in the one case\n * where a producer stored it uncompressed. Everything below follows from that\n * one constraint — the module ships into browser bundles and runs on untrusted\n * bytes, so it has no inflater and cannot read a deflated part.\n *\n * Structural preconditions (both required): the OPC content-types part\n * `[Content_Types].xml` AND the OPC package-relationships part `_rels/.rels`.\n * Which Office format it is comes from the content types the first part\n * DECLARES when it is readable, and otherwise from which main part the package\n * contains (`word/document.xml`, `xl/workbook.xml`, `ppt/presentation.xml`).\n * A package carrying a VBA project reports the macro-enabled mime — computed\n * BEFORE the declared lookup and applied to its answer, so a package that\n * declares the plain type and ships `vbaProject.bin` reports macro-enabled,\n * and a decoy `Override` cannot select the plainer of two declared types.\n *\n * **What this does NOT prove, and no name-only reader can.** A deflated part's\n * CONTENT is unreadable here, so an archive that names its entries like a\n * package and fills them with arbitrary bytes reports the Office mime. That is\n * the same standing as every other magic-byte family this module reports —\n * `%PDF-` followed by arbitrary bytes reports `application/pdf` — because a\n * content sniffer answers \"what format does this claim to be\", not \"is this\n * safe\". The attachment gate is a FORMAT gate; a product that needs the bytes\n * to be a real, parseable document gets that from `/documents`' extractor,\n * which fails loud on a package it cannot read.\n */\nfunction sniffOoxml(bytes: Uint8Array): string | null {\n const entries = readZipCentralDirectory(bytes)\n if (!entries) return null\n\n const contentTypes = entries.find((entry) => entry.name === OPC_CONTENT_TYPES_PART)\n if (!contentTypes) return null\n const names = entries.map((entry) => entry.name)\n if (!names.includes(OPC_PACKAGE_RELATIONSHIPS_PART)) return null\n\n // Order is load-bearing: a VBA project part makes the package macro-enabled\n // whatever its content-types part declares, and the declared lookup below\n // RETURNS, so this has to be known before it runs.\n const macroEnabled = names.some((name) => name.endsWith(OOXML_VBA_PROJECT_PART_SUFFIX))\n\n const declared = storedZipEntryText(bytes, contentTypes)\n if (declared !== null) {\n if (!declared.includes(OPC_CONTENT_TYPES_NAMESPACE)) return null\n // Every match, not the first: `includes` over the whole part means a decoy\n // `Override` declaring a second main-part type is indistinguishable from\n // the real one, so the macro-enabled reading wins whenever one is present.\n const matches = OOXML_DECLARED_MAIN_PART_TYPES.filter((row) => declared.includes(row.declaredType))\n const row = matches.find((candidate) => candidate.macroEnabled) ?? matches[0]\n if (row) return macroEnabled || row.macroEnabled ? row.macroEnabledMime : row.mime\n }\n\n const canonical = OOXML_MAIN_PARTS.find((main) => names.includes(main.part))\n const format = canonical ?? OOXML_MAIN_PARTS.find((main) => names.some((name) => name.startsWith(main.directory)))\n if (!format) return null\n return macroEnabled ? format.macroEnabledMime : format.mime\n}\n\nfunction sniffMagicBytes(bytes: Uint8Array): string | null {\n if (bytesStartWith(bytes, 0, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) return 'image/png'\n if (bytesStartWith(bytes, 0, [0xff, 0xd8, 0xff])) return 'image/jpeg'\n if (asciiAt(bytes, 0, 'GIF87a') || asciiAt(bytes, 0, 'GIF89a')) return 'image/gif'\n if (sniffBmp(bytes)) return 'image/bmp'\n if (bytesStartWith(bytes, 0, [0x49, 0x49, 0x2a, 0x00])) return 'image/tiff' // little-endian\n if (bytesStartWith(bytes, 0, [0x4d, 0x4d, 0x00, 0x2a])) return 'image/tiff' // big-endian\n if (bytesStartWith(bytes, 0, [0x00, 0x00, 0x01, 0x00])) return 'image/x-icon'\n if (asciiAt(bytes, 0, '%PDF-')) return 'application/pdf'\n // OOXML (.docx/.xlsx/.pptx) is a PKZIP container, so the container is\n // inspected before an archive is reported as a generic one — see\n // `sniffOoxml`. An archive that is not an Office package stays\n // `application/zip`, which no default allow-list admits.\n if (bytesStartWith(bytes, 0, [0x50, 0x4b, 0x03, 0x04])) return sniffOoxml(bytes) ?? 'application/zip'\n if (bytesStartWith(bytes, 0, [0x1f, 0x8b])) return 'application/gzip'\n if (sniffId3(bytes) || bytesStartWith(bytes, 0, [0xff, 0xfb])) return 'audio/mpeg'\n if (asciiAt(bytes, 0, 'OggS')) return 'audio/ogg'\n\n const ebml = sniffEbml(bytes)\n if (ebml) return ebml\n\n const riff = sniffRiff(bytes)\n if (riff) return riff\n\n const ftyp = sniffFtyp(bytes)\n if (ftyp) return ftyp\n\n return null\n}\n\n/** SVG is valid UTF-8 but must round-trip byte-identical (image tools read\n * it from the box as a file), so it is classified binary. Conservative\n * match: the document's first element is `<svg`, or an `<?xml` prolog is\n * followed by an `<svg` element within the first ~1KB (comments/doctype may\n * sit between). Plain XML without an svg root, or prose that merely\n * mentions \"<svg\", stays text. */\nfunction sniffSvgText(decoded: string): boolean {\n let text = decoded\n if (text.charCodeAt(0) === 0xfeff) text = text.slice(1)\n text = text.trimStart()\n if (/^<svg[\\s>/]/.test(text)) return true\n if (!text.startsWith('<?xml')) return false\n return /<svg[\\s>/]/.test(text.slice(0, 1024))\n}\n\n/** Decide whether uploaded bytes are binary or text, and identify the mime\n * type when it can be determined from content. Magic bytes are checked\n * first; anything unmatched falls back to a fatal UTF-8 decode. A NUL byte\n * or a decode failure means binary. Valid UTF-8 that is an SVG document is\n * binary (byte-identity matters for image tooling). Content that matches\n * nothing and does not decode as text is binary with an unknown mime —\n * extension-based guessing happens at the call site, not here. */\nexport function sniffBinary(bytes: Uint8Array): SniffResult {\n const mime = sniffMagicBytes(bytes)\n if (mime) return { binary: true, mime }\n\n if (bytes.includes(0x00)) return { binary: true, mime: null }\n\n let decoded: string\n try {\n decoded = new TextDecoder('utf-8', { fatal: true }).decode(bytes)\n } catch {\n return { binary: true, mime: null }\n }\n if (sniffSvgText(decoded)) return { binary: true, mime: 'image/svg+xml' }\n return { binary: false, mime: null }\n}\n","/**\n * Shared attachment validation core — constants, type-gate, and filename\n * sanitization used by BOTH the (server) attachment upload route and the\n * (browser) composer's client-side pre-validation, so a rejection never\n * differs depending on which side classified the bytes first.\n *\n * ≈ gtm-agent's `src/lib/attachment-limits.ts`, minus what agent-app already\n * has (`ATTACHMENT_MAX_COUNT`/`MAX_ATTACHMENT_TOTAL_BYTES`/\n * `attachmentTotalSizeErrorMessage` lived in `./resolve-attachments` and are\n * re-homed here so the whole validation vocabulary — count cap, size caps,\n * and type gate — has one address). Import-free besides `./wire`\n * (`formatBytes`) and `./binary-sniff` (`SniffResult`): `/web-react`\n * re-exports `/chat-routes` modules into browser bundles\n * (`tests/browser-safe-subpaths.test.ts` walks the graph), so nothing here\n * may reach a Node builtin or an engine package.\n */\n\nimport { formatBytes } from './wire'\nimport {\n OOXML_PRESENTATION_MACRO_ENABLED_MIME,\n OOXML_PRESENTATION_MIME,\n OOXML_SPREADSHEET_MACRO_ENABLED_MIME,\n OOXML_SPREADSHEET_MIME,\n OOXML_WORD_MACRO_ENABLED_MIME,\n OOXML_WORD_MIME,\n} from './binary-sniff'\nimport type { SniffResult } from './binary-sniff'\n\n/** Ceiling on a binary attachment's raw (pre-encoding) byte size. */\nexport const MAX_BINARY_ATTACHMENT_BYTES = 10 * 1024 * 1024\n\n/** Ceiling on a text attachment's raw byte size. Text hydrates through\n * inline prompt parts, a separate path that remains proxy-capped (see\n * `INLINE_PARTS_MAX_BYTES` in `./wire`). */\nexport const MAX_TEXT_ATTACHMENT_BYTES = 950 * 1024\n\n/** Most files a single request may carry: the composer staging cap, the\n * upload route's per-request cap, and the chat body's `attachments` cap. */\nexport const ATTACHMENT_MAX_COUNT = 10\n\n/** Aggregate raw-byte ceiling across one message's attachments. */\nexport const MAX_ATTACHMENT_TOTAL_BYTES = 25 * 1024 * 1024\n\n/**\n * Accept list for the composer file picker + type validation, same grammar as\n * the native `<input accept>` attribute. Images plus the text/doc types a\n * product's store actually reads.\n *\n * Every extension here is one the default {@link ALLOWED_ATTACHMENT_SNIFFED_MIMES}\n * actually admits — a picker that offers a format the gate then rejects is a\n * defect, so `tests/chat-routes/attachment-validation.test.ts` walks this\n * string and proves each entry uploads.\n */\nexport const ATTACHMENT_ACCEPT =\n 'image/*,.pdf,.docx,.xlsx,.pptx,.txt,.md,.csv,.json,.yaml,.yml,.html'\n\n/** The Office (OOXML) package mimes the default allow-list admits — the\n * formats professionals actually send: Word contracts, Excel workpapers,\n * PowerPoint decks. Admitting the format is a gate decision only; whether a\n * product can EXTRACT text from one is that product's concern. */\nexport const OOXML_SNIFFED_MIMES: ReadonlySet<string> = new Set([\n OOXML_WORD_MIME,\n OOXML_SPREADSHEET_MIME,\n OOXML_PRESENTATION_MIME,\n])\n\n/** Macro-enabled Office package mimes (`.docm`/`.xlsm`/`.pptm`), reported by\n * `sniffBinary` but deliberately NOT in the default allow-list: a package\n * carrying a VBA project is a different risk decision than a plain document,\n * and it is the product's to make. Opt in by widening the allow-list at the\n * route's `allowedSniffedMimes` seam:\n * `new Set([...ALLOWED_ATTACHMENT_SNIFFED_MIMES, ...MACRO_ENABLED_OOXML_SNIFFED_MIMES])`. */\nexport const MACRO_ENABLED_OOXML_SNIFFED_MIMES: ReadonlySet<string> = new Set([\n OOXML_WORD_MACRO_ENABLED_MIME,\n OOXML_SPREADSHEET_MACRO_ENABLED_MIME,\n OOXML_PRESENTATION_MACRO_ENABLED_MIME,\n])\n\n/** Sniffed-mime counterpart of `ATTACHMENT_ACCEPT`: the binary formats\n * `sniffBinary` can identify from content among the accepted types.\n * Values must match `sniffBinary`'s output strings verbatim, or every\n * upload of that format fails the type gate — locked by the round-trip test\n * that sniffs real bytes of every mime listed here. */\nexport const ALLOWED_ATTACHMENT_SNIFFED_MIMES: ReadonlySet<string> = new Set([\n 'image/png',\n 'image/jpeg',\n 'image/gif',\n 'image/bmp',\n 'image/tiff',\n 'image/x-icon',\n 'image/webp',\n 'image/svg+xml',\n 'image/avif',\n 'image/heic',\n 'image/heif',\n 'application/pdf',\n ...OOXML_SNIFFED_MIMES,\n])\n\n/** Extensions whose magic-byte family is unambiguous, mapped to the mime\n * `sniffBinary` emits for genuine content of that format. Keep in sync with\n * `ATTACHMENT_ACCEPT`: an extension only belongs here if its format has a\n * detectable magic-byte signature. Text extensions (.txt/.md/.csv/.json/\n * .yaml/.yml/.html) are deliberately absent — they have no magic bytes to\n * compare against, so they ride the plain UTF-8 gate instead. AVIF/HEIC/HEIF\n * extensions are also deliberately absent: the ISO-BMFF brand-to-extension\n * mapping in that family isn't one-to-one (a legitimate `.heic` can carry a\n * `mif1` brand), so an extension-implies-mime entry would reject genuine\n * files. They still ride the allowlist gate below, which is what catches an\n * mp4 renamed `.avif` (its content sniffs `video/mp4`, not an allowed mime).\n * The Office extensions ARE here: a `.zip` renamed `.docx` sniffs\n * `application/zip` (its container carries neither of the two OPC parts an\n * Office package must have), so the entry turns a generic 415 into a mismatch\n * that names what the file actually is. Sniffing answers what format the\n * bytes CLAIM to be, not whether they are a readable document — see\n * `sniffOoxml`'s note on what a reader with no inflater can and cannot prove.\n * The macro-enabled and EBML-video extensions are listed for the same reason\n * even though the default allow-list refuses them — a product that widens\n * the allow-list gets the renamed-file check with it. */\nconst EXTENSION_IMPLIES_SNIFFED_MIME: Readonly<Record<string, string>> = {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n bmp: 'image/bmp',\n tif: 'image/tiff',\n tiff: 'image/tiff',\n ico: 'image/x-icon',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n pdf: 'application/pdf',\n docx: OOXML_WORD_MIME,\n xlsx: OOXML_SPREADSHEET_MIME,\n pptx: OOXML_PRESENTATION_MIME,\n docm: OOXML_WORD_MACRO_ENABLED_MIME,\n xlsm: OOXML_SPREADSHEET_MACRO_ENABLED_MIME,\n pptm: OOXML_PRESENTATION_MACRO_ENABLED_MIME,\n webm: 'video/webm',\n mkv: 'video/x-matroska',\n}\n\n/** Represent the result of checking an attachment's type with success or specific failure details */\nexport type AttachmentTypeCheckResult =\n | { succeeded: true }\n | { succeeded: false; code: 'attachment_type_mismatch' | 'attachment_type_not_allowed'; message: string }\n\n/**\n * Cross-check a filename's extension against its sniffed content.\n *\n * Text content (`sniff.binary === false`) always passes here — it has no\n * magic bytes to compare, so it rides the existing UTF-8 gate instead. For\n * binary content: an extension with an unambiguous magic-byte family (e.g.\n * `.pdf`) must match the sniffed mime, or the upload is a mismatch (a\n * renamed file). Otherwise the sniffed mime must be one of `allowed`\n * (default {@link ALLOWED_ATTACHMENT_SNIFFED_MIMES}), or the upload is\n * rejected outright. The `allowed` param feeds a route's override seam (a\n * product accepting a narrower or wider set than the default).\n */\nexport function checkAttachmentType(\n fileName: string,\n sniff: SniffResult,\n allowed: ReadonlySet<string> = ALLOWED_ATTACHMENT_SNIFFED_MIMES,\n): AttachmentTypeCheckResult {\n if (sniff.binary === false) return { succeeded: true }\n\n const extension = fileName.split('.').pop()?.toLowerCase() ?? ''\n const impliedMime = EXTENSION_IMPLIES_SNIFFED_MIME[extension]\n if (impliedMime && sniff.mime && sniff.mime !== impliedMime) {\n return {\n succeeded: false,\n code: 'attachment_type_mismatch',\n message: `${fileName} has a .${extension} extension, but its content is ${sniff.mime}`,\n }\n }\n\n if (!sniff.mime || !allowed.has(sniff.mime)) {\n return {\n succeeded: false,\n code: 'attachment_type_not_allowed',\n message: sniff.mime\n ? `${fileName}'s content (${sniff.mime}) is not an allowed attachment type`\n : `${fileName}'s content is not a recognized attachment type`,\n }\n }\n\n return { succeeded: true }\n}\n\n/**\n * Rewrite a filename into the store-path charset (`A-Za-z0-9._-` per\n * segment) — attachment paths double as store keys, sandbox file paths, and\n * in-message path references, none of which tolerate spaces or punctuation.\n * Runs of unsupported characters collapse to one `-`; leading dots/dashes are\n * stripped so the name can't read as a hidden segment. The original name is\n * preserved separately (the returned `ChatAttachmentInput.name`), so\n * sanitization loses nothing.\n */\nexport function sanitizeAttachmentFileName(name: string): string {\n const sanitized = name\n .trim()\n .replace(/[^A-Za-z0-9._-]+/g, '-')\n .replace(/^[.-]+/, '')\n return sanitized || 'file'\n}\n\n/** Human-readable error naming both the actual size and the limit that was\n * exceeded. Shared so the server route and the composer pre-check report\n * the same message shape. */\nexport function attachmentSizeErrorMessage(name: string, actualBytes: number, limitBytes: number): string {\n return `${name} is ${formatBytes(actualBytes)}; attachments are limited to ${formatBytes(limitBytes)}`\n}\n\n/** Human-readable error for a chat message whose combined attachments exceed\n * the aggregate raw-byte ceiling. */\nexport function attachmentTotalSizeErrorMessage(totalBytes: number, limitBytes: number): string {\n return `Attachments total ${formatBytes(totalBytes)}; each message is limited to ${formatBytes(limitBytes)}`\n}\n"],"mappings":"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","names":["row"]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|