qqpilot-dsh-plugin
把 QQPilot 的 QQ 消息接入 DeepSeek Harness(DSH)的桥接插件: 对 QQPilot 暴露一个 OpenAI Chat Completion 兼容端点,由 DSH 里配置的模型通过智能体工具决定 「回复什么 / 不回复 / 附带图片」;提供 工具 / 兼容 / 静默 三种运行模式,并在设置页内置 可视化聊天动态(实时看到收到的 QQ 消息、AI 的决定、回复内容、耗时与 token)。
QQPilot 原本只会把聊天记录 POST 给一个 OpenAI 兼容的 /chat/completions 并取回文本。
本插件接管这个端点后,把请求转成一次 DSH 模型调用,再把模型的决定收敛回 OpenAI 响应:
QQPilot ──POST /qqpilot/v1/chat/completions──▶ 本插件(DSH Host)
│
┌──────────────┴───────────────┐
│ 工具模式(默认) │ 兼容模式 / 静默模式
│ 给模型 2 个工具: │ 兼容:不给工具,直接返回文本
│ qqpilot_reply(text, images) │ 静默:不调用模型,只记录消息
│ qqpilot_do_not_reply │
└──────────────┬───────────────┘
▼
DSH llm.stream(使用 DSH 当前默认模型)
│
◀── choices[0].message.content(字符串)+ 可选 message.images[] ──┘
功能
- OpenAI 兼容端点:
POST /qqpilot/v1/chat/completions(非流式),另提供GET /qqpilot/v1/models。 - 三种运行模式(设置页可切换)
- 工具模式(默认,agent):模型通过
qqpilot_reply/qqpilot_do_not_reply决定是否回复、回什么。 - 兼容模式(compat):不给工具,直接把模型输出作为回复返回,适合不支持 tool calling 的模型。
- 静默模式(silent):不调用模型、不消耗 token,只把收到的消息记录到聊天动态里(观察/挂机用)。
- 工具模式(默认,agent):模型通过
- 智能体工具(工具模式)
qqpilot_reply:模型决定回复时调用;参数text(正文)与images(图片路径 / http(s) URL / data URL)。qqpilot_do_not_reply:模型决定保持沉默时调用,插件返回空content,QQPilot 跳过发送。- 模型没有调用工具而是直接输出文本时,文本仍然会作为回复返回(不会因模型“忘记调工具”而丢消息)。
- 多轮工具循环上限可配置;工具调用失败时可自动回退到兼容输出。
- 可视化聊天状态(两种入口,均已内置)
- DSH 聊天窗口内:聊天输入框上方常驻一条可折叠的「QQPilot」状态条,显示当前模式、最新一条 QQ 消息与 AI 决策;展开后可看最近 6 条收/发记录(含 DSH 工具调用),每 2 秒自动刷新。
- 设置页 → 聊天动态:完整记录视图,支持暂停刷新、查看耗时/token/工具调用与错误。
- 可调用 DSH 工具:工具模式下可把 DSH 的工具(默认只读 + 联网:
read/glob/grep/read_image/web_search/web_fetch)一并提供给桥接后的 AI;AI 可先查资料/读文件,再用qqpilot_reply回复。可在设置页选择「关闭 / 安全 / 全部」策略,危险工具(pwsh/bash/write/edit/subagent/workflow/plugin_manager)默认拒绝。 - 模型选择:默认跟随 DSH 全局默认模型;设置页「模型」卡片可改为仅 QQPilot 指定的 provider/model(下拉框,保存后下一轮生效),也可在
cordis.patch.yml固定。 - 返回图片:回复图片会被规范化成
data:image/...;base64,...放进choices[0].message.images[].image_url.url(QQPilot 唯一稳妥的图片返回形式);仅有图片没有文本时会自动 补一个短 caption,避免 QQPilot 把整条回复当作空内容跳过。 - 接收图片:QQPilot 的 vision 请求(
content分段数组中的image_url)会通过 DSHattachments服务转成模型可读的图片块;没有附件服务时退化为文字占位,不会让请求失败。 - 设置页:DSH 设置 →「QQPilot 接入」,含「概览 / 聊天动态 / 设置」三个页签:端点地址、运行模式、 附加指令、请求统计、测试连接与实时聊天状态。
- 运行时状态:设置页的修改持久化在
~/.dsh/qqpilot-dsh-plugin.json(mode、compatibilityMode、extraInstructions),重启 DSH 后仍生效。
安装
本仓库就是一个 DSH bundle 包(package.json 里 dsh.bundle.patch 指向 cordis.patch.yml),无需构建步骤。
方式一:

