dsh-plugins/mock

44Star4Fork1Issue0Watching

为 DeepSeek Harness 提供"伪 LLM"通道:用脚本让 agent 不调用真实模型,而是按 JSON/JSONL 剧本触发预设的工具调用,用于离线测试、教学演示与 CI 回归。

语言
Python
License
MIT
分支
main
dsh-plugindsh-plugin-marketdsh-plugins

安装

$ dsh plugin --profile web add github:Ephemeral-AI-Lab/dsh-plugins/mock

在终端中运行以上命令,通过 dsh CLI 安装此插件。可在右上角切换 Profile。 第一次用 dsh?看这篇新手教程

落地页对应的仓库子路径是 mock,npm 包名 dsh-mockmock/package.json:2-4),是 Ephemeral-AI-Lab/dsh-plugins monorepo 下的子包之一。本百科聚焦 mock 路径对应的能力。

一句话定位

为 DeepSeek Harness 注册一个"伪 LLM"通道,让 agent 在不调用真实模型的前提下,按你写好的剧本触发预设的工具调用,用于离线复现、自动化测试与教学演示。

核心能力

  • 注册内部专用的 provider/model mock/mocksrc/index.ts:132):被 AgentLoop 用来把"LLM 输出"换成预先编排的"工具调用流",真实的模型选择器不显示它。
  • 提供 /mock 斜杠命令(src/index.ts:133-145):命令处理器只把整行原样塞回 session inbox 当作用户消息,让正常 AgentLoop 来执行——不绕过任何宿主逻辑。
  • run 子命令:在输入框直接写 tool({"k":"v"}) 触达单次工具调用,或写 [a({...}),b({...})] 触发一组并行调用(src/parser.ts:71-108)。
  • replay 子命令:从本地脚本文件读取预先编排的剧本逐步重放(src/parser.ts:111-132),可选 --overwrite-wait-time-ms N 覆盖剧本里所有显式等待(src/converter.ts:134-160)。
  • 同时支持两种 replay 源格式:canonical dsh-mock-script JSON 和原生的 DSH JSONL session 日志,自动识别转换(src/converter.ts:172-251src/index.ts:341-360)。
  • 实时把状态推给宿主 UI:在对话输入框上方渲染一个紧凑的 Mock status dock,展示当前第几步、总共几步、是否在等待工具回执(src/client/index.ts:19-26src/client/MockStatusRow.tsx:90-92)。
  • 状态经 mockStatus projection 暴露(src/projection.ts:34-57),失活或中止会回写为 cancelled,下次会话恢复时识别为过期。

技术实现

  • 语言: TypeScript(tsconfig.json:3-21 严格模式,目标 ES2024 / NodeNext
  • 关键依赖:
    • @deepseek-ai/dsh-llm(>=0.1.0-rc.5):扩展 LlmAdapter 注册内部 mock 路由
    • @deepseek-ai/commands(>=0.1.0-rc.6):用 ctx.commands.register 注册 /mock
    • @deepseek-ai/cordis(>=4.0.0):宿主插件框架,监听 agent/pre-step / agent/request / agent/error / agent/disposed 等事件
    • @deepseek-ai/dsh-session-projection(>=0.1.0-rc.6):注册 mockStatus projection 把状态折叠给客户端
    • @deepseek-ai/dsh-client-ui-slots + @deepseek-ai/dsh-client-ui-conversation + @deepseek-ai/dsh-client-runtime:把 MockStatusDock 注入到 conversation.input.dock 槽位
    • react(>=18.2.0):状态行是 React 组件
    • zod(^4.4.3):projection schema 强校验
  • 架构模式: Cordis 插件,cordis.patch.yml 同时禁用 directory-picker 并把 mock 注入到宿主插件目录(cordis.patch.yml:1-9)。apply(ctx) 注册一个 MockAdapterprovider:'mock',再注册 /mock 命令并通过 agent/pre-step 拦截钩子把 /mock … 文本转成 plan 提交;adapter 内部按编译好的脚本逐步产出 StreamChunk,由宿主 AgentLoop 正常执行 tool 调用。
  • 入口文件: mock/src/index.tsexport name = 'mock'inject = ['llm','commands']src/index.ts:29-30),客户端入口为 mock/src/client/index.ts

适用场景

当你需要让 DSH agent 在不消耗真实模型配额的前提下,按一份固定剧本触发工具调用——比如把一段 DSH JSONL 录播回放成回归测试用例、给新人演示 agent 与工具的交互流程、给 CI 跑一段无网环境下的端到端验证——都可以装这个插件;只要能写出一份 canonical JSON 或现成的 session 日志,/mock replay 一行就能重跑。

前置依赖与兼容性

依赖最低版本说明
@deepseek-ai/cordis>=4.0.0peerDependencies,宿主插件框架
@deepseek-ai/dsh-llm>=0.1.0-rc.5peerDependencies,注册 LLM adapter 必需
@deepseek-ai/dsh-commands>=0.1.0-rc.6peerDependencies,注册 /mock 命令必需
@deepseek-ai/dsh-agent>=0.1.0-rc.6devDependencies,运行时直接持有 Agent 实例
@deepseek-ai/dsh-session + @deepseek-ai/dsh-session-projection>=0.1.0-rc.6peerDependencies,写 mock/status 事件并暴露 projection
@deepseek-ai/dsh-client-runtime + @deepseek-ai/dsh-client-locale + @deepseek-ai/dsh-client-ui-conversation + @deepseek-ai/dsh-client-ui-slots>=0.1.0-rc.6peerDependencies,package.json:27-35 把这四个列在 dsh.client.inject,缺一 Web 端 dock 加载不到
react>=18.2.0peerDependencies,状态行组件运行时
zod^4.4.3devDependencies,projection schema 校验
平台DSH Webpackage.json:28 显式声明 dsh.client.platform: "web",命令行/桌面端未声明
Node.js未声明package.jsonengines 字段;devDependencies 中 @types/node ^22.20.0
原生模块仅依赖纯 JS 包,无 node-gyp 构建项

