fix: preserve multi-image prompt batches

This commit is contained in:
Tianyi Cui
2026-07-30 00:58:35 +08:00
parent 60c8f5ea69
commit 6312e43a11
35 changed files with 336 additions and 149 deletions
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.md
2026-07-22-web-multimodal-image-input-and-durable-attachments.md: ab1bd449f065346bc327984eacc85f71d4fd5a28
2026-07-22-web-multimodal-image-input-and-durable-attachments.zh.md: d51f9135352ba09b2cd2a083ade6b62f4a276216
2026-07-22-web-multimodal-image-input-and-durable-attachments.md: 134e3cf6137abec41398d7f4ded9b838e197a5fc
2026-07-22-web-multimodal-image-input-and-durable-attachments.zh.md: f0c16d10c57a499b605fffead9f35eb2f6149004
@@ -112,7 +112,7 @@ type PromptInputPart =
}
```
Base64 crosses JSON-RPC once and is discarded after persistence. Version one accepts at most one image per prompt. The host validates canonical base64, individual bytes, magic-byte MIME, intrinsic dimensions, and intrinsic pixel count while durably saving that image. Only after the save succeeds does it call the agent with normalized text and a durable image block. A failure appends no user event and exposes no attachment path or raw bytes.
Base64 crosses JSON-RPC once and is discarded after persistence. The host validates canonical base64, image count, aggregate bytes, individual bytes, magic-byte MIME, intrinsic dimensions, and decoded-pixel count: it validates the complete batch through the seam's storage-free `validateImage` before saving any member, so one malformed image cannot strand the batch's valid members as unreferenced objects. Only after every image succeeds does it call the agent with normalized text and durable image blocks in the submitted order. A failure appends no user event and exposes no attachment path or raw bytes.
`session.attachment` is a read-only, session-scoped endpoint. The host serves bytes only when a durable event in that session references the requested attachment identifier. The client deduplicates loads by session and attachment identifier while that session is rendered, revokes resolved URLs on rendered-session disposal, and invalidates late loads so an unmounted session cannot repopulate the cache.
@@ -138,7 +138,7 @@ Composer thumbnails and each `MessageImage` own ephemeral original-preview state
### Limits and trust boundaries
Version one accepts at most one PNG, JPEG, WebP, or GIF per prompt. SVG and remote URLs are excluded. Default deployment limits are 5 MiB and 40 million intrinsic pixels per image; they are validated backend configuration and projected to the client for fast-path guidance, while host validation remains authoritative. The client connection carrier independently caps buffered API request bodies from the single-image byte limit plus base64 and envelope expansion; a body without a declared length is rejected the moment it crosses the cap rather than drained to its end.
Version one accepts up to 10 ordered PNG, JPEG, WebP, or GIF images per prompt by default. SVG and remote URLs are excluded. Default deployment limits are 5 MiB and 40 million intrinsic pixels per image plus 20 MiB aggregate image bytes per prompt; all are validated backend configuration and projected to the client for fast-path guidance, while host validation remains authoritative. The client connection carrier independently caps buffered API request bodies from the aggregate image-byte limit plus base64 and envelope expansion; a body without a declared length is rejected the moment it crosses the cap rather than drained to its end.
Malformed base64, unsupported or mismatched media, truncated headers, excess bytes, excess image count, excess pixels, missing objects, and integrity mismatches return stable structured failures. Original filenames are reduced to a display basename, control characters are removed, and no local path is logged or returned to the browser.
@@ -112,7 +112,7 @@ type PromptInputPart =
}
```
Base64 只跨越一次 JSON-RPC,并在持久化后丢弃。第一版每条提示词最多接受一张图片。宿主在持久保存该图片时,会校验规范 base64、单张图片字节数、与魔数字节一致的 MIME、固有尺寸和固有像素数。只有保存成功后,宿主才会用规范化文本和一个持久图片块调用 agent。失败时不追加用户事件,也不公开任何附件路径或原始字节。
Base64 只跨越一次 JSON-RPC,并在持久化后丢弃。宿主会校验规范 base64、图片数量、总字节数、单张图片字节数、与魔数字节一致的 MIME、固有尺寸和解码像素数:它会在保存任何成员之前,通过服务边界上不触碰存储的 `validateImage` 校验完整批次,因此一张畸形图片不会把批次中的有效成员留成无引用对象。只有每张图片都成功后,宿主才会用规范化文本和按提交顺序排列的持久图片块调用 agent。失败时不追加用户事件,也不公开任何附件路径或原始字节。
`session.attachment` 是只读且限定于会话作用域的端点。只有该会话中的持久事件引用了所请求的附件标识符,宿主才提供字节。会话处于渲染状态时,客户端会按会话和附件标识符对加载操作去重;已渲染会话释放时会撤销已解析的 URL,并使延迟完成的加载失效,以免已卸载的会话重新写入缓存。
@@ -138,7 +138,7 @@ Pi-AI 适配器是首条视觉输入路径:它在请求时解析 `ctx.attachme
### 限制与信任边界
第一版每条提示词最多接受一张 PNG、JPEG、WebP 或 GIF 图片。不接受 SVG 和远程 URL。默认部署限制为每张图片 5 MiB 和 4,000 万个固有像素;这些限制属于经过校验的后端配置,并会投影给客户端以提供快速路径指引,而宿主校验仍是权威结果。客户端连接载体根据单张图片字节上限加上 base64 和请求封装的膨胀量,独立限制 API 请求体的缓冲大小;未声明长度的请求体在越过上限的瞬间即被拒绝,而不是先读完再拒。
第一版默认每条提示词最多接受 10 张有序的 PNG、JPEG、WebP 或 GIF 图片。不接受 SVG 和远程 URL。默认部署限制为每张图片 5 MiB 和 4,000 万个固有像素,外加每条提示词 20 MiB 图片总字节数;这些限制都属于经过校验的后端配置,并会投影给客户端以提供快速路径指引,而宿主校验仍是权威结果。客户端连接载体根据图片总字节数上限加上 base64 和请求封装的膨胀量,独立限制 API 请求体的缓冲大小;未声明长度的请求体在越过上限的瞬间即被拒绝,而不是先读完再拒。
格式错误的 base64、不支持或不匹配的媒体、截断的文件头、超出字节限制、超出图片数量、超出像素限制、对象缺失和完整性不匹配都会返回稳定的结构化错误。原始文件名只保留用于显示的末段,控制字符会被移除,并且任何本地路径都不会写入日志或返回浏览器。