mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
feat(images): expand source upload envelope
This commit is contained in:
+1
-1
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md
|
||||
2026-07-05-reconstructable-requests.md: 3f49ba71a6b98a84b05530c900e902b0cf9f6449
|
||||
2026-07-05-reconstructable-requests.zh.md: 8eee44449140d656a669ac506057e4fa09c2f747
|
||||
2026-07-05-reconstructable-requests.zh.md: 7b8a9df65b60f975bc3ae60b2c1b0c3a8cc22e95
|
||||
|
||||
@@ -51,6 +51,6 @@ Status: implemented
|
||||
- 在提供方处仍需全价计算的内容是固有的且已记录的:压缩(其 `compaction/*` 事件和替换条目)、真正的提示词、工具或配置变更(reason 为 `change` 的 `request/header`),或带漂移的进程边界(不同的 `resume` 快照)。提供方自身的 reasoning-content 排除由服务端管理。
|
||||
- `agent/pre-step` 是当前请求的消息通道;直接修改 inbox 则是最终进入后续请求的通道。
|
||||
- 工具结果裁剪无需新机制:一个已记录的单条目 surface replace(`start === end`),携带同一 `callId` 下裁剪后的 `tool/result`——属压缩家族,回放正确,缓存失效由相同的压力逻辑批量处理。
|
||||
- 无法读取的被引用附件对象仍会让模型请求失败;[附件自动隔离](../../proposed/bug-fix/2026-08-20-attachment-read-quarantine.md)记录了不削弱字节精确重建的拟议恢复方案。
|
||||
- 无法读取的被引用附件对象仍会让模型请求失败;[附件自动隔离](../../proposed/bug-fix/2026-08-20-attachment-read-quarantine.zh.md)记录了不削弱字节精确重建的拟议恢复方案。
|
||||
- 会话日志每个循环实例增长一个 `request/header` 快照,并在真正变更时增加快照。它比 delta 编解码器更大,但相对分片密集型日志仍然很小,并只保留一种回放表示。`SESSION_FORMAT_VERSION` 保持 `0`;旧的 delta 事件被拒绝而非迁移。
|
||||
- 快照预期输出变更一次(每个 transcript(文本记录)增加其 header 事件);写入文件系统的 fixture(测试前置数据)以规范化的撰写形式存储,工具参数使用 cwd 相对路径,因为回放只对 cwd 无关的参数路径做往返。
|
||||
|
||||
+2
-2
@@ -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-08-20-unified-image-request-pipeline.md
|
||||
2026-08-20-unified-image-request-pipeline.md: 07632e9e0c3aac33d89acd8aebc0f0114550ddb6
|
||||
2026-08-20-unified-image-request-pipeline.zh.md: 9d95346dab2a7747c4bcef9f213ec0fa8e5ba067
|
||||
2026-08-20-unified-image-request-pipeline.md: c4af375d94ebf2b52fbdd0e8d3d4ee715f87f50e
|
||||
2026-08-20-unified-image-request-pipeline.zh.md: a1e10c63804b42da127bd115c35587191f0a60f0
|
||||
|
||||
@@ -14,7 +14,7 @@ The image path has two explicit versions. The attachment backend owns a provider
|
||||
|
||||
### Provider-independent master
|
||||
|
||||
Admission fully decodes each source under a configurable 32MiB, 100MP, and 16384px-per-side envelope. It applies EXIF orientation, removes metadata and color profiles, converts to 8-bit sRGB/sRGBA, and preserves aspect ratio while limiting the long edge to `masterMaxDimension`, 2048px by default. `sourceWidth` and `sourceHeight` record orientation-applied dimensions when preparation reduces the raster.
|
||||
Admission accepts at most 20 images and 200MiB of encoded source bytes per message. Each source is fully decoded under configurable 20MiB, 64,000,000-pixel, and 8192px-per-side limits. Preparation applies EXIF orientation, removes metadata and color profiles, converts to 8-bit sRGB/sRGBA, and preserves aspect ratio while limiting the long edge to `masterMaxDimension`, 2048px by default. `sourceWidth` and `sourceHeight` record orientation-applied dimensions when preparation reduces the raster.
|
||||
|
||||
The master has an independent `masterMaxBytes` safety cap, 4MiB by default. Alpha is never flattened. A nearest-neighbour bounded sample classifies color complexity without averaging high-frequency pixels. Confirmed low-color input tries PNG, with palette encoding only when no alpha channel is present, followed by WebP qualities 85, 80, and 75. Other alpha input tries WebP at those qualities; other opaque input tries JPEG. Candidates execute in order and stop at the first result within the cap. Dimensions shrink only after every candidate at one size exceeds the cap. The source extension does not classify a PNG as low color. A clean, single-frame 8-bit sRGB/sRGBA PNG, JPEG, or WebP within both master limits passes through byte-identically and retains content-addressed deduplication. GIF, animation, metadata, orientation, 16-bit PNG, and incompatible color spaces force conversion. The source and a converted output are each fully decoded once; the output must match its format, dimensions, depth, color space, and alpha facts before its digest enters the reference.
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ Status: implemented
|
||||
|
||||
### 提供方无关的主版本
|
||||
|
||||
准入在可配置的 32MiB、1 亿像素和单边 16384px 源图范围内完整解码每张图片。处理会应用 EXIF 方向,删除元数据和色彩配置文件,转换为 8-bit sRGB/sRGBA,并保持宽高比把长边限制到 `masterMaxDimension`,默认 2048px。处理缩小光栅时,`sourceWidth` 和 `sourceHeight` 记录应用方向后的源尺寸。
|
||||
每条消息最多准入 20 张图片,源图编码字节总量不超过 200MiB。每张源图会在可配置的 20MiB、64,000,000 像素和单边 8192px 限制内完整解码。处理会应用 EXIF 方向,删除元数据和色彩配置文件,转换为 8-bit sRGB/sRGBA,并保持宽高比把长边限制到 `masterMaxDimension`,默认 2048px。处理缩小光栅时,`sourceWidth` 和 `sourceHeight` 记录应用方向后的源尺寸。
|
||||
|
||||
主版本有独立的 `masterMaxBytes` 安全上限,默认 4MiB。透明通道绝不铺平。系统通过 nearest-neighbour 对有界样本判断色彩复杂度,不会通过像素平均把高频图片误判为低色数。确认的低色数输入先尝试 PNG,只有不带 alpha 通道时才使用 palette,随后依次尝试质量 85、80、75 的 WebP;其他透明输入依次尝试这些质量的 WebP;其他非透明输入依次尝试这些质量的 JPEG。候选按顺序执行,首个不超过上限的结果会立即返回。同一尺寸的候选全部超限后才会缩小尺寸。源扩展名不会把 PNG 归类为低色数图片。处于两个主版本上限内的干净、单帧、8-bit sRGB/sRGBA PNG、JPEG 或 WebP 按字节原样直通,并保留内容寻址去重。GIF、动图、元数据、方向、16-bit PNG 和不兼容色彩空间都会触发转换。源图和转换输出各完整解码一次;输出的格式、尺寸、位深、色彩空间和透明通道事实通过校验后,其摘要才会进入引用。
|
||||
|
||||
@@ -42,7 +42,7 @@ Status: implemented
|
||||
|
||||
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.md)继续跟踪。
|
||||
持久附件对象之后缺失或无法通过完整性校验时,系统仍会明确失败。持久隔离和经校验恢复需要新增会话事件,由[隔离不可读历史附件](../../proposed/bug-fix/2026-08-20-attachment-read-quarantine.zh.md)继续跟踪。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/bug-fix/2026-08-20-attachment-read-quarantine.md
|
||||
2026-08-20-attachment-read-quarantine.md: 28e0f26cee2ec1e257fd4d43b4edc4300e2c6f23
|
||||
2026-08-20-attachment-read-quarantine.zh.md: bdc1d580a5159edcd288552e1bde9d80ea1eafd8
|
||||
2026-08-20-attachment-read-quarantine.zh.md: 7f4ceae4e1fe9e656ed762de9828e14145976dc3
|
||||
|
||||
@@ -6,7 +6,7 @@ Status: proposed
|
||||
|
||||
## 问题
|
||||
|
||||
已接纳的 `ImageAttachmentRef` 会留在持久历史中,因此在被压缩替换前都会参与之后的每次请求。引用对象丢失、完整性校验失败或无法读取时,`AttachmentStore.readImage()` 会返回 `ATTACHMENT_NOT_FOUND`、`ATTACHMENT_CORRUPT` 或 `ATTACHMENT_READ_FAILED`。未变化的历史随后会让之后每次模型请求在同一对象上失败,使会话无法继续,即使其余消息仍可使用。这是[可重建请求](../../implemented/architecture/2026-07-05-reconstructable-requests.md)保留为明确失败的对象不可用情况。
|
||||
已接纳的 `ImageAttachmentRef` 会留在持久历史中,因此在被压缩替换前都会参与之后的每次请求。引用对象丢失、完整性校验失败或无法读取时,`AttachmentStore.readImage()` 会返回 `ATTACHMENT_NOT_FOUND`、`ATTACHMENT_CORRUPT` 或 `ATTACHMENT_READ_FAILED`。未变化的历史随后会让之后每次模型请求在同一对象上失败,使会话无法继续,即使其余消息仍可使用。这是[可重建请求](../../implemented/architecture/2026-07-05-reconstructable-requests.zh.md)保留为明确失败的对象不可用情况。
|
||||
|
||||
## 提案
|
||||
|
||||
|
||||
@@ -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 docs/config-catalog.md
|
||||
config-catalog.md: dd91a870ecb338e784acdd1ffa0a470fa33d8813
|
||||
config-catalog.zh.md: a412a4f0afe652863cda1edad0e344b17e1697ac
|
||||
config-catalog.md: 661e9a50200fd5c650c389d9bb631c04de61d228
|
||||
config-catalog.zh.md: 4299bccc1f59899bd78fd64f915c784e26eea49d
|
||||
|
||||
@@ -327,15 +327,15 @@ Source: [`packages/core/agent-tool-presentation/src/index.ts:38`](../packages/co
|
||||
export interface Config {
|
||||
/** Explicit harness home; omitted follows `DSH_HOME`, then `~/.dsh`. */
|
||||
dshHome?: string
|
||||
/** Maximum encoded bytes accepted for one submitted image. */
|
||||
/** Maximum encoded bytes accepted for one submitted image. Default: 20 MiB. */
|
||||
maxImageBytes?: number
|
||||
/** Maximum image count accepted in one submitted message. */
|
||||
/** Maximum image count accepted in one submitted message. Default: 20. */
|
||||
maxImagesPerMessage?: number
|
||||
/** Maximum aggregate encoded image bytes accepted in one submitted message. */
|
||||
/** Maximum aggregate encoded image bytes accepted in one submitted message. Default: 200 MiB. */
|
||||
maxMessageImageBytes?: number
|
||||
/** Maximum intrinsic width multiplied by height accepted for one submitted image. */
|
||||
/** Maximum intrinsic width multiplied by height accepted for one submitted image. Default: 64,000,000. */
|
||||
maxImagePixels?: number
|
||||
/** Maximum intrinsic width and maximum intrinsic height accepted for one submitted image. */
|
||||
/** Maximum intrinsic width and maximum intrinsic height accepted for one submitted image. Default: 8192px. */
|
||||
maxImageDimension?: number
|
||||
/** Long-edge pixel cap of the stored provider-independent master version. */
|
||||
masterMaxDimension?: number
|
||||
@@ -413,7 +413,7 @@ export interface ConnectionConfig {
|
||||
* that is not a bare, canonical authority fails the plugin load.
|
||||
*/
|
||||
trustedHosts?: string[]
|
||||
/** Maximum buffered JSON body for every `/api` request. */
|
||||
/** Maximum buffered JSON body for every `/api` request. Default: 300 MiB. */
|
||||
maxRequestBodyBytes?: number
|
||||
}
|
||||
```
|
||||
|
||||
@@ -329,15 +329,15 @@ export interface Config {
|
||||
export interface Config {
|
||||
/** Explicit harness home; omitted follows `DSH_HOME`, then `~/.dsh`. */
|
||||
dshHome?: string
|
||||
/** Maximum encoded bytes accepted for one submitted image. */
|
||||
/** Maximum encoded bytes accepted for one submitted image. Default: 20 MiB. */
|
||||
maxImageBytes?: number
|
||||
/** Maximum image count accepted in one submitted message. */
|
||||
/** Maximum image count accepted in one submitted message. Default: 20. */
|
||||
maxImagesPerMessage?: number
|
||||
/** Maximum aggregate encoded image bytes accepted in one submitted message. */
|
||||
/** Maximum aggregate encoded image bytes accepted in one submitted message. Default: 200 MiB. */
|
||||
maxMessageImageBytes?: number
|
||||
/** Maximum intrinsic width multiplied by height accepted for one submitted image. */
|
||||
/** Maximum intrinsic width multiplied by height accepted for one submitted image. Default: 64,000,000. */
|
||||
maxImagePixels?: number
|
||||
/** Maximum intrinsic width and maximum intrinsic height accepted for one submitted image. */
|
||||
/** Maximum intrinsic width and maximum intrinsic height accepted for one submitted image. Default: 8192px. */
|
||||
maxImageDimension?: number
|
||||
/** Long-edge pixel cap of the stored provider-independent master version. */
|
||||
masterMaxDimension?: number
|
||||
@@ -415,7 +415,7 @@ export interface ConnectionConfig {
|
||||
* that is not a bare, canonical authority fails the plugin load.
|
||||
*/
|
||||
trustedHosts?: string[]
|
||||
/** Maximum buffered JSON body for every `/api` request. */
|
||||
/** Maximum buffered JSON body for every `/api` request. Default: 300 MiB. */
|
||||
maxRequestBodyBytes?: number
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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 docs/subsystems/attachment.md
|
||||
attachment.md: ec9d1f27bdde4a4d5b6e6e7328260bcb4af49948
|
||||
attachment.zh.md: e79c2df4ca168bcae4fd45e61812a4f86ce2194b
|
||||
attachment.md: ea15172e3e1fafec2e09c3bedc2590fc7551eb2e
|
||||
attachment.zh.md: c04114c9691fa1ba03446f903c4baf5ae021da4c
|
||||
|
||||
@@ -52,6 +52,8 @@ interface ImageAttachmentLimits {
|
||||
}
|
||||
```
|
||||
|
||||
The local backend admits at most 20 images and 200 MiB of encoded source data per message. One source may use up to 20 MiB, 64,000,000 pixels, and 8192 pixels on either side. These source limits precede the independent 2048-pixel, 4 MiB master preparation stage.
|
||||
|
||||
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
|
||||
|
||||
@@ -52,6 +52,8 @@ interface ImageAttachmentLimits {
|
||||
}
|
||||
```
|
||||
|
||||
本地后端每条消息最多准入 20 张图片,源图编码数据总量不超过 200 MiB。单张源图不得超过 20 MiB、64,000,000 像素和单边 8192 像素。这些源文件限制先于独立的 2048 像素、4 MiB 主版本处理阶段执行。
|
||||
|
||||
引用记录固有尺寸和编码长度,使客户端无需先解码即可排布历史记录;每次权威读取仍会根据对象重新校验摘要、媒体签名、尺寸和元数据。
|
||||
|
||||
## 提交与经校验读取的数据
|
||||
|
||||
@@ -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 packages/attachment/attachment-local/README.md
|
||||
README.md: 77b68357d5a961549bef0a015b8e48ba02fbd702
|
||||
README.zh.md: 05932c93e40d42a7f8fcdcf906f6669f6f8f7073
|
||||
README.md: 6141b7559492aa4c50831c8124a917bfdb704f4b
|
||||
README.zh.md: 2a8ed6e1aef8022aba5053bf1ef0f9728340d086
|
||||
|
||||
@@ -4,7 +4,7 @@ English | [中文](README.zh.md)
|
||||
|
||||
The private local implementation of [`@deepseek-ai/dsh-attachment`](../attachment). Objects land at `<DSH_HOME>/attachments/v1/objects/<sha256-prefix>/<sha256>` and are addressed by an opaque `sha256:` id. Each process proves a home durable once by syncing every ancestor entry to the filesystem root. Writes use a private staging directory, owner-only files, a synced temporary file, an atomic exclusive hard-link publish, and directory syncs on the publication path (POSIX; Windows relies on filesystem metadata journaling) so the reported reference survives a crash.
|
||||
|
||||
Admission fully decodes the raster against a wide source envelope: 32MiB, 100MP, and 16384px per side by default. It then prepares a provider-independent master. EXIF orientation is applied, metadata and color profiles are removed, pixels become 8-bit sRGB/sRGBA, and the long edge is reduced proportionally to `masterMaxDimension` (2048px by default). The master has its own `masterMaxBytes` safety cap (4MiB by default). Alpha is retained. A nearest-neighbour bounded sample classifies color complexity without averaging high-frequency pixels. Confirmed low-color images try PNG, using a palette only when the input has no alpha channel, then WebP at qualities 85, 80, and 75. Other alpha images try WebP at those qualities; other opaque images try JPEG. Each candidate runs only after the preceding candidate exceeds the cap. Dimensions shrink only after every candidate at one size exceeds the cap. A clean, single-frame 8-bit sRGB/sRGBA PNG, JPEG, or WebP already within both master limits passes through byte-identically; 16-bit PNG, GIF, animated input, metadata, orientation, and incompatible color spaces force conversion. The source and a converted master are each fully decoded once. `saveImages` prepares and verifies every master once before publishing the batch, so validation failure leaves no partial references and commit does not repeat full image encoding.
|
||||
Admission accepts at most 20 images and 200MiB of encoded source bytes per message. Each source may use up to 20MiB, 64,000,000 pixels, and 8192px per side. It then prepares a provider-independent master. EXIF orientation is applied, metadata and color profiles are removed, pixels become 8-bit sRGB/sRGBA, and the long edge is reduced proportionally to `masterMaxDimension` (2048px by default). The master has its own `masterMaxBytes` safety cap (4MiB by default). Alpha is retained. A nearest-neighbour bounded sample classifies color complexity without averaging high-frequency pixels. Confirmed low-color images try PNG, using a palette only when the input has no alpha channel, then WebP at qualities 85, 80, and 75. Other alpha images try WebP at those qualities; other opaque images try JPEG. Each candidate runs only after the preceding candidate exceeds the cap. Dimensions shrink only after every candidate at one size exceeds the cap. A clean, single-frame 8-bit sRGB/sRGBA PNG, JPEG, or WebP already within both master limits passes through byte-identically; 16-bit PNG, GIF, animated input, metadata, orientation, and incompatible color spaces force conversion. The source and a converted master are each fully decoded once. `saveImages` prepares and verifies every master once before publishing the batch, so validation failure leaves no partial references and commit does not repeat full image encoding.
|
||||
|
||||
Request versions live below `<DSH_HOME>/attachments/v1/request-images/`. `readImageRequest` scales the stored master under a total-pixel budget without enlargement, then enforces a separate encoded-byte cap. The request encoder uses the same color branches, with PNG (palette only without alpha) before WebP 85 and 80 for low-color images, WebP 85 then 80 for other alpha images, and JPEG 85 then 80 for other opaque images. It also executes candidates lazily and reduces dimensions only after both quality attempts exceed the request cap. Its cache identity includes the master id, transform version, pixel and byte budgets, optional master-coordinate crop, and fixed encoder settings. Cached bytes are fully decoded and checked as 8-bit sRGB/sRGBA before use. Concurrent calls for one identity share one transform and cache write; cancelling one waiter does not cancel the shared work. `readImageRequests` schedules batches through the service's FIFO limiter. `imageCompressionConcurrency` controls simultaneous master and request transforms from 1 through 8 and defaults to 2; file publication remains ordered after preparation. `cropImage` maps coordinates measured on a model preview back to the master, crops the master rather than the preview, and commits the crop as another durable attachment.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
这是 [`@deepseek-ai/dsh-attachment`](../attachment) 的私有本地实现。对象存放在 `<DSH_HOME>/attachments/v1/objects/<sha256-prefix>/<sha256>`,并通过不透明的 `sha256:` 标识符寻址。每个进程都会把每级祖先目录项同步到文件系统根目录,以此一次性证明 home 已持久化。写入使用私有暂存目录、仅所有者可访问的文件、经过同步的临时文件、原子且排他的硬链接发布,并对发布路径执行目录同步(适用于 POSIX;Windows 依赖文件系统元数据日志),确保已报告的引用能够在崩溃后继续存在。
|
||||
|
||||
准入针对宽松的源图范围完整解码光栅,默认上限为 32MiB、1 亿像素和单边 16384px。随后生成提供方无关的主版本:应用 EXIF 方向,删除元数据和色彩配置文件,转换为 8-bit sRGB/sRGBA,并保持宽高比把长边限制到 `masterMaxDimension`(默认 2048px)。主版本有独立的 `masterMaxBytes` 安全上限(默认 4MiB)。透明通道会保留。系统用 nearest-neighbour 对有界样本分类,不会通过像素平均把高频图片误判为低色数。确认的低色数图片先尝试 PNG,只有不带 alpha 通道时才使用 palette,随后依次尝试质量 85、80、75 的 WebP;其他透明图片依次尝试这些质量的 WebP;其他非透明图片依次尝试这些质量的 JPEG。只有前一个候选超限时才会执行下一个候选;同一尺寸的候选全部超限后才缩小尺寸。已经处于两个主版本上限内的干净、单帧、8-bit sRGB/sRGBA PNG、JPEG 或 WebP 按字节原样直通;16-bit PNG、GIF、动图、元数据、方向和不兼容色彩空间都会触发转换。源图和转换后的主版本各完整解码一次。`saveImages` 在发布任何批次成员前为每张图片各准备并验证一次主版本,因此校验失败不会留下部分引用,提交阶段也不会重复执行完整图片编码。
|
||||
每条消息最多准入 20 张图片,源图编码字节总量不超过 200MiB。每张源图不得超过 20MiB、64,000,000 像素和单边 8192px。随后生成提供方无关的主版本:应用 EXIF 方向,删除元数据和色彩配置文件,转换为 8-bit sRGB/sRGBA,并保持宽高比把长边限制到 `masterMaxDimension`(默认 2048px)。主版本有独立的 `masterMaxBytes` 安全上限(默认 4MiB)。透明通道会保留。系统用 nearest-neighbour 对有界样本分类,不会通过像素平均把高频图片误判为低色数。确认的低色数图片先尝试 PNG,只有不带 alpha 通道时才使用 palette,随后依次尝试质量 85、80、75 的 WebP;其他透明图片依次尝试这些质量的 WebP;其他非透明图片依次尝试这些质量的 JPEG。只有前一个候选超限时才会执行下一个候选;同一尺寸的候选全部超限后才缩小尺寸。已经处于两个主版本上限内的干净、单帧、8-bit sRGB/sRGBA PNG、JPEG 或 WebP 按字节原样直通;16-bit PNG、GIF、动图、元数据、方向和不兼容色彩空间都会触发转换。源图和转换后的主版本各完整解码一次。`saveImages` 在发布任何批次成员前为每张图片各准备并验证一次主版本,因此校验失败不会留下部分引用,提交阶段也不会重复执行完整图片编码。
|
||||
|
||||
请求版本保存在 `<DSH_HOME>/attachments/v1/request-images/`。`readImageRequest` 在不放大小图的前提下,把存储的主版本缩放到总像素预算内,再执行独立的编码字节上限。请求编码器使用同一分类分支:低色数图片先尝试 PNG,只有不带 alpha 通道时才使用 palette,再尝试质量 85 和 80 的 WebP;其他透明图片依次尝试质量 85 和 80 的 WebP;其他非透明图片依次尝试质量 85 和 80 的 JPEG。候选仍按需执行,两个质量档均超限后才缩小尺寸。缓存身份包含主版本 ID、变换策略版本、像素和字节预算、可选的主版本坐标裁剪区域以及固定编码参数。缓存字节在使用前会完整解码并校验为 8-bit sRGB/sRGBA。同一身份的并发调用共享一次变换和缓存写入;取消一个等待方不会取消共享任务。`readImageRequests` 通过服务的 FIFO 限流器调度批次。`imageCompressionConcurrency` 控制同时执行的主版本和请求版本变换,范围为 1 至 8,默认值为 2;文件发布仍在准备结束后按顺序执行。`cropImage` 把模型在预览图上测得的坐标映射回主版本,从主版本而非预览图裁剪,并把裁剪结果提交为另一个持久附件。
|
||||
|
||||
|
||||
@@ -27,15 +27,15 @@ export type { PreparedImageFile } from './store.ts'
|
||||
export { previewCropToMaster, readRequestImageFile, requestImageDimensions, requestImageVariantId } from './request-image.ts'
|
||||
|
||||
/** Default maximum encoded bytes for one submitted image; oversized sources are refused, not shrunk. */
|
||||
export const DEFAULT_MAX_IMAGE_BYTES = 32 * 1024 * 1024
|
||||
export const DEFAULT_MAX_IMAGE_BYTES = 20 * 1024 * 1024
|
||||
/** Default maximum images in one prompt. */
|
||||
export const DEFAULT_MAX_IMAGES_PER_MESSAGE = 20
|
||||
/** Default maximum aggregate image bytes in one prompt. */
|
||||
export const DEFAULT_MAX_MESSAGE_IMAGE_BYTES = 100 * 1024 * 1024
|
||||
export const DEFAULT_MAX_MESSAGE_IMAGE_BYTES = 200 * 1024 * 1024
|
||||
/** Default maximum intrinsic pixels for one submitted image. */
|
||||
export const DEFAULT_MAX_IMAGE_PIXELS = 100_000_000
|
||||
export const DEFAULT_MAX_IMAGE_PIXELS = 64_000_000
|
||||
/** Default per-side pixel cap for one submitted image. */
|
||||
export const DEFAULT_MAX_IMAGE_DIMENSION = 16384
|
||||
export const DEFAULT_MAX_IMAGE_DIMENSION = 8192
|
||||
/**
|
||||
* Default long-edge target of the stored image master. A larger source
|
||||
* is admitted and downscaled to this edge, so admission bounds what rides
|
||||
@@ -53,15 +53,15 @@ export const MAX_IMAGE_COMPRESSION_CONCURRENCY = 8
|
||||
export interface Config {
|
||||
/** Explicit harness home; omitted follows `DSH_HOME`, then `~/.dsh`. */
|
||||
dshHome?: string
|
||||
/** Maximum encoded bytes accepted for one submitted image. */
|
||||
/** Maximum encoded bytes accepted for one submitted image. Default: 20 MiB. */
|
||||
maxImageBytes?: number
|
||||
/** Maximum image count accepted in one submitted message. */
|
||||
/** Maximum image count accepted in one submitted message. Default: 20. */
|
||||
maxImagesPerMessage?: number
|
||||
/** Maximum aggregate encoded image bytes accepted in one submitted message. */
|
||||
/** Maximum aggregate encoded image bytes accepted in one submitted message. Default: 200 MiB. */
|
||||
maxMessageImageBytes?: number
|
||||
/** Maximum intrinsic width multiplied by height accepted for one submitted image. */
|
||||
/** Maximum intrinsic width multiplied by height accepted for one submitted image. Default: 64,000,000. */
|
||||
maxImagePixels?: number
|
||||
/** Maximum intrinsic width and maximum intrinsic height accepted for one submitted image. */
|
||||
/** Maximum intrinsic width and maximum intrinsic height accepted for one submitted image. Default: 8192px. */
|
||||
maxImageDimension?: number
|
||||
/** Long-edge pixel cap of the stored provider-independent master version. */
|
||||
masterMaxDimension?: number
|
||||
|
||||
@@ -19,7 +19,11 @@ import LocalAttachmentStore, {
|
||||
describe('local attachment service', () => {
|
||||
it('resolves every omitted admission limit explicitly', () => {
|
||||
const service = new LocalAttachmentStore(new Context(), {})
|
||||
expect(DEFAULT_MAX_IMAGE_BYTES).toBe(32 * 1024 * 1024)
|
||||
expect(DEFAULT_MAX_IMAGE_BYTES).toBe(20 * 1024 * 1024)
|
||||
expect(DEFAULT_MAX_IMAGES_PER_MESSAGE).toBe(20)
|
||||
expect(DEFAULT_MAX_MESSAGE_IMAGE_BYTES).toBe(200 * 1024 * 1024)
|
||||
expect(DEFAULT_MAX_IMAGE_PIXELS).toBe(64_000_000)
|
||||
expect(DEFAULT_MAX_IMAGE_DIMENSION).toBe(8192)
|
||||
expect(service.imageLimits).toEqual({
|
||||
maxImageBytes: DEFAULT_MAX_IMAGE_BYTES,
|
||||
maxImagesPerMessage: DEFAULT_MAX_IMAGES_PER_MESSAGE,
|
||||
|
||||
@@ -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 packages/client/connection/README.md
|
||||
README.md: a7562b9dac57930b1abc0b76b9079a6865a38b35
|
||||
README.zh.md: 24c56e598ebd4b5ca39e433c5782399909f528b8
|
||||
README.md: 71ef204a589bb67c15ccab58d3cac5a13782ce27
|
||||
README.zh.md: 6d33ac3c13cdfceeba6e7472b618084267d09bbc
|
||||
|
||||
@@ -23,4 +23,4 @@ None; this package neither assembles nor sends a provider request.
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **History resumes an unattached session** — opening history may create the host-side agent and add latency to the first open; there is no persistence-only read path.
|
||||
- **The `/api` bridge buffers each request body in memory** — `maxRequestBodyBytes` (default 160 MiB, sized for the default 100 MiB aggregate image limit after base64 expansion plus envelope headroom) is therefore also the per-request resident bound; a streaming body path would be needed to lower it without shrinking the image limits.
|
||||
- **The `/api` bridge buffers each request body in memory** — `maxRequestBodyBytes` (default 300 MiB, sized for the default 200 MiB aggregate image limit after base64 expansion plus envelope headroom) is therefore also the per-request resident bound; a streaming body path would be needed to lower it without shrinking the image limits.
|
||||
|
||||
@@ -23,4 +23,4 @@ node 半侧在桥接或 upgrade 前守卫 `/api` 下的每个入口(`src/api-r
|
||||
## 已知限制与暂缓事项
|
||||
|
||||
- **History 会恢复未附加的会话**:打开 history 可能创建宿主侧 agent,并增加首次打开的延迟;没有仅从持久化读取的路径。
|
||||
- **`/api` 桥把每个请求体整体缓冲在内存里**:`maxRequestBodyBytes`(默认 160 MiB,按默认 100 MiB 图片总量上限经 base64 膨胀加信封余量得出)因此同时是单请求的驻留内存上界;要降低它而不缩小图片限额,需要流式请求体路径。
|
||||
- **`/api` 桥把每个请求体整体缓冲在内存里**:`maxRequestBodyBytes`(默认 300 MiB,按默认 200 MiB 图片总量上限经 base64 膨胀加信封余量得出)因此同时是单请求的驻留内存上界;要降低它而不缩小图片限额,需要流式请求体路径。
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http'
|
||||
|
||||
/** Default carrier cap for all HTTP RPC bodies: sized for the default
|
||||
* aggregate image limit (100 MiB) after base64 expansion plus envelope
|
||||
* headroom (~134.3 MiB required), rounded up for slack. The bridge buffers
|
||||
* aggregate image limit (200 MiB) after base64 expansion plus envelope
|
||||
* headroom (~267.7 MiB required), rounded up for slack. The bridge buffers
|
||||
* each body in memory, so this cap is also the per-request resident bound. */
|
||||
export const DEFAULT_MAX_REQUEST_BODY_BYTES = 160 * 1024 * 1024
|
||||
export const DEFAULT_MAX_REQUEST_BODY_BYTES = 300 * 1024 * 1024
|
||||
|
||||
/** Transport-independent request handler consumed by the Host HTTP bridge. */
|
||||
export interface FetchHandler {
|
||||
|
||||
@@ -57,7 +57,7 @@ export interface ConnectionConfig {
|
||||
* that is not a bare, canonical authority fails the plugin load.
|
||||
*/
|
||||
trustedHosts?: string[]
|
||||
/** Maximum buffered JSON body for every `/api` request. */
|
||||
/** Maximum buffered JSON body for every `/api` request. Default: 300 MiB. */
|
||||
maxRequestBodyBytes?: number
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { AttachmentStore } from '@deepseek-ai/dsh-attachment'
|
||||
import { RpcId, type ClientRequest } from '@deepseek-ai/dsh-host-apiproxy/api'
|
||||
import type { WebServer, WebRoute, WebUpgradeRoute } from '@deepseek-ai/dsh-host-webserver'
|
||||
import { API_PATH, apply, HOST_EVENTS_PATH, inject, MUX_EVENTS_PATH, type HostConnectionHandle } from '../src/index.ts'
|
||||
import { DEFAULT_MAX_REQUEST_BODY_BYTES } from '../src/http-bridge.ts'
|
||||
|
||||
/** Structural webServer fake recording both route registries. */
|
||||
function fakeHttpServer(
|
||||
@@ -90,6 +91,11 @@ async function mounted(config?: { trustedHosts?: string[] }): Promise<{
|
||||
}
|
||||
|
||||
describe('connection node half', () => {
|
||||
it('reserves enough default carrier capacity for the 200 MiB image batch', () => {
|
||||
expect(DEFAULT_MAX_REQUEST_BODY_BYTES).toBe(300 * 1024 * 1024)
|
||||
expect(DEFAULT_MAX_REQUEST_BODY_BYTES).toBeGreaterThan(Math.ceil(200 * 1024 * 1024 * 4 / 3) + 1024 * 1024)
|
||||
})
|
||||
|
||||
it('fails loud when the carrier cap cannot hold the configured image batch', () => {
|
||||
const ctx = new Context()
|
||||
const routes: WebRoute[] = []
|
||||
|
||||
Reference in New Issue
Block a user