这一章短,但有一个容易被忽略的结构性后果:因为认证要动态解析,第 03 章那个循环必须持有一个 getApiKey 回调而不是一个字符串。一个看似琐碎的运维事实,反向约束了核心 API 的形状。
集合在 models.stream() 时经所属 provider 解析认证并合并进请求,而每次请求里显式传的值优先于一切——环境变量、存储的凭证、OAuth token 都在其后。ai/README.md:330
const resolvedApiKey =
(config.getApiKey ? await config.getApiKey(config.model.provider) : undefined) || config.apiKey;
streamAssistantResponse 内部,每个 turn 都会执行一次。README 对该字段的注释写得很直白:给会过期的 OAuth token 用。agent-loop.ts:305如果认证在构造 Agent 时固定成一个字符串,一个跑 40 分钟的 run 会在 token 过期的那一刻整体失败——而且失败在最不该失败的地方(工具都跑完了,只差最后一次总结)。
models.getAuth(provider) 返回 provider 级认证;传 model 则会带上该模型的静态 headers。ai/README.md:343
这个 API 的存在本身说明了一件事:当有三类凭证来源时,「现在到底用的哪个」是高频问题,值得一个专门的查询入口。
支持 Claude Pro / ChatGPT 这类订阅账号(而不只是 API key),意味着要实现 OAuth 流程、token 刷新、以及各家不同的账号态处理。这是 packages/ai 里 auth/oauth 单独成目录的原因。
收益是准入门槛:用户不需要先去开一个 API 账单账户。对一个想要广泛使用的开源 CLI,这可能是采用率上最重要的一个功能——但它的成本全部落在 pi-ai 这一层,且每家 provider 的实现都不一样。
多来源凭证系统最容易出的问题是优先级不确定:环境变量和存储凭证同时存在时用哪个?pi 的答案是把优先级写成一条可背诵的规则,并且让最显式的那个赢。
这条规则让调试可行:怀疑认证有问题时,直接在调用里传一个 key,如果好了就说明是解析问题而不是凭证问题。
场景一个跑了 40 分钟的 run,中途 OAuth token 过期了。pi 的行为是什么?
B。认证在每个 turn 的 LLM 调用前重新解析,不在构造时固定。agent-loop.ts:305
场景环境变量里有 ANTHROPIC_API_KEY,你调用时又显式传了另一个 key。哪个生效?
B。解析顺序的第一条规则:显式值优先于 provider 解析出来的一切。ai/README.md:330
ac4ac9e(2026-08-07 快照)。