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.
This commit is contained in:
Jinjing 2026-04-25 16:45:01 -07:00 committed by GitHub
parent 3bf2b31c96
commit 50ebd11003
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 31 additions and 6 deletions

View File

@ -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'),

View File

@ -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

View File

@ -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 }
}