dsh-api: An HTTP control plane layer on dsh's listening ports

前言

用脚本、桌面壳或编辑器插件驱动 dsh 时,常遇到一个问题:改语言、切工作区、感知 agent 什么时候空闲,这些能力都长在 dsh 进程内部。外部进程要么直接伸手调它的 in-process services,要么各写各的通道,接口不统一,耦合也重。

dsh-api 的做法是把这件事收敛到一个入口:dsh 本来就在 127.0.0.1 上监听 HTTP,插件直接在这条 socket 上挂一组 /dsh-api/* JSON 路由,同机的任何进程——桌面包装器、浏览器扩展、CLI、编辑器集成——都从这里走。下面介绍它的定位、接口、安装和常见问题。

这是什么

dsh-api 是 lilming123 维护的 dsh 插件,当前版本 0.1.0,MIT 许可证。它把 dsh 的内部能力——语言设置、工作区注册表、companion 桥接——以 JSON 路由的形式暴露在 dsh 已监听的 127.0.0.1 端口上。不新开端口,除 Node 本身外零运行时依赖,路由前缀默认 /dsh-api

DSH 生态的理念是「一切皆插件」,这个插件遵循的正是这个前提:装进 profile,随 dsh web 一起加载。

核心功能:两层路由

路由分两层,挂在同一个 /dsh-api 前缀下。

原生路由

插件加载后即可用,不依赖任何额外进程:

方法 路径 用途
GET /dsh-api/health 存活检查与基本身份信息(dsh 端口、cwd、companion 是否注册)
GET /dsh-api/language 读取 locale.preference
POST /dsh-api/language 写入 locale.preferencezhen
GET /dsh-api/workspace/list 列出 workspaceRegistry 的全部工作区
GET /dsh-api/workspace/current 当前 cwd 与 companion 快照
POST /dsh-api/workspace/create { path, title? } 注册新工作区
GET /dsh-api/events SSE 事件流

事件流

GET /dsh-api/events 是一条 SSE 长连接,事件有五类:

  • ready:连接建立;
  • agent-idle:任一 agent/status 事件从 running 转为 idle 时触发,携带 sessionIdtitlepreviousStatus 等字段;
  • approval-needed:对 dsh approval/request 瀑布的只读旁路——插件观察请求、广播摘要,控制权原样交回真正的应答链;
  • heartbeat:每 25 秒一帧,防止代理空闲断连;
  • server-stopping:dsh 关闭前广播,之后 socket 关闭。

companion 桥接路由

第二层需要已注册 companion。companion 指任何本地进程:向 $DSH_HOME/dsh-api-companion.json 写入 { port, token, pid, ... },并实现 /companion/* 协议。未注册 companion 时,这些路由返回 503,原生路由不受影响:

方法 路径 用途
GET /dsh-api/companion/state companion 状态快照
POST /dsh-api/workspace/open 切换 dsh cwd(companion 下重启 dsh)
POST /dsh-api/input/paste 注入文本到 dsh UI
POST /dsh-api/window/show 聚焦宿主窗口
POST /dsh-api/window/reload 重载宿主窗口
POST /dsh-api/app/quit 退出宿主应用

安装与启用

先装插件,再确认加载方式:

dsh plugin --profile web add github:lilming123/dsh-api

README 中还列出了 npm 形式(dsh plugin --profile web add dsh-api),但标注为 once published,当前是否可直接使用以仓库 README 为准。

安装后的行为:

1、dsh plugin 是 pnpm 薄封装,包落入 $DSH_HOME/profiles/<profile>/node_modules/
2、dsh-api 注册进该 profile 的 bundle 列表;
3、下次 dsh web 自动加载,不需要 --patch

Node 版本要求 >= 20。另外,桌面端 dsh-desktop 检测到已安装的 dsh-api 会跳过其内置回退版本;只有你直接驱动 dsh 时才需要手动装这份插件。

配置

插件有两个配置项,写在 loader entry 的 config 里:

默认值 用途
basePath /dsh-api HTTP 路由前缀
companionFile $DSH_HOME/dsh-api-companion.json companion 发现文件,按需读取

例如把路由前缀改成 /control,在 $DSH_HOME/profiles/web/cordis.patch.yml 写:

- id: dsh-api
  config:
    basePath: /control

这样所有路由就以 /control 开头。

典型用法

经过上面的步骤,插件已随 dsh web 加载。下面用 curl 走一遍常用接口(端口沿用 README 示例中的 3181,以你的实际启动参数为准)。

1、存活检查:

curl http://127.0.0.1:3181/dsh-api/health

返回 dsh 端口、cwd、companion 是否注册等信息,可用来确认插件已加载。

2、订阅事件流:

curl -N http://127.0.0.1:3181/dsh-api/events

-N 让 curl 不做缓冲,直接打印 SSE 帧:readyagent-idleapproval-needed,以及每 25 秒一帧的 heartbeat

3、写语言偏好与注册工作区:POST /dsh-api/language 接收 { "language": "zh" | "en" }POST /dsh-api/workspace/create 接收 { path, title? }path 指向要注册的目录,title 可选。

4、跑示例脚本:仓库 examples/ 下有两个可直接运行的文件,curl.sh 遍历所有端点,events.mjs 是零依赖的 Node SSE 订阅示例,适合作为自己集成的起点。

5、改源码调试时用开发模式:

git clone https://github.com/lilming123/dsh-api.git
cd dsh-api

mkdir -p "$DSH_HOME/profiles/web/dsh-api-dev"
ln -sf "$PWD/index.mjs" "$DSH_HOME/profiles/web/dsh-api-dev/index.mjs"
cat > /tmp/dsh-api-dev.patch.yml <<'YML'
- insert:
    - id: dsh-api-dev
      name: ./dsh-api-dev/index.mjs
YML

dsh web --patch /tmp/dsh-api-dev.patch.yml --port 3181

dsh-api 是纯 ESM 插件,没有构建步骤。做法是把 clone 下来的 index.mjs 软链进 profile 的 dsh-api-dev/ 目录,再用 --patch 把它插进加载列表,指定 3181 端口启动。

安全模型

三类约束值得知道:

  • 只绑定 dsh 已监听的 127.0.0.1,不新开端口,外网进不来;
  • 变更类请求校验 Origin:无 Origin 头(CLI 场景)与回环 origin 放行,其余返回 403;
  • companion 桥接路由通过 x-dsh-api-companion-token 请求头转发发现文件中的 token,由 companion 侧校验,不匹配即拒绝。

常见问题

按现象查:

  • /dsh-api/* 全部 404:插件没加载。检查 ~/.dsh/profiles/web/package.jsondsh.profile.bundles 是否含 dsh-api~/.dsh/profiles/web/node_modules/dsh-api 是否存在,缺哪个就重跑安装命令;
  • POST /dsh-api/workspace/create 返回 503:当前 dsh 上下文缺 workspaceRegistry——要么 dsh 版本早于该服务,要么 profile patch 把它裁掉了。升级 dsh(npm i -g @deepseek-ai/dsh)后再试;
  • companion 路由返回 503:没有注册 companion。直接跑 dsh 而不经 dsh-desktop 之类的壳时这是预期行为,原生路由(/health/language/workspace/*/events)照常可用;
  • SSE 流每约 60 秒断一次:反向代理在空闲杀连接。插件已每 25 秒发 heartbeat,仍断就调大代理空闲超时,或去掉代理——dsh-api 绑定 127.0.0.1,本来就不需要代理。

适用场景与注意

适合谁:

  • 给 dsh 做桌面壳、浏览器扩展、编辑器集成的工具作者,需要一个统一 HTTP 入口,不想各自摸 in-process services;
  • 需要监听 agent 空闲、审批事件做外部自动化的脚本;
  • 想从外部读取或注册工作区、切换 dsh cwd 的场景。

注意:插件以当前 dsh 进程的权限运行,通过它暴露的接口能触达 dsh 进程能触达的资源。安装前建议先读一遍源码(主体是 index.mjs,纯 ESM,没有构建步骤),并确认 MIT 许可证符合你的使用方式。

小结

dsh-api 的价值在于收敛:复用 dsh 已有的 127.0.0.1 socket,一套固定的 /dsh-api/* JSON 路由,外加一层可选的 companion 桥接,让外部工具驱动 dsh 有了统一入口。仓库见 GitHub:https://github.com/lilming123/dsh-api;社区目录页(独立站点,与 DeepSeek、幻方无官方从属关系):https://www.skillhub.cn/plugins/lilming123/dsh-api

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

Xiaoye