fix: accept RT-exchanged tokens missing chatgpt_account_id claim#1
Closed
xiaoliu10 wants to merge 1 commit into
Closed
fix: accept RT-exchanged tokens missing chatgpt_account_id claim#1xiaoliu10 wants to merge 1 commit into
xiaoliu10 wants to merge 1 commit into
Conversation
Owner
Author
|
Closed because this PR was accidentally opened against the fork. Reopening against the upstream repository. |
There was a problem hiding this comment.
Pull request overview
This PR improves RT-only account import robustness by tolerating RT-exchanged access tokens that sometimes omit the chatgpt_account_id claim, extracting needed metadata from either access_token or an accompanying id_token, and plumbing that metadata through the account creation flow.
Changes:
- Add
extractCodexTokenMetadata()to deriveaccountId/userId/email/planTypefromaccess_token+ optionalid_token. - Update RT-only import to use extracted metadata and to allow “missing
chatgpt_account_id” validation failures when the exchanged token is still fresh. - Extend account pool/registry APIs to accept optional metadata and add/adjust unit tests + changelog entry.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/services/account-import.test.ts | Adds RT-exchange scenarios covering missing chatgpt_account_id in access_token, including fallback to id_token. |
| tests/unit/routes/accounts-import-export.test.ts | Updates jwt-utils mocks to include extractCodexTokenMetadata. |
| tests/unit/auth/rt-reuse-race.test.ts | Updates jwt-utils mocks to include extractCodexTokenMetadata. |
| tests/unit/auth/jwt-utils.test.ts | Adds unit test for extractCodexTokenMetadata fallback behavior. |
| src/services/account-import.ts | Extracts metadata from RT exchange results, passes it through to the pool, and relaxes validation for fresh RT-exchanged tokens missing chatgpt_account_id. |
| src/auth/oauth-pkce.ts | Adds explicit scope on refresh to encourage id_token return (openid). |
| src/auth/jwt-utils.ts | Introduces CodexTokenMetadata and extractCodexTokenMetadata() helper. |
| src/auth/account-registry.ts | Accepts optional metadata in addAccount() and uses it as fallback for account/user/email/plan fields. |
| src/auth/account-pool.ts | Plumbs optional metadata through to the registry. |
| CHANGELOG.md | Adds an “Unreleased → Fixed” entry for the RT-only import compatibility fix. |
Comments suppressed due to low confidence (1)
src/services/account-import.ts:83
- The warmup hook still derives
accountIdonly fromextractChatGptAccountId(resolved.token). For RT-exchanged tokens missingchatgpt_account_id, you now haveresolved.metadata?.accountId(possibly fromid_token), but it won’t be used, so warmup (if enabled) will be called with a null accountId even when it’s known.
const entryId = this.pool.addAccount(resolved.token, resolved.rt, resolved.metadata);
this.scheduler.scheduleOne(entryId, resolved.token);
if (entry.label) {
this.pool.setLabel(entryId, entry.label);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -101,7 +106,7 @@ export class AccountRegistry { | |||
| existing.refreshToken = refreshToken; | |||
| } | |||
| existing.email = profile?.email ?? existing.email; | |||
| - 隐式续链反向校验缺失导致客户端持续看到上游 `invalid_request_error: No tool output found for function call call_X`:`evaluateImplicitResume` 此前只做 forward 检查(新输入里的 `function_call_output.call_id` 必须命中上一轮 stored function_call),漏了反向(上一轮 stored function_call 必须在新输入里有 output)。当上一轮模型并发吐 N 个 tool_use、客户端只回 N-1 个 tool_result 时,proxy 仍然 resume + `previous_response_id` 发出去,上游存的 context 里那个未回复的 function_call 触发 400。新增反向检查 → 走完整重放(`reason: "unanswered_tool_calls"`),同时 `error-classification.ts` 加 `isUnansweredFunctionCallError`,proxy-handler catch 块兜底:strip `previous_response_id` + 完整历史重放 + 同账号重试一次(与 `previous_response_not_found` 同款),避免 ws/sse 路径上的 400 静默吞掉变成 502 | ||
| - `codex-to-anthropic.ts` / `codex-to-openai.ts` / `codex-to-gemini.ts` 非流式 collect 路径里把上游错误事件抛成 `new Error(...)`,丢失 status 信息,handleNonStreaming 的 collectErr 再通过正则匹配 `HTTP/X.X NNN` 状态码必然失败 → 一律 502 兜底,客户端拿到的是模糊的 502 而不是上游真实的 400/429。改为统一 `codexApiErrorFromEvent(evt.error)` 抛 `CodexApiError(status, body)`,按 error code 映射到 400/401/402/403/429(默认 502);handleNonStreaming 的 collectErr 也加一条 `instanceof CodexApiError` 分支直接透传 status,不再走正则降级 | ||
| - `streamResponse` 流式路径里上游错误此前只往 SSE 写一条 `stream_error` 事件、零日志,客户端能看到错误但 proxy `dev-YYYY-MM-DD.log` 里完全没记录,排查时无证据链。catch 块加 `console.warn` 打 `status / msg / body`,留下 call_id 等关键现场 | ||
| - 修复 RT-only 导入兼容性:Refresh Token 换取的 access token 缺少 `chatgpt_account_id` 时,自动从 JWT `sub` 或导入 ID 回填账号标识,避免导入失败(`src/auth/jwt-utils.ts`、`src/services/account-import.ts`、`src/auth/account-pool.ts`、`src/auth/account-registry.ts`) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
修复 Refresh Token 换取的 access token 缺少
chatgpt_account_idclaim 时导入失败的问题。OpenAI RT exchange 返回的 access token 有时不携带chatgpt_account_id,导致validateManualToken拒绝该 token,RT-only 导入无法完成。Changes
src/auth/jwt-utils.ts:新增extractCodexTokenMetadata()从 access_token + id_token 中提取 accountId / userId / email / planType,兼容 claim 在任一 token 中的情况src/services/account-import.ts:RT exchange 后用extractCodexTokenMetadata提取元数据传给 pool;新增canAcceptRtExchangeToken()允许缺少chatgpt_account_id但未过期的 RT 换取 token 通过验证;ImportDeps.refreshToken返回类型增加id_tokensrc/auth/account-registry.ts:addAccount()新增可选metadata参数,当extractChatGptAccountId返回 null 时回退到metadata.accountId,email/planType 同理src/auth/account-pool.ts:透传metadata参数到 registrysrc/auth/oauth-pkce.ts:refreshAccessToken返回类型增加id_token字段CHANGELOG.md:[Unreleased] → Fixed补充条目jwt-utils.test.ts补充extractCodexTokenMetadata用例;account-import.test.ts补充 RT exchange 缺 claim 场景;accounts-import-export.test.ts/rt-reuse-race.test.ts补齐extractCodexTokenMetadatamockTest Plan
npm test— 251 files, 2452 passed, 1 skippednpx tsc --noEmit— zero errors