密钥只有一处会被真正读到,就是 ~/.dsh/.credentials.yaml。设置页里保存的 key 也只写进这里,不会出现在任何模型配置块中。同名环境变量排在它后面。记住这个顺序,MISSING_CREDENTIAL 一类的报错基本十分钟能定位。下面按存哪、解析、报错、验证四步走一遍,命令都能直接复制。
密钥存哪:一个 600 权限的 YAML
桌面端走 设置 → 模型 → 添加提供商 → 自定义模型 API,填 Provider ID、API 地址、协议和模型,保存。配置块进 profile 的补丁文件,密钥单独进 $DSH_HOME/.credentials.yaml。两份文件拆开,是为了让配置能导出、能分享、能进版本控制,密钥永远留在本机。桌面端装在哪、四种会话模式怎么用,安装那篇 有完整流程。
这个文件只有三个顶层键。本机实测的结构如下,值已替换:
version: 1
records:
llm-pi-ai/openai-codex:
kind: grant
payload:
type: oauth
access: eyJhbGciOi...
refs:
DEEPSEEK_API_KEY: sk-...
MINIMAX_CN_API_KEY: sk-...
refs 按环境变量名存明文密钥。records 按 <owner>/<id> 存插件级授权,每条带 api-key 或 grant 标签。授权记录的内容存储层不解释,原样保留,只有写出它的插件才懂。在文件里看到一坨 base64 不必惊讶,那多半是某次账号登录留下的 token。
权限是这份文件唯一的安全边界。产品创建时就是本人可读,POSIX 系统上发现其他用户也能读,会直接拒绝加载并提示 chmod 600。本机 stat 的结果是 -rw-------,正常。自己确认一条命令够:
stat -f '%Sp %N' ~/.dsh/.credentials.yaml # macOS stat -c '%A %n' ~/.dsh/.credentials.yaml # Linux
有个容易忽略的边界:agent 的工具进程以你的身份运行,读这个文件和读别的文件没有区别。产品不会主动把路径交给 agent,也不把内容塞进环境变量,但这不是内核级隔离。真要隔离供应商密钥,得换存储后端,改权限没用。
四层顺序:受管文件永远赢
同一个引用名可能在不同地方都出现过。解析按固定顺序走,第一个找到值的层胜出,后面的层直接不看。
| 优先级 | 位置 | 能否在这层写 | 覆盖谁 |
|---|---|---|---|
| 1 | 受管文件 $DSH_HOME/.credentials.yaml 的 refs 段 |
能,设置页与 set 都写这里 |
全部 |
| 2 | 启动时的环境变量,如 DEEPSEEK_API_KEY=... dsh |
不能,只读 | 下面两个 .env |
| 3 | 项目 .env,取启动时工作目录 |
不能,只读 | home 的 .env |
| 4 | home .env,即 $DSH_HOME/.env |
不能,只读 | 不覆盖任何层 |
受管文件排第一的理由很实际。在产品里点保存的 key 必须对所有 provider 一致生效,否则同一台机器上两个路由读到两个不同的值,排查成本高到没法接受。启动环境排第二,是为了兼容 CI 密钥、容器 -e 和一次性 DEEPSEEK_API_KEY=... dsh 这类用法。
第三层取的是启动时的工作目录。桌面端双击图标启动,工作目录通常是应用包内部或者用户主目录,落在项目根目录的概率很低。命令行在项目目录里敲 dsh 才会命中这一层。想要稳定就把值写进受管文件,别赌 cwd。
还有一个时间维度,环境层是启动瞬间的快照。启动之后再 export,进程看不到。改完环境变量必须完全退出应用重开,export 完直接切回窗口是不管用的。
受管文件反过来是热加载的。文件监听默认开着,改动落地 100 毫秒后重载,不用重启。手改 YAML 之后切回窗口就能生效,这一点比环境变量好用得多。
apiKeyEnv 的名字必须能当 shell 变量
provider 配置里的 apiKeyEnv 填的是引用名,不是密钥本身。名字合法性有一条硬规则,必须匹配 ^[A-Za-z_][A-Za-z0-9_]*$,字母或下划线开头,后面只允许字母、数字、下划线。带连字符、带点、带中文的名字不算合法引用,解析时按根本没设过处理。
设置页不会让你填这个名字。它按 Provider ID 自动派生,规则是大写化,把非字母数字的字符全换成下划线,末尾拼上 _API_KEY。几个常见 ID 算出来是这样:
| Provider ID | 命中的 refs 键 | 说明 |
|---|---|---|
siliconflow |
SILICONFLOW_API_KEY |
纯小写,直接大写化 |
acme-gateway |
ACME_GATEWAY_API_KEY |
短横线换下划线 |
ollama-local |
OLLAMA_LOCAL_API_KEY |
同上,本地服务也走这条 |
zai |
ZAI_API_KEY |
三个字母,最短的一类 |
my.site.v2 |
MY_SITE_V2_API_KEY |
点号同样换成下划线 |
手写配置块时真正会踩的地方在这里。Provider ID 允许小写字母、数字和短横线,所以 acme-gateway 是合法 ID,但把 acme-gateway 原样填进 apiKeyEnv 就错了,ACME_GATEWAY_API_KEY 才对。判断方法简单到不用记规则:名字里出现连字符就是错的。
手写一条工具本身不认识的 provider,api、baseURL、非空的 models 三项缺一不可。缺项在写入那一刻就被拒,报错会点名是哪条路由、哪个模型。想省事可以用设置页的 获取可用模型,但它有个前置条件:先填好 API 地址,按钮才可用。填了地址再点,是它唯一能用的顺序。
模型列表接口的兼容程度分三档。OpenAI 的两种协议能读标准的 data 数组,也认带 models 映射的增强返回。Anthropic Messages 走它自己的列表路由。其余协议一律问不出来,只能手填模型 ID。列表返回的只是候选信息,不会落盘,真正决定这条路由能服务什么的还是配置文件。
没有鉴权的本地服务也会报 MISSING_CREDENTIAL
把本地 Ollama、vLLM、LM Studio 这类服务接进来时,经常会觉得莫名其妙。服务端根本不检查 key,客户端却报凭据缺失。原因是 OpenAI 兼容的那套实现固定要求一个 API key 或一个 Authorization 头,它不看服务端到底认不认。
两种办法都能过。给 apiKeyEnv 指一个随便有值的引用,比如 LOCAL_DUMMY_KEY,再往受管文件里塞一行任意字符串。或者在这条 provider 的 headers 里直接写 Authorization: Bearer local。前者更省事,以后换关键值不用动配置。
完整的 provider 配置块、compat 开关怎么填、后端上下文要开多大,接本地 Ollama 那篇 已经写透了,本篇不重复。
五个报错码怎么区分
失败码是稳定的,不会随小版本漂移。定位时先看码,再看它有没有触到网络,这两步能砍掉大半无效排查。
| 报错码 | 含义 | 是否在网络请求前返回 | 先查什么 |
|---|---|---|---|
MISSING_CREDENTIAL |
引用解析不到任何值 | 是 | 四层里哪层有值,名字拼对没有 |
INVALID_CREDENTIAL |
值找到了但不可用 | 是 | 密钥格式、是否过期或被吊销 |
UNKNOWN_MODEL |
模型未在该路由声明 | 是 | models 列表里的 ID 是否字符级对齐 |
QUOTA |
额度或账单层面终止 | 否 | 账户余额与套餐,重试无意义 |
RATE_LIMIT |
暂时性限流 | 否 | 并发与退避,可恢复 |
单看文字容易混的是前两个。MISSING_CREDENTIAL 表示这个引用没解析到值,问题在配置或文件层。INVALID_CREDENTIAL 表示值找到了但不可用,比如格式不对、过期、被吊销,报错会点名要修哪个引用,但绝不带密钥的任何片段。看到后者别再翻配置文件,去检查 key 本身。
UNKNOWN_MODEL 值得单独说。它发生在网络 I/O 之前,也就是说请求根本没有发出去。遇到它去查网络、查余额、查代理全是白费。要查的是这条路由的 models 列表里有没有这个 ID,字符级对齐,大小写和斜杠都算数。
QUOTA 与 RATE_LIMIT 的区别决定要不要重试。前者终止,重试只会继续失败。后者暂时,退避之后能恢复。默认策略是普通模式五次重试,需要时可在这条 provider 的 retryPolicy 里单独改,maxRetries 就是次数。
踩坑一:删掉 key 之后反而通了
这个现象第一次见会以为见鬼。把受管文件里那条 key 删了,请求却成功了。删除只删掉最上面一层,下面三层的同名值立刻露出来接管,启动环境里留着的旧 key 就这样无声无息地复活。
产品对空值的处理指向同一件事。受管文件里不允许存空字符串,要停用就删整条记录。空串会被当作非法值拒掉,因为空值在语义上等于没有这条记录,与其留一个假占位,不如让下一层接管。
排查这类问题的动作是看 describe 回的 source 字段,它会说明当前这个值来自哪一层。只看 configured 为真没有意义,四层里任何一层有值都是真。
踩坑二:改完 API 地址保存被拒
设置页里改 baseURL、apiKeyEnv、显示名,点保存,弹出拒绝提示,而这三个字段本身都没错。真凶是同一条路由下另一个模型有问题。配置更新校验整条 provider,只要有一个模型不可服务,整条就被挡住。
典型场景是从聚合网关抄来一组模型 ID,其中某个模型需要单独声明协议。这时候你只想改个显示名,也会连坐失败。解法是把那个模型的 api 补上,或者直接删掉它,在同一个编辑草稿里一起提交。分两次保存是过不去的。
已经写坏但还没修的路由不会消失。加载时会把它保留成一条带诊断的可编辑行,可以继续点开改。修好或者删掉,诊断才清掉。这条行为对排错是好事,至少不会因为一次手滑丢掉整段配置。
踩坑三:权限一松整库不加载
明文 YAML 加 0600 的组合,容错空间很小。用编辑器另存、从备份恢复、跨机器拷贝,都可能把权限改成 644。POSIX 系统上这一改不是警告,是启动直接失败,报错里会给你 chmod 600 的命令。
热加载时的表现又不一样。文件改坏了进程不会崩,它继续用上一次成功解析的内容,同时打一条告警。于是症状变成改了 key 却没有变化。这时候先看日志有没有告警,再回头确认文件本身能不能解析。
还有一个保护值得知道:磁盘上的文件解析不了时,保存操作会失败,不会拿新内容覆盖掉自己读不懂的东西。所以手改到一半去点保存,配置不会丢,但也不会生效。Windows 没有可查的权限位,这项检查在那边直接跳过,靠目录权限兜底。
三个动作确认 key 真被读到
改完不要直接发消息试。前两个动作不发请求就能确认状态,最后一个才真正花 token。
- 在设置页把这条 provider 的 key 删掉,模型选择器里它应该消失,设置入口仍然在。这是设计好的行为:凭据缺失时模型发现返回空目录,路由被隐藏,配置行保留下来供你修。
- 看
describe回读的configured、source、writable三个字段。它永远不返回密钥本身,所以把这几个字段贴出来求助是安全的。 - 命令行确认版本与挂载状态,两条命令都不发请求。
dsh --version dsh --profile web --dump-config | grep -A2 llm-pi-ai
本机实测第一行输出是 0.2.1-rc.5。第二条能在合成出来的配置树里看到 llm-pi-ai 这一项,且后面不跟 config 段。没有 config 就是休眠态,路由数为零,模型选择器里不会多出任何一项。要让路由活起来必须有 provider 配置,这跟有没有 key 是两件事。
用命令行跑同一套配置还有一处容易误判。图形界面登录得到的账号凭据属于 records 段,跟 refs 段的 key 走两条通道。命令行会话里没有对应账号登录时,会报 MISSING_CREDENTIAL 并点名具体引用,而文件里的 key 一直都在。遇到这种情况别去重填 key。需要连插件一起排查的话,技能手动安装那篇 里有同一套命令行验证手法。
FAQ
问:key 写进项目 .env 了,为什么桌面端读不到?
项目 .env 取的是启动时的工作目录。双击图标启动的桌面端,工作目录通常不是项目根目录,这一层就被跳过了。写进受管文件最稳,或者从项目目录里用命令行启动。
问:两个 provider 能用同一个 API key 吗?
能,但更推荐各用各的引用名。两个 provider 指向同一个 refs 键时,改一处会同时影响两条路由,而且删除某条路由时凭据清理的判断会变得不可预期。清理只在引用与派生的 <ROUTE>_API_KEY 完全一致且可写时才发生,自定义引用会被保留。
问:手改 YAML 会不会覆盖掉界面里刚存的设置?
不会。两边同时改的时候,改动会被合并保留,写入走跨进程的文件锁。注释和未动到的条目也会原样留着,只有你删掉的那条会连同它正上方的注释一起消失。
站内接着看
先把桌面端装上再谈凭据:桌面端安装教程。要接本地模型看这篇:接本地 Ollama 的三个坑。要装插件与技能:SKILL.md 四个失效点。
延伸阅读
- DeepSeek 服务中断 45 分钟:11 月 7 日 15 时 11 分起7 小时前
- Anthropic 线程机制 首字延迟降 35%4 天前
- DeepSeek 网页和 API 服务今日出现部分中断,提示服务器暂时不可用3 周前
- DeepSeek Harness 技能怎么手动装:SKILL.md 四个失效点8 小时前
继续读
同一批更新里这几篇值得一看:
- DeepSeek Harness 技能怎么手动装:SKILL.md 四个失效点2026-11-07
- Qwen-Image 与 FLUX 显存对比:22GB 卡跑 1024 实测2026-11-07
- ComfyUI 装 FLUX 模型:safetensors 损坏与下拉框空白怎么修2026-11-07
想快速查定义?对应的词条在这里:
- DeepSeek词条 · AI 自学笔记
