# Agent Note: 统一规范化附件、请求版本与提供方文件

Status: implemented

[English](2026-08-20-unified-image-request-pipeline.md) | 中文

## Problem

持久图片历史、提供方分辨率、内联请求大小和远端文件复用有不同限制。过去把已接纳图片直接作为之后每次请求发送的字节，导致一个字节上限和一份光栅同时承担四种职责。普通大图会被拒绝；干净的 16-bit PNG 可以进入历史，之后才被 DeepSeek 拒绝；重复 base64 使长会话请求持续增长；提供方拒绝后，同一持久图片还会进入每次后续请求。

## Decision

图片路径有两个显式版本。附件后端拥有提供方无关的持久规范化附件。每条支持图片的模型路由拥有确定性请求策略，附件后端从该附件派生并缓存确切请求版本。会话历史只包含规范化附件引用；内联字节和提供方文件 ID 都是瞬时请求投影。

### 提供方无关的规范化附件

每条消息最多准入 20 张图片，源图编码字节总量不超过 200MiB。每张源图会在可配置的 20MiB、64,000,000 像素和单边 8192px 限制内完整解码。规范化过程会应用 EXIF 方向，删除元数据和色彩配置文件，转换为 8-bit sRGB/sRGBA，并保持宽高比缩放进 `normalizedImageMaxPixels` 总像素预算（默认 2048×2048），随后受 `normalizedImageMaxDimension` 长边上限约束（默认 8192px）。缩放减小光栅时，`originalDimensions` 记录规范化之前、应用方向之后的输入宽高。

规范化附件有独立的 `normalizedImageMaxBytes` 编码字节目标，默认 4MiB。透明通道绝不铺平。编码路由只看解码出的 alpha 事实：透明输入编码为 WebP（effort 0），非透明输入编码为 JPEG，共用 85/75/60 质量阶梯；全部档位都超过目标时保留最小产物，见取代本节的[按 alpha 路由的质量阶梯记录](../bug-fix/2026-08-24-alpha-routed-image-quality-ladders.zh.md)。处于全部规范化限制内的干净、单帧、8-bit sRGB/sRGBA PNG、JPEG 或 WebP 按字节原样直通，并保留内容寻址去重。GIF、动图、元数据、方向、16-bit PNG 和不兼容色彩空间都会触发转换。源图和转换输出各完整解码一次；输出的格式、尺寸、位深、色彩空间和透明通道事实通过校验后，其摘要才会进入引用。

批量准入在发布任何成员前，为每张图片各准备并验证一次规范化附件。校验失败不会开始写入。发布直接使用这些已准备字节，因此大批次不会在提交时重复完整解码和编码。之后发生的存储失败不会返回部分引用；按现有存储规则，已经发布的不可变对象可能保持不可达。

### 确定性请求版本

`AttachmentStore.readImageRequest` 按路由拥有的总像素和编码字节预算派生请求版本。缩放公式为 `min(1, sqrt(maxPixels / (width * height)))`，不会放大小图，随后向预算内取整，确保编码光栅不超过总像素上限。DeepSeek V4 Flash Vision Exp 默认使用总像素 640,000 和原始编码字节目标 1MiB。其 catalog 只使用一个 `imagePixelBudget` 字段：正整数选择确切总像素预算，`low` 选择总像素 512×512，省略时使用路由默认值。2048×1024 规范化附件在这个硬上限下会投影为 1130×565。请求编码与规范化共用同一套 alpha 路由和 85/75/60 质量阶梯，按需执行；没有任何档位达到目标时保留最小产物（见[按 alpha 路由的质量阶梯记录](../bug-fix/2026-08-24-alpha-routed-image-quality-ladders.zh.md)）。普通 agent 轮次、直接 `ctx.llm.stream` 调用、压缩和其他辅助流都使用同一派生过程。

