DeepSeek Harness 手写路由支持的三种线协议分别是什么
先说清楚这篇讨论的是哪一层。DeepSeek Harness(仓库 deepseek-ai/deepseek-harness)的模型接入层里有两个随仓适配器,一个是 dsh-llm-deepseek,另一个是 dsh-llm-pi-ai。前者只占一个 provider route,叫 deepseek-official(packages/llm/llm-deepseek/src/index.ts:47),走的是自己写的 fetch 加 SSE,跟本文说的协议表没关系。本文说的是后者:当你在配置里手写一个自定义 provider、需要告诉它「这个端点说的是哪种线协议」时,那个字段能填什么。
答案是三个,一个不多。
在往下读之前先记住一条限定:该仓库建立于 2026-08-13,我们采集的快照是 47f9438,版本 0.1.0-rc.5,没有任何 GitHub Release,README 自述处于开发者预览阶段并明写未来会有破坏兼容性的变更。下面提到的字段名、取值和错误码,随时可能变。
表本身:三行
协议表在 packages/llm/llm-pi-ai/src/provider.ts:47-51,原样抄下来是这样:
const PROTOCOLS: Readonly<Record<string, () => ProviderStreams>> = {
'openai-completions': openAICompletionsApi,
'openai-responses': openAIResponsesApi,
'anthropic-messages': anthropicMessagesApi,
}
左边是你在配置里写的字符串,右边是 pi-ai 库里对应的实现工厂。这三个名字分别指向 openAICompletionsApi、openAIResponsesApi、anthropicMessagesApi。注释里对这张表的定位写得很直白:每一项都是 pi-ai 那边匹配的 provider 工厂所用的同一个工厂,因此手写路由抵达的实现,与目录路由抵达的实现是同一个。
紧跟着的 supportedProtocols()(provider.ts:61)就一行,返回 Object.keys(PROTOCOLS)。也就是说合法取值域直接由这张表推导,顺序就是表里的书写顺序。再往上一层,packages/llm/llm-pi-ai/src/config.ts:235 里 profile 的 api 字段写的是 z.union(supportedProtocols())——配置 schema 的取值域不是另一份手抄清单,而是同一张表。这意味着你填错名字时,报错来自配置解析期,而不是等到发请求那一刻。
顺序也不是无所谓的。provider.ts 里 supportedProtocols() 的注释原文说这是「most-reached first」,并解释:顺序即表的顺序因而稳定,提供选择的配置界面会把第一项作为默认值,所以「手写网关最常说的那种协议、也是端点探询唯一读得懂的那种」排在最前面。这句注释同时解释了下一节要讲的探询限制。
为什么只有三种
同一个文件 provider.ts:32-45 的注释把「窄」写成了一个刻意的决定,理由分两类,而不是一类。
第一类是这套配置形状表达不了的。注释逐个点名:Bedrock 要用 AWS 凭据加 region 做 SigV4 签名;Vertex 需要 project、location 和 application-default credentials;Azure 需要 provider 环境再加一个 api-version;Codex 走 OAuth 认证。这几种都不是「一个 key、一个 endpoint、一组 header」能描述完的,所以注释写:把它们放进来等于「offering them would hand back a provider that cannot authenticate」——给回去一个根本认证不了的 provider。
第二类是暂时没人要。注释接着写,剩下那些协议缺席是「for want of a consumer rather than a blocker」,并说明每一个都是「one line here once a deployment needs it」——需要时在这里加一行即可。
这两类的差别值得留意:前一类是配置形状的边界,后一类只是当下的取舍。事实卡把这两句都完整记下来了,我们不替它推断哪一类以后会怎么变。
还有一句同样在这段注释里,很容易被跳过:目录路由仍然可以通过它自带的 provider 抵达全部协议,被拒绝的只是显式 override。所以「只有三种」这个说法要加限定词——是「手写路由能命名的线协议只有三种」,不是「这个适配器只会说三种协议」。
它跟模型发现是两张不同的表
很多人把「能配」和「能自动拉模型列表」当成一回事,在这里它们是两张表。
模型发现实现在 packages/llm/llm-pi-ai/src/discovery.ts,可探询的协议集合 LISTABLE_PROTOCOLS 在 discovery.ts:38-41,只有两项:openai-completions 和 openai-responses。其余协议一律抛 DISCOVERY_UNSUPPORTED(discovery.ts:226-231)。另外,草稿里没指定协议时,默认按 openai-completions 去问(discovery.ts:225)。
对照回上一节的三行表,差的那一项是 anthropic-messages:它能作为 api 字段的合法取值写进配置,但探询模型列表这条路对它不通。用户侧文档 docs/user/guide/providers.md:92 的说法与之对应:模型发现调用的是 OpenAI 兼容的 GET /models 端点,端点不提供这个接口的就手工填模型。
发现流程里还有两个细节值得知道,因为它们会改变你看到的现象:一是目录路由短路——若 catalogModels(provider) 非空,直接从安装目录返回,根本不发网络请求(discovery.ts:201-211);二是 URL 拼接把 baseURL 当前缀而不是可解析 URL 来处理,写法是 `${baseURL.replace(/\/+$/, '')}/models`(discovery.ts:86-88)。
落到配置上怎么写
api 这个字段属于 pi-ai 适配器的 provider profile。profile 的字段在 config.ts:65-141,api? 只是其中一项,同级还有 apiKeyEnv?、baseURL?、models?、modelOverrides?、defaultInput?、headers?、transport?、streamIdleTimeoutMs? 等等。docs/user/guide/providers.md:37-48 给出的示例 YAML 是 llm-pi-ai.providers.<route> 这样的结构,其中出现的字段包括 apiKeyEnv、api、baseURL、models[].id、models[].input。
按仓库中的参数语义,最小骨架大致是这个形状:
llm-pi-ai:
providers:
<你的路由名>:
apiKeyEnv: <你的凭据引用>
api: openai-completions
baseURL: <你的端点>
models:
- id: <模型 id>
以上为按仓库中的参数语义组合的示例,未经实测,以官方文档与 --help 的实际输出为准。密钥本身不写在这里:apiKeyEnv 的语义是「POSIX 风格环境变量名」的凭据引用(packages/credentials/credentials/src/types.ts:13),docs/user/guide/providers.md:13 写 key 存在 $DSH_HOME/.credentials.yaml,settings 里只留引用。
另有两组字段写了会被明确拒绝(config.ts:276-291):profile 里写 provider 会提示它已经「moved to the providers dict key」——路由名现在是 dict 的键;写 maxRetries 或 maxRetryDelayMs 会提示已移除,请改用 dsh-llm-retry。
顺带一提配置形状本身:Config.providers 是以 provider route 为键的 dict(config.ts:172-179),空或省略就是「休眠姿态」——插件挂载但零路由。packages/bundle/base/cordis.patch.yml:88-94 的注释也是这个口径,写它 mounted dormant,直到 llm-pi-ai: 的 settings 段提供 provider profile 为止。
三条可执行的判定
现象一:配置解析期报 api 取值非法。 判定动作是回 provider.ts:47-51 数那三行,再确认你写的字符串是否逐字相同(连字符、全小写)。处置就是改成三者之一。如果改完仍报错,说明问题不在 api 字段——config.ts 里对 models、modelOverrides、contextWindow 等也有独立校验,出错时会点名具体的 key(catalog.ts:338-357 等处)。
现象二:用文档里说的「Fetch available models」拉不到模型列表,报 DISCOVERY_UNSUPPORTED。 判定动作是比对两处:你的 api 值,和 discovery.ts:38-41 的 LISTABLE_PROTOCOLS。若你写的是 anthropic-messages,就属于这条路本来就不通,处置是按 providers.md:92 的说法手工填模型。若报的是 DISCOVERY_FAILED 而不是 DISCOVERY_UNSUPPORTED,那就不是协议这个原因——该码出现在响应体超限、请求失败等分支(discovery.ts:98、:142、:216 等)。
现象三:某个供应商在目录里有条目,但你手写路由时发现协议表里没有对应的取值。 这不矛盾。目录路由由 pi-ai 安装目录自带的 provider 提供,能抵达全部协议;协议表约束的只是手写路由的显式 override。判定动作是确认你走的是目录条目还是自定义条目。
一处口径不一致
这里有两处对同一件事的表述对不上,照实记下来,只陈述差异。
docs/user/guide/providers.md:19 把 Codex 与 Bedrock、Vertex、Azure 并列,描述为用 OAuth、只填 API-key 字段配不起来。而 .agents/notes/implemented/bug-fix/2026-08-13-oauth-only-providers-withheld.md:37 写的是:只提供 OAuth 而没有 api-key 方法的目录 provider 会被目录整体扣留,openai-codex 是安装目录里唯一这样的。对照 apps/web/tests/snapshots/models-settings/empty.expected.md:25-60 这份 Models 设置页的 golden 快照文本,截至 2026-08-16 我们数出的 36 个选项里确实没有 openai-codex。
两处对「Codex 在设置页上还看不看得到」的表述不一致。以我们实读的仓库状态为准,说到这里为止。
需要说明的是,那 36 个名字来自测试期望文件里的一个下拉选项列表,不是我们跑出来的界面;@earendil-works/pi-ai 这个依赖不在仓库内,vendor/ 下也没有它,我们没有安装 node_modules,所以无法从源码直接枚举安装目录里的供应商。关于那份名单本身,我们另有一篇专门讲。
延伸阅读
- 从头读起:DeepSeek Harness 是什么:建仓三天、13 万 star 的 Agent 框架
- 本专题共 45 篇,完整分组目录见专题页
- DeepSeek Harness 的模型接入分层:adapter、provider 与手写路由
- DeepSeek Harness 支持哪些供应商:清单不在仓库,只有一份快照
本文依据 DeepSeek Harness 官方仓库(github.com/deepseek-ai/deepseek-harness)的 README、docs/ 下的
架构与子系统文档、以及 packages/ 下的源码整理,核对日 2026-08-16,对应仓库快照 47f9438(版本 0.1.0-rc.5)。
本文内容为仓库源码与文档口径,我们没有安装、也没有运行过这个项目,
因此不涉及界面外观、操作手感与运行速度的任何描述。
该仓库建立于 2026-08-13,README 自述处于开发者预览阶段并明确说明未来会有破坏兼容性的变更,
文中出现的命令、配置与默认值随时可能变动,请以仓库最新内容为准。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。