From 50ebd110031ef7d8fc6decabde1af74f516d54d6 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Sat, 25 Apr 2026 16:45:01 -0700 Subject: [PATCH] feat(fs): raise preview size cap for PDFs and images (#1096) Local IPC bumps to 50MB; SSH relay bumps to 10MB (bounded by the 16MB JSON-RPC frame cap). Text and search paths keep the 5MB cap. --- src/main/ipc/filesystem.ts | 15 ++++++++++++--- src/relay/fs-handler-utils.ts | 14 ++++++++++++++ src/relay/fs-handler.ts | 8 +++++--- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/main/ipc/filesystem.ts b/src/main/ipc/filesystem.ts index e59f91261..9247e2038 100644 --- a/src/main/ipc/filesystem.ts +++ b/src/main/ipc/filesystem.ts @@ -45,6 +45,14 @@ import { getSshFilesystemProvider } from '../providers/ssh-filesystem-dispatch' import { getSshGitProvider } from '../providers/ssh-git-dispatch' const MAX_FILE_SIZE = 5 * 1024 * 1024 // 5MB +// Why: previewable binaries (PDFs, images) are rendered by the viewer as +// base64 blobs, not parsed as text — 5MB is tight for real-world PDFs, and +// raising this cap only affects binary preview, not text/search paths. +// The relay (SSH) uses a smaller 10MB cap because its JSON-RPC frames are +// bounded by MAX_MESSAGE_SIZE = 16MB; the local IPC path has no such limit, +// so 50MB covers real-world PDFs (specs, datasheets, image-heavy contracts). +// See src/relay/fs-handler-utils.ts for the remote-side reasoning. +const MAX_PREVIEWABLE_BINARY_SIZE = 50 * 1024 * 1024 // 50MB const DEFAULT_SEARCH_MAX_RESULTS = 2000 const MAX_MATCHES_PER_FILE = 100 const SEARCH_TIMEOUT_MS = 15000 @@ -124,14 +132,15 @@ export function registerFilesystemHandlers(store: Store): void { } const filePath = await resolveAuthorizedPath(args.filePath, store) const stats = await stat(filePath) - if (stats.size > MAX_FILE_SIZE) { + const mimeType = PREVIEWABLE_BINARY_MIME_TYPES[extname(filePath).toLowerCase()] + const sizeLimit = mimeType ? MAX_PREVIEWABLE_BINARY_SIZE : MAX_FILE_SIZE + if (stats.size > sizeLimit) { throw new Error( - `File too large: ${(stats.size / 1024 / 1024).toFixed(1)}MB exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit` + `File too large: ${(stats.size / 1024 / 1024).toFixed(1)}MB exceeds ${sizeLimit / 1024 / 1024}MB limit` ) } const buffer = await readFile(filePath) - const mimeType = PREVIEWABLE_BINARY_MIME_TYPES[extname(filePath).toLowerCase()] if (mimeType) { return { content: buffer.toString('base64'), diff --git a/src/relay/fs-handler-utils.ts b/src/relay/fs-handler-utils.ts index f400656d7..984221543 100644 --- a/src/relay/fs-handler-utils.ts +++ b/src/relay/fs-handler-utils.ts @@ -11,6 +11,20 @@ import { execFile, type ChildProcess } from 'child_process' // ─── Constants ─────────────────────────────────────────────────────── export const MAX_FILE_SIZE = 5 * 1024 * 1024 +// Why: previewable binaries (PDFs, images) are rendered by the viewer as +// base64 blobs, not parsed as text — 5MB is tight for real-world PDFs, and +// raising this cap only affects binary preview, not text/search paths. +// Why 10MB (not 50MB like the local main-process cap): the SSH relay ships +// every JSON-RPC response in a single framed message capped at +// MAX_MESSAGE_SIZE = 16MB (see src/relay/protocol.ts and +// src/main/ssh/relay-protocol.ts). A file here is sent as base64 inside JSON, +// so 10MB on disk → ~13.3MB base64 → ~13.4MB framed payload, leaving headroom +// under the 16MB frame cap. Raising this cap without first landing streaming +// reads would cause the encoder to throw "Message too large" and the decoder +// to discard oversized frames for borderline files. The proper path to a +// higher remote cap is streaming fs.readFile over the relay, not bumping +// MAX_MESSAGE_SIZE (which would introduce head-of-line blocking on the mux). +export const MAX_PREVIEWABLE_BINARY_SIZE = 10 * 1024 * 1024 export const SEARCH_TIMEOUT_MS = 15_000 export const MAX_MATCHES_PER_FILE = 100 export const DEFAULT_MAX_RESULTS = 2000 diff --git a/src/relay/fs-handler.ts b/src/relay/fs-handler.ts index bb2ca2297..798d176be 100644 --- a/src/relay/fs-handler.ts +++ b/src/relay/fs-handler.ts @@ -17,6 +17,7 @@ import type { RelayContext } from './context' import { expandTilde } from './context' import { MAX_FILE_SIZE, + MAX_PREVIEWABLE_BINARY_SIZE, DEFAULT_MAX_RESULTS, IMAGE_MIME_TYPES, isBinaryBuffer, @@ -82,14 +83,15 @@ export class FsHandler { const filePath = expandTilde(params.filePath as string) await this.context.validatePathResolved(filePath) const stats = await stat(filePath) - if (stats.size > MAX_FILE_SIZE) { + const mimeType = IMAGE_MIME_TYPES[extname(filePath).toLowerCase()] + const sizeLimit = mimeType ? MAX_PREVIEWABLE_BINARY_SIZE : MAX_FILE_SIZE + if (stats.size > sizeLimit) { throw new Error( - `File too large: ${(stats.size / 1024 / 1024).toFixed(1)}MB exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit` + `File too large: ${(stats.size / 1024 / 1024).toFixed(1)}MB exceeds ${sizeLimit / 1024 / 1024}MB limit` ) } const buffer = await readFile(filePath) - const mimeType = IMAGE_MIME_TYPES[extname(filePath).toLowerCase()] if (mimeType) { return { content: buffer.toString('base64'), isBinary: true, isImage: true, mimeType } }