news 2026/9/13 22:53:03

qwen-code 会话附件引用(Session Attachment References)设计解析:从 base64 内联到文件名引用的存储与生命周期

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
qwen-code 会话附件引用(Session Attachment References)设计解析:从 base64 内联到文件名引用的存储与生命周期

qwen-code 会话附件引用(Session Attachment References)设计解析:从 base64 内联到文件名引用的存储与生命周期

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

导读

本文以 qwen-code 设计文档 docs/design/session-attachment-references.md 为骨架,系统讲解 daemon 如何将图片与任意文件字节落盘到会话附件目录、以文件名引用(attachment reference)替代 base64/字节内联,从而避免在 daemon 请求、队列、事件与回放(replay)数据中重复携带大体积负载的设计。读完本文,你将掌握附件引用的数据结构与命名规则、存储位置与所有权模型、8 MiB 单文件上限等边界约束,以及session_attachments能力与/session/:id/attachmentsHTTP 路由在 TypeScript SDK 与 web-shell 中的真实调用形态。

背景问题:内联负载带来的重复膨胀

在引入附件引用之前,图片 base64 与文件字节会被直接嵌入 daemon 的请求体、消息队列、事件流和会话回放数据中。同一份附件在「上传 → 排队 → 转发给 ACP 子进程 → 会话记录回放」的每一跳都会被复制一份,产生以下问题:

  • 负载重复膨胀:同一字节在多份请求/事件中反复出现,内存与网络开销随链路长度线性放大;
  • 回放困难:附件需要在 daemon 重启后仍可预览,内联字节若只存在于进程内存中,重启即丢失;
  • 无统一出处:不同客户端各自携带字节,缺少单一可信来源(single source of truth)。

设计文档给出的解法非常直接:daemon 将图片和任意文件字节写入 workspace runtime 的附件目录,只返回一个基于文件名的引用,后续所有链路传递的都是这个轻量引用。

核心数据结构:基于文件名的附件引用

daemon 在存储附件后返回如下引用对象:

{ type: 'image' | 'resource'; attachmentId: string; mimeType: string; size: number; }

各字段含义:

字段说明
typeimage(图片)或resource(任意文件资源)
attachmentId附件在存储目录中的文件名,即引用本身
mimeType附件 MIME 类型(如image/pngapplication/pdf
size附件字节大小

这一结构在 TypeScript SDK 中有完全对应的类型定义,见 packages/sdk-typescript/src/daemon/types.ts#L4676-L4681:

export type DaemonSessionAttachmentReference = Record<string, unknown> & { type: 'image' | 'resource'; attachmentId: string; mimeType: string; size: number; };

配套的读取结果类型为DaemonSessionAttachmentData{ data: string; mimeType: string },data 为 base64 字符串),见 packages/sdk-typescript/src/daemon/types.ts#L4683-L4686。

命名规则与去重

  • attachmentId 就是存储文件名,没有独立的 ID 体系;
  • 重名文件采用平台通用约定追加序号:name (1).extname (2).ext,以此类推;
  • 不存在内存中的附件索引,也没有 sidecar 元数据;MIME 类型与大小在读取时直接从存储文件推导,从而避免了索引与实际文件之间的一致性维护成本。

引用在链路中的流转

Prompt 与 mid-turn API 将引用对象随队列、事件、会话记录元数据(transcript metadata)一起传递,但只有真正向 ACP 子进程派发(dispatch)时才由 bridge 解析引用——即「引用轻量传递、字节延迟物化」。

  • TypeScript 会话客户端通过带鉴权的附件路由(authenticated attachment route)读取同一份引用,用于预览与回放渲染;
  • 文本类资源解析为 ACP text;其他文件格式解析为 ACP blob,原始字节不会在浏览器中被解码或改写。

对应实现中,上传与解析的桥接逻辑位于 packages/channels/base/src/DaemonChannelBridge.ts(含removeAttachment钩子、session_attachments能力预检与按批次扇出上传、以及不支持该能力时的图片内联降级路径);浏览器侧的预览/回放水合逻辑见 packages/web-shell/client/daemon/session/actions.ts 与 packages/web-shell/client/daemon/session/types.ts。

存储位置与所有权模型

存储路径

附件落盘于 workspace runtime 的附件目录:

~/.qwen/tmp/<workspace-hash>/attachments/session-<id>/

其中<workspace-hash>是 workspace 的哈希标识,session-<id>是会话 ID;使用自定义 runtime 目录时,路径等价替换。

所有权与鉴权

  • resolved live-session owner 与客户端授权保护每一次上传(upload)、读取(read)与移除(remove)操作;
  • 关闭 daemon 或客户端断开连接只关闭文件句柄,不会删除文件——附件是持久化资源,不随连接生命周期消失;
  • 永久删除会话时,其附件目录一并移除,避免孤儿文件残留。

生命周期策略:刻意从简

设计上刻意排除了常见的复杂回收机制:

  • 无 TTL:附件不设过期时间;
  • 无 sweeper:没有后台清扫任务;
  • 无 retained-media cache:不维护媒体缓存;
  • 无重启重建索引:daemon 重启不需要重建任何附件索引。

这与「无内存索引、无 sidecar 元数据」的设计一脉相承:附件就是普通文件,文件名即 ID,状态完全可由文件系统自身表达。

大小限制与配额

  • 单个附件上限 8 MiB8 * 1024 * 1024字节);
  • 会话没有累计附件大小或数量上限

在源码中,单文件上限常量定义为SESSION_ATTACHMENT_MAX_ITEM_BYTES = 8 * 1024 * 1024,另有SESSION_ATTACHMENT_MAX_NAME_BYTES = 255限制文件名长度,见 packages/acp-bridge/src/sessionAttachments.ts#L13-L14。同一文件中还定义了受支持的图片 MIME 类型集合(image/bmpimage/gifimage/jpegimage/pngimage/webp)。

