dsh(DeepSeek Harness)是 DeepSeek 官方开源的 Agent Harness,原生走 OpenAI Chat Completions 接口,配置集中在 ~/.dsh/ 目录。a4api 自 0.1.4 起支持 dsh 目标:配置方案勾选 dsh 后,切换动作写入 settings.yaml.credentials.yaml 两个文件;自 0.1.5 起 dsh 统一经本地翻译代理的透传端点连接上游,由代理归一上游流式分片格式。配置文件被 watcher 热加载,切换后新会话即生效、无需重启。本文章介绍 dsh 的配置结构、写入内容、max_tokens 输出上限策略与常见问题。

1. dsh 的两个配置文件

dsh 的数据目录默认为 ~/.dsh(可用环境变量 DSH_HOME 覆盖),与切换直接相关的是两个文件:

文件 角色
settings.yaml 全局设置,按 namespace 分段;本工具托管其中两段
.credentials.yaml 凭证文件,明文存放 API Key 环境变量对应的值

两者都可用独立环境变量覆盖路径(A4API_DSH_SETTINGS_PATHA4API_DSH_CREDENTIALS_PATH),主要用于测试。

2. 切换写入了什么

2.1 settings.yaml 的两段

llm-deepseek:
  baseURL: http://127.0.0.1:17890
  apiKeyEnv: DEEPSEEK_API_KEY
  maxTokens: 131072
agent-default-model:
  provider: deepseek-official
  model: deepseek-v4-flash
字段 说明
llm-deepseek.baseURL 指向本地翻译代理的地址(dsh 经代理的 /chat/completions 透传端点连接上游)
llm-deepseek.apiKeyEnv 固定为 DEEPSEEK_API_KEY,即凭证文件中的键名
llm-deepseek.maxTokens 单次输出上限,取值策略见第 3 节
agent-default-model.provider 固定为 deepseek-official——dsh 只注册这一个 provider 路由
agent-default-model.model 配置方案中的模型名

settings.yaml 中其他段落(如 ui-onboarding)原样保留,切换只重写上述两段。

2.2 .credentials.yaml 的凭证

DEEPSEEK_API_KEY: 本地代理鉴权token

由于 dsh 统一经本地代理连接,这里写入的是代理鉴权 token而非真实 API Key——真实 Key 由代理从加密数据库解密持有并在转发时使用,不落盘明文。仅在代理不可用的防御性直连场景下才写真实 Key。

3. max_tokens 输出上限

dsh 适配器默认的单次输出上限是 256000,而多数上游(如智谱)的输出上限是 131072,超限请求会被直接打回 INVALID_REQUEST。因此切换时对 maxTokens 执行三级优先:

graph LR A[确定 maxTokens] --> B{方案中显式
填写了 max_tokens?} B -->|是| C[使用显式值] B -->|否| D{用户曾手动在
settings.yaml 设置?} D -->|是| E[保留手动值] D -->|否| F[兜底安全值
131072] style A fill:#e8f4f8,stroke:#1a6b8a,stroke-width:2px style B fill:#ebdef0,stroke:#8e44ad,stroke-width:2px style C fill:#d5f5e3,stroke:#27ae60,stroke-width:2px style D fill:#ebdef0,stroke:#8e44ad,stroke-width:2px style E fill:#d6eaf8,stroke:#1a6b8a,stroke-width:2px style F fill:#fdebd0,stroke:#b7950b,stroke-width:2px

显式填写的值永远优先;没有显式值时绝不能放行 dsh 默认的 256000,这就是配置方案的 max_tokens 字段存在的意义(见配置方案与一键切换)。

4. 为什么必须经本地代理透传

dsh 原生走 Chat Completions,理论上可以直连上游;但部分上游(如 opencode zen)会在流式分片的 tool_calls 里用 null 填充后续分片缺失的字段,dsh 的适配器会把工具名与 ID 覆盖为空,表现为 unknown tool "" 报错、工具调用失效。

0.1.5 起 a4api 让 dsh 统一经本地翻译代理的 /chat/completions 透传端点连接上游:透传时把流式分片中 tool_calls 的 null 字段归一为省略键,从根上规避该问题。这是 dsh 目标始终需要代理运行的原因。

graph LR A[dsh 会话] --> B[本地翻译代理
chat/completions 透传] B --> C[tool_calls null 归一
为省略键] C --> D[OpenAI 兼容上游] style A fill:#e8f4f8,stroke:#1a6b8a,stroke-width:2px style B fill:#ebdef0,stroke:#8e44ad,stroke-width:2px style C fill:#ffecd6,stroke:#e67e22,stroke-width:2px style D fill:#d5f5e3,stroke:#27ae60,stroke-width:2px

代理的鉴权方式、进程生命周期等机制与 Claude Code / Codex 场景完全共用,详见本地翻译代理

5. 生效方式与备份

  • 热加载生效:dsh 对 settings.yaml 与 credentials 文件都有 watcher,切换后无需重启任何进程,新开的会话立即使用新服务商与模型;正在运行中的旧会话不受影响。
  • 双文件备份:两个文件在修改前分别备份到数据目录(命名 dsh.settings.时间戳.yaml.bakdsh.credentials.时间戳.yaml.bak,各自滚动保留 5 份),写入均为临时文件加原子替换。

6. 常见问题

问:为什么 dsh 目标不能选 Anthropic 类型的服务商?

答:dsh 只注册 deepseek-official 一个 provider 路由,原生说 Chat Completions 协议,无法消费 Anthropic 端点。给 dsh 用请选择 OpenAI 兼容类型服务商;选择不符时切换会在写入前直接报错提示。

问:切换后需要重启 dsh 吗?

答:不需要。watcher 热加载让配置改动即时可见,开一个新会话就是新模型;正在跑的旧会话继续用旧配置直到结束,互不干扰。

问:之前偶发的 unknown tool 报错还会出现吗?

答:0.1.5 起 dsh 流量统一经代理透传并做 null 归一,该问题已从根源解决;若你从旧版本升级,确认版本号后重新切换一次 dsh 配置即可,见版本更新说明

问:我的 dsh 装在自定义目录怎么办?

答:设置环境变量 DSH_HOME 指向你的 dsh 数据目录即可,a4api 会跟随它定位 settings.yaml 与凭证文件;也可以用更细粒度的两个路径变量分别覆盖两个文件的完整路径。