ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

qwen-code 钉钉富文本多图消息完整送达方案:从 richText 回调到 Web Shell 回放的实现指南

qwen-code 钉钉富文本多图消息完整送达方案:从 richText 回调到 Web Shell 回放的实现指南 qwen-code 钉钉富文本多图消息完整送达方案从 richText 回调到 Web Shell 回放的实现指南【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code本指南基于仓库内实现计划文档 docs/plans/dingtalk-richtext-multi-image.md 及其配套设计文档 docs/design/dingtalk-richtext-multi-image.md 编写并对照packages/channels下的实际源码与测试进行印证。在 qwen-code 的 DingTalk 频道场景中用户在一句消息里同时发送多张图片时旧实现只会把第一张图传给多模态模型其余图片全部丢失。本文以官方实现计划为主线完整讲解一次钉钉richText回调中的每张图片如何被下载、归一化、经 Agent Client ProtocolACP发送给模型、并由 daemon 持久化到会话中供 Web Shell 回放的整条链路包括四个层次的接口契约、顺序保证、兼容性策略、失败语义以及每项任务的 TDD 步骤与验证命令。读完本文你可以掌握该方案的架构脉络并能够在自己的频道适配器中复用这套有序图片集合的桥接模式。背景与问题一张richText回调里的多张图片为何只保留第一张DingTalk钉钉机器人通过 Stream 模式接收消息时一条富文本消息的content.richText数组中可以包含多个picture类型的 part。设计文档 docs/design/dingtalk-richtext-multi-image.md 明确指出旧实现的缺陷The DingTalk adapter extracts every part intodownloadCodes[], but the processing path callsattachMediaonly fordownloadCodes[0]. Consequently the model and daemon-backed Web Shell receive only the first image.也就是说适配器其实已经把所有图片 part 提取成了downloadCodes: string[]数组但后续处理路径只针对downloadCodes[0]调用了attachMedia。最终用户多模态模型和 daemon 支撑的 Web Shell 会话都只能看到第一张图。方案的目标Goal与整体架构Architecture在原计划文档中定义如下目标将一条 DingTalkrichText回调中的每一张图片都送达多模态模型并持久化每一个图片引用供 Web Shell 回放。架构把旧的单图输入与结构化图片附件统一归一化为有序的桥接层图片数组ordered bridge-level image array。DingTalk 下载每一个图片 partACP 为每个图片块发出一条内容块daemon 在提交单条 prompt 之前上传每张图片。技术栈TypeScript、Vitest、DingTalk Stream 适配器、Agent Client ProtocolACP、Qwen daemon 会话附件 API。全局约束四条不可触碰的红线实现计划在正文开始前列出了四条全局约束任何实现细节都不得违反保留imageBase64与imageMimeType的兼容性所有现存适配器与桥接调用方仍可用旧的单图字段不能因为新增images数组就破坏它们。保留源顺序图片顺序必须从content.richText[]一路贯穿到模型 prompt 与 Web Shell transcript不允许中途重排。不改动活跃 turn 期间独立消息的采集逻辑本方案只解决一条回调内的多张图片不改变 turn 进行中陆续到达的独立消息如何被引导或聚合。不引入任何新依赖或新配置纯代码层面的行为修正。配合设计文档 docs/design/dingtalk-richtext-multi-image.md 的兼容性说明还可以补充两点无效的部分遗留字段对只有imageBase64没有imageMimeType之类与今天一样会被忽略现有单图调用方的排序与行为保持不变。四层数据流从钉钉回调到 Web Shell 回放整个方案可以拆成四个层次每一层对应计划中的一个 Task。数据流的方向是DingTalk richText 回调 │ Task 1: DingtalkAdapter 逐个 downloadCode 调用 attachMedia ▼ Envelope.attachments[]有序、data-backed 图片附件 │ Task 2: ChannelBase 归一化为 ChannelPromptImage[] ▼ ChannelAgentBridgePromptOptions.images[] ├─ Task 3: AcpBridge → 每张图一个 ACP image 内容块在 text 块之前 └─ Task 4: DaemonChannelBridge → 每张图一次 session.uploadAttachment 引用块下面按 Task 顺序展开并结合当前仓库源码说明落地后的真实形态从源码结构看该计划中的改动已经在 packages/channels 中落地。Task 1DingTalk 下载每一个图片 part适配器层涉及文件packages/channels/dingtalk/src/DingtalkAdapter.ts、packages/channels/dingtalk/src/DingtalkAdapter.test.ts接口契约消费按回调顺序消费extractContent(data).downloadCodes: string[]。产出Envelope.attachments其中每个成功下载的图片对应一个>expect(downloadCodes).toEqual([picture-1, picture-2]); expect(envelope.attachments).toEqual([ { type: image, data: Buffer.from([1]).toString(base64), mimeType: image/png, }, { type: image, data: Buffer.from([2]).toString(base64), mimeType: image/png, }, ]);运行测试并验证 REDcd packages/channels/dingtalk npx vitest run src/DingtalkAdapter.test.ts -t downloads every picture in one richText callback预期失败原因旧实现只请求了picture-1且只有一个附件。实现有序多下载把只取第一个替换为顺序循环for (const downloadCode of content.downloadCodes) { await this.attachMedia( envelope, downloadCode, content.mediaType, content.fileName, content.placeholder, ); }关键点是每个成功的图片下载都追加一个>运行定向适配器测试验证 GREENcd packages/channels/dingtalk npx vitest run src/DingtalkAdapter.test.ts提交git add packages/channels/dingtalk/src/DingtalkAdapter.ts packages/channels/dingtalk/src/DingtalkAdapter.test.ts git commit -m fix(channels): retain all DingTalk rich-text images源码印证当前 DingtalkAdapter.ts 中processMessage分支已经实现了上述循环——当content.downloadCodes.length 0 content.mediaType时按回调顺序遍历所有downloadCode依次调用attachMedia随后再处理quoted.media回复中引用的图片。而 attachMedia 对image类型会把下载到的字节转为base64并追加到envelope.attachmentsMIME 类型取自下载响应头并以image/jpeg为兜底非图片媒体则写入临时目录供 agent 读取。测试 DingtalkAdapter.test.ts 正是计划中的downloads every picture in one richText callback用例它 mock 了fetch记录两次downloadCode最终断言下载顺序与两个附件的内容、顺序完全一致。Task 2ChannelBase 承载有序图片集合桥接契约层涉及文件packages/channels/base/src/ChannelAgentBridge.ts、packages/channels/base/src/ChannelBase.ts、packages/channels/base/src/ChannelBase.test.ts接口契约产出ChannelPromptImage { data: string; mimeType: string }与ChannelAgentBridgePromptOptions.images?: ChannelPromptImage[]。兼容输入imageBase64?: string加imageMimeType?: string。TDD 步骤写一个含两个>images: [ { data: first, mimeType: image/png }, { data: second, mimeType: image/jpeg }, ];保留旧测试但把断言改成同构的单元素images形状。运行定向测试验证 REDcd packages/channels/base npx vitest run src/ChannelBase.test.ts -t image增加桥接图片类型与归一化逻辑export interface ChannelPromptImage { data: string; mimeType: string; } export interface ChannelAgentBridgePromptOptions { images?: ChannelPromptImage[]; imageBase64?: string; imageMimeType?: string; displayText?: string; }按遗留字段优先、附件顺序随后的次序构建images并传给promptBridge.prompt没有图片数据的附件继续保留原有的文件路径渲染行为。运行 ChannelBase 全部测试验证 GREEN要求非图片附件行为零变化。提交git add packages/channels/base/src/ChannelAgentBridge.ts packages/channels/base/src/ChannelBase.ts packages/channels/base/src/ChannelBase.test.ts git commit -m feat(channels): carry ordered prompt images源码印证接口定义与归一化逻辑已在 ChannelAgentBridge.ts 落地。resolvePromptImages(options)是整条链路的单一事实来源images非空时优先采用否则回退到imageBase64imageMimeType组成的单元素数组两者皆无则返回空数组随后过滤掉缺data或mimeType的条目一个畸形附件只会降级为该图不参与 prompt与旧字段守卫行为一致并统一清理 MIME去掉参数段如image/png; charsetbinary、转小写同时把非标准别名image/jpg归一化为image/jpeg——这与 daemon 附件存储自身的命名规则保持对齐。Task 3ACP 将每张图片作为原生内容块发送模型侧涉及文件packages/channels/base/src/AcpBridge.ts、packages/channels/base/src/AcpBridge.test.ts接口契约消费ChannelAgentBridgePromptOptions.images保留遗留单图回退。产出ACP prompt 内容其中每个{ type: image, data, mimeType }块都位于 text 块之前。TDD 步骤用两张字面量图片调用prompt断言连接层收到prompt: [ { type: image, data: first, mimeType: image/png }, { type: image, data: second, mimeType: image/jpeg }, { type: text, text: describe both }, ];验证 RED旧实现未消费imagescd packages/channels/base npx vitest run src/AcpBridge.test.ts -t multiple images实现图片迭代用遗留字段对作为回退归一化options.images先 push 每个图片块再 push text 块不改动 ACP 元数据。验证 GREEN要求既有单图行为不回归。提交git add packages/channels/base/src/AcpBridge.ts packages/channels/base/src/AcpBridge.test.ts git commit -m feat(channels): send all prompt images over ACP源码印证AcpBridge.ts 的prompt实现正是先图后文for (const image of resolvePromptImages(options))逐张 push{ type: image, data, mimeType }随后才 push{ type: text, text }并把displayText等元信息放入_meta。这保证了多模态模型收到的是完整的、按原始顺序排列的视觉输入。Task 4Daemon 持久化每一张图片供 Web Shell 回放会话侧涉及文件packages/channels/base/src/DaemonChannelBridge.ts、packages/channels/base/src/DaemonChannelBridge.test.ts接口契约消费有序的ChannelAgentBridgePromptOptions.images含遗留回退。产出每张图片一次session.uploadAttachment调用 一个附件引用 prompt 块。TDD 步骤扩展失败测试传两张图返回两个不同的附件引用断言上传顺序与如下 promptprompt: [ { type: image, attachmentId: image.png, mimeType: image/png, size: 12 }, { type: image, attachmentId: image-2.jpeg, mimeType: image/jpeg, size: 13, }, { type: text, text: describe }, ];验证 RED旧实现只上传单图cd packages/channels/base npx vitest run src/DaemonChannelBridge.test.ts -t stores channel images实现有序上传与唯一命名遍历所有归一化图片生成确定性文件名image.png、image-2.jpeg、…逐个await uploadAttachment在 text 块之前 push 所有返回的引用。验证 GREENprompt 中包含全部已持久化的附件引用。提交git add packages/channels/base/src/DaemonChannelBridge.ts packages/channels/base/src/DaemonChannelBridge.test.ts git commit -m feat(channels): persist all channel images in daemon sessions源码印证DaemonChannelBridge.ts 的实现比计划更进一步使用Promise.allSettled将多张图片的上传扇出并行执行注释明确说明命名按 index 消歧、prompt 顺序来自数组顺序因此无需串行化上传本身每个图片先经channelImageName(image.mimeType, index)生成image.png、image-2.jpeg这类确定性名称再通过decodeChannelImage校验 base64 数据超出 daemon 附件大小上限会被跳过最后调用session.uploadAttachment上传。MIME 子类型无法识别或解码失败时只会打印告警并跳过该图而不会让整轮 turn 失败——这与计划中单图失败不阻断整轮的失败语义一致若 daemon 上传整体失败则维持既有的 prompt 失败语义绝不在图片集合不完整的情况下静默提交。Task 5端到端验证完整行为涉及文件仅当验证暴露缺陷时才修改上述文件运行证据记录在最终交接与 Issue/PR 文本中不提交凭据或回调负载。验证命令从各包目录运行npm run build npx tsc --noEmit -p packages/channels/base/tsconfig.json npx tsc --noEmit -p packages/channels/dingtalk/tsconfig.json cd packages/channels/base npx vitest run src/ChannelBase.test.ts src/AcpBridge.test.ts src/DaemonChannelBridge.test.ts cd packages/channels/dingtalk npx vitest run src/DingtalkAdapter.test.ts预期全部命令退出码为 0。随后是运行时验证重载频道 workernpm run dev -- channel reload再用npm run dev -- channel status确认 DingTalk worker 处于运行状态。执行五图 E2E把五张手图片作为一条 DingTalk 消息一起发出逐一验证恰好接受一个 DingTalk 回调完成五次媒体下载五次 daemon 附件上传均返回 HTTP 201持久化的用户 turn 中按顺序出现五个attachmentReferencesWeb Shell 展示五张图片预览模型正确报告五只手。自审最终 diff不带任何过滤地阅读git diff HEAD^与全部未跟踪文件确认补丁中没有密钥、回调负载、无关的 lockfile 改动或独立消息缓冲行为的变更。失败语义与顺序保证的设计权衡设计文档 docs/design/dingtalk-richtext-multi-image.md 对失败行为做了明确定义这在实际联调中非常重要DingTalk 媒体下载保持顺序执行延续回调顺序某个下载失败时沿用既有的per-media 告警行为且不会阻止其他成功下载的图片进入该 turn。daemon 上传失败维持现有 prompt 失败语义turn 不会带着静默缺失的图片集合被提交——要么全部图片就位要么这轮 prompt 明确失败避免模型基于不完整视觉输入给出错误结论。从源码看resolvePromptImages对畸形条目的过滤、DaemonChannelBridge对超限/未知 MIME 的跳过都是按图降级而非整轮降级。顺序保证则贯穿四层适配器按richText顺序下载 →ChannelBase按遗留字段优先、附件顺序随后归一化 →AcpBridge按数组顺序推入内容块 →DaemonChannelBridge按数组顺序命名、上传并放置引用。用户看到的第一张图就是模型看到的第一张图也是 Web Shell 里渲染的第一张图。测试矩阵与验收标准计划与设计文档共同定义了五类测试构成完整的兼容性防线DingTalk 适配器一个含多个 picture part 的richText回调下载每个 code产出有序图片附件。ChannelBase遗留单图输入与结构化图片输入都能归一化为有序桥接图片集合且不丢弃后面的附件。ACP 桥接每张图都成为 text 块之前的 ACP image 内容块。Daemon 桥接每张图都被上传、在 prompt 中被引用从而可被会话持久化与 Web Shell 回放。既有单图测试保持绿色证明兼容性没有回退。最终验收标准Acceptance criteria非常直观一次在一条 DingTalk 消息中发送五张图片daemon 会话 turn 中产生五个附件引用Web Shell 显示五张图片所选多模态模型按原始顺序收到全部五张图。总结这条钉钉富文本多图完整送达方案的价值在于它以最小侵入零新依赖、零新配置修复了一个真实的用户可见缺陷并把多图作为一等公民贯穿了适配器、桥接层、ACP 与 daemon 会话附件体系。计划文档提供的逐任务 TDD 步骤RED → GREEN → commit与全局约束是理解该改动设计意图的最佳入口而resolvePromptImages的归一化逻辑、AcpBridge的先图后文内容块编排、DaemonChannelBridge的并行上传与确定性命名则是这套方案在 packages/channels 源码中的具体落地形态。如果你正在为自己的频道适配器增加多图能力这套有序图片集合 遗留字段回退 分桥接层消费的契约设计可以直接复用。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表