/** * Chat attachments (DES-SYSTEM-CHAT "Crew" §2, api-types 0.137.0). * * `POST /chats/:id/attachments` takes multipart/form-data file parts: images (png, jpeg, gif, webp), * UTF-8 text and pdf. Each file's TYPE is decided from its bytes (a declared type that the bytes do * not bear out is refused), and each upload is held to a per-file cap and a total cap. The files are * stored under the state home (`/chats/attachments//`, 0700 / 0600) and removed * with their chat on `DELETE /chats/:id`. * * A message names attachment ids. Before the seat's turn, each named file is COPIED read-only (0444) * into the chat's private scratch root (`/attachments/`, a read root of the chat's run * from its launch), and the turn's instructions list each one: its path (an image opens as an image * in a CLI that reads images), and for text and pdf the extracted text. The canonical copy under the * state home is never handed to a seat. */ import type { ChatAttachment, ChatAttachmentKind, ChatAttachmentLimits } from '../core/types.js'; export declare const ATTACHMENT_MAX_FILE_BYTES: number; export declare const ATTACHMENT_MAX_MESSAGE_BYTES: number; export declare const ATTACHMENT_MAX_FILES = 10; /** Extracted text handed to the seat per file, at most. */ export declare const ATTACHMENT_TEXT_EXCERPT_CHARS = 16000; export declare const ATTACHMENT_LIMITS: ChatAttachmentLimits; /** A refusal with its HTTP status and wire code (`ChatAttachmentError`). */ export declare class AttachmentError extends Error { readonly status: 400 | 404 | 413 | 415; readonly code: 'attachment_too_large' | 'attachments_too_large' | 'attachment_type' | 'attachment_unknown' | 'attachment_malformed'; readonly name_?: string | undefined; readonly bytes?: number | undefined; constructor(status: 400 | 404 | 413 | 415, code: 'attachment_too_large' | 'attachments_too_large' | 'attachment_type' | 'attachment_unknown' | 'attachment_malformed', message: string, name_?: string | undefined, bytes?: number | undefined); body(): Record; } export interface UploadedFile { filename: string; declaredType: string | null; data: Buffer; } /** The boundary of a `multipart/form-data` content type, or null. */ export declare function boundaryOf(contentType: string | undefined): string | null; /** Parse a buffered multipart body into its FILE parts (a part with a filename); fields are ignored. */ export declare function parseMultipart(body: Buffer, boundary: string): UploadedFile[]; /** Decide an upload's kind and media type from its bytes. `null`: not an accepted type. */ export declare function sniffAttachment(data: Buffer, filename: string): { kind: ChatAttachmentKind; mediaType: string; } | null; /** A file name safe to put on disk: no path, no control characters, at most 100 characters. */ export declare function safeName(filename: string): string; /** One pdf content stream inflates to at most this; the whole pdf's streams to at most the total. */ export declare const PDF_INFLATE_STREAM_BYTES: number; export declare const PDF_INFLATE_TOTAL_BYTES: number; /** Best-effort text from a pdf: the strings shown by `Tj` / `TJ` in its (Flate or plain) content * streams. Good for the common text pdf; a scanned page has none, and says so. */ export declare function extractPdfText(data: Buffer, maxChars?: number): string; export declare function defaultChatAttachmentsDir(): string; /** The wire fields of an attachment record (never its stored path). */ export declare function attachmentWire(m: ChatAttachment): ChatAttachment; export declare class ChatAttachmentStore { readonly dir: string; private readonly now; constructor(opts?: { dir?: string; now?: () => number; }); private chatDir; private ensureDir; /** Validate every file first (all or nothing), then store them. */ add(chatId: string, files: UploadedFile[]): ChatAttachment[]; private readMeta; /** The chat's attachments, oldest first. */ list(chatId: string): ChatAttachment[]; /** The named attachments of the chat, in the order named — refused when one is unknown or the set * is over the per-message caps. */ resolve(chatId: string, ids: readonly string[]): Array; count(chatId: string): number; /** The chat is deleted: its attachments go with it. */ removeChat(chatId: string): void; /** Whether any attachment directory exists under `chatId`. */ exists(chatId: string): boolean; /** Move a chat's attachments to another id (an archived history entry the id was reused over). * Never over an occupied destination. */ rename(fromChatId: string, toChatId: string): void; } export interface StagedAttachment extends ChatAttachment { /** The read-only copy the seat reads. */ seatPath: string; /** Extracted text for text and pdf (bounded); `null` for images. */ text: string | null; truncated: boolean; } /** * Copy the named attachments read-only (0444) into `stagingDir` (the chat's scratch root's * `attachments/`, a read root of its run) and extract their text. The seat never gets the * canonical path under the state home. */ export declare function stageForSeat(atts: ReadonlyArray, stagingDir: string): StagedAttachment[]; /** The block a turn's instructions carry for its attachments. */ export declare function attachmentsBlock(staged: readonly StagedAttachment[]): string; //# sourceMappingURL=chat-attachments.d.ts.map