安装方式

dsh plugin --profile web add github:Ephemeral-AI-Lab/dsh-plugins/mock

配置项

本插件无需额外配置。它不读取任何环境变量或 ctx.config,也没有配置文件;唯一的"配置面"由 /mock 命令参数决定:

命令 / 参数位置类型说明默认值
/mock run <tool({...})>/mock run [a({...}),b({...})]输入框一次或并行一组工具调用inline 触发;多工具必须放在 [ ] 里(src/parser.ts:80-100
/mock replay <path>输入框绝对路径或相对会话工作目录的脚本文件读取脚本后按步重放;相对路径要求 session 当前 header.cwd 已设置(src/index.ts:362-368
--overwrite-wait-time-ms <N>replay 命令尾部非负整数覆盖剧本里所有显式 wait 步骤的毫秒数(src/converter.ts:134-148不传则保留剧本里写的原始值

常见问题

Q: 装完之后我能在模型下拉框里直接选 "mock" 吗?

A: 不能。mock 路由被刻意从对外暴露的模型目录里隐藏(README.md:21-28),普通的模型选择器不会列出它;唯一激活方式是输入 /mock run/mock replay 这类斜杠命令。

Q: /mock run/mock replay 的差别是什么?

A: run 是 inline 触发——直接写一段 tool({"k":"v"})[a({...}),b({...})] 让 agent 立即执行,session 内一次用完即弃;replay 是从本地脚本文件读一份预先编排的剧本逐步重放,适合固定场景的回归测试。两者内部都用同一个 MockAdapter,差别只在脚本来源(src/index.ts:312-339)。

Q: replay 脚本支持哪些文件格式?

A: 两种:(1) canonical dsh-mock-script JSON——{"type":"dsh-mock-script","version":1,"steps":[…]},步骤可以是 toolparallelwait;(2) 原生 DSH JSONL session 日志——以 {"type":"session"} 开头,转换器会自动折叠出工具调用并组装成 canonical 脚本(src/converter.ts:172-251)。ZIP 压缩包直接传入会被显式拒绝,需要先解压拿出 session.jsonlsrc/replay-input.ts:9-17)。

Q: 用 mock 触发的工具调用,会被宿主正常记录吗?

A: 会。MockAdapter 只负责产出 StreamChunk,工具查找、参数校验、授权、执行、结果回写和持久化全部走宿主 AgentLoop 与 ToolRuntime 流水线(src/mock-adapter.ts:89-95);session 事件日志里出现的就是常规的 tool/calltool/resultturn 等条目。

Q: 一次 mock turn 结束后,下一会话会自动继续走真实模型吗?

A: 会。agent/request 钩子会记录该 session 上一次用的真实 provider/model;mock turn 跑完后,下一次普通 turn 自动还原(src/index.ts:217-247),不需要手动切换。

Q: 怎么卸载 mock?

A: 用 dsh plugin --profile web remove 移除安装源即可。effect 回收时会调 adapter.dispose()、注销 /mock 命令与 llm adapter,并清空 session 维度的状态缓存(src/index.ts:282-293)。

上手难度

进阶 — 入门用 /mock run tool({...}) 触一次调用很简单,但要做有意义的 replay(写 canonical JSON 或导出 JSONL)需要先了解 DSH session 事件结构;并且需要理解 agent 内部 provider/model 切换的工作机制才能在出错时排查。

已知问题与限制

  • v1 处于不稳定发布:dsh-mock@0.1.1 的命令、API 与 UI 都可能在稳定版前再变(README.md:3-5)。
  • 直接把 ZIP 包当作 replay 源会被拒:ReplayInputError(UNSUPPORTED_ARCHIVE) 提示先解压出 session.jsonl 再传(src/replay-input.ts:3-17)。
  • replay 模式显式拒绝嵌套/子 agent 工具:工具名匹配正则 (?:^|[-_])(nested|subagent|agent[-_]spawn|spawn[-_]agent)(?:$|[-_]) 时直接抛 UNSUPPORTED_NESTED_TOOLsrc/index.ts:370-375)。
  • inline /mock run 不支持 wait 步骤——wait 语法只能在 canonical 脚本里使用(src/parser.ts:92,103)。
  • 仅声明 dsh.client.platform: "web"package.json:28),命令行 / 桌面端是否可用源码中未明确。
  • canonical 脚本版本被钉死为 1,其它版本会被 MockScriptError(INVALID_SCRIPT) 拒绝(src/converter.ts:84-85)。
  • MockAdapter 只产出 chunk、不做执行:如果宿主的 ToolRuntime 不认识 replay 脚本里写到的工具名,tool/call 事件虽然会出现但宿主侧会报"unknown tool",mock 本身不会"凭空"伪造工具。