Back to home@f25h-233

dsh-cli-switch

LLM-provider plugin for DeepSeek Harness: use local AI CLIs (claude / opencode / gemini / cursor / codex) as model backends, hot-switch in the model selector. DSH 的 LLM provider 层插件:本地 AI CLI 当模型后端,一键热切换。

Stars
0
Language
TypeScript
Created
Aug 20, 2026
Updated
Aug 20, 2026
GitHub repo

Introduction

dsh-cli-switch

⚠️ 开发中(WIP)· Work in Progress

本仓库处于早期开发阶段:M1(opencode-cli 文本对话)+ M2(claude-cli 完全剥壳)冒烟通过,工具执行链路尚未开放(设计见 ROADMAP M4 专题 A)。欢迎围观、试用、提 issue——但不建议生产使用;接口与行为可能随时变更,恕不另行通知。

DeepSeek Harness 的 LLM provider 层插件:把各家本地 AI CLI(claude / opencode / gemini / cursor / codex…)接成 DSH 的"模型后端",模型选择器里一键热切换。

定位一句话:壳是 DSH 的(agent loop、工具、沙箱、日志、UI 全归 DSH),芯是各家 CLI 里的模型——各家 CLI 在本插件中只保留"思考 + 生成 tool_use"的能力,手和脊髓全部截掉换成 DSH 的。