能力声明与统一 HTTP 接口

能力:session_attachments

统一能力标识为session_attachments,在 serve 能力表中声明为v1起可用,见 packages/cli/src/serve/capabilities.ts#L57-L58。

HTTP 表面:/session/:id/attachments

统一 HTTP 路由为/session/:id/attachments不存在session_media/mediamediaId之类的兼容路径——设计上明确只保留一套接口。

TypeScript SDK 在 packages/sdk-typescript/src/daemon/DaemonClient.ts 中提供了四个对应的客户端方法:

方法HTTP 路由作用
uploadSessionAttachment(sessionId, data, name, mimeType, opts?)POST /session/:id/attachments?name=<name>上传字节,请求体为原始字节流,Content-Type即附件 MIME,返回引用对象
readSessionAttachment(sessionId, attachmentId, opts?)GET /session/:id/attachments/:attachmentId读取附件内容,返回{ data: base64, mimeType }
listSessionAttachments(sessionId, opts?)GET /session/:id/attachments按上传顺序列出该会话当前存储的全部附件引用
removeSessionAttachment(sessionId, attachmentId, opts?)DELETE /session/:id/attachments/:attachmentId删除附件,返回{ removed: boolean }

从实现细节可以印证设计中的几个要点:

  • 引用即文件名:上传 URL 以 query 参数name携带原始文件名,daemon 据此落盘并返回attachmentId(即存储文件名),见 DaemonClient.ts#L4080-L4102;
  • 读取时推导 MIME 与大小:读取响应的mimeType直接取自响应头content-type,缺失时回退为application/octet-stream,见 DaemonClient.ts#L4128-L4163;
  • 浏览器兼容:读取实现将Uint8Array分块(每块0x8000字节)再btoa编码,避免超出引擎参数上限,注释明确说明该包同时面向 Node 与浏览器环境;
  • mid-turn 预检enqueueMidTurnMessage的文档注释提示调用方应先预检session_attachments能力,旧 daemon 会忽略 media 字段并丢弃图片内容,见 DaemonClient.ts#L4212-L4229。

web-shell 会话层将这四个方法进一步封装为uploadAttachment/readAttachment/listAttachments/removeAttachment(含read_attachmentremove_attachment等权限动作),并会在附加附件块前预检能力,见 packages/web-shell/client/daemon/session/types.ts#L565-L581 与 packages/web-shell/client/daemon/session/actions.ts#L2546-L2625。

实现层面的安全加固:目录防替换校验

虽然设计文档保持简洁,但从实现可以看到一层额外的持久化安全措施:附件存储目录被包装为DurableAttachmentDirectory,通过持有目录句柄并校验dev(设备号)与ino(inode 号)来检测目录是否被替换(如符号链接攻击或目录被删除重建),校验失败时抛出'Session attachment parent directory changed.',见 packages/acp-bridge/src/sessionAttachments.ts#L23-L71。这层校验与文档「resolved live-session owner 和客户端授权保护每一次上传、读取、移除」的所有权要求互相配合,共同构成附件读写的安全边界。

小结:引用式附件设计的取舍

会话附件引用方案的核心取舍可以概括为:

  1. 以文件名替代字节内联,让队列、事件与回放数据只携带轻量引用,字节只存在于磁盘与最终物化环节;
  2. 文件系统即状态,无索引、无 sidecar、无 TTL、无重建,生命周期完全由会话删除驱动,换来极低的维护复杂度;
  3. 边界清晰:单文件 8 MiB、文件名 255 字节上限、统一session_attachments能力与/session/:id/attachments路由,且不保留任何旧式/media兼容路径。

对需要集成 qwen-code daemon 的客户端而言,接入路径非常明确:预检session_attachments能力 → 通过POST /session/:id/attachments上传并拿到attachmentId→ 在 prompt/mid-turn 内容块中携带引用 → 需要预览或回放时通过GET路由按 ID 读取、按需DELETE。相关类型定义、客户端方法与桥接实现均可在 packages/sdk-typescript/src/daemon、packages/acp-bridge/src/sessionAttachments.ts 与 packages/channels/base/src/DaemonChannelBridge.ts 中进一步查阅。

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 22:51:39

备忘录模式在工作流草稿箱与状态回退中的实现

备忘录模式在工作流草稿箱与状态回退中的实现做政企协同办公或复杂审批流系统时&#xff0c;用户经常在表单填写一半时临时退出&#xff0c;或者在多步驳回、撤销操作时要求“一键还原到上一步编辑状态”。很多团队初期的做法简单粗暴&#xff1a;前端本地存 localStorage&…

作者头像 李华
网站建设 2026/9/13 22:43:15

Firecrawl 实战:将网站转换为大模型可用数据

本文摘要&#xff1a;传统爬虫直接获取的 HTML 包含导航、脚本、广告等噪音&#xff0c;无法作为大语言模型&#xff08;LLM&#xff09;的优质上下文。Firecrawl 是一款开源的网页数据转换引擎&#xff0c;它提供了一条清晰的管线&#xff1a;输入 URL → 智能爬取/渲染 → 输…

作者头像 李华
网站建设 2026/9/13 22:39:16

具身机器人OpenAPI二次开发这5条对接文档必须撕开

想做具身机器人 OpenAPI 二次开发&#xff1f;这 5 条对接文档设计必须撕开 最近帮一位做具身机器人二次开发的客户做对接支持&#xff0c;对方工程师感慨&#xff1a;“接口字段定义能看懂&#xff0c;但放到实际业务场景里不知道该怎么用。” 这也是今天想重点聊聊的话题。 我…

作者头像 李华