- 删除 docs/plan 下 00–18 的计划、评审、验收、发布记录与升级证据,docs/spikes 与 spikes 验证代码
(保留 spikes/movieclaw-src 本地克隆),以及 4 份一次性审查 / 调研;全部可在 ceb66f8 查到
- MP 对照移到 docs/porting/moviepilot-v3-comparison.md;经验教训改为 docs/plan/lessons.md(补第 25 条)
- docs/plan/README.md:文档放哪、当前状态、待办
- 引用改写:代码注释指向在维护的文档,其余注明已删除与 git 位置;清掉原有 6 个失效链接
- README 开头改为当前状态与入口;CHANGELOG 补齐 0.3.8–0.3.14 与工程变更
Co-Authored-By: Claude Opus 5.5 <[email protected]>
22 KiB
Pi 模型上游与订阅登录(pi-ai / pi-coding-agent 0.87.1)
2026-10-01 升级补充:当前接入目标已更新为 pi-agent-core/pi-ai 0.99.2,执行结果见 全面升级记录(已删除,见 git
ceb66f8)。下文保留 0.87.1 的研究基线,不用历史计数验收新版。新版实际目录为 42 个内置 provider(新增 TypeSafe 分类服务),合同和目录回归已同步;TypeSafe 没有 Agent 聊天模型,目录说明已标明。OAuth 共 9 个 provider:新增
openai原生 Sign in with ChatGPT,浏览器流程与回调端口 1455;现有openai-codex仍保留 device/browser 两种模式。新版openai是动态注册的公开客户端流程,没有照搬旧 Codex 客户端身份提示。来源:0.99.2 OpenAI provider、原生 ChatGPT OAuth。Pi 原生 MCP 客户端可复用,但本次依赖升级没有新增外部 MCP 工具配置;本项目对外 MCP 服务端、权限和审批仍按既有设计运行。
只读研究结论,依据本机
~/.cache/deno/npm/registry.npmjs.org/@earendil-works/{pi-ai,pi-coding-agent}/0.87.1的 dist 源码与随包 docs(docs/providers.md、models.md、custom-provider.md)。 目的:设置页支持 pi 的全部上游与订阅式登录。结论以代码为准,未核实的不写。
0. 一句话结论
- pi-ai 内置 41 个 provider(
providers/all.js的builtinProviders()),模型表为随包静态目录(providers/data/*.json,生成时间 2026-09-22),只有 Radius 支持动态拉取。 - 订阅/OAuth 登录共 8 种:Anthropic(Claude Pro/Max)、OpenAI Codex(ChatGPT Plus/Pro)、GitHub Copilot、xAI(SuperGrok/X Premium)、Kimi Code、Meta(Muse)、OpenRouter(OAuth 换永久 key)、Radius(pi 自家网关)。
- OpenCode Go / OpenCode Zen 没有 OAuth:两者都只能用 API Key(
OPENCODE_API_KEY,在 opencode.ai 控制台生成),订阅体现在 key 上。 - Google Gemini CLI / Antigravity 的 OAuth 在 0.87.1 已不存在(全包 grep
antigravity|gemini-cli|cloudcode-pa无结果)。Google 只剩google(Gemini API Key)和google-vertex(API Key / ADC / 服务账号)。 - 登录流程完全通过可注入的
AuthInteraction回调(prompt/notify)驱动,可以在无浏览器服务端由 Web 设置页复现,不用 fork pi。
1. pi-ai provider 注册表
1.1 关键 API(实际签名,dist/models.d.ts)
createModels(options?: { credentials?: CredentialStore; modelsStore?: ModelsStore; authContext?: AuthContext }): MutableModels
createProvider<TApi>(input: {
id: string; name?: string; baseUrl?: string; headers?: ProviderHeaders;
auth: ProviderAuth; // { apiKey?: ApiKeyAuth; oauth?: OAuthAuth },至少一个
models: readonly Model<TApi>[]; // 静态基线
fetchModels?: (ctx: RefreshModelsContext) => Promise<readonly Model<TApi>[]>; // 动态覆盖层
filterModels?: (models, credential) => readonly Model<TApi>[];
api: ProviderStreams | Partial<Record<TApi, ProviderStreams>>; // 按 model.api 分派
}): Provider<TApi>
Models.getModels(provider?: string): readonly Model<Api>[] // 同步,取最近一次已知列表
Models.getModel(provider: string, id: string): Model<Api> | undefined
Models.getAvailable(providerId?, opts?): Promise<readonly Model<Api>[]> // 仅鉴权完整的 provider,且经 filterModels
Models.refresh({ providers?, force?, allowNetwork?, signal? }) // 只对动态 provider 生效
Models.checkAuth(providerId) / getAuth(providerId | model, overrides?)
Models.login(providerId, "api_key" | "oauth", interaction: AuthInteraction): Promise<Credential>
Models.logout(providerId)
getSupportedThinkingLevels(model) / clampThinkingLevel(model, level) // "off"|"minimal"|"low"|"medium"|"high"|"xhigh"|"max"
子路径导出:@earendil-works/pi-ai/providers/<id>(如 providers/openai-codex 导出 openaiCodexProvider())、providers/all(builtinProviders()、builtinModels()、getBuiltinModels(p)、getBuiltinProviders())、api/<name>.lazy。
1.2 通用能力(所有 provider 一致)
- 流式:全部走
stream/streamSimple事件流(text/thinking/toolcall delta)。工具调用:10 种 API 适配器全部支持。 - thinking:
Model.reasoning: boolean+ 可选thinkingLevelMap(null 表示该档不支持);请求时streamSimple(..., { reasoning: ThinkingLevel }),用clampThinkingLevel夹到模型支持的档位。OpenAI 兼容端点的参数格式由compat.thinkingFormat决定(openai/openrouter/deepseek/together/baseten/zai/qwen/qwen-chat-template/chat-template/string-thinking/ant-ling)。 - 缓存:通用请求选项
cacheRetention: "none"|"short"|"long"+sessionId(做缓存亲和/路由),由各适配器映射;目录里只有 anthropic 的 15 个模型声明了promptCache寿命。 - baseUrl 在 Model 级(每个模型自带
baseUrl);provider 级baseUrl只是元数据。覆盖做法 = 克隆模型改baseUrl(pi-coding-agent 的provider-composer.js就是config.baseUrl ?? model.baseUrl)。OAuth 的toAuth()也可返回baseUrl(Copilot 按 token 计算代理域名)。
1.3 内置 provider 一览(id / 认证 / API / 目录模型数)
API Key 型的 key 同时支持环境变量(下表)和存储凭据;“OAuth”栏为订阅登录名称。
| id | 名称 | 认证 | API | 模型数 |
|---|---|---|---|---|
| anthropic | Anthropic | Key ANTHROPIC_API_KEY(另认 ANTHROPIC_OAUTH_TOKEN、ANTHROPIC_AUTH_TOKEN)+ OAuth Claude Pro/Max |
anthropic-messages | 15 |
| openai | OpenAI | Key OPENAI_API_KEY |
openai-responses | 41 |
| openai-codex | OpenAI Codex | 仅 OAuth(ChatGPT Plus/Pro) | openai-codex-responses(SSE/WebSocket) | 8 |
| azure-openai-responses | Azure OpenAI | Key AZURE_OPENAI_API_KEY + AZURE_OPENAI_BASE_URL 或 _RESOURCE_NAME |
azure-openai-responses | 41 |
| github-copilot | GitHub Copilot | Key COPILOT_GITHUB_TOKEN + OAuth(device code) |
anthropic/openai-completions/openai-responses 混合 | 32 |
| Google Gemini | Key GEMINI_API_KEY |
google-generative-ai | 22 | |
| google-vertex | Vertex AI | Key GOOGLE_CLOUD_API_KEY / ADC / 服务账号文件 + project/location |
google-vertex | 14 |
| amazon-bedrock | Bedrock | Bearer token / AWS profile / 凭据链 | bedrock-converse-stream | 165 |
| xai | xAI | Key XAI_API_KEY + OAuth(SuperGrok/X Premium,device code) |
openai-responses | 4 |
| openrouter | OpenRouter | Key OPENROUTER_API_KEY + OAuth(PKCE 换永久 key) |
anthropic/openai-completions | 386 |
| opencode | OpenCode Zen | Key OPENCODE_API_KEY |
anthropic/google/openai 三种混合 | 73 |
| opencode-go | OpenCode Go | Key OPENCODE_API_KEY(同一个变量) |
anthropic/openai-completions/openai-responses | 30 |
| kimi-coding | Kimi For Coding | Key KIMI_API_KEY + OAuth Kimi Code(device code) |
anthropic-messages | 4 |
| meta | Meta | Key META_API_KEY + OAuth Muse(device code) |
openai-responses | 5 |
| radius | Radius(pi 网关) | Key RADIUS_API_KEY + OAuth;动态模型表 |
pi-messages | 30 |
| vercel-ai-gateway | Vercel AI Gateway | Key AI_GATEWAY_API_KEY |
anthropic-messages | 246 |
| cloudflare-ai-gateway | CF AI Gateway | Key CLOUDFLARE_API_KEY + env 中 ACCOUNT_ID/GATEWAY_ID |
三种混合 | 51 |
| cloudflare-workers-ai | CF Workers AI | Key + CLOUDFLARE_ACCOUNT_ID |
openai-completions | 18 |
| mistral | Mistral | Key MISTRAL_API_KEY |
mistral-conversations | 33 |
| deepseek / groq / cerebras / together / baseten / fireworks / huggingface / nvidia / ant-ling | 各自 | Key(DEEPSEEK_API_KEY、GROQ_API_KEY、CEREBRAS_API_KEY、TOGETHER_API_KEY、BASETEN_API_KEY、FIREWORKS_API_KEY、HF_TOKEN、NVIDIA_API_KEY、ANT_LING_API_KEY) |
openai-completions(fireworks 另有 anthropic) | 2/7/2/22/21/33/76/19/3 |
| moonshotai / moonshotai-cn | Moonshot 国际/国内 | Key MOONSHOT_API_KEY |
openai-completions | 4/4 |
| minimax / minimax-cn | MiniMax 国际/国内 | Key MINIMAX_API_KEY / MINIMAX_CN_API_KEY |
anthropic-messages | 3/3 |
| zai / zai-coding-cn | Z.AI Coding Plan 国际/国内 | Key ZAI_API_KEY / ZAI_CODING_CN_API_KEY |
openai-completions | 7/4 |
| qwen-token-plan / -cn / -individual | 阿里云百炼 Token Plan | Key QWEN_TOKEN_PLAN_API_KEY / _CN_API_KEY(individual 复用前者) |
openai-completions | 20/20/9 |
| xiaomi / xiaomi-token-plan-cn/-ams/-sgp | 小米 MiMo | Key XIAOMI_API_KEY / XIAOMI_TOKEN_PLAN_{CN,AMS,SGP}_API_KEY |
openai-completions | 6/4/4/4 |
另有 faux(测试假模型,已在用)。模型列表来源:除 Radius 外全部是随包 JSON(不联网);Copilot 有 filterModels,按 OAuth 凭据里的 availableModelIds 过滤;OpenCode 系列请求自动带 x-opencode-session(取 sessionId)。
1.4 OpenAI 兼容通用上游(自定义端点)
pi-ai 没有“generic provider”工厂,做法就是我们现在 packages/agent/src/pi/model.ts 的写法:createProvider({ id, auth, models:[{ id, api:"openai-completions", baseUrl, reasoning, input, cost, contextWindow, maxTokens, compat? , headers? }], api: openAICompletionsApi() })。pi-coding-agent models.json 的 provider 字段可参照:name, baseUrl, apiKey, api, headers, authHeader, compat, modelOverrides, models[];常用 compat:supportsDeveloperRole、supportsReasoningEffort、maxTokensField、thinkingFormat、supportsUsageInStreaming、supportsStrictMode。api 也可选 anthropic-messages/openai-responses/google-generative-ai,对应“Anthropic 兼容 / Responses 兼容 / Gemini 兼容”端点。
2. 订阅/OAuth 登录(实现在 pi-ai dist/auth/oauth/*,pi-coding-agent 只做 UI 与存储)
/login 命令(core/slash-commands.js + modes/interactive/interactive-mode.js)只是把 AuthPrompt/AuthEvent 渲染到 TUI,流程本身全在 pi-ai 的 OAuthAuth.login(interaction)。
| provider | 流程 | 回调 / 手工粘贴 | token 形状与刷新 | 请求时如何用(toAuth) |
|---|---|---|---|---|
| anthropic | 浏览器 PKCE,claude.ai/oauth/authorize,token platform.claude.com/v1/oauth/token |
监听 127.0.0.1:53692/callback;同时给 manual_code 提示,可粘 redirect URL 或 code#state;state = PKCE verifier |
access/refresh,expires 提前 5 分钟;refresh_token grant |
{ apiKey: access };适配器识别 sk-ant-oat 后改 Bearer,加 beta claude-code-20250219,oauth-2025-04-20,强制注入 “You are Claude Code…” 系统提示并把工具名改成 Claude Code 风格 |
| openai-codex | 先 select:浏览器 PKCE(auth.openai.com,redirect http://localhost:1455/auth/callback)或 device code(auth.openai.com/codex/device,15 分钟) |
浏览器模式同时有回调监听与 manual_code(接受完整 URL / code#state / code= 串 / 裸 code) |
access/refresh/expires + accountId(从 JWT 的 chatgpt_account_id 解出);refresh_token grant |
{ apiKey: access },适配器从 token 解 accountId 加请求头,baseUrl chatgpt.com/backend-api |
| github-copilot | 先 text 问 GHE 域名(空=github.com),然后 device code(/login/device/code,scope read:user) |
无回调,轮询 | refresh 存 GitHub OAuth token,access 是 Copilot 短期 token(expires_at 提前 5 分钟);另存 enterpriseUrl、availableModelIds;登录时还会自动对需开启的模型调 /models/{id}/policy |
{ apiKey: access, baseUrl: 按 token 计算的代理域名 },固定伪装 VS Code Copilot Chat 头 |
| xai | device code(auth.x.ai/oauth2/device/code) |
轮询 | access/refresh,缺省 1h,提前 5 分钟 | { apiKey: access } |
| kimi-coding | device code(auth.kimi.com) |
轮询 | access/refresh;refresh 最多重试 3 次 | { headers: { Authorization: Bearer access } } |
| meta | device code(auth.meta.com/oidc/device/...),再用身份 token 调 api.meta.ai/muse-code/key 铸 24h key |
轮询 | refresh 存身份 token,access 存铸出的 key;“刷新”= 重新铸 key(auth.meta.com 不支持 refresh grant) |
{ apiKey: access } |
| openrouter | 浏览器 PKCE,openrouter.ai/auth?callback_url=...,换 api/v1/auth/keys |
监听 127.0.0.1:<随机端口>/oauth/callback/<uuid> + manual_code |
得到永久 API key,expires = MAX_SAFE_INTEGER,refresh="",不刷新 |
{ apiKey: access } |
| radius | 先 select:浏览器(127.0.0.1:1456/oauth/callback)或 device code;网关默认 radius.pi.dev |
同上 | access/refresh,提前 60s;凭据中带 gatewayConfig(模型目录) |
{ apiKey: access } |
通用机制:
AuthInteraction = { signal?, prompt(p: AuthPrompt): Promise<string>, notify(e: AuthEvent): void }。AuthPrompt.type ∈ text | secret | select | manual_code;AuthEvent.type ∈ info | auth_url | device_code | progress。浏览器型流程会同时等回调和manual_code,谁先到用谁。- 存储:
CredentialStore { read, list, modify(providerId, fn), delete },按 provider id 一份凭据。pi-coding-agent 的auth.json(~/.pi/agent/auth.json,0600,proper-lockfile 加锁)形如:{ "anthropic": { "type":"oauth", "access":"…", "refresh":"…", "expires": 1790000000000 }, "openai-codex": { "type":"oauth", …, "accountId":"…" }, "opencode": { "type":"api_key", "key":"…" }, "cloudflare-workers-ai": { "type":"api_key", "key":"…", "env": { "CLOUDFLARE_ACCOUNT_ID":"…" } } };key以!开头表示执行命令取值。 - 刷新:
Models.getAuth()内按需刷新——剩余 < 5 分钟(或overrides.minOAuthValidityMs)时在modify锁内双重检查、调oauth.refresh()、写回;失败抛ModelsError(code:"oauth"),旧凭据保留,需重新登录。没有后台定时刷新。 - 与
createProvider衔接:OAuth token 不当作“配置的 apiKey”,而是toAuth(credential)在每次请求前转成ModelAuth { apiKey?, headers?, baseUrl? }。我们只要把自己的CredentialStore交给createModels({ credentials }),内置 provider 就会自动使用并刷新 OAuth 凭据。
3. 无浏览器服务端完成 OAuth
**可行,且不用改 pi。**做法:服务端调用 models.login(providerId, "oauth", interaction),interaction 由我们实现成“挂起式”:
POST /oauth/start:建 pending login(内存,TTL 15 分钟,带 AbortController),启动login();prompt(select)用 start 请求里的method(browser/device_code)作答,prompt(text)用enterpriseDomain等参数作答;notify(auth_url|device_code)写进 pending 记录;start 在拿到第一个事件后返回{ loginId, authUrl? , userCode?, verificationUri?, expiresIn? }。- 用户在自己的浏览器授权。浏览器型的 redirect 指向用户本机的 localhost,页面会打不开,这是预期的:让用户复制地址栏里的完整 URL。
POST /oauth/complete { loginId, input }:把 input 交给挂起的manual_codePromise;pi 自己解析 URL/code#state、校验 state、换 token;成功后经我们的CredentialStore.modify写 secret 表。device code 型不需要 complete,前端轮询GET /oauth/status?loginId=直到done/failed。
各 provider 的回调情况:
- 必须粘回 URL(redirect 固定 localhost,无法改成我们的域名):Anthropic(53692)、OpenAI Codex 浏览器模式(1455)、Radius 浏览器模式(1456)。redirect_uri 与 client_id 在授权服务器登记,不能换成公网地址。
- 优先走 device code(不用粘 URL,体验最好):Codex(可选)、Copilot、xAI、Kimi、Meta、Radius(可选)。Codex 的 device code 若返回 404,说明账号未开启该方式,需回退浏览器模式。
- OpenRouter:
callback_url由客户端自由指定,理论上可直接回调到我们的设置页;但 pi 的实现写死本地监听,用 pi 就走“粘 URL”;要直回调需自己实现(流程很简单:PKCE + POST/api/v1/auth/keys)。
注意事项:
- pi 仍会在服务端
listen这些 localhost 端口(对方连不上,无害)。Anthropic 端口被占时会直接 reject 导致登录失败(Codex/Radius 会降级为只等粘贴)→ 同一 provider 的登录要串行,容器内别占 53692/1455/1456。 - pending login 只能活在 api 进程内存(interaction 是闭包),多实例部署需粘性路由,或者直接限制单实例处理登录。
- OAuth 模块通过
process.versions.node判断后动态import("node:http"/"node:crypto"),Deno 的 npm 兼容层满足条件,但要做一次 spike 实测(尤其 Codex 的_randomBytes是异步赋值)。
4. 映射方案
4.1 contracts settings.ts:pi 段改为“上游列表 + 默认上游”
export const PiUpstream = z.object({
id: Id, // 我们的上游 id(同一 provider 可有多个,例如两个 Codex 账号)
label: z.string().min(1).max(64),
provider: z.string().min(1), // pi provider id(见 §1.3)或 "custom-openai" | "custom-anthropic" | "fake"
authKind: z.enum(["api_key", "oauth", "ambient", "none"]),
apiKeySecretRef: Id.nullable(), // api_key:secret 表引用
oauthSecretRef: Id.nullable(), // oauth:secret 表中存 pi Credential JSON(access/refresh/expires/+扩展字段)
providerEnv: z.record(z.string(), z.string()).nullable(), // CF ACCOUNT_ID、Vertex project/location、Azure resource 等非密值
baseUrl: z.url().nullable(), // 仅 api_key/custom 允许;OAuth 型置 null
api: z.enum(["openai-completions","openai-responses","anthropic-messages","google-generative-ai"]).nullable(), // 仅 custom
compat: z.record(z.string(), z.unknown()).nullable(), // 仅 custom
model: z.string().min(1).nullable(),
thinkingLevel: ThinkingLevel,
note: z.string().max(500).nullable(),
status: z.enum(["ok", "needs_login", "refresh_failed", "untested"]), // 服务端维护,只读
});
export const PiSettings = z.object({
upstreams: z.array(PiUpstream).max(32),
defaultUpstreamId: Id.nullable(),
contextBudget, compaction, subagentsEnabled, // 保持原字段
});
能力/任务级 upstreamId? 覆盖默认上游(没有就用 defaultUpstreamId)。迁移:旧 {provider, baseUrl, model, apiKeySecretRef} 生成一条 upstream 并设为默认。
4.2 运行时装配
- 每个上游建一个
Models实例,里面只放它对应的那个内置 provider(providers/<id>工厂),credentials用一个只映射到本上游 secret 的CredentialStore适配器。这样绕开“pi 按 provider id 只存一份凭据”的限制,同一 provider 的多个账号可以并存。 - baseUrl 覆盖:包一层 provider,
getModels: () => base.getModels().map(m => ({ ...m, baseUrl }))。 - thinking:
clampThinkingLevel(model, upstream.thinkingLevel)后传reasoning。 ambient型(Bedrock 凭据链、Vertex ADC)依赖服务端环境;设置页只做提示,不存密钥。
4.3 UI 流程
- 新增上游 → 从
/pi/providers能力表选 provider(按“订阅登录 / API Key / 云凭据 / 自定义兼容端点”分组;OpenCode Go 和 Zen 放在 API Key 组并注明“opencode 订阅 key”)。 - API Key 型:填 key(和所需 env)→ 保存 → “测试连通”。
- OAuth 型三步:① 选方式(有 device code 的默认选它)→ ② 显示授权链接或
userCode+ 验证地址(复制按钮、倒计时)→ ③ 浏览器型:粘贴地址栏 URL 提交;device 型:自动轮询。成功后显示账号信息(Codex accountId、Copilot 域名)和过期时间。 - 模型下拉:
/models返回getAvailable()的结果(包括 id、name、reasoning、支持的 thinking 档位、contextWindow、input),同时允许手填模型 id(目录滞后于上游时用)。
4.4 后端端点
| 端点 | 作用 |
|---|---|
GET /pi/providers |
能力表:id、name、authKinds、oauth{name,isSubscription,methods:["browser" |
GET/POST/PATCH/DELETE /pi/upstreams[/{id}] |
CRUD;密钥写 secret 表,响应只回 ref 与掩码 |
POST /pi/upstreams/{id}/oauth/start |
{ method, enterpriseDomain? } → { loginId, authUrl?, userCode?, verificationUri?, expiresIn } |
POST /pi/upstreams/{id}/oauth/complete |
{ loginId, input }(粘贴的 URL / code) |
GET /pi/upstreams/{id}/oauth/status |
`pending |
POST /pi/upstreams/{id}/test |
getAuth + 发一个 1 token 的 completeSimple,回延迟与错误分类 |
GET /pi/upstreams/{id}/models |
getAvailable(),动态 provider 先 refresh() |
POST /pi/upstreams/{id}/logout |
删除 secret,状态改为 needs_login(不会撤销提供方侧的授权) |
刷新:worker 定时任务(每 10 分钟)对 OAuth 上游调 models.getAuth(providerId, { minOAuthValidityMs: 30*60_000 }),让 pi 在锁内刷新并写回 secret;OpenRouter(永不过期)跳过。ModelsError.code==="oauth" → 状态改为 refresh_failed,创建待处理事项“上游 X 需要重新登录”;Agent 选上游时跳过不可用的,按配置回落到默认上游或报错。secret 表的 modify 要做行级锁(SELECT … FOR UPDATE),因为 api 和 worker 两个进程都可能刷新(refresh token 会轮换,并发刷新会让其中一方失效)。
5. 版本与许可
- pi-ai / pi-coding-agent 均为 MIT。OAuth client_id 全部明文或 base64 写在公开源码里:Anthropic
9d1c250a-e61b-44d9-88ed-5944d1962f5e(Claude Code 的客户端)、Codexapp_EMoamEEZ73f0CkXaXp7hrann(Codex CLI)、CopilotIv1.b507a08c87ecfe98(VS Code Copilot)、xAIb1a00492-…、Kimi17e5f671-…、Meta1031625952748946、Radiuspi-gateway。这些是公共客户端(PKCE / device code,没有 secret)。技术上可以直接复用;复用边界最好是“调用 pi-ai 的登录实现”,不要把 ID 抄进我们自己的代码,这样上游改 ID 或流程时跟着升级就行。 - 升级时需要回归:目录 JSON 随版本变化(模型会增删);OAuth 端点和伪装头(Copilot 的
Editor-Version、Anthropic 的 Claude Code 身份)随时可能被提供方收紧。锁定 0.87.1,每次升级重新跑本文件的核对。 - 条款风险提醒:这些订阅登录借用的是官方 CLI/IDE 的客户端身份(Anthropic 路径还会把请求伪装成 Claude Code)。把个人订阅用于自动化后台服务可能不符合各家的用户条款或使用政策,账号有被限流或封禁的风险。上线前请逐家确认条款,设置页也要对用户明示(这里不下法律结论)。