mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-14 04:01:35 +00:00
217 lines
8.4 KiB
TypeScript
217 lines
8.4 KiB
TypeScript
/** Durable attachment storage seam (`ctx.attachments`). @module @deepseek-ai/dsh-attachment */
|
|
|
|
import { Context, Service } from '@deepseek-ai/cordis'
|
|
import { AttachmentError } from './error.ts'
|
|
import type {
|
|
FileAttachmentRef,
|
|
ImageAttachmentLimits,
|
|
ImageAttachmentRef,
|
|
ImageRequestPolicy,
|
|
RequestImageAttachment,
|
|
SaveFileAttachment,
|
|
SaveFileStreamAttachment,
|
|
SaveImageAttachment,
|
|
StoredImageAttachment,
|
|
} from './types.ts'
|
|
|
|
export { AttachmentId, ImageVariantId } from './brand.ts'
|
|
export { AttachmentError, isImageAdmissionError } from './error.ts'
|
|
export type { AttachmentErrorCode, ImageAdmissionErrorCode } from './error.ts'
|
|
export { admitEncodedFile, admitEncodedImages, admitPromptContent } from './admission.ts'
|
|
export { requestImageDimensions } from './request-projection.ts'
|
|
export type {
|
|
AttachmentId as AttachmentIdType,
|
|
AdmittedPromptContentPart,
|
|
EncodedFileAttachment,
|
|
EncodedImageAttachment,
|
|
FileAttachmentRef,
|
|
ImageAttachmentLimits,
|
|
ImageAttachmentRef,
|
|
ImageRequestPolicy,
|
|
ImageMediaType,
|
|
PromptContentPart,
|
|
RequestImageAttachment,
|
|
SaveFileAttachment,
|
|
SaveFileStreamAttachment,
|
|
SaveImageAttachment,
|
|
StoredImageAttachment,
|
|
} from './types.ts'
|
|
|
|
declare module '@deepseek-ai/cordis' {
|
|
interface Context {
|
|
attachments: AttachmentStore
|
|
}
|
|
}
|
|
|
|
/** Immutable binary attachment service. Implementations validate bytes before publishing a reference. */
|
|
export abstract class AttachmentStore extends Service {
|
|
constructor(ctx: Context) {
|
|
super(ctx, 'attachments')
|
|
}
|
|
|
|
/** Deployment-resolved image policy used by authoritative and fast-path validation. */
|
|
abstract readonly imageLimits: ImageAttachmentLimits
|
|
|
|
/**
|
|
* Validate one image without persisting it.
|
|
* Batch callers validate every member before saving any member.
|
|
* @param input - encoded bytes, declared media type, and optional display name.
|
|
* @returns completion after the encoded raster has been fully decoded.
|
|
*/
|
|
abstract validateImage(input: SaveImageAttachment): Promise<void>
|
|
|
|
/**
|
|
* Validate one ordered image batch before committing any member.
|
|
* Validation failures start no writes; storage failures return no partial
|
|
* references, although already published content-addressed objects may stay
|
|
* unreachable until a future retention policy collects them.
|
|
* @param inputs - encoded images in their owning message order.
|
|
* @returns durable references in the exact input order.
|
|
*/
|
|
protected validateImageBatch(inputs: readonly SaveImageAttachment[]): void {
|
|
const { maxImagesPerMessage, maxMessageImageBytes, mediaTypes } = this.imageLimits
|
|
if (inputs.length > maxImagesPerMessage) {
|
|
throw new AttachmentError('Image batch exceeds the configured image-count limit.', 'TOO_MANY_IMAGES')
|
|
}
|
|
const totalBytes = inputs.reduce((sum, input) => sum + input.data.byteLength, 0)
|
|
if (totalBytes > maxMessageImageBytes) {
|
|
throw new AttachmentError('Image batch exceeds the configured aggregate image-byte limit.', 'IMAGES_TOO_LARGE')
|
|
}
|
|
for (const input of inputs) {
|
|
if (!mediaTypes.includes(input.mediaType)) {
|
|
throw new AttachmentError(`Image type ${input.mediaType} is not accepted by this deployment.`, 'UNSUPPORTED_IMAGE_TYPE')
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate and durably commit one ordered image batch.
|
|
* @param inputs - encoded images in owning-message order.
|
|
* @returns durable normalized attachment references in the same order after every member succeeds.
|
|
*/
|
|
async saveImages(inputs: readonly SaveImageAttachment[]): Promise<readonly ImageAttachmentRef[]> {
|
|
this.validateImageBatch(inputs)
|
|
for (const input of inputs) await this.validateImage(input)
|
|
|
|
const refs: ImageAttachmentRef[] = []
|
|
for (const input of inputs) refs.push(await this.saveImage(input))
|
|
return refs
|
|
}
|
|
|
|
/**
|
|
* Validate and durably commit one image before its owning session event is appended.
|
|
* The returned reference describes the persisted normalized image. When
|
|
* normalization reduces the raster, its `originalDimensions` records the
|
|
* orientation-applied input dimensions.
|
|
* @param input - encoded bytes, declared media type, and optional display name.
|
|
* @returns the durable content-addressed normalized image reference.
|
|
*/
|
|
abstract saveImage(input: SaveImageAttachment): Promise<ImageAttachmentRef>
|
|
|
|
/**
|
|
* Read one image and verify that bytes still match the recorded reference.
|
|
* @param ref - durable reference from the session log.
|
|
* @param signal - optional cancellation for backend read and verification work.
|
|
* @returns the verified bytes and normalized attachment reference.
|
|
* @throws the signal reason when aborted, or a storage error when verification fails.
|
|
*/
|
|
abstract readImage(ref: ImageAttachmentRef, signal?: AbortSignal): Promise<StoredImageAttachment>
|
|
|
|
/**
|
|
* Locate the provider-owned normalized object in the harness host filesystem.
|
|
* @param ref - durable normalized attachment reference.
|
|
* @returns an absolute host path, or undefined when this backend is not host-file-backed.
|
|
* @throws an AttachmentError when the durable reference is invalid.
|
|
*/
|
|
imageHostPath(ref: ImageAttachmentRef): string | undefined {
|
|
void ref
|
|
return undefined
|
|
}
|
|
|
|
/**
|
|
* Durably commit one file byte-for-byte before its owning session event is
|
|
* appended. Files carry no admission limits: any byte content and length is
|
|
* accepted, and the stored object is the exact submitted bytes. Backends
|
|
* without verbatim file storage keep this default rejection.
|
|
* @param input - exact bytes and optional display name.
|
|
* @returns the durable content-addressed file reference.
|
|
*/
|
|
saveFile(input: SaveFileAttachment): Promise<FileAttachmentRef> {
|
|
void input
|
|
return Promise.reject(new AttachmentError(
|
|
'The mounted attachment provider cannot store verbatim files.',
|
|
'ATTACHMENT_FILES_UNSUPPORTED',
|
|
))
|
|
}
|
|
|
|
/**
|
|
* Durably commit one file byte-for-byte from bounded chunks. Providers must
|
|
* apply backpressure and must not collect the complete file in memory.
|
|
* Backends without streamed verbatim storage keep this default rejection.
|
|
* @param input - ordered exact bytes, optional cancellation, and display name.
|
|
* @returns the durable content-addressed file reference.
|
|
*/
|
|
saveFileStream(input: SaveFileStreamAttachment): Promise<FileAttachmentRef> {
|
|
void input
|
|
return Promise.reject(new AttachmentError(
|
|
'The mounted attachment provider cannot stream verbatim files.',
|
|
'ATTACHMENT_FILES_UNSUPPORTED',
|
|
))
|
|
}
|
|
|
|
/**
|
|
* Read and verify one verbatim stored file as bounded chunks. Providers must
|
|
* not collect the complete file in memory. Backends without verbatim file
|
|
* reads keep this default rejection.
|
|
* @param ref - durable reference from the session log.
|
|
* @param signal - optional cancellation for backend reads and verification work.
|
|
* @returns exact file bytes in order; integrity failures reject the iteration.
|
|
*/
|
|
async *readFileStream(
|
|
ref: FileAttachmentRef,
|
|
signal?: AbortSignal,
|
|
): AsyncIterable<Uint8Array> {
|
|
signal?.throwIfAborted()
|
|
void ref
|
|
await Promise.reject(new AttachmentError(
|
|
'The mounted attachment provider cannot read verbatim files.',
|
|
'ATTACHMENT_FILES_UNSUPPORTED',
|
|
))
|
|
}
|
|
|
|
/**
|
|
* Locate the verbatim stored file object in the harness host filesystem.
|
|
* @param ref - durable file reference.
|
|
* @returns an absolute host path, or undefined when this backend is not host-file-backed.
|
|
* @throws an AttachmentError when the durable reference is invalid.
|
|
*/
|
|
fileHostPath(ref: FileAttachmentRef): string | undefined {
|
|
void ref
|
|
return undefined
|
|
}
|
|
|
|
/**
|
|
* Generate or read one deterministic model-request version from the stored normalized image.
|
|
* @param ref - durable provider-independent normalized attachment reference.
|
|
* @param policy - exact route pixel budget and encoded-byte target; a target no ladder quality meets yields the smallest ladder output.
|
|
* @param signal - optional cancellation.
|
|
* @returns request bytes and the cache/upload identity covering every transform input.
|
|
*/
|
|
readImageRequest(
|
|
ref: ImageAttachmentRef,
|
|
policy: ImageRequestPolicy,
|
|
signal?: AbortSignal,
|
|
): Promise<RequestImageAttachment> {
|
|
signal?.throwIfAborted()
|
|
void ref
|
|
void policy
|
|
return Promise.reject(new AttachmentError(
|
|
'The mounted attachment provider cannot derive model-request images.',
|
|
'ATTACHMENT_PROJECTION_UNSUPPORTED',
|
|
))
|
|
}
|
|
|
|
}
|
|
|
|
export default AttachmentStore
|