状态:M1 冒烟通过(2026-08-20:opencode-cli 文本对话 + 不落盘 + T5' 剥壳实证);M2 冒烟通过(同日:claude-cli 完全剥壳——SDK 路线工具往返 + 生产环境 24 工具全可见零泄漏 + 不落盘)。计划见 ROADMAP.md,冒烟证据见 docs/smoke/m1.mddocs/smoke/m2.md


1. 背景与生态位

  • DSH(deepseek-ai/deepseek-harness)是 DeepSeek 官方 agent harness,2026-08-13 发布,7 天 16.6 万星,MIT / TypeScript / Cordis 插件架构,"Everything is a Plugin"。
  • DSH 官方已在 subagent 层做了 claude-code / codex 集成(dsh-subagent-claude-code 用 Agent SDK、dsh-subagent-codex 用 codex app-server)——那是"临时工"模式:one-shot 委派任务给产品、拿回最终答案,一个任务一个进程,官方明确不做流式/续传。
  • 本插件做的是 provider 层——"模型脑"模式:产品只当模型通道,持续会话、工具执行、沙箱审批、会话日志、UI 渲染全部是 DSH 的。这个生态位还没人做
  • 已存在的 katsos/dsh-claude-cli(4 星)验证了 claude 单家剥壳可行,本插件是其泛化版本。

2. 核心概念:剥壳 = 截肢 + 接管反射弧

任何完整 agent(有脑有手有循环)塞进 LLM provider 插槽时:

部件SDK 家族(claude)ACP 家族(opencode/cursor/gemini)处置
脑(模型思考 + tool_use 决策)保留——这正是 provider 的职责
脊髓(自己的 agent loop)砍掉——DSH 的 agent loop 接管驱动(ACP 家族:loop 在 agent 侧,客户端只收通知,见"双层现实")
手(内置工具)disallowedTools 按名前缀过滤(R3 实证:'*' 会把我们声明的工具也移除)✘ 项目级 opencode.jsonc tools:{...:false} 按名禁用(R1 实证 8 原生全移除)剁掉——模型看不到任何原生工具
记忆/设置/MCP/钩子清除——统一走 DSH 的会话与凭据体系
假手(MCP bridge)+ tool_use 流式回客户端(input_json_delta,R3)→ DSH 执行+ 工具由 agent 服务端执行,客户端只收 tool_call 通知(R4)接上——DSH 工具伪装成 MCP server 喂给模型

双层现实(M1.5 调研实证)——剥壳有两个层面,两个家族的边界不同:

  • ACP 家族(opencode / cursor / gemini)= 工具面可控 + 执行在 agent 侧:原生工具可按名禁用(项目级 opencode.jsonc,R1 实证 8/8 从模型可见面移除),但 MCP 工具由 agent 服务端执行tool_use 从不回客户端——ACP 规范没有"客户端执行工具并回传结果"的消息类型,这是遥控器设计而非 opencode 特例(R4:opencode 源码级证据——event.tshandleToolPart() 把服务端 ToolPart 状态机翻译成 tool_call/tool_call_update 通知,客户端纯旁观;gemini/cursor 走官方 ACP SDK 同构受限)。客户端能做的:收 tool_call/tool_call_update 通知做 wire 级剥壳断言、权限请求自动拒、桥接报错回传。完整工具执行链 = M4 专题 A(SSE bridge + ctx.tools.execute,R2 实证 API 存在)。
  • SDK 家族(claude)= 完全剥壳input_json_delta 把 tool_use 参数逐字流式回客户端(R3 实证),DSH 执行工具、结果回灌模型——本 README 此前的"tool_use 转发回 DSH 执行"只在本家族成立。

工具名单源原则:工具名永远由 DSH 定义(模型只会"看到什么 schema 发什么 tool_use"),不需要映射表/正则——但协议层会加命名空间前缀:MCP 挂载后模型看到的工具名 = <挂载名>_<工具名>(实证:dsh-cli-switch-bridge_fs_read,R1),claude SDK 路线 = mcp__<server>__<tool>(实证:mcp__dsh-bridge__fs_read,R3)→ 客户端按单一前缀规则(非映射表)翻译回 DSH 裸名。翻译回裸名后:未注册工具名 = 剥壳不彻底,处理方式是拒绝 + 明确报错,绝不模糊匹配(那是绕过 schema 校验和 approval 的漏洞)。

3. DSH 侧接口(插件必须遵守)

插件 = Cordis 插件,核心就一个注册动作:

class MyAdapter extends LlmAdapter {
  async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> { … }
}
export function apply(ctx: Context, config: Config) {
  ctx.llm.registerAdapter(['my-provider'], new MyAdapter(…))
}

StreamChunk 协议铁律(官方 cookbook:docs/cookbook/adding-an-llm-adapter.md):

  • 流类型:block-start / text-delta / reasoning-delta / tool-call-delta / block-end / usage / finish
  • usage 必须在 finish 之前发;finish 之后什么都不发
  • 工具 argumentsraw JSON 字符串端到端;流式片段用 argumentsDelta
  • index 按首次出现顺序分配,同一块的每个 delta 复用同一 index
  • 失败归一化为 finish { kind: 'error' | 'aborted', failure }——错误也是终态
  • 凭据用 cordis 原生 schemastery(!!js process.env.XXX),绝不在代码里读 key 文件
  • 无 API key 场景官方认可:"profile 命名 no credential 时通过 provider 自己的 ambient discovery 或 OAuth 认证"——这正是各家 CLI 本地登录态的用武之地

参考实现:packages/llm/llm-deepseek(直接 HTTP+SSE)、packages/llm/llm-pi-ai(包装第三方库)。官方 subagent 包 packages/subagent/subagent-claude-codepackages/subagent/subagent-codex 有进程生命周期与认证的现成代码可借。

4. 各家 CLI 可剥壳性矩阵(2026-08-20 调研结论)

CLIheadless工具禁用MCP stdioACP总评
claude (2.1.237 / SDK 0.3.220)-pdisallowedTools 按名前缀过滤(R3:'*' 会误伤自己声明的工具)SDK mcpServers 挂 bridge(需 NDJSON 帧,R3)❌ 无(Agent SDK 私有流)M2 冒烟通过(完全剥壳:工具往返 + 24 工具零泄漏 + 不落盘)
opencode (1.18.18)run --format jsonopencode.jsonc tools:{...:false} 按名禁用(R1 实证)opencode.json mcp;session/new 只收 http/sse 挂载opencode acpM1 冒烟通过(文本 + 剥壳实证)
gemini-cli-p --output-format stream-jsonPolicy Engine denysettings.json mcpServers--acp(实验性)✅ 全符合
cursoragent -p❌ 禁不掉(只读模式兜底).cursor/mcp.jsonagent acp + request_permission🟡 可行
codex (0.147.0)exec --json❌ 无通用禁用config.toml mcp_servers❌ 无 ACP;codex mcp-server(实验性)可拒审批🟡 部分可行
windsurf❌ 无程序化 CLI❌ 不可行(已并入 Devin Desktop)

ACP 是事实标准:opencode / gemini / cursor 全有 ACP(session/prompt 流式 + request_permission 可拒 = 天然的"吐 tool_use 不执行"机制);codex 的 mcp-server 是 ACP 风格(thread/turn)。→ 一个 AcpAdapter 基类吃下四家。claude 走 Agent SDK 私有流,单独一个 adapter。

5. 架构设计

浏览器 Web UI(DSH 原装,零改动)
        │ session/event + API Gateway
        ▼
DSH agent loop(turn/step 驱动、prompt 组装、工具调度)
        │ ctx.llm.stream()
        ▼
┌─────────────────────────────────────────────────────────┐
│ dsh-cli-switch(Cordis 插件,注册多个 provider route)     │
│                                                         │
│  ┌─────────────────────┐   ┌─────────────────────────┐  │
│  │ AcpAdapter 基类     │   │ ClaudeSdkAdapter        │  │
│  │ (ACP client 骨架)   │   │ (@anthropic-ai/         │  │
│  │  ├ opencode route   │   │  claude-agent-sdk       │  │
│  │  ├ gemini route     │   │  query() 流式 +         │  │
│  │  ├ cursor route     │   │ 剥壳按名前缀(R3 实证) │  │
│  │  └ codex route      │   │  + mcpServers 挂 bridge)│  │
│  └─────────┬───────────┘   └───────────┬─────────────┘  │
│            │ 进程生命周期/认证/错误分类   │               │
│  ┌─────────▼───────────────────────────▼─────────────┐  │
│  │ 公共框架:spawnCli / stream 协议翻译 / MCP bridge   │  │
│  │ bridge.mjs = DSH 工具 → MCP server(工具名单源)     │  │
│  └───────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘
        │           │
        ▼           ▼
   opencode 子进程   claude 子进程(SDK)
   (ACP stdio)      (私有消息流)

6. 设计原则(已定案)

快死是默认,降级必须显式

  1. provider 不可用(没装/没登录/版本不兼容)→ 明确诊断(稳定 code + cause chain),不自动换芯——用户意图明确,静默换模型违反 DSH "model-visible means logged" 不变式
  2. 瞬时故障(网络抖、启动闪断)→ 分类重试(复用 dsh-llm-retry 的 retryableCodes:RATE_LIMIT/超时可重试,INVALID_CREDENTIAL/QUOTA 不重试)
  3. 能力差异(某家不流 reasoning、不支持多模态)→ 声明式协商:注册时用 resolveModelInfo() 声明能力,UI 据此禁用/标记,运行时只走支持的路——这是"优雅降级"唯一合法形态
  4. 协议/剥壳错误(未注册工具名、帧损坏)→ 终态,绝不模糊。适用范围:SDK 家族(claude)——tool_use 回客户端,收到未注册工具名(mcp__ 前缀翻译回裸名后查表)直接拒绝;ACP 家族——tool_use 从不回客户端(R4),剥壳断言以 wire 级 tool_call/tool_call_update 通知为准(T5' 模式),权限请求自动拒 + 明确诊断;T5' 实证 bridge 报错后模型可能编造内容(幻觉防护 = M4 专题 A)
  5. fallback 链(高级选项):用户配置显式声明(如 codex-cli → claude-cli → deepseek-official)+ 每次降级写进会话日志(UI 可见)——降级本身变成可重放的事实

7. 开发环境

  • DSH 要求:Node 22.19+ / 24+,pnpm 11.7(corepack),pnpm install + pnpm run typecheck
  • DSH 源码:D:\github\free_workspace\deepseek-harness\(tarball 解压,git clone 直连不稳)
  • 插件安装:dsh plugin --profile web add ../dsh-cli-switch(profile 层栈启动时读)或 --patch 一次性覆盖
  • 验证:dsh --profile web --dump-config 看插件行;模型选择器(ui-model-selection)里按 provider 分组出现
  • Windows 下 opencodeBin 解析:npm 全局装的 opencode.cmd shim,spawn 无 shell 直接 ENOENT → resolveOpencodeBin() 自动解析为真实 .exe%APPDATA%\npm\node_modules\opencode-ai\bin\opencode.exe%NPM_CONFIG_PREFIX%/%LOCALAPPDATA% 全局路径探测;src/opencode/bin-resolve.ts + 6 用例测试)
  • 本机 claude 2.1.237 已装(-p/--mcp-config/--output-format stream-json 实锤可用;SDK 0.3.220 工具往返 R3 实测);opencode 1.18.18 已装(C:/Users/qwe13/AppData/Roaming/npm/node_modules/opencode-ai/bin/opencode.exe

8. 风险与开放问题

  • ToS:Claude Code 订阅条款对自动化调用的限制;DSH 官方把产品集成标"生产安装排除"——玩玩可以,商用前查条款
  • DSH 兼容性破坏期:官方明确 "THERE WILL BE COMPATIBILITY-BREAKING CHANGES",插件要跟随
  • 各家 CLI 版本漂移:codex 0.147.0、SDK 0.3.220 等 pin 版本要定期刷新
  • ACP client 端:DSH 只有 ACP server(packages/acp),client 端要自己实现(协议公开,codex app-server / opencode acp 是参考实现)
  • Windows 优先:本机 Windows,claude.exe 无 batch shim(官方已验证),子进程生命周期(process-tree 终止)要按 Windows 语义写

9. 参考

  • DSH 架构:deepseek-harness/docs/architecture.mddocs/cookbook/adding-an-llm-adapter.md
  • 官方产品集成(可借代码):packages/subagent/subagent-claude-code/packages/subagent/subagent-codex/、Agent Note 2026-08-04-claude-code-and-codex-subagent-backends.md
  • 先例插件:katsos/dsh-claude-cli(bridge.mjs 思路)
  • 调研原始数据:D:\github\free_workspace\.cache\research\cli_backend\