/** * Shared attachment validation core — constants, type-gate, and filename * sanitization used by BOTH the (server) attachment upload route and the * (browser) composer's client-side pre-validation, so a rejection never * differs depending on which side classified the bytes first. * * ≈ gtm-agent's `src/lib/attachment-limits.ts`, minus what agent-app already * has (`ATTACHMENT_MAX_COUNT`/`MAX_ATTACHMENT_TOTAL_BYTES`/ * `attachmentTotalSizeErrorMessage` lived in `./resolve-attachments` and are * re-homed here so the whole validation vocabulary — count cap, size caps, * and type gate — has one address). Import-free besides `./wire` * (`formatBytes`) and `./binary-sniff` (`SniffResult`): `/web-react` * re-exports `/chat-routes` modules into browser bundles * (`tests/browser-safe-subpaths.test.ts` walks the graph), so nothing here * may reach a Node builtin or an engine package. */ import type { SniffResult } from './binary-sniff'; /** Ceiling on a binary attachment's raw (pre-encoding) byte size. */ export declare const MAX_BINARY_ATTACHMENT_BYTES: number; /** Ceiling on a text attachment's raw byte size. Text hydrates through * inline prompt parts, a separate path that remains proxy-capped (see * `INLINE_PARTS_MAX_BYTES` in `./wire`). */ export declare const MAX_TEXT_ATTACHMENT_BYTES: number; /** Most files a single request may carry: the composer staging cap, the * upload route's per-request cap, and the chat body's `attachments` cap. */ export declare const ATTACHMENT_MAX_COUNT = 10; /** Aggregate raw-byte ceiling across one message's attachments. */ export declare const MAX_ATTACHMENT_TOTAL_BYTES: number; /** * Accept list for the composer file picker + type validation, same grammar as * the native `` attribute. Images plus the text/doc types a * product's store actually reads. * * Every extension here is one the default {@link ALLOWED_ATTACHMENT_SNIFFED_MIMES} * actually admits — a picker that offers a format the gate then rejects is a * defect, so `tests/chat-routes/attachment-validation.test.ts` walks this * string and proves each entry uploads. */ export declare const ATTACHMENT_ACCEPT = "image/*,.pdf,.docx,.xlsx,.pptx,.txt,.md,.csv,.json,.yaml,.yml,.html"; /** The Office (OOXML) package mimes the default allow-list admits — the * formats professionals actually send: Word contracts, Excel workpapers, * PowerPoint decks. Admitting the format is a gate decision only; whether a * product can EXTRACT text from one is that product's concern. */ export declare const OOXML_SNIFFED_MIMES: ReadonlySet; /** Macro-enabled Office package mimes (`.docm`/`.xlsm`/`.pptm`), reported by * `sniffBinary` but deliberately NOT in the default allow-list: a package * carrying a VBA project is a different risk decision than a plain document, * and it is the product's to make. Opt in by widening the allow-list at the * route's `allowedSniffedMimes` seam: * `new Set([...ALLOWED_ATTACHMENT_SNIFFED_MIMES, ...MACRO_ENABLED_OOXML_SNIFFED_MIMES])`. */ export declare const MACRO_ENABLED_OOXML_SNIFFED_MIMES: ReadonlySet; /** Sniffed-mime counterpart of `ATTACHMENT_ACCEPT`: the binary formats * `sniffBinary` can identify from content among the accepted types. * Values must match `sniffBinary`'s output strings verbatim, or every * upload of that format fails the type gate — locked by the round-trip test * that sniffs real bytes of every mime listed here. */ export declare const ALLOWED_ATTACHMENT_SNIFFED_MIMES: ReadonlySet; /** Represent the result of checking an attachment's type with success or specific failure details */ export type AttachmentTypeCheckResult = { succeeded: true; } | { succeeded: false; code: 'attachment_type_mismatch' | 'attachment_type_not_allowed'; message: string; }; /** * Cross-check a filename's extension against its sniffed content. * * Text content (`sniff.binary === false`) always passes here — it has no * magic bytes to compare, so it rides the existing UTF-8 gate instead. For * binary content: an extension with an unambiguous magic-byte family (e.g. * `.pdf`) must match the sniffed mime, or the upload is a mismatch (a * renamed file). Otherwise the sniffed mime must be one of `allowed` * (default {@link ALLOWED_ATTACHMENT_SNIFFED_MIMES}), or the upload is * rejected outright. The `allowed` param feeds a route's override seam (a * product accepting a narrower or wider set than the default). */ export declare function checkAttachmentType(fileName: string, sniff: SniffResult, allowed?: ReadonlySet): AttachmentTypeCheckResult; /** * Rewrite a filename into the store-path charset (`A-Za-z0-9._-` per * segment) — attachment paths double as store keys, sandbox file paths, and * in-message path references, none of which tolerate spaces or punctuation. * Runs of unsupported characters collapse to one `-`; leading dots/dashes are * stripped so the name can't read as a hidden segment. The original name is * preserved separately (the returned `ChatAttachmentInput.name`), so * sanitization loses nothing. */ export declare function sanitizeAttachmentFileName(name: string): string; /** Human-readable error naming both the actual size and the limit that was * exceeded. Shared so the server route and the composer pre-check report * the same message shape. */ export declare function attachmentSizeErrorMessage(name: string, actualBytes: number, limitBytes: number): string; /** Human-readable error for a chat message whose combined attachments exceed * the aggregate raw-byte ceiling. */ export declare function attachmentTotalSizeErrorMessage(totalBytes: number, limitBytes: number): string;