【免费下载链接】opencodex
Universal provider proxy for OpenAI Codex & Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code
本篇技术指南围绕 opencodex 在 Kiro(CodeWhisperer)网关对齐工程中的 Phase 90(P0-3)OAuth 刷新加固展开:通过按 provider 的刷新单飞(singleflight)消除并发临近过期请求导致的重复刷新与凭证竞写,并在刷新前后对本地 Kiro CLI SQLite 令牌缓存做重载以优先采用更新的外部会话。读完本文,你将掌握 opencodex OAuth 凭据解析链(getValidAccessToken → resolveAccessSnapshotForAccount → refreshAndPersistAccessToken)的完整调用结构、REFRESH_SKEW_MS 等关键常量与容量/陈旧淘汰策略,以及如何在隔离环境中以 bun:sqlite 种子夹具复现并验证这些竞态场景。
一、安全边界:OAuth 刷新为何需要专项加固
本阶段设计文档 90_phase_oauth_singleflight_reload.md 首先界定了安全边界(trust boundary):
本地凭据存储(
OPENCODEX_HOME/auth.json)+ 导入的 Kiro CLI SQLite 令牌缓存 → 出站 Kiro 运行时Authorization头。
该链路上被保护的对象是 OAuth access/refresh 令牌与正确的持久化凭据状态,而主要故障模式(main failure mode)有三类:
- 并发临近过期请求用同一个旧 refresh token 刷新——多个请求同时发现 access token 即将过期,各自独立发起刷新,上游可能因 refresh token 复用被吊销(refresh-token reuse revocation),或刷新出的新令牌互相覆盖;
- 对
auth.json的竞态写入——同一 provider 的多路刷新各自saveCredential,后写覆盖先写,丢失更新; - 错过更新的 Kiro CLI 令牌——外部
kiro-cli进程已经刷新出更新的令牌,而 opencodex 仍基于自己存储的旧快照走桌面刷新端点,白白产生一次网络往返且可能引入过期路由元数据。
REFRESH_SKEW_MS(预过期倾斜窗口)是本链路的核心标尺,在 src/oauth/index.ts 中定义为 60_000 毫秒:expires > Date.now() + REFRESH_SKEW_MS 即视为有效,其余为临近过期/过期,需要进入刷新路径。
二、范围界定:本次加固做什么、不做什么
设计文档明确了"在 opencodex 当前所有权内正确实现的加固",三项必做:
| 项 | 内容 | 源码落点 |
|---|---|---|
| 1 | 通用 per-provider 刷新单飞:围绕 getValidAccessToken 的刷新工作做去重 | tokenRefreshes Map + resolveAccessSnapshotForAccount(src/oauth/index.ts) |
| 2 | Kiro 专属刷新前 SQLite 重载:若已安装的 Kiro CLI 有新鲜令牌,持久化/使用它,而不是打桌面刷新端点 | readKiroCliSqliteCredential(src/oauth/kiro-credentials.ts) |
| 3 | Kiro 专属刷新失败后恢复:若外部 Kiro CLI 在我们失败期间/之后已刷新,则导入它完成恢复 | matchingRotatedKiroCliCredential(src/oauth/kiro.ts) |
明确不在范围内(out of scope)的包括:
- AWS SSO OIDC / device-registration 刷新路径——
refreshAwsSsoOidcToken(src/oauth/kiro.ts)属于另一套 client-pair 换发机制,不在本次单飞改造内; - 多账户故障转移(multi-account failover)——在 00_plan.md 的差距矩阵中已说明:opencodex 在更上层拥有自己的 Codex 多账户池(
codexAccounts),Kiro 单凭据导入是既定设计,非适配器差距; - 改变登录时的 auth-source 优先级——本次只修"已有 opencodex Kiro 凭据后的陈旧刷新竞态",绝不削弱登录导入的优先级次序。
三、核心机制一:按 provider 的刷新单飞
设计文档规划了模块级 const tokenRefreshes = new Map<string, Promise<string>>()。当前源码中该结构已演化为携带飞行元数据的 OAuthTokenRefreshFlight(src/oauth/index.ts):
const MAX_OAUTH_TOKEN_REFRESH_FLIGHTS = 32;
const OAUTH_TOKEN_REFRESH_FLIGHT_STALE_MS = 120_000;
interface OAuthRefreshFlightEvidence { flightId: string; dispatched: boolean }
interface OAuthTokenRefreshFlight extends OAuthRefreshFlightEvidence {
promise: Promise<OAuthAccessSnapshot>;
startedAt: number;
abort: AbortController;
}
const tokenRefreshes = new Map<string, OAuthTokenRefreshFlight>();
Map 的键是 ${provider}\u0000${accountId}——按 (provider, account) 粒度去重,这正是多账户(multiauth)场景下"守护刷新后台账户时绝不切换活跃账户"的保证基础。getValidAccessToken(src/oauth/index.ts)的完整决策流程如下:
resolveAccessSnapshotForAccount(provider, accountId)
├─ 读取账户凭据(getAccountCredentialWithStatus)
├─ 若 expires > now + REFRESH_SKEW_MS → 直接返回快照(快路径,不触碰 SQLite/fetch)
├─ 若已有进行中的 flight 且未超时(startedAt ≤ 120s)→ 返回 existing.promise(加入同一次刷新)
├─ 若已有 flight 但已陈旧 → abort 旧 flight(OAuthTokenRefreshStaleError),删除后重建
├─ 若 flights 数量已达 32 → 抛 OAuthTokenRefreshBusyError(retryable)
└─ 否则新建 flight,在 finally 中仅当 map 中仍是自己时 delete
三个关键设计点值得展开:
- 快路径优先:凭据仍有效时不进入任何刷新/重载逻辑,既不读 SQLite 也不发 fetch,保证"已有有效凭据保持快速路径"这一验收标准;
- 陈旧飞行替换:
OAUTH_TOKEN_REFRESH_FLIGHT_STALE_MS = 120_000界定一次刷新飞行的存活上限,超时的 owner 会被OAuthTokenRefreshStaleError中止并以新 flight 替换,配合OAuthTokenRefreshBusyError(retryable = true)形成有界的并发控制; - finally 幂等清理:
if (tokenRefreshes.get(key) === flight) tokenRefreshes.delete(key)——单飞 Map 在成功/失败后都必须清空,避免内存泄漏与后续请求错误地加入一个已终结的 promise。
forceRefreshOAuthAccessSnapshot(src/oauth/index.ts)定义了强制刷新 provider 白名单 FORCE_REFRESH_PROVIDERS = { xai, github-copilot, kiro, google-antigravity, orcarouter-oauth }——上游 401 重放路径可携带被拒快照的 generation 进入同一单飞解析流程。
四、核心机制二:Kiro 刷新前的 SQLite 重载
设计文档为 refreshAndPersistAccessToken(provider, def, cred) 规划的 Kiro 分支逻辑为:
provider === "kiro"时先调用readKiroCliSqlite();- 若导入的令牌有效期超过
REFRESH_SKEW_MS,则saveCredential(provider, imported)并直接返回imported.access,完全跳过桌面刷新端点; - 否则才调用
def.refresh(cred.refresh),持久化并返回; - 若 refresh 抛错且 provider 为 Kiro,再次重读 SQLite,若此时已有有效令牌则采用之,否则原样抛出。
当前源码中该分派函数为 refreshAndPersistAccessToken,按 provider 分发到带锁刷新器:
if (provider === "xai") return refreshXaiAccountWithLock(...);
if (provider === "anthropic") return refreshAnthropicAccountWithLock(...);
return refreshGenericAccountWithLock(provider, accountId, def, cred, { signal });
每个带锁刷新器都通过 createOAuthRefreshIntentLock(provider, accountId).acquire() 取得意图锁,并基于 captureConfigGeneration() 的快照执行"读取→刷新→mergeAccountCredential(compare-and-swap)→记录审计事件"的原子序列;merged()(src/oauth/index.ts)在合并时保留旧凭据中缺失的 projectId / apiBaseUrl / email / accountId / kiro 字段,防止轮换后路由元数据丢失。
而"读取 Kiro CLI SQLite"的真实导出函数名为 readKiroCliSqliteCredential()(设计文档写作 readKiroCliSqlite,实际实现见 src/oauth/kiro-credentials.ts),其底层 inspectKiroCliSqliteSources(同文件 L444-L447)只读 SQLite 缓存(不触碰 JSON 凭据文件),按平台解析本机会话库路径(resolveKiroCliNativeSessionEntries,L156-L170):
| 平台 | Kiro CLI 会话库路径 |
|---|---|
| Windows | %LOCALAPPDATA%\Kiro-Cli\data.sqlite3(LOCALAPPDATA 缺失时回退 %USERPROFILE%\AppData\Local,再回退平台原生 home) |
| macOS | ~/Library/Application Support/kiro-cli/data.sqlite3 |
| Linux | ~/.local/share/kiro-cli/data.sqlite3 |
此外还支持 KIROCLI_DB_PATH / KIRO_CLI_DB_FILE 环境变量显式指定数据库,以及 KIROCLI_TOKEN_KEY 在多令牌库中选定键;令牌键按 kirocli:odic:token、kirocli:oidc:token、kirocli:social:token、codewhisperer:odic:token 的顺序择优,并为每个位置产出结构化的 KiroImportDiagnostic(missing / unreadable / schema_mismatch / token_ambiguous / token_found 等)供诊断。
五、核心机制三:刷新失败后的 SQLite 恢复
当桌面/SSO 刷新端点失败而外部 Kiro CLI 恰好完成了一次自己的刷新时,单飞内的恢复逻辑在 src/oauth/kiro.ts 的 matchingRotatedKiroCliCredential 中落地:
function matchingRotatedKiroCliCredential(refresh, credential?) {
const storedIdentities = [credential?.kiro?.profileArn, credential?.accountId].filter(Boolean);
if (storedIdentities.length === 0 || new Set(storedIdentities).size !== 1) return undefined;
const local = readKiroCliSqliteCredential();
if (!local?.profileArn || storedIdentities.some(id => id !== local.profileArn)) return undefined;
if (!local.refresh || local.refresh === refresh) return undefined; // 不是新轮换
return local;
}
其安全性要点:只有本机 SQLite 中持有与存储账户相同的 profileArn、且 refresh token 与旧值不同时才判定为"外部已轮换",从而避免借用无关账户的会话。随后 metadataForRotatedKiroCliCredential(L633-L655)在保留存储账户路由元数据(ssoRegion 等)的前提下合并本地新元数据,仅在 aws_sso_oidc 类型下补入本地 client pair。刷新请求本身带 30 秒超时(kiroRefreshSignal,L556-L559),并把失败响应映射为携带 httpStatus 与白名单 oauthError 的 KiroTokenRefreshError(L59),供上层 terminal() 判断"凭据是否已死"。
值得说明:刷新前重载分支在 refreshGenericAccountWithLock 中通过 readImportedKiroCredential()/detectGrokCliToken() 等先例已有"本地优先"思想,而 Kiro 专属的"刷新前重载 + 失败后恢复"双保险正是 Phase 90 引入的差异点。
六、登录侧不变的优先级契约
设计文档特别强调:Kiro 重载路径绝不削弱登录时的凭据优先级。查看 loginKiro(src/oauth/kiro.ts)可见完整导入优先级为:
kiro-cliSQLite 导入(readImportedKiroCredential)→ 2.KIRO_ACCESS_TOKEN环境变量 → 3. 手动粘贴(仅 CLI)→ 4.forceLogin时先对 CLI 会话做快照备份(inspectKiroCliSessionSnapshot)、再logout/login、最后回读新令牌并登记恢复事务。
Phase 90 的刷新重载只发生在"opencodex 已持有 Kiro 凭据之后的陈旧刷新竞态"场景,是刷新路径的优化而非登录路径的改写,因此不改变上述导入顺序,也不触发 kiro-cli 的登出/登入副作用。
七、测试与验证:如何复现这些竞态
设计文档规划了新增 tests/oauth-refresh.test.ts,四个核心用例与验收一一对应:
| 用例 | 断言目标 |
|---|---|
| 并发过期的 Kiro 调用 | 共享同一次刷新请求,两者返回同一个 access token(单飞去重) |
| SQLite 中有新鲜 Kiro 令牌 | 刷新端点被调用前即完成导入(前置重载) |
| 刷新失败但 SQLite 已有有效令牌 | 恢复成功(失败后重载) |
| 存储凭据未过期 | 直接返回、不触发刷新(快路径) |
关键测试基建(与既有 tests/providers/kiro/kiro-oauth.test.ts 同构):
- 环境隔离:
mkdtempSync生成独立临时目录,同时改写HOME、LOCALAPPDATA、USERPROFILE(Windows 原生解析器优先于HOME,见该文件 L37-L54),afterEach逐一还原并清理; - 种子夹具:
seedKiroCliDb(L104-L121)用bun:sqlite按平台创建auth_kv表并写入kirocli:social:token等令牌键,可选device-registration与state表中的 profile 元数据——与 kiro-oauth 测试共用同一套 schema; - auth.json 播种:通过
saveCredential写入本地凭据存储; - 网络打桩:mock
globalThis.fetch以断言"刷新端点是否被调用、调用几次"。
验证命令与项目 CI 纪律(源自 00_plan.md 的 PABCD 流程):
bun x tsc --noEmit
bun test tests/oauth-refresh.test.ts tests/providers/kiro/kiro-oauth.test.ts
设计文档还给出明确的验收标准(Acceptance):
- 任何路径不得记录令牌明文(No tokens are logged);
- 单飞 Map 在成功/失败后必须清空(map always clears);
- 已有有效凭据保持快速路径,不触碰 SQLite/fetch(existing valid credentials remain fast-path);
- Kiro 重载路径只在已有 opencodex Kiro 凭据后生效,绝不在登录时削弱凭据优先级。
对应的原子提交信息为:fix(oauth): singleflight refresh and reload Kiro CLI tokens。
八、总结
Phase 90 的本质是把"刷新"从各自为战的惰性操作收敛为有界、去重、可恢复的受控流程:tokenRefreshes 单飞 Map 在 (provider, account) 粒度上消除重复刷新与 auth.json 竞写;REFRESH_SKEW_MS、MAX_OAUTH_TOKEN_REFRESH_FLIGHTS、OAUTH_TOKEN_REFRESH_FLIGHT_STALE_MS 三个常量分别界定有效窗口、并发上限与陈旧淘汰;Kiro 刷新前/后的 SQLite 重载则让 opencodex 与外部 kiro-cli 会话始终保持一致,优先采用更新鲜的本地令牌、在失败时借道恢复。整套机制与登录导入优先级严格解耦,并有隔离化种子夹具与 bun test 用例逐条锁定验收标准——这对任何"代理端 OAuth 凭据管理"类系统都是可直接借鉴的加固范本。进一步深入可阅读 src/oauth/store.ts(凭据存储与意图锁)与 143_kiro-gateway-parity 系列其余阶段文档。
【免费下载链接】opencodex
Universal provider proxy for OpenAI Codex & Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



