Files
deepseek-harness/docs/subsystems/attachment.md
T
Turtle 93a1b569ac Merge remote-tracking branch 'origin/master' into codex/remove-cordis-catalog-line-numbers
# Conflicts:
#	docs/subsystems/agent-team.i18n.yaml
#	docs/subsystems/approval.i18n.yaml
#	docs/subsystems/client-modules.i18n.yaml
#	docs/subsystems/client-modules.md
#	docs/subsystems/client-modules.zh.md
#	docs/subsystems/code-runtime.i18n.yaml
#	docs/subsystems/commands.i18n.yaml
#	docs/subsystems/commands.md
#	docs/subsystems/commands.zh.md
#	docs/subsystems/compaction.i18n.yaml
#	docs/subsystems/compaction.md
#	docs/subsystems/compaction.zh.md
#	docs/subsystems/core.i18n.yaml
#	docs/subsystems/core.md
#	docs/subsystems/core.zh.md
#	docs/subsystems/credentials.i18n.yaml
#	docs/subsystems/credentials.md
#	docs/subsystems/credentials.zh.md
#	docs/subsystems/feedback.i18n.yaml
#	docs/subsystems/filesystem.i18n.yaml
#	docs/subsystems/goal.i18n.yaml
#	docs/subsystems/http-server.md
#	docs/subsystems/http-server.zh.md
#	docs/subsystems/invariants.i18n.yaml
#	docs/subsystems/invariants.md
#	docs/subsystems/invariants.zh.md
#	docs/subsystems/jobs.i18n.yaml
#	docs/subsystems/llm-streaming.i18n.yaml
#	docs/subsystems/llm-streaming.md
#	docs/subsystems/llm-streaming.zh.md
#	docs/subsystems/permission-presets.md
#	docs/subsystems/permission-presets.zh.md
#	docs/subsystems/persistence.i18n.yaml
#	docs/subsystems/persistence.md
#	docs/subsystems/persistence.zh.md
#	docs/subsystems/plan.i18n.yaml
#	docs/subsystems/plan.md
#	docs/subsystems/plan.zh.md
#	docs/subsystems/sandbox.i18n.yaml
#	docs/subsystems/sandbox.md
#	docs/subsystems/sandbox.zh.md
#	docs/subsystems/schedule.i18n.yaml
#	docs/subsystems/session-projection.i18n.yaml
#	docs/subsystems/session-projection.md
#	docs/subsystems/session-projection.zh.md
#	docs/subsystems/session-query.i18n.yaml
#	docs/subsystems/session-reference.i18n.yaml
#	docs/subsystems/session-reference.md
#	docs/subsystems/session-reference.zh.md
#	docs/subsystems/session-telemetry.md
#	docs/subsystems/session-telemetry.zh.md
#	docs/subsystems/session-title.i18n.yaml
#	docs/subsystems/session.i18n.yaml
#	docs/subsystems/session.md
#	docs/subsystems/session.zh.md
#	docs/subsystems/settings.i18n.yaml
#	docs/subsystems/settings.md
#	docs/subsystems/settings.zh.md
#	docs/subsystems/shell.i18n.yaml
#	docs/subsystems/shell.md
#	docs/subsystems/shell.zh.md
#	docs/subsystems/skills.i18n.yaml
#	docs/subsystems/skills.md
#	docs/subsystems/skills.zh.md
#	docs/subsystems/spill.i18n.yaml
#	docs/subsystems/storage.i18n.yaml
#	docs/subsystems/subagent.i18n.yaml
#	docs/subsystems/subagent.md
#	docs/subsystems/subagent.zh.md
#	docs/subsystems/subprocess.i18n.yaml
#	docs/subsystems/system-prompt.i18n.yaml
#	docs/subsystems/system-prompt.md
#	docs/subsystems/system-prompt.zh.md
#	docs/subsystems/tasks.md
#	docs/subsystems/tasks.zh.md
#	docs/subsystems/terminal.md
#	docs/subsystems/terminal.zh.md
#	docs/subsystems/token-meter.i18n.yaml
#	docs/subsystems/tools.i18n.yaml
#	docs/subsystems/tools.md
#	docs/subsystems/tools.zh.md
#	docs/subsystems/typert.i18n.yaml
#	docs/subsystems/typert.md
#	docs/subsystems/typert.zh.md
#	docs/subsystems/user-interaction.i18n.yaml
#	docs/subsystems/user-questions.i18n.yaml
#	docs/subsystems/user-questions.md
#	docs/subsystems/user-questions.zh.md
#	docs/subsystems/web.i18n.yaml
#	docs/subsystems/workflow.i18n.yaml
#	docs/subsystems/workflow.md
#	docs/subsystems/workflow.zh.md
#	docs/subsystems/workspace.i18n.yaml
#	docs/subsystems/workspace.md
#	docs/subsystems/workspace.zh.md
#	packages/typert/generator/tests/cordis-catalog.spec.ts
2026-08-20 19:48:43 +08:00

6.8 KiB

Durable Image Attachments

English | 中文

The attachment seam separates binary image ownership from the session log. A producer gives validated encoded bytes to ctx.attachments; the service publishes an immutable content-addressed reference only after the object is durable. Session events and model-visible ImageBlocks contain that reference and metadata, never a browser object URL, host temporary path, provider URL, or base64 payload.

Unsent browser drafts may stay in memory and native clients may stage them in operating-system temporary storage. Once the host accepts a user message, its images move below <DSH_HOME>/attachments/v1 before the user event is appended. Structured model image output follows the same persist-before-event rule.

Source: packages/attachment/attachment/src/types.ts

Identity and verified metadata

AttachmentId is a branded opaque string. The local backend currently emits sha256:<digest>, but consumers must neither parse that representation nor derive a filesystem path from it.

/** Raster image formats accepted by the version-one attachment path. */
type ImageMediaType = 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif'
/** Durable, serializable metadata for one immutable image object. */
interface ImageAttachmentRef {
  /** Opaque storage identifier; never a filesystem path or bearer URL. */
  attachmentId: AttachmentId
  /** Media type verified from the stored bytes. */
  mediaType: ImageMediaType
  /** Exact encoded byte length. */
  bytes: number
  /** Intrinsic encoded width in pixels. */
  width: number
  /** Intrinsic encoded height in pixels. */
  height: number
  /** Optional display name stripped of local path information. */
  name?: string
}
/** Deployment-resolved limits used by upload admission and request buffering. */
interface ImageAttachmentLimits {
  maxImageBytes: number
  maxImagesPerMessage: number
  maxMessageImageBytes: number
  maxImagePixels: number
  /** Maximum intrinsic width and maximum intrinsic height in pixels for one image. */
  maxImageDimension: number
  mediaTypes: readonly ImageMediaType[]
}

The reference records intrinsic dimensions and encoded length so clients can lay out history without decoding first, while every authoritative read still re-checks digest, media signature, dimensions, and metadata against the object.

Commit and verified-read payloads

/** Base64-encoded image upload accompanying one wire request. */
interface EncodedImageAttachment {
  /** Declared media type, verified against the decoded bytes during admission. */
  mediaType: ImageMediaType
  /** Canonical base64 encoding of the image bytes. */
  data: string
  /** Optional display name; it is never interpreted as a path. */
  name?: string
}
/** Request to validate and durably commit one image. */
interface SaveImageAttachment {
  data: Uint8Array
  /** Caller-declared media type, checked against fully decoded bytes. */
  mediaType: ImageMediaType
  /** Optional browser/provider display name; it is never interpreted as a path. */
  name?: string
}
/** Stored image bytes returned after reference and digest verification. */
interface StoredImageAttachment {
  ref: ImageAttachmentRef
  data: Uint8Array
}

saveImage() validates bytes and atomically commits one object before returning its reference. validateImage() runs the same admission checks without persisting anything; batch callers validate every member through it before saving any member, so validation rejection leaves no partial objects behind. admitEncodedImages() is the wire entry for base64 uploads: it enforces canonical base64, then delegates batch admission to saveImages(), which owns the count and aggregate-byte limits and the validate-all-before-save order. readImage() accepts a reference from an authorized session path and returns bytes only after integrity verification. The service is deliberately retention-neutral: resumed and forked sessions may share objects, so reference-aware garbage collection is deferred rather than tied to any one session's deletion.

Cordis API

Generated from source by scripts/gen-cordis-catalog.ts (verified fresh by pnpm run verify-cordis-catalog in doc-sync; regenerate with pnpm run gen-cordis-catalog) — the language sides differ only in locale-specific paired document paths. Signature blocks use a ts cordis-catalog fence and keep the original source JSDoc; dispatch modes are defined in the primer, and the framework-inherited ctx API lives in cordis-api/inherited.md.

ctx.attachmentsAttachmentStore (abstract seam)

Immutable binary attachment service. Implementations validate bytes before publishing a reference.

/**
 * 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.
 */
async saveImages(inputs: readonly SaveImageAttachment[]): Promise<readonly ImageAttachmentRef[]>

/**
 * Validate and durably commit one image before its owning session event is appended.
 * @param input - encoded bytes, declared media type, and optional display name.
 * @returns a durable content-addressed 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 canonical reference.
 * @throws the signal reason when aborted, or a storage error when verification fails.
 */
abstract readImage(ref: ImageAttachmentRef, signal?: AbortSignal): Promise<StoredImageAttachment>

Source: packages/attachment/attachment/src/index.ts