refactor: narrow web image input v1

This commit is contained in:
Tianyi Cui
2026-07-29 23:18:57 +08:00
parent 8348a409aa
commit 3a4c436d83
69 changed files with 280 additions and 613 deletions
@@ -6,19 +6,13 @@ import z from 'schemastery'
import { AttachmentStore } from '@deepseek-ai/dsh-attachment'
import type { ImageAttachmentLimits, ImageAttachmentRef, SaveImageAttachment, StoredImageAttachment } from '@deepseek-ai/dsh-attachment'
import { resolveDshHome } from '@deepseek-ai/dsh-paths'
import { readImageFile, saveImageFile, validateImageFile } from './store.ts'
import { readImageFile, saveImageFile } from './store.ts'
export { detectImage } from './image.ts'
export { readImageFile, saveImageFile, validateImageFile } from './store.ts'
export { AttachmentError } from '@deepseek-ai/dsh-attachment'
export type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment'
export { readImageFile, saveImageFile } from './store.ts'
/** Default maximum encoded bytes for one image. */
export const DEFAULT_MAX_IMAGE_BYTES = 5 * 1024 * 1024
/** Default maximum images in one prompt. */
export const DEFAULT_MAX_IMAGES_PER_MESSAGE = 10
/** Default maximum aggregate image bytes in one prompt. */
export const DEFAULT_MAX_MESSAGE_IMAGE_BYTES = 20 * 1024 * 1024
/** Default maximum intrinsic pixels for one image. */
export const DEFAULT_MAX_IMAGE_PIXELS = 40_000_000
@@ -28,10 +22,6 @@ export interface Config {
dshHome?: string
/** Maximum encoded bytes accepted for one image. */
maxImageBytes?: number
/** Maximum image count accepted in one submitted message. */
maxImagesPerMessage?: number
/** Maximum aggregate encoded image bytes accepted in one submitted message. */
maxMessageImageBytes?: number
/** Maximum intrinsic width multiplied by height accepted for one image. */
maxImagePixels?: number
}
@@ -41,8 +31,6 @@ export class LocalAttachmentStore extends AttachmentStore {
static Config: z<Config> = z.object({
dshHome: z.string(),
maxImageBytes: z.number().step(1).min(1).default(DEFAULT_MAX_IMAGE_BYTES),
maxImagesPerMessage: z.number().step(1).min(1).default(DEFAULT_MAX_IMAGES_PER_MESSAGE),
maxMessageImageBytes: z.number().step(1).min(1).default(DEFAULT_MAX_MESSAGE_IMAGE_BYTES),
maxImagePixels: z.number().step(1).min(1).default(DEFAULT_MAX_IMAGE_PIXELS),
})
@@ -55,17 +43,11 @@ export class LocalAttachmentStore extends AttachmentStore {
this.root = resolve(join(resolveDshHome(config.dshHome), 'attachments', 'v1'))
this.imageLimits = Object.freeze({
maxImageBytes: config.maxImageBytes ?? DEFAULT_MAX_IMAGE_BYTES,
maxImagesPerMessage: config.maxImagesPerMessage ?? DEFAULT_MAX_IMAGES_PER_MESSAGE,
maxMessageImageBytes: config.maxMessageImageBytes ?? DEFAULT_MAX_MESSAGE_IMAGE_BYTES,
maxImagePixels: config.maxImagePixels ?? DEFAULT_MAX_IMAGE_PIXELS,
mediaTypes: Object.freeze(['image/png', 'image/jpeg', 'image/webp', 'image/gif'] as const),
})
}
validateImage(input: SaveImageAttachment): void {
validateImageFile(input, this.imageLimits)
}
async saveImage(input: SaveImageAttachment): Promise<ImageAttachmentRef> {
return saveImageFile(this.root, input, this.imageLimits)
}
@@ -52,15 +52,6 @@ function validateAdmission(metadata: Omit<ImageAttachmentRef, 'attachmentId' | '
}
}
/**
* Run the full admission policy for one image without touching storage.
* @param input - encoded bytes and declared metadata.
* @param limits - resolved storage policy.
*/
export function validateImageFile(input: SaveImageAttachment, limits: ImageAttachmentLimits): void {
validateAdmission(inspectMetadata(input.data, input.mediaType), limits)
}
/**
* Make a directory's entries durable (fsync on a read-only directory handle).
* A synced file alone does not survive a crash when its directory entry never