`variantId` 和缓存路径覆盖规范化附件 ID、变换策略版本、路由像素和字节预算及固定编码参数。新缓存条目在发布前会完整解码。缓存命中只探测文件头，校验格式、8-bit sRGB/sRGBA、尺寸和透明通道，不会再次完整解码光栅；不匹配时会重新生成。因此，同一策略下的 DeepSeek Files 和 pi-ai 内联 base64 使用相同的确定性字节。内联计量使用派生字节经过 base64 膨胀后的长度，不使用规范化附件字节数。同一进程内相同 `variantId` 的调用共享一次变换和缓存写入。每个调用方可以取消自己的等待；只有全部等待方都取消时，共享变换才会中止。调用方对单数 `readImageRequest` 使用 `Promise.all` 保持结果顺序。本地实现通过一个 FIFO 限流器运行规范化和请求变换，`imageCompressionConcurrency` 的可配置范围为 1 至 8，默认值为 2。全部规范化附件准备完成后，批次仍按顺序发布。

请求大小 offload 是确定性的从旧到新投影。读取附件前，每条路由先以 `min(附件字节数, 请求版本字节上限)` 作为保守上界，移除超出预算的最旧前缀。系统只读取并转换保留的附件，因此已省略的缺失或损坏对象不会阻塞请求。第二次投影使用确切派生长度，但不会重新加入已省略图片。DeepSeek 默认上限为 128MiB 和 600 张引用图片。被移除前缀会越过连续的 64MiB 字节边界，并按 20 张图片数量步长递增，因此 129 张 1MiB 图片会移除最旧的 65 张并保留 64MiB；持久历史超过 192MiB 前，该前缀保持不变。Pi-ai 保留可配置的 base64 请求上限。每张省略图片都会变成逐图占位文本，保留自己的身份和本次工具执行环境解析出的访问方式，嵌套工具结果图片也使用相同规则；追加式会话历史继续保留原始引用。

### 稳定句柄

每张保留请求图片前都有显示名称或完整附件 ID，以及实际请求尺寸。附件提供方可以给出宿主对象位置；LLM 消费方将其与当前文件系统映射组合后，再加入绝对只读路径、规范化尺寸和媒体类型。描述会说明规范化或请求投影可能缩小或重新编码上传图片，因此模型不能从任一版本推断上传图片原本的属性。用户消息、工具结果、agent loop 请求、压缩和直接 `ctx.llm.stream` 调用共享这套投影。路径独立于确定性请求版本解析，不进入其 `variantId`、持久引用或会话日志。

### DeepSeek Files 生命周期

直接 `deepseek-official` 适配器通常通过 OpenAI 兼容 Files API 上传每张保留的请求版本，并发送 `file_id` 内容块。文件解析失败时，[有界内联回退](../bug-fix/2026-08-21-deepseek-files-inline-fallback.zh.md)会发送相同的确定性请求版本。默认 catalog 把 `deepseek-v4-flash-vision-exp` 公布为支持图片。上传 ID 按端点和 API key 作用域以及 `variantId` 写入索引。上传默认请求 7 天有效期，并记录返回的 `expires_at`；本地映射剩余时间不超过一小时时会直接替换，不会先查询远端文件。索引绝不存储 API key。

只有上传响应返回完整文件对象、匹配的字节数和 `expires_at` 时，上传结果才会写入索引。缺失或不一致的响应不会留下本地映射，后续请求会重新上传。同一作用域和 `variantId` 的并发解析共享一次提供方上传；单个等待方无法取消其他等待方，全部等待方取消时才会停止上传。格式损坏的上传索引按空缓存处理，并在下一次成功上传时替换；文件系统 I/O 失败仍是错误。如果 chat 报告 ID 已过期、删除、缺失或无效，并指出本次请求使用的一个或多个 ID，适配器只删除这些映射。如果响应只说明文件状态失效而没有指出具体 ID，适配器会删除该次 chat 使用的全部映射。受影响的请求字节会重新上传，chat 只重试一次。第二次仍报告文件失效时，适配器会按响应清理映射并返回错误，不会发起第三次 chat。一次上传配额错误会先列出配置数量的最旧 `dsh-` 文件，再删除收集到的文件并重试一次；分页完成后才删除，避免游标失效。公开文件操作提供列表、查询、删除、单个变体释放和整个作用域释放。每个 Files 请求都携带 Harness 的共享 `User-Agent`。客户端执行文档规定的 Files 单次上传 128MiB、chat 单图 32MiB、10,000 个文件、25GiB，以及一小时到 30 天有效期限制。

### 诊断

16-bit RGB 或 RGBA PNG 属于普通可接纳输入，会转换为 8-bit sRGB/sRGBA。本地转换失败时，`read_image` 会写明路径、检测到的 16-bit PNG、所需规范形式和手工转换方法。如果 DeepSeek 拒绝已规范化请求版本，主错误会写明附件 ID 或显示名称、持久消息和图片位置、规范化媒体类型、8-bit sRGB/sRGBA 位深、尺寸和提供方消息。多图片错误无法确定对象时会列出全部候选图片。原始提供方正文保留为错误 cause，不会成为唯一可见消息。

持久附件对象之后缺失或无法通过完整性校验时，系统仍会明确失败。持久隔离和经校验恢复需要新增会话事件，由[隔离不可读历史附件](../../proposed/bug-fix/2026-08-20-attachment-read-quarantine.zh.md)继续跟踪。

## Alternatives considered

**使用一份 1MiB 规范化附件同时负责存储和请求。** 这种做法让模型分辨率决定持久图片细节，并把本地存储、内联膨胀、Files 配额和模型像素合并成一个设置。独立的规范化和请求策略会明确区分这些职责。

**拒绝超过提供方尺寸或达到编码质量下限的图片。** 提供方限制属于具体路由，未来请求可能改用另一个模型。按比例规范化和投影请求版本可以接纳普通大图，同时约束每种后续表示。

**把 PNG 当作截图，并拒绝 16-bit PNG。** 文件格式不能说明像素复杂度，16-bit RGB/RGBA 是可转换位深，不是不支持的图片类型。像素采样和转换后探测能提供所需事实。

**把 DeepSeek data URL 作为首选传输方式。** 内联 base64 会在每次请求中重复字节，并按请求正文大小限制可用图片历史。Files API 引用会复用上传后的确定性请求字节，并提供显式有效期和删除操作；有界回退只在文件解析失败时使用 data URL。

**永久信任本地索引中的文件 ID。** 远端过期、删除和上传响应丢失会使本地与提供方状态不一致。按响应失效和一次重新上传可以恢复，同时避免无界重试；响应没有给出可安全使用的精确目标时，必须使该次请求使用的全部文件失效。

**历史中出现图片后拒绝选择纯文本模型。** 持久历史可能比最初读取它的模型存活更久。按请求生成的占位文本可以保持会话可用，无需改写历史。

**请求每次越过上限就移除一张图片。** 这种做法会在几乎每次新增图片后改写较早的请求消息。按固定步长递增的移除前缀会降低缓存失效频率，同时遵守配置的上限。

## Verification

包测试会生成 16-bit RGB 和 RGBA PNG fixture，验证 8-bit 转换与干净 8-bit 字节直通、字节压力下保留透明通道、首个候选合规后停止编码、字节目标不可达时保留最小阶梯产物、正方形和宽屏 640,000 像素投影、相同变体与上传 singleflight 且不会共享取消、变换并发上限、缓存与上传身份、跳过已保守 offload 的历史附件读取、批量只准备一次、Files 响应不一致、进入刷新余量时不查询远端并更新 ID、单个 ID、多个 ID 和模糊失效响应只恢复一次、文件解析失败后回退到有界全内联请求、删除配额文件前完成分页、规范化提供方诊断、纯文本投影，以及普通请求与压缩共享请求字节。无需密钥的组装快照覆盖真实工具 schema 和图片请求路径。使用凭据的测试只使用内置 `deepseek-official` 路由及其已配置端点，不使用自定义提供方条目。

## Consequences

持久规范化附件最多占用独立的本地安全上限，请求缓存和远端 Files 还会占用额外派生存储。确定性身份和 singleflight 使这些成本可以被共享同一 DSH home 的轮次和会话复用。同时执行两个变换会降低批次延迟，但峰值 RSS 高于串行执行；内存更紧张的部署可以把上限设为 1。编码器或变换策略版本变化会为未来内容产生新身份，不会改写已有历史。DeepSeek 图片请求优先复用 Files；有界的陈旧 ID 恢复会处理远端状态不一致，文件解析失败则使用较小的内联预算。缺失或损坏的持久附件仍需要单独的隔离设计。
