附件
源码更新(
0.1.5-alpha.1):0.1.3-alpha.1 起 Web 支持上传任意类型的通用文件——文件与图片在同一草稿附件栏混排、后台上传带进度与取消、切换会话后仍可见,模型通过保存的只读路径按需读取,而不接收文件字节。图片路径保留身份、尺寸、媒体类型、规范化提示与只读执行侧路径;编辑前先复制到可写路径,持久附件引用仍按内容寻址。请求字节是按模型路由缓存的投影,不是任意宿主路径。源码:packages/attachment/attachment、packages/attachment/attachment-local、packages/client/ui-attachment与packages/fs/tool-fs/src/read-image.ts。
一句话版:
ctx.attachments是持久化二进制附件缝:校验并原子提交不可变的图片字节与通用文件字节,分别返回可序列化的ImageAttachmentRef与FileAttachmentRef。attachment-local是它的本地文件系统实现,ui-attachment是纯 React 附件原子。
未发送的浏览器草稿不在这里;字节只在用户提交 prompt 或 provider 适配器提交结构化模型输出时才进入持久存储。
一、是什么
附件能力解决一件事:把图片与通用文件字节持久化,让会话日志只保存内容寻址引用 + 校验过的元数据,而不是浏览器路径、object URL、provider URL 或 base64。两个核心包都是产品包。
| 包 | ctx key | 角色 |
|---|---|---|
attachment | ctx.attachments | 不可变图片/文件引用、图片限制、存储服务 |
attachment-local | (注册在 ctx.attachments 上) | DSH_HOME 下的内容寻址私有存储 |
ui-attachment | 无(纯呈现层) | 纯 React 附件原子:混合草稿附件栏、历史图库、大图灯箱 |
二、引用与写入 API(attachment)
ctx.attachments 校验并原子提交不可变的图片字节与通用文件字节,分别返回可序列化的 ImageAttachmentRef 与 FileAttachmentRef;消费者从不把浏览器路径、object URL、provider URL 或 base64 持久化进会话事件。
validateImage:跑同一套准入策略但不持久化saveImages(rc.7):按序批量提交——先校验全部成员再逐张写;准入失败零写入,存储失败不返回部分引用(已发布的内容寻址对象可能留下,由后续保留策略收集)saveImage:在每个模型可见会话事件发布之前提交每个已接受的图片readImage:按已记录元数据校验内容寻址对象;调用方可取消,实现观察取消并把取消保留为取消(不翻译成存储失败)saveFile/saveFileStream:提交已有字节数组/按背压与取消语义提交有界分块;两种文件写入路径都不设准入限制,字节原样保存readFileStream/fileHostPath:校验式有界分块读取/定位存储对象,供只读按需读取投影admitEncodedFile与admitPromptContent:编码文件准入;Host prompt 消费方把有序文本、编码图片和已解析的文件引用一并交给ctx.attachments.admitPromptContent()isAttachmentError:识别附件错误并按code路由,不依赖原型链
未发送的 composer 图片与文件仍是浏览器拥有的临时草稿。
Web 提示词准入(packages/api/session-controller)拒绝既无非空白文本也无附件的消息(0.1.5);仅图片或仅文件的消息仍然有效,空白队列编辑同样被拒绝。
三、本地存储(attachment-local)
attachment-local 是 attachment 的私有本地实现,默认挂载(base 组合)。
- 图片对象落在
<DSH_HOME>/attachments/v1/objects/<sha256-prefix>/<sha256>,由不透明的sha256:id 寻址 - 通用文件字节的唯一规范对象落在
<DSH_HOME>/attachments/v1/file-objects/<digest-prefix>/<digest>;每条引用路径<DSH_HOME>/attachments/v1/files/<digest-prefix>/<digest>/<name>都是只读硬链接,因此名称不同但字节相同的文件不会重复占用磁盘 - 每个进程把家目录祖先逐级 sync 到文件系统根,证明家目录只 durable 一次,避免误把"别的进程已创建但还没 sync 的目录"当作安全边界
- 写入用私有暂存目录、owner-only 文件、synced 临时文件、原子排他硬链接发布、发布路径上的目录 sync(POSIX;Windows 依赖文件系统元数据 journaling),保证上报的引用能扛住崩溃
- 图片的写入准入与读取都完整解码 raster 后才接受格式与尺寸;文件按字节原样保存,不设准入限制,读取时校验完整摘要与记录的字节数
DSH_HOME走共享路径策略:显式配置 →$DSH_HOME→~/.dsh。会话日志只含引用与校验过的元数据,从不含这个宿主路径readImage把可选取消转发进文件系统读,并保留取消而不是包成ATTACHMENT_READ_FAILED- 通用文件的字节从不进入 provider 请求:每条路由收到一行确定性 handle 文本,指出文件身份与只读进程路径;执行环境无法映射时,handle 会说明读不到该文件
四、浏览器 UI 原子(ui-attachment)
纯 React 呈现层:组件保持纯 props,附件数据、上传状态、图片加载与回调都来自声明这些槽位的持有方,字符串经属主插件的 locale 命名空间解析;ui-conversation 与(工具结果图库)ui-tool 是当前消费者。它通过 ctx.slots.inject 等待 conversation.input.attachments、conversation.message.images、conversation.trajectory.images 与 tool.call.images。
AttachmentRail:图片与通用文件按选择顺序进入同一条不换行的草稿附件栏,条目统一 64px 高——图片是 64px 方形缩略图,文件是 240px 宽的 DeepSeek Web 卡片(16px 圆角、文档图标、文件名、大写扩展名与字节大小)。溢出隐藏时用边缘箭头翻页,滚动条保持隐藏;文件上传时显示 spinner/进度(首次上报前为不定态),失败显示重试,移除按钮在悬停或键盘聚焦时出现、触摸设备上保持可见(prefers-reduced-motion: reduce下瞬时滚动)MessageImage/ImageGallery:Chat 把文件与图片放在同一靠右、可换行的排列中并保持来源顺序;单张且无其他附件时按长边 240px 渲染,多附件时每张图显示为 64px 方块,与 240×64px 文件卡同排。加载失败给显式重试;点击打开ImageLightboxImageLightbox:文档级模态预览,Escape / 背景点击 / 关闭控件关闭,卸载时焦点还给打开者
模型可见性:无——它在浏览器渲染纯 React 原子,不进任何模型请求。
五、配置
attachment-local 的准入限制与规范化策略全部可配(0.1.1,默认值见下表);存储根仍由 DSH_HOME 解析。挂载行即默认组合里的:
| 配置项 | 默认 | 含义 |
|---|---|---|
maxImageBytes | 20 MiB | 单张图片字节上限 |
maxImagesPerMessage | 20 | 每条消息图片数上限 |
maxMessageImageBytes | 200 MiB | 每条消息图片字节总和上限 |
maxImagePixels | 64 M | 单张像素数上限 |
maxImageDimension | 8192 | 单边长上限 |
normalizedImageMaxDimension | 2048 | 规范化重编码后的单边长上限 |
normalizedImageMaxBytes | 4 MiB | 规范化重编码后的字节上限 |
imageCompressionConcurrency | 2(上限 8) | 压缩/规范化并发 |
- id: attachment-local
name: '@deepseek-ai/dsh-attachment-local'
提交前每张图按规范化策略做确定性重编码(超限时缩到规范化上限内),落盘的是规范化编码后的字节;saveImage/saveImages 返回的引用旁附带源事实与规范化事实。这些限额只作用于图片:通用文件写入路径不设类型与大小限制。readImageRequest(0.1.1)是"读 + 按模型路由策略投影请求载荷"的合并 API,带请求级去重。
六、已知限制
- 光栅图片限制只作用于图片:图片在部署限额内接受 PNG、JPEG、WebP、GIF;其他文件按字节原样保存、不设类型与大小限制,音频与视频暂无专门处理
- 保留与垃圾回收延后(恢复/派生会话可能共享不可变对象)
- 持久化未发送草稿、音频与视频需要独立的生命周期与 provider 契约
七、验证
# 看 attachment-local 是否装载
dsh web --dump-config | grep -iE "attachment"
# 看附件对象目录(owner-only,按 sha256 前缀分桶)
ls -la ~/.dsh/attachments/v1/objects/
# 通用文件:规范对象与只读引用路径
ls -la ~/.dsh/attachments/v1/file-objects/ ~/.dsh/attachments/v1/files/