UNPKG

@juspay/neurolink

Version:

The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002

899 lines (898 loc) • 35.6 kB
/** * File detection and processing types for unified file handling */ import type { ImageWithAltText } from "./multimodal.js"; /** * Supported file types for multimodal input */ export type FileType = "csv" | "image" | "pdf" | "audio" | "video" | "archive" | "text" | "svg" | "docx" | "pptx" | "xlsx" | "unknown"; /** * Office document types */ export type OfficeDocumentType = "docx" | "pptx" | "xlsx"; /** * Transcode target for a vision-incompatible image (`adapters/imageFormatSupport.ts`). * * Defaults to `"png"` everywhere a caller does not pick one: sources routed * through that module are frequently lossless (TIFF, BMP, ICO) or already * carry alpha, and a lossy re-encode of an image the model is about to read * closely is the wrong default. `"jpeg"` is available for callers who know * their source has no alpha and prefer the smaller payload. */ export type VisionImageOutputFormat = "png" | "jpeg"; /** * Outcome of a vision-compatibility pass over one image. * * See `adapters/imageFormatSupport.ts` — `converted` is false both when the * source format was already universally accepted and when no decoder could * read it, so callers must not treat it as a success flag. */ export type VisionImageConversion = { readonly buffer: Buffer; readonly mimeType: string; /** True when the bytes were re-encoded; false when they were left alone. */ readonly converted: boolean; }; /** * Knobs for one vision-compatibility pass. * * `autoOrient` exists so a caller that also wants EXIF orientation applied * gets it inside the same decode. Rotating first and transcoding second means * two full decode/re-encode cycles, and for the lossy formats in both sets * (TIFF, which sharp writes JPEG-compressed by default) the intermediate * re-encode costs a generation of image quality that the model then reads. * * `outputFormat` picks the transcode target; see {@link VisionImageOutputFormat} * for the default and why it stays PNG unless a caller opts into JPEG. */ export type VisionTranscodeOptions = { readonly autoOrient?: boolean; readonly outputFormat?: VisionImageOutputFormat; }; /** * What a bounded header probe could establish about an image's EXIF * orientation tag, without decoding the image or reading all of its bytes. * * `"inconclusive"` is not a failure — it is the honest answer whenever the * prefix ran out before the parse reached a verdict, or the container is one * this probe does not parse. Callers must treat it as "find out the expensive * way", never as "no tag". */ export type ExifOrientationProbe = "absent" | "present" | "inconclusive"; /** * Outcome of an EXIF-orientation pass over one image. * * See `adapters/imageFormatSupport.ts` — `normalized` is false both when the * image carried no orientation tag (or an already-upright `1`) and when the * pass could not run (sharp unavailable, decode failure), so callers must not * treat it as a success flag; either way `buffer` is safe to send as-is. */ export type ImageOrientationNormalization = { readonly buffer: Buffer; /** True when the bytes were re-encoded to apply and strip EXIF orientation. */ readonly normalized: boolean; }; /** * Outcome of an audio-compatibility pass over one file. * * See `adapters/audioFormatSupport.ts`. As with images, `converted` is false * both when the container was already acceptable and when nothing could * re-encode it, so it is not a success flag — the caller decides what to do * from the resulting `mimeType`. */ export type AudioConversionResult = { readonly buffer: Buffer; readonly mimeType: string; /** True when the bytes were re-encoded; false when they were left alone. */ readonly converted: boolean; }; /** * One audio file destined for native delivery to a provider. * * Carries the bytes rather than a path because the decision to send audio is * made per provider, after detection has already read the file — re-reading it * from disk at dispatch time would be a second read of something already in * memory. */ export type MultimodalAudioEntry = { /** Raw audio bytes, as detected. */ buffer: Buffer; /** Display name; may be a full path, so log only its basename. */ filename: string; /** Detected MIME type of `buffer`. */ mimeType: string; }; /** * One video file destined for native delivery to a provider. * * Mirrors {@link MultimodalAudioEntry}: the bytes travel rather than the path, * because whether a video is sent at all is decided per provider, after * detection has already read the file. * * `durationSec` rides along because the native-delivery gate is expressed in * seconds as well as bytes, and re-probing the container at dispatch time * would mean a second ffprobe run for something the processor already * measured. It is optional: probing can fail (no ffmpeg, an exotic container), * and an unknown duration must not by itself disqualify a clip that is * comfortably under the size ceiling. */ export type MultimodalVideoEntry = { /** Raw video bytes, as detected. */ buffer: Buffer; /** Display name; may be a full path, so log only its basename. */ filename: string; /** Detected MIME type of `buffer`. */ mimeType: string; /** Clip length in seconds, when the processor was able to measure it. */ durationSec?: number; }; /** * How one provider handles an attached video. * * The table lives in `adapters/videoFormatSupport.ts`; this is its row shape. * `apiType` names the mechanism that is actually implemented, not the one a * provider theoretically offers — Gemini also exposes a resumable Files API * for clips beyond the inline ceiling, and until that is wired up calling this * row "files-api" would misdescribe what happens to a 200 MB upload. */ export type VideoProviderConfig = { /** Whether raw video bytes can be handed to this provider at all. */ readonly supportsNativeVideo: boolean; /** How the video reaches the model. */ readonly apiType: "inline" | "frame-extraction"; /** * Inline source-byte budget for the whole request, in MB. A clip is * delivered natively only while its bytes plus every inline part already in * the request stay within it; otherwise it falls back to keyframes. * Meaningless when `supportsNativeVideo` is false, and set to 0 there rather * than to a number that reads like a real limit. */ readonly maxSizeMB: number; /** Longest clip accepted natively, in seconds. 0 when not applicable. */ readonly maxDurationSec: number; /** Whether the provider hears the video's audio track as well as seeing it. */ readonly supportsAudio: boolean; /** * Keyframe budget to aim for when this provider gets frames instead of the * video. Advisory: an explicit `videoOptions.frames` always wins. */ readonly recommendedFrameCount: number; }; /** * Media collected during file detection that a provider may be able to * consume directly, rather than as a text summary. * * Grouped rather than passed as two more positional parameters: the message * converters already take text, images, PDFs, provider and model, and each * new modality added one more argument to a call nobody could read. A bag * also means the next modality is a field, not another signature change at * every call site. */ export type NativeMediaAttachments = { readonly audio?: MultimodalAudioEntry[]; readonly video?: MultimodalVideoEntry[]; /** * Transcode target for an incompatible image (see * `GenerateOptions.imageOptions`). Not itself a native attachment, but * bundled here for the same reason audio and video are: it is one more * per-request option the message converters need at the same call sites. */ readonly outputFormat?: VisionImageOutputFormat; }; /** * Outcome of asking whether one video may be handed to one provider as bytes. * * A plain boolean collapsed "this provider never watches video" with "this * provider would have, but the clip is 400 MB" — and the two want different * log lines and different advice. `reason` is populated exactly when * `deliver` is false, and is phrased for a user to read. * * The accepting arm declares `reason?: undefined` rather than omitting the * field. Not decoration: `build:react-hooks` type-checks this graph without * `--strict`, and there a negated boolean-literal discriminant does not * narrow, so `decision.reason` inside `if (!decision.deliver)` fails to * compile unless the property exists on both arms. Declaring it keeps the * union exact under strict and compilable under both. */ export type VideoDeliveryDecision = { readonly deliver: true; readonly reason?: undefined; } | { readonly deliver: false; readonly reason: string; }; /** * Broad category a file format belongs to, as a human would name it. * * Distinct from {@link FileType}, which is the *routing* type the detector * emits. The two deliberately differ where one processor handles several * formats: an .odt has `modality: "document"` but `fileType: "docx"`, and a * .svg has `modality: "image"` but `fileType: "svg"` because it is sanitised as * markup rather than sent to a vision API. */ export type FileModality = "image" | "audio" | "video" | "document" | "data" | "archive"; /** * One format in the canonical file-type registry. * * `extensions[0]` and `mimeTypes[0]` are canonical; the remaining entries are * aliases accepted on input. See `processors/config/fileTypeRegistry.ts`. */ export type FileFormatEntry = { /** Human-readable format name, used in registry-conflict errors. */ readonly label: string; /** Extensions with leading dots, lowercase; first is canonical. */ readonly extensions: readonly string[]; /** MIME types, lowercase; first is canonical. */ readonly mimeTypes: readonly string[]; /** Routing type the detector emits for this format. */ readonly fileType: FileType; /** Category a human would put this format in. */ readonly modality: FileModality; }; /** * File with metadata — allows callers to pass filename alongside a Buffer. * * This is the recommended way for applications (e.g. Slack bots) to pass * files that were downloaded as Buffers but still have original filenames. * * @example * ```typescript * files: [ * { buffer: pdfBuffer, filename: "quarterly-report.pdf" }, * { buffer: videoBuffer, filename: "meeting-recording.mov", mimetype: "video/quicktime" } * ] * ``` */ export type FileWithMetadata = { buffer: Buffer; filename: string; mimetype?: string; }; /** * File input can be Buffer, string (path/URL/data URI), or an object with metadata. */ export type FileInput = Buffer | string | FileWithMetadata; /** * File source type for tracking input origin */ export type FileSource = "url" | "path" | "buffer" | "datauri"; /** * File detection result with confidence scoring */ export type FileDetectionResult = { type: FileType; mimeType: string; extension: string | null; source: FileSource; metadata: { size?: number; filename?: string; confidence: number; }; }; /** * File processing result after detection and conversion */ export type FileProcessingResult = { type: FileType; content: string | Buffer; mimeType: string; /** * Additional images extracted from the file (e.g., video keyframes, audio * cover art). * * An entry may carry alt text, which is how a video keyframe states the * timestamp it was sampled at. Bare bytes still work — the field was * widened, not changed. */ images?: Array<Buffer | string | ImageWithAltText>; metadata: { confidence: number; size?: number; filename?: string; extension?: string | null; rowCount?: number; totalLines?: number; columnCount?: number; columnNames?: string[]; sampleData?: string | unknown[]; hasEmptyColumns?: boolean; /** Enhanced column metadata with type detection and statistics */ columnMetadata?: CSVColumnMetadata[]; /** Data quality warnings */ dataQualityWarnings?: CSVDataQualityWarning[]; /** Overall data quality score (0-100) */ dataQualityScore?: number; /** Whether headers were detected */ hasHeaders?: boolean; /** Detected delimiter */ detectedDelimiter?: string; /** Detected (or overridden) character encoding used to decode the CSV (#362) */ detectedEncoding?: string; /** Confidence (0-100) of the detected encoding (#362) */ encodingConfidence?: number; /** Original→sanitized column-name mapping when sanitizeColumnNames is on (#378) */ columnNameMapping?: Array<{ original: string; sanitized: string; }>; /** True when the parse hit its time budget and returned partial rows (#379) */ parseTimedOut?: boolean; version?: string; estimatedPages?: number | null; provider?: string; apiType?: PDFAPIType; /** Provider's citations requirement for visual PDF analysis (#349). */ requiresCitations?: boolean | "auto"; officeFormat?: OfficeDocumentType; pageCount?: number; slideCount?: number; sheetCount?: number; sheetNames?: string[]; author?: string; createdDate?: string; modifiedDate?: string; hasFormulas?: boolean; hasImages?: boolean; frameCount?: number; hasKeyframes?: boolean; /** * Clip length in seconds, when the processor could measure it. * * Surfaced here so the native-delivery gate does not have to re-probe a * container the processor has already opened. Absent when probing failed * — an unknown duration, not a zero-length clip. */ durationSec?: number; /** * Media duration in seconds, read from the container header. * * Sourced from the demuxed stream rather than from the transcription * backend, so it is present for every audio file — including one that was * never transcribed because no provider was configured. */ duration?: number; /** * Language of the transcript, as reported by the transcription provider * (BCP-47 or ISO-639-1 depending on the backend), or the language the * caller pinned via `AudioProcessorOptions.language`. * * Absent when the file was not transcribed — its absence therefore means * "unknown", never "not speech". */ language?: string; /** * Character count of the transcript. * * `0` is a meaningful value distinct from `undefined`: it means a provider * ran and returned an empty transcript (silence, music, no speech), where * `undefined` means no transcription was attempted at all. */ transcriptionLength?: number; /** Which backend produced the transcript, e.g. "openai-whisper". */ transcriptionProvider?: string; }; }; /** * Sample data format options for CSV metadata * - 'json': JSON string representation (default, backward compatible) * - 'object': Structured array of row objects (best for programmatic use) * - 'csv': CSV formatted string preview * - 'markdown': Markdown table format */ export type SampleDataFormat = "object" | "json" | "csv" | "markdown"; /** * Detected data type for a CSV column */ export type CSVColumnDataType = "string" | "number" | "integer" | "float" | "boolean" | "date" | "datetime" | "email" | "url" | "empty" | "mixed"; /** * Data quality warning for CSV columns */ export type CSVDataQualityWarning = { column: string; type: "empty_values" | "invalid_name" | "mixed_types" | "high_null_rate" | "duplicates" | "inconsistent_format"; message: string; severity: "info" | "warning" | "error"; affectedRows?: number; }; /** * Rich metadata for a single CSV column */ export type CSVColumnMetadata = { name: string; /** Original header text before sanitization, when sanitizeColumnNames rewrote it (#378) */ originalName?: string; index: number; detectedType: CSVColumnDataType; /** Confidence of type detection (0-100) */ typeConfidence: number; /** Count of null/empty values */ nullCount: number; /** Count of unique values */ uniqueCount: number; /** Sample values from this column (up to 5) */ sampleValues: string[]; /** For numeric columns: min value */ minValue?: number; /** For numeric columns: max value */ maxValue?: number; /** For numeric columns: average value */ avgValue?: number; /** For date columns: detected format (e.g., 'YYYY-MM-DD', 'MM/DD/YYYY') */ dateFormat?: string; /** Column name validation issues */ nameIssues?: string[]; }; /** A parsed CSV row: string-keyed with string (or missing) cell values (#384). */ export type CSVRow = Record<string, string | undefined>; /** * How `CSVProcessor.parseCSVFile` opens the file. * * `followSymlinks: false` opens with O_NOFOLLOW, so a symlink at the final * path component is refused (ELOOP) rather than followed. The sandboxed * analyzeCSV tool passes it after containment has resolved the real path, * closing the window between that check and the open. Defaults to `true`; on * Windows, where O_NOFOLLOW does not exist, the option has no effect. */ export type CSVFileOpenOptions = { followSymlinks?: boolean; }; /** Result of decoding a buffer with encoding detection (#362). */ export type DecodedBuffer = { /** Decoded text with any BOM removed. */ text: string; /** iconv-lite label actually used to decode. */ encoding: string; /** Detection confidence 0-100 (100 for BOM/override, 0 for the UTF-8 fallback). */ confidence: number; }; /** * CSV processor options */ export type CSVProcessorOptions = { maxRows?: number; formatStyle?: "raw" | "markdown" | "json"; includeHeaders?: boolean; sampleDataFormat?: SampleDataFormat; extension?: string | null; /** * Character encoding override (#362). When omitted, the encoding is detected * from a BOM then `chardet`, falling back to UTF-8. Accepts any label * `iconv-lite` supports (e.g. "utf-8", "utf-16le", "windows-1252", "latin1"). */ encoding?: string; /** * Rewrite column headers into valid identifiers (#378). Opt-in; default false * preserves the raw header strings as object keys. */ sanitizeColumnNames?: boolean; /** Case style used when `sanitizeColumnNames` is on (#378). Default "snake_case". */ columnNameCase?: "camelCase" | "snake_case"; /** * Wall-clock cap for the streaming parse in milliseconds (#379). On timeout the * parse returns the rows collected so far and flags `metadata.parseTimedOut`, * rather than hanging forever. Defaults: 30s for strings, 5min for files. */ parseTimeoutMs?: number; /** * Skip blank / whitespace-only data rows (#373). Default `true`: blank lines * are excluded from the returned content (including raw CSV text) and from * `metadata.rowCount`. Set to `false` to preserve empty lines literally. */ skipEmptyLines?: boolean; }; /** * PDF API types for different providers */ export type PDFAPIType = "document" | "files-api" | "unsupported"; /** * PDF provider configuration */ export type PDFProviderConfig = { maxSizeMB: number; maxPages: number; supportsNative: boolean; /** * Whether this provider needs source citations enabled for visual PDF * analysis (#349). `"auto"` = enable when the request requires visual * grounding (currently Bedrock's Converse document blocks); `false` = the * provider handles PDFs without an explicit citations flag. Surfaced on * `FileProcessingResult.metadata.requiresCitations` so downstream provider * adapters can act on it instead of the value being dead config. */ requiresCitations: boolean | "auto"; apiType: PDFAPIType; }; /** * PDF processor options */ export type PDFProcessorOptions = { provider?: string; model?: string; maxSizeMB?: number; bedrockApiMode?: "converse" | "invokeModel"; /** * Whether to enforce page limits by throwing an error (default: true) * Set to false to bypass limit enforcement (logs warning instead) */ enforceLimits?: boolean; /** Password for an encrypted PDF (used on the image-conversion path) (#258). */ password?: string; }; /** * Audio provider configuration for transcription services * * Describes the capabilities and limitations of each audio transcription provider * (e.g., OpenAI Whisper, Google Speech-to-Text, Azure Speech Services). * * @example OpenAI Whisper configuration * ```typescript * const openaiConfig: AudioProviderConfig = { * maxSizeMB: 25, * maxDurationSeconds: 600, * supportedFormats: ['mp3', 'mp4', 'm4a', 'wav', 'webm'], * supportsLanguageDetection: true, * requiresApiKey: true, * costPer60s: 0.006 // $0.006 per minute * }; * ``` * * @example Google Speech-to-Text configuration * ```typescript * const googleConfig: AudioProviderConfig = { * maxSizeMB: 10, * maxDurationSeconds: 480, * supportedFormats: ['flac', 'wav', 'mp3', 'ogg'], * supportsLanguageDetection: true, * requiresApiKey: true, * costPer15s: 0.004 // $0.016 per minute ($0.004 per 15 seconds) * }; * ``` */ export type AudioProviderConfig = { /** Maximum audio file size in megabytes */ maxSizeMB: number; /** Maximum audio duration in seconds */ maxDurationSeconds: number; /** Supported audio formats (e.g., 'mp3', 'wav', 'm4a', 'flac', 'ogg') */ supportedFormats: string[]; /** Whether the provider supports automatic language detection */ supportsLanguageDetection: boolean; /** Whether the provider requires an API key for authentication */ requiresApiKey: boolean; /** Optional: Cost per 60 seconds of audio in USD */ costPer60s?: number; /** Optional: Cost per 15 seconds of audio in USD */ costPer15s?: number; }; /** * A transcription backend `AudioProcessor` can drive (#413). * * Deliberately narrower than `AudioProcessorOptions.provider`, which stays a * free-form `string` on the public surface for backward compatibility: a * caller's spelling is normalised onto this union (accepting aliases such as * "whisper" for "openai") and an unrecognised one is reported rather than * silently ignored. */ export type AudioTranscriptionProvider = "openai" | "google" | "azure"; /** * Outcome of choosing a transcription backend (#413). * * A union rather than a nullable provider so the "nothing usable" case is * forced to carry the reason — the failure mode this replaces was every * unavailable backend collapsing into an indistinguishable empty result. */ export type AudioTranscriptionSelection = { provider: AudioTranscriptionProvider; label: string; reason?: undefined; } | { provider?: undefined; label?: undefined; reason: string; }; /** * What a transcription attempt produced (#409/#416). * * `transcriptionSkippedReason` is present on exactly the attempts that yielded * no transcript, so a caller can tell "no speech in this audio" from "no * backend was reachable". */ export type AudioTranscriptionOutcome = { transcript: string | undefined; hasTranscript: boolean; transcriptionProvider: string | undefined; transcriptionLanguage: string | undefined; transcriptionDuration: number | undefined; transcriptionSkippedReason: string | undefined; }; /** * Audio processor options */ export type AudioProcessorOptions = { /** AI provider to use for transcription (e.g., 'openai', 'google', 'azure') */ provider?: string; /** Transcription model to use (e.g., 'whisper-1', 'chirp-3') */ transcriptionModel?: string; /** Language code for transcription (e.g., 'en', 'es', 'fr') */ language?: string; /** OpenAI/Whisper-only context prompt to guide transcription accuracy; ignored by Google and Azure */ prompt?: string; /** Maximum audio duration in seconds (default: 600) */ maxDurationSeconds?: number; /** Maximum file size in megabytes */ maxSizeMB?: number; }; /** * Keyframe-extraction knobs for an attached video (#478). * * These back the `--video-frames` / `--video-quality` / `--video-format` CLI * flags and `GenerateOptions.videoOptions`. Each is clamped to the processor's * own ceiling — a caller cannot raise `frames` above VIDEO_CONFIG.MAX_FRAMES. */ export type VideoProcessorOptions = { /** Max keyframes to extract. Clamped to the processor's MAX_FRAMES ceiling. */ frames?: number; /** Encoder quality 1-100 for the extracted frames. */ quality?: number; /** Frame encoding. Defaults to jpeg. */ format?: "jpeg" | "png"; /** * Transcribe the clip's spoken audio (#433). Off by default: it costs an * ffmpeg pass plus a Whisper call, and most attached video is silent * screen capture. * * Best-effort — a clip with no audio track, no `OPENAI_API_KEY`, or a * failed call still processes, and the reason lands in * `ProcessedVideo.transcriptionSkippedReason`. */ transcribeAudio?: boolean; }; /** * Office processor options for Word, PowerPoint, and Excel documents * * @example Word document processing (docx) * ```typescript * const options: OfficeProcessorOptions = { * format: "docx", * extractTextOnly: false, * includeMetadata: true * }; * ``` * * @example PowerPoint processing (pptx) * ```typescript * const options: OfficeProcessorOptions = { * format: "pptx", * includeSlideNotes: true, // pptx-specific * includeMetadata: true * }; * ``` * * @example Excel processing (xlsx) * ```typescript * const options: OfficeProcessorOptions = { * format: "xlsx", * processAllSheets: true, // xlsx-specific * includeMetadata: true * }; * ``` */ export type OfficeProcessorOptions = { /** Office document format type */ format?: OfficeDocumentType; /** Whether to extract text only (true) or preserve formatting (false). Applies to: docx, pptx, xlsx */ extractTextOnly?: boolean; /** Maximum file size in megabytes. Applies to: docx, pptx, xlsx */ maxSizeMB?: number; /** Whether to include metadata (author, created date, etc.). Applies to: docx, pptx, xlsx */ includeMetadata?: boolean; /** For spreadsheets (xlsx only): whether to process all sheets or just the first */ processAllSheets?: boolean; /** For presentations (pptx only): whether to include slide notes */ includeSlideNotes?: boolean; /** * For spreadsheets (xlsx only): restrict processing to the named sheet. * When the workbook has no sheet by that name the result says so and lists * the names it does have, rather than silently falling back to every sheet. * Omit to process every sheet (the default). */ sheetName?: string; /** * For spreadsheets (xlsx only): how sheet data is rendered into the prompt. * - `raw` (default): tab-separated preview — the long-standing behaviour * - `csv`: comma-separated values with RFC 4180 quoting * - `markdown`: a markdown table per sheet * - `json`: an array of row objects keyed by the header row */ formatStyle?: "raw" | "markdown" | "json" | "csv"; }; /** * File detector options */ export type FileDetectorOptions = { maxSize?: number; timeout?: number; allowedTypes?: FileType[]; /** * When set, local file paths must resolve inside this base directory; * anything that escapes it (absolute path, `../` traversal, or a symlink * pointing outside) is rejected. Containment is enforced on the real, * symlink-resolved path of both the base dir and the target, so a symlink * inside the base cannot be used to reach a file outside it. Servers that * accept file paths from untrusted callers should set this to sandbox * filesystem access; SDK callers loading their own files can omit it. */ allowedBaseDir?: string; audioOptions?: AudioProcessorOptions; csvOptions?: CSVProcessorOptions; officeOptions?: OfficeProcessorOptions; videoOptions?: VideoProcessorOptions; confidenceThreshold?: number; provider?: string; /** Maximum number of retry attempts for network requests (default: 3) */ maxRetries?: number; /** Initial retry delay in milliseconds with exponential backoff (default: 1000) */ retryDelay?: number; /** * Caller-provided MIME type hint (e.g. "text/plain", "application/json"). * Used when the filename has no extension and magic-byte detection cannot * identify the content — the common Slack/Curator extension-less-buffer * case. When set to a trustworthy mimetype (not "application/octet-stream"), * it short-circuits the detection strategy loop with a high-confidence * result so small files on the eager file-processing path still honor the * hint (the lazy FileReferenceRegistry path has its own hint-handling). */ mimetypeHint?: string; /** * Caller-provided filename hint, the companion to {@link mimetypeHint}. * * The unified file path unwraps a `FileWithMetadata` to its `buffer` before * detection runs, so the object's `filename` is gone by the time extension * resolution looks for one — and TAR in particular cannot be identified any * other way, because its "ustar" marker sits at byte 257 rather than at * offset 0. Passing the name alongside the bytes keeps `.odp`, `.rtf` and * `.tar` routed to the processors that can actually read them. */ filenameHint?: string; }; /** * Google AI Studio Files API types */ export type GoogleFilesAPIUploadResult = { file: { name: string; displayName: string; mimeType: string; sizeBytes: string; createTime: string; updateTime: string; expirationTime: string; sha256Hash: string; uri: string; }; }; /** Options for converting PDF pages to images. */ export type PDFImageConversionOptions = { /** Scale factor for image quality (1-4, default: 2) */ scale?: number; /** Maximum number of pages to convert (default: 20 from PDF_LIMITS.DEFAULT_MAX_PAGES) */ maxPages?: number; /** Output format (default: png). Only PNG is currently implemented by PDFProcessor. */ format?: "png"; /** * Per-page pixel ceiling (#260). Any page whose width×height×scale² would * exceed this is uniformly downscaled to stay under it, preventing a huge * page from allocating gigabytes of canvas. Default: PDF_LIMITS.DEFAULT_MAX_CANVAS_PIXELS. */ maxCanvasPixels?: number; /** Password for an encrypted PDF (passed to the underlying renderer) (#258). */ password?: string; /** Per-page progress callback invoked as each page is rendered (#302). */ onProgress?: (progress: PDFImageConversionProgress) => void | Promise<void>; }; /** Progress reported per page during streaming conversion (#302). */ export type PDFImageConversionProgress = { /** Number of pages successfully converted so far. */ pagesConverted: number; /** Total pages in the document (known up-front from the renderer). */ totalPages: number; /** Elapsed time since conversion started, in milliseconds. */ elapsedMs: number; }; /** A single streamed page result (#302). `error` is set when that page failed. */ export type PDFImagePage = { /** 1-based page index. */ pageIndex: number; /** Base64-encoded PNG for the page (empty string when `error` is set). */ image: string; /** Byte size of the rendered PNG (0 when `error` is set). */ imageSizeBytes: number; /** Populated when this page failed to render (#294). */ error?: string; }; /** * A PDF opened for page rendering. Rendering goes through pdf-parse so the * process loads exactly one pdfjs-dist copy — a second copy fails pdfjs's * API-vs-Worker version check for whichever library loads second. */ export type PDFRenderDocument = { /** Total pages in the document. */ length: number; /** Render a 1-based page to a PNG buffer. */ getPage: (pageNumber: number) => Promise<Buffer>; /** Release pdfjs resources held by the document. */ destroy: () => Promise<void>; }; /** * A single PDF queued for multimodal message building, normalised from either * submission surface — `input.pdfFiles` or `input.content` with `type: "pdf"` * — so both can share the aggregate page/size guard (#309). */ export type MultimodalPdfEntry = { /** Raw PDF bytes. */ buffer: Buffer; /** Display name; may be a full path, so log only its basename. */ filename: string; /** * Page count when known. Null/undefined on the `input.content` path whenever * the caller omitted `metadata.pages`; the aggregate guard resolves those * from `buffer` rather than treating them as zero. */ pageCount?: number | null; /** Password for an encrypted PDF (#258). */ password?: string; /** Per-page pixel ceiling for the image fallback (#260). */ maxCanvasPixels?: number; /** Render scale for the image fallback (#297). */ scale?: number; /** Max pages converted by the image fallback (#297). */ maxPages?: number; }; /** Result of PDF to image conversion. */ export type PDFImageConversionResult = { /** Array of base64-encoded PNG images (one per successfully converted page) */ images: string[]; /** Number of pages converted */ pageCount: number; /** Total conversion time in milliseconds */ conversionTimeMs: number; /** Any warnings during conversion */ warnings?: string[]; /** Per-page failures — present only when some pages failed to render (#294). */ errors?: Array<{ page: number; error: string; }>; }; /** Options for filename sanitization. */ export type SanitizeFileNameOptions = { /** Maximum length for the filename (default: 255) */ maxLength?: number; /** Replacement character for invalid chars (default: '_') */ replacement?: string; /** Whether to block dangerous extensions (default: true) */ blockDangerousExtensions?: boolean; /** Whether to allow hidden files starting with dot (default: false) */ allowHiddenFiles?: boolean; }; /** Options for display name sanitization. */ export type SanitizeDisplayNameOptions = { /** Maximum length for the name (default: 100) */ maxLength?: number; /** Whether to allow unicode characters (default: true) */ allowUnicode?: boolean; }; /** Result of SVG sanitization. */ export type SvgSanitizationResult = { /** Sanitized SVG content */ content: string; /** Items that were removed during sanitization */ removedItems: string[]; /** Whether any content was modified */ wasModified: boolean; }; /** Contract implemented by each file-detection strategy. */ export type DetectionStrategy = { detect(input: FileInput): Promise<FileDetectionResult>; };