mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
- gate model selection on steering-placement image carriers from enqueue until their steering/message event publishes; release the gate when an admission ends idle without publication (both behaviorally asserted) - reject session.updateQueue edits carrying non-text blocks at the RPC boundary (queue edits cannot bypass image admission) - extend the durable-directory walk past a first-created DSH_HOME to the deepest pre-existing ancestor - strip Windows-style separators from attachment display names on POSIX - verify attachment reads with a header-only probe (digest already proves the bytes decoded fully at admission); document the read path - make SessionInputShell.addImages refusal observable and keep workspace transfers/composer intake from leaking refused drafts - own ONE recursive image walk (dsh-llm contentHasImage) across apiproxy, pi-ai, compact-basic, and the DeepSeek text-only assertion - drop the redundant canonical-base64 regex and the no-op role read - move AttachmentId/AttachmentError out of types.ts (brand.ts/error.ts); document why AttachmentError does not extend HarnessError - document the hard attachments inject in both consumer READMEs
67 lines
2.5 KiB
TypeScript
67 lines
2.5 KiB
TypeScript
/** Raster inspection: full decode at admission, header-only probe on verified reads. */
|
|
|
|
import sharp, { type Sharp } from 'sharp'
|
|
import { AttachmentError } from '@deepseek-ai/dsh-attachment'
|
|
import type { ImageMediaType } from '@deepseek-ai/dsh-attachment'
|
|
|
|
/** Decoded metadata from a supported image. */
|
|
export interface DetectedImage {
|
|
mediaType: ImageMediaType
|
|
width: number
|
|
height: number
|
|
}
|
|
|
|
const MEDIA_TYPES: Readonly<Record<string, ImageMediaType>> = {
|
|
png: 'image/png',
|
|
jpeg: 'image/jpeg',
|
|
webp: 'image/webp',
|
|
gif: 'image/gif',
|
|
}
|
|
|
|
async function imageMetadata(image: Sharp): Promise<DetectedImage> {
|
|
const metadata = await image.metadata()
|
|
const mediaType = MEDIA_TYPES[metadata.format as string]
|
|
if (mediaType === undefined) {
|
|
throw new AttachmentError('Unsupported or malformed image data.', 'INVALID_IMAGE')
|
|
}
|
|
return { mediaType, width: metadata.width, height: metadata.height }
|
|
}
|
|
|
|
/**
|
|
* Parse a supported raster's header and return its intrinsic metadata without
|
|
* decoding pixels. Digest-verified reads use this: admission already proved
|
|
* that these exact bytes decode completely, so the read path only re-derives
|
|
* the reference fields instead of paying the full-raster decode again.
|
|
* @param data - complete encoded image bytes.
|
|
* @returns verified format and dimensions.
|
|
*/
|
|
export async function probeImage(data: Uint8Array): Promise<DetectedImage> {
|
|
try {
|
|
return await imageMetadata(sharp(data, { failOn: 'error', limitInputPixels: false }))
|
|
} catch (error) {
|
|
if (error instanceof AttachmentError) throw error
|
|
throw new AttachmentError('Unsupported or malformed image data.', 'INVALID_IMAGE', { cause: error })
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fully decode a supported raster and return its intrinsic metadata.
|
|
* @param data - complete encoded image bytes.
|
|
* @param maxPixels - decoded-pixel admission limit.
|
|
* @returns verified format and dimensions.
|
|
*/
|
|
export async function detectImage(data: Uint8Array, maxPixels?: number): Promise<DetectedImage> {
|
|
try {
|
|
const image = sharp(data, { failOn: 'error', limitInputPixels: false })
|
|
const detected = await imageMetadata(image)
|
|
if (maxPixels !== undefined && detected.width * detected.height > maxPixels) {
|
|
throw new AttachmentError('Image exceeds the configured decoded-pixel limit.', 'IMAGE_TOO_MANY_PIXELS')
|
|
}
|
|
await image.raw().toBuffer()
|
|
return detected
|
|
} catch (error) {
|
|
if (error instanceof AttachmentError) throw error
|
|
throw new AttachmentError('Unsupported or malformed image data.', 'INVALID_IMAGE', { cause: error })
|
|
}
|
|
}
|