方式二:在 DSH 当前 profile 里安装本目录(推荐,开发时本地链接):
plugin_manager → install_bundle → target = <本目录绝对路径>
或命令行:
dsh plugin --profile <profile> add C:\path\to\qqpilot-dsh-plugin
dsh --profile <profile>
方式二:已安装后直接启用/停用 bundle(插件会热加载,无需改代码):
plugin_manager → set_bundle / set_plugin
安装成功后宿主日志会出现:
[qqpilot] QQPilot 接入已就绪 endpoint=http://127.0.0.1:<端口>/qqpilot/v1 模型=<provider>/<model> 兼容模式=false
配置 QQPilot
编辑 QQPilot 的 config.ini([general] 段):
# 指向本插件暴露的地址;末尾不要带 /,QQPilot 会直接拼 "/chat/completions"
server_url = http://127.0.0.1:55068/qqpilot/v1
# QQPilot 对 127.0.0.1 / localhost 不会发送 Authorization,这里留空即可
api_key =
# 本插件使用 DSH 当前默认模型;QQPilot 里的 modelname 不会被用于路由
modelname = deepseek-flash
# 需要把 QQ 里的图片也交给模型时开启(插件会把 image_url 转成 DSH 图片块)
isvisionmodel = true
remote_server_timeout = 300
端口以 DSH Web 实际监听为准(设置页里会显示完整端点)。如果 DSH 换了端口,QQPilot 的
server_url也要同步改。 QQPilot 的 Ollama / builtin 后端不会走本插件,请保持forceollamaapi = false。
三种运行模式
| 工具模式(默认,agent) | 兼容模式(compat) | 静默模式(silent) | |
|---|---|---|---|
| 是否调用模型 | 是 | 是 | 否(不消耗 token) |
| 模型收到的工具 | qqpilot_reply / qqpilot_do_not_reply |
无 | 无 |
| 决定回复 | 模型调用 qqpilot_reply |
模型直接输出文本 | 永不回复 |
| 决定沉默 | 模型调用 qqpilot_do_not_reply(返回空 content) |
模型输出空文本 | 总是返回空 content |
| 图片 | qqpilot_reply.images 支持本地路径 / URL / data URL |
不支持(只返回文本) | 不支持 |
| 聊天动态 | 记录完整决策 | 记录完整决策 | 只记录收到的消息(观察用) |
| 适合 | 支持 tool calling 的模型;希望“该不该插话”由模型自己判断 | 不支持工具的模型;或希望完全沿用 QQPilot system.txt 的直接输出 | 挂机观察 / 临时静音,不花钱 |
在 DSH 设置 → QQPilot 接入 → 概览(或设置) 里切换运行模式,立即生效并持久化; 也可以在该页「聊天动态」页签实时查看收到的消息与 AI 的回复/沉默/错误。
选择模型(三层,按优先级从高到低)
- 插件设置页 →「模型」卡片(推荐):默认「跟随 DSH 默认模型」;关掉开关后可从下拉框选择
provider 与 model(例如
deepseek-official/deepseek-flash、llamacpp/MiniCPM5-1B-F16.gguf、xiaomi/mimo-v2.6-flash),点「保存模型」后下一轮请求生效,持久化到~/.dsh/qqpilot-dsh-plugin.json。 - DSH 全局默认模型:未指定时插件实时读取 DSH 当前的默认模型,随 DSH 底部模型选择器 / Settings → Models 变化。
cordis.patch.yml的provider/model:给默认值用;设置页的显式指定会覆盖它。
模型列表通过 DSH
llm服务的listProviders()/listModels()实时读取;某个 provider 读取失败会在设置页提示。 不同模型能力差异较大:工具调用建议使用支持 tool calling 的模型(如 DeepSeek 系列);本地小模型(1B 级)在 多工具场景下可能很慢甚至超时,可把 DSH 工具策略设为「关闭」或改用更大模型。
配置项
cordis.patch.yml 中 config: 支持以下字段(设置页暴露运行模式与附加指令):
| 字段 | 默认 | 说明 |
|---|---|---|
mode |
agent |
运行模式:agent(工具)/ compat(兼容)/ silent(静默) |
compatibilityMode |
false |
旧版兼容字段;true 等价于 mode: compat(仍可用,会被 mode 覆盖) |
provider / model |
空 | 空 = 跟随 DSH 默认模型;也可在这里给默认值(设置页「模型」卡片可覆盖) |
reasoningEffort |
空 | 推理强度,空 = 跟随服务商默认 |
extraInstructions |
空 | 追加到系统指令里的额外要求(三种模式都生效) |
maxToolRounds |
4 |
工具循环最大轮数 |
dshTools |
safe |
是否向桥接后的 AI 提供 DSH 工具:off / safe(只读+联网)/ all(除危险工具外全部) |
dshToolDeny |
["pwsh","bash","write","edit","subagent","workflow","plugin_manager"] |
始终拒绝的 DSH 工具名 |
maxDshToolCalls |
6 |
单次请求内最多执行多少次 DSH 工具 |
historyLimit |
40 |
最多转发的历史消息条数 |
requestTimeoutMs |
180000 |
单次请求的模型调用超时(毫秒) |
fallbackToCompat |
true |
工具模式调用报错时是否回退为兼容输出 |
temperature |
0 |
>0 时发送;0 表示不发送该参数 |
maxTokens |
0 |
>0 时发送;0 表示使用服务商默认 |
maxInboundImages |
8 |
单次请求最多读取几张入站图片 |
maxReplyImages |
4 |
单次回复最多附带几张图片 |
maxReplyChars |
500 |
回复长度上限(Unicode 字符),按 [[NEXT]] / 换行边界优雅截断;0 = 不截断(QQPilot 自身仍会硬截断到 500) |
maxImageBytes |
8388608 |
单张图片大小上限(8 MiB) |
imageRoots |
[] |
允许读取本地图片的根目录;默认允许 DSH 工作区 / cwd |
imageOnlyCaption |
🖼️ |
只有图片没有文本时的占位 caption |
logRequests |
false |
是否打印每次请求的摘要到宿主日志 |
HTTP 接口
| 方法 & 路径 | 用途 |
|---|---|
POST /qqpilot/v1/chat/completions |
QQPilot 使用的 OpenAI 兼容端点 |
GET /qqpilot/v1/models |
模型目录(返回 DSH 当前模型) |
GET /qqpilot/api/config |
设置页读取配置 / 端点 / 统计 / 运行时长 / 可用 DSH 工具列表 |
POST /qqpilot/api/config |
设置页写入 {mode?, compatibilityMode?, extraInstructions?, dshTools?} |
GET /qqpilot/api/activity?since=&limit= |
设置页「聊天动态」轮询:最近的消息 + AI 决策 |
POST /qqpilot/api/test |
设置页“测试连接”:走一遍完整回复流程 |
GET /qqpilot/api/status |
统计信息 |
GET /qqpilot/api/tools |
DSH 工具桥诊断:策略、可见工具、专用 bridge agent、全局/作用域工具 |
POST /qqpilot/api/tools/test |
通过真实工具管线执行一次 read(读插件自身 package.json),验证 DSH 工具调用链路 |
GET /qqpilot/api/models |
模型目录:DSH provider/model 列表、当前默认选择、生效模型 |
与 QQPilot 的输出约定
choices[0].message.content必须是字符串;空字符串表示这次不回复(QQPilot 会静默跳过)。- 回复文本会被 QQPilot 按
[[NEXT]]拆成多条消息、按\n每行拆成一条消息,并硬截断到 500 字符。 所以模型输出应是纯文本、不要依赖 Markdown 换行排版。 - 图片放在非标准扩展字段
choices[0].message.images[],值必须是data:image/<ext>;base64,...data URL; 文本为空时 QQPilot 不会上传图片,因此插件会在“只有图片”时补 caption。 - 更精确的协议依据(源码行号、字段硬性要求、失败时的静默行为)见 PROTOCOL.md。
验证
# 离线:用假 ctx / 假 llm 直接跑路由与工具循环(无需安装)
node test/host-unit.mjs
# 在线:模拟 QQPilot 发真实请求并严格校验响应形状
node test/mock-qqpilot.mjs --url http://127.0.0.1:55068/qqpilot/v1
# 在线:多场景验收(回复 / 文本路径 / 模式探测 / 图片)
node test/verify.mjs --url http://127.0.0.1:55068/qqpilot/v1
故障排查
| 现象 | 可能原因 |
|---|---|
| 设置页显示“加载失败” | 插件未加载 / 路由未注册;看宿主日志有无 [qqpilot] 报错 |
| QQPilot 端“机器人不理人” | server_url 写错(多了 /、端口不对);模型调用报错被记为 500;看宿主日志 [qqpilot] 请求处理失败 |
| 模型总是沉默 | 工具模式提示词让模型判断“不必插话”;可换成兼容性模式,或在 extraInstructions 里要求更积极回复 |
| 图片没发出去 | 图片路径不在 imageRoots / 工作区内;图片超过 maxImageBytes;模型只给了文本 |
| 模型调用报 401 / 无可用模型 | DSH 里没有可用的默认模型或凭据;先在 DSH 设置里选好模型 |
安全与兼容性
- 端点注册在 DSH 的
webServer上,默认仅本机可访问;QQPilot 走回环地址,不需要 API key。 如需从其它机器接入,请在网络层限制访问,并自行评估server_url使用非回环地址时 QQPilot 会发送Authorization: Bearer <api_key>的行为。 - 目标环境:DSH
0.2.1-rc.2系列(使用公开服务webServer/llm/attachments/agentDefaultModel), Node.js>=22.13.0。无第三方运行时依赖。 - 插件卸载/热重载时自动注销所有路由与资源(
ctx.effect管理)。
验收结果(2026-10-24,DSH 0.2.1-rc.2 / 本机)
已通过 plugin_manager install_bundle 安装进 desktop profile 并热加载(application: applied):
| 检查 | 结果 |
|---|---|
GET /qqpilot/api/config |
200,endpoint=http://127.0.0.1:55068/qqpilot/v1,模型 deepseek-official/deepseek-flash |
POST /qqpilot/api/test(工具模式) |
{"ok":true,"mode":"tool","reply":{"text":"连接正常,测试通过。"}},约 1s |
| 兼容性模式切换 + 持久化 | POST 开关生效;~/.dsh/qqpilot-dsh-plugin.json 写入 {"compatibilityMode":true},切回 false 正常 |
test/mock-qqpilot.mjs |
PASS:HTTP 200,content="等于2",响应形状符合 QQPilot 契约 |
test/verify.mjs |
PASS:硬场景 2/2;基础回复 / 文本路径 / 模式探测 / 图片返回全部通过 |
| 返回图片 | 模型调用 qqpilot_reply(images=[data:image/png;...]) 后响应带 1 张 message.images[] data URL(是否发图由模型决定:另一次运行模型选择只发文字,属模型行为,不算协议失败) |
| 接收图片 | 入站 image_url data URL 经 attachments 转成图片块,模型正确回答了图片内容 |
| 静默 | 模型调用 qqpilot_do_not_reply 时返回空 content(统计 silences≥1) |
| 设置页 | Web Client settings.section 已出现 qqpilot(order 62,label「QQPilot 接入」),active=true |
| 离线单测 | node test/host-unit.mjs 全部 PASS(路由、系统消息合并、工具循环、图片、三种模式、聊天动态、旧配置升级、[[NEXT]] 清洗、400 校验) |
端口和模型取决于当前 DSH 会话,重启后以设置页显示的 endpoint 为准。
第二轮(可视化聊天状态 + 运行模式 + 深色模式修复)
| 检查 | 结果 |
|---|---|
| 运行模式切换 | POST /qqpilot/api/config {mode} 在 silent / compat / agent 间即时切换并持久化到 ~/.dsh/qqpilot-dsh-plugin.json |
| 静默模式 | 请求 1ms 返回空 content,usage=0,不调用模型;聊天动态记录 mode=silent, kind=silence |
| 兼容模式 | 返回模型直出文本;聊天动态记录 mode=compat, kind=reply |
| 工具模式 | 模型经工具决定回复;聊天动态记录 mode=agent, kind=reply、耗时与 tokens |
| 聊天动态接口 | GET /qqpilot/api/activity?since=0&limit=10 返回 3 条(静默/兼容/工具),含 sender(从 [username] 解析)、入站文本、回复、图片数、elapsedMs、usage;增量 since=latestId 正常 |
| 设置页 | 设置页出现「概览 / 聊天动态 / 设置」页签与运行模式选择器;Web Client Slot 仍为 active(HMR 重载后) |
| 深色模式按钮 | 主按钮文字与开启态开关圆点改用 --dsw-alias-label-primary-foreground(浅色=白、深色=近黑),不再出现白字白底 |
可视化聊天状态为浏览器渲染;Slot 注册与接口数据已验证,实际视觉效果请在 DSH 设置页确认(必要时刷新页面加载最新客户端代码)。
第三轮(聊天窗口状态条 + DSH 工具调用)
| 检查 | 结果 |
|---|---|
| 聊天窗口状态条 | Web Client conversation.input.dock 中已注册 qqpilot-status(order 70,active);输入框上方显示 QQPilot 模式与最新消息,展开看最近 6 条,2 秒自动刷新 |
| 设置页并存 | settings.section 的 qqpilot 仍 active(两个入口同时保留) |
| DSH 工具可见 | GET /qqpilot/api/config 的 availableDshTools 返回 read, glob, grep, web_search, web_fetch, read_image(安全策略) |
| DSH 工具作用域 | 插件自动创建专用 qqpilot-bridge Agent(挂载当前默认 preset),工具在该 Agent 作用域内执行,不污染用户会话;GET /qqpilot/api/tools 可看到 35 个可见工具与过滤结果 |
| DSH 工具真实调用 | POST /qqpilot/api/tools/test → ok:true,read 读取插件 package.json,9ms 返回内容(真实 ctx.tools.execute 管线) |
| 危险工具拒绝 | pwsh / bash / write / edit / subagent / workflow / plugin_manager 默认在 dshToolDeny,不会提供给模型 |
| 离线单测 | 新增“安全工具可见并可执行”“关闭策略不提供工具”“未知工具不执行”场景,全部 PASS |
工具调用发生在工具模式下;兼容模式与静默模式不会向模型提供任何工具。首次进入聊天窗口若看不到状态条,刷新一次页面即可加载新的客户端代码。
第四轮(模型选择)
| 检查 | 结果 |
|---|---|
| 模型目录接口 | GET /qqpilot/api/models 返回 7 个 provider(deepseek-official / deepseek-account / xiaomi / llamacpp / spark-x2 / qwen35 / deepseek-r1-0528-qwen3-8b)及各自模型 |
| 设置页模型卡片 | 设置 → QQPilot 接入 → 设置 → 模型:跟随 DSH 默认模型开关 + provider/model 下拉 + 保存模型 |
| 指定模型生效 | POST {provider:"deepseek-official",model:"deepseek-flash"} 后,config.modelSource=override,QQPilot 模拟请求 954ms 返回 "等于2" |
| 恢复默认 | POST {provider:"",model:""} 后 modelSource=default,当前默认 llamacpp/MiniCPM5-1B-F16.gguf |
| 持久化 | provider / model / reasoningEffort 写入 ~/.dsh/qqpilot-dsh-plugin.json |
| 离线单测 | 新增模型目录 + 指定/恢复模型场景,全部 PASS |
License
MIT
No comments yet. Be the first to write one.