前言¶
DSH 本身是 HTTP(S) 出口,但智能体在运行时常需要更强的出口策略:切换节点、按域名分流、走固定的 Xray 配置。常规做法是在系统层面配代理环境变量,或者把整个 DSH 的请求单独接出去。前者影响面大、依赖环境;后者容易漏掉 web_fetch 这类只问代理策略层的调用,导致「模型 API 进了代理、网页抓取还直连」的分裂。
下面介绍 TwinsEarth 维护的 DSH 插件 @twinsearth/dsh-2xray(仓库 TwinsEarth/DSH-2Xray)。它定位为 Host-only bundle,在 DSH 进程内承担 xray-core 的启动守护和流量接管,不需要额外构建。
这是什么¶
插件同时扮演两个角色:xray-core 客户端的进程管理者,以及 DSH 进程内 HTTP(S) 流量的路由者。它生成 xray-core 客户端配置,启动并守护 xray 子进程;在 xray 本地入站确认可用后,把 DSH 进程的全局 HTTP(S) 出口接到该入站;可选地通过零依赖 gRPC 控制面做多节点切换与流量统计。
维护者:TwinsEarth。包名 @twinsearth/dsh-2xray,当前版本 0.8.0,许可证 MIT,要求 Node >= 24.9.0。0.8.0 适配 DSH 0.2.2-rc.1,DSH 0.2.1 及以前需使用 0.7.0。除控制面外无运行时依赖,是一个 Host-only bundle。
核心功能¶
接入方式上,插件调用 @deepseek-ai/dsh-http-proxy 的 installProxyFromEnvironment(),而不是自己裸装 setGlobalDispatcher()。原因是 DSH 在第一个插件挂载前就已经安装了策略 dispatcher,web_fetch 只询问 proxyRouteFor(),裸装会破坏出口的一致性。
启动与接管遵循固定顺序:写配置 → 起进程 → TCP 就绪 → 204 功能探测 → 接管 dispatcher。只有探测通过后才会接管,避免 dispatcher 指向尚未监听的端口导致整体 ECONNREFUSED。停止时先摘路由,再 terminate,最后等退出。
端口策略是固定端口 + 占用即失败,不做静默换端口。换端口后 DSH 还指向旧端口,表现为持续连接失败,用户无法判断原因。
子进程优先使用 ctx.subprocess,它在 Windows 上是带 KILL_ON_JOB_CLOSE 的 Job Object,可以避免 Electron 关闭后遗留孤儿 xray 占住端口。若没有 Job Object,进程被强杀时 cordis dispose 可能不会执行。
多节点场景下,插件为每个节点生成一个 outbound 和一个 balancer,切换节点只改 RoutingService.OverrideBalancerTarget,进程、入站、dispatcher 都不动。控制面用 node:http2 + 手写 protobuf 实现,不引入 @grpc/grpc-js,目的是减少每个 profile 的依赖和供应链面。
重启策略只针对意外崩溃:退出码 23(配置错)不重启,端口占用不重启,上游不可达也不重启,因为进程本身是健康的。
安装与启用¶
插件是 Host-only bundle,无需构建。安装命令由插件管理器负责,不提供命令行插件添加形式。
npm i @twinsearth/dsh-2xray
安装后由插件管理器启用;或者在 DSH 插件页选择「从目录安装」,指向本地 Bundle 目录,例如 E:\GPT\dsh-2xray。
接着需要把 xray 可执行文件放到下面任一位置,解析优先级从高到低:
1. 插件自己的 bin/xray(.exe)(随 bundle 分发时使用)
2. 配置字段 binary.path
3. 环境变量 DSH_XRAY_BIN
4. <DSH_HOME>\xray-proxy\bin\xray.exe(DSH_HOME 默认 %USERPROFILE%\.dsh)
5. 放进 PATH
第三步是编辑 profile 的 cordis.patch.yml(或 bundle 默认配置),填入 server.* 或 nodes[],把 enabled 改成 true,然后重启 profile。bundle 自带配置默认 enabled: false,避免安装即报错。需要留意的是,bundle 的 cordis.patch.yml 是整块配置,profile 层覆盖时必须重述要保留的字段;注册行的 name 必须等于包名 @twinsearth/dsh-2xray,否则 patch 会按「name mismatch」跳过。
单节点配置示例:
- id: xray-proxy
name: '@twinsearth/dsh-2xray'
config:
enabled: true
server:
address: example.com
port: 443
uuid: 00000000-0000-0000-0000-000000000000
flow: xtls-rprx-vision
serverName: www.example.com
password: REPLACE_WITH_43_CHAR_BASE64URL_X25519_PUBLIC_KEY
shortId: ''
fingerprint: chrome
spiderX: /
inbound:
protocol: http
host: 127.0.0.1
port: 20808
takeover: auto
多节点配置示例:
- id: xray-proxy
name: '@twinsearth/dsh-2xray'
config:
enabled: true
activeNode: hk-01
nodes:
- id: hk-01
address: hk.example.com
port: 443
uuid: 11111111-2222-4333-8444-555555555555
flow: xtls-rprx-vision
serverName: www.example.com
password: REPLACE_WITH_43_CHAR_BASE64URL_X25519_PUBLIC_KEY
shortId: ''
- id: jp-01
address: jp.example.com
port: 443
uuid: 11111111-2222-4333-8444-555555555555
flow: xtls-rprx-vision
serverName: www.example.com
password: REPLACE_WITH_43_CHAR_BASE64URL_X25519_PUBLIC_KEY
shortId: ''
control:
enabled: true
port: 10085
stats: true
nodes[] 与 server.* 二选一,每个节点需要 id/tag 以及 server.* 各字段。activeNode 指定启动时钉住的 node id 或 outbound tag。control.enabled 为 true 时才会开 gRPC 控制面;control.stats 打开 stats 与 policy.system 流量计数。
典型用法¶
默认单节点场景下,最直接的用法是:按上面的示例填好 server.*,保持 inbound.protocol 为 http,takeover 用默认的 auto。这样插件会在探测通过后接管 DSH 进程的全局出口,无需手工设置代理环境变量。
启用 gRPC 控制面后,可以切换节点:启动顺序为写配置 → 起进程 → TCP 就绪 → 204 功能探测 → 接管 dispatcher → OverrideBalancerTarget(activeNode) 钉住出口。之后切换节点只改 balancer override,不动进程和入站。插件还提供三个可选的模型工具:xray_status(与 status.json 相同的状态快照)、xray_switch_node、xray_stats,均由 ctx.inject(['tools']) 可选注册;工具注册失败不影响代理本身工作,避免代理被工具可用性拖累。
关于入站协议,默认 HTTP;SOCKS 只作为可选附加,因为 DSH 代理策略明确拒绝 socks5:// URL,SOCKS 只能走内置 dispatcher,且失去 web_fetch 的覆盖。SOCKS 适合给非 Node 客户端使用。
功能探测默认请求 http://www.gstatic.com/generate_204,这是唯一能证明 uuid、flow、SNI、REALITY 全链路可用的检查,默认超时 5000ms。
适用场景与注意¶
适合需要在 DSH 内统一管理 xray 出口、做多节点切换、或希望在进程内看到明确状态和日志的开发者。如果已经通过系统代理或环境变量较好地解决了出口问题,没必要额外引入一层。
需要注意的几点:
- 插件以当前 DSH 进程权限运行,安装前应检查源码与许可证。许可证是 MIT,仓库提供
LICENSE文件;Xray 二进制自身另有许可说明(见bin/README.md)。 - 版本必须与 DSH 匹配:0.8.0 适配 DSH 0.2.2-rc.1,DSH 0.2.1 及以前使用 0.7.0。
- 端口策略是固定端口,冲突即失败,需自行确保
inbound.port未被占用,且与control.port不冲突。 takeover可选auto/force/off。已有代理策略时先试auto,它会保留现有策略;只有在明确需要覆盖时才用force。noProxy不支持 CIDR,内网后缀按routing.bypassDomains配置。
插件目录页在 SkillHub(社区目录,与 DeepSeek / 幻方无官方从属关系),源码和文档在 GitHub:
- https://www.skillhub.cn/plugins/TwinsEarth/DSH-2Xray
- https://github.com/TwinsEarth/DSH-2Xray