diff --git a/README.md b/README.md index 47a2da7..3147352 100644 --- a/README.md +++ b/README.md @@ -34,15 +34,7 @@ const providers: OAuthLoginProviders = { [CODEX_PROVIDER]: { oauthConfig: codexOAuthConfig, exchange: (code, verifier, now) => exchangeCodexCode(code, verifier, now), - // `refresh` only ever receives the stored refresh secret, so the prior - // tokens passed here supply just that; refreshCodexTokens carries the - // account id forward from a caller-supplied `previous` only when the - // refresh response itself omits `id_token`, which it usually does. - refresh: (refreshSecret, now) => - refreshCodexTokens(refreshSecret, now, { - access: "", - refresh: refreshSecret, - }), + refresh: (refreshSecret, now) => refreshCodexTokens(refreshSecret, now), // The Codex backend rejects inference without this header value. metadata: (tokens) => "accountId" in tokens && typeof tokens.accountId === "string" diff --git a/src/oauth.test.ts b/src/oauth.test.ts index c784974..6f8b30a 100644 --- a/src/oauth.test.ts +++ b/src/oauth.test.ts @@ -55,4 +55,15 @@ describe("Codex oauth — refresh account id continuity", () => { ); expect(refreshed.accountId).toBe("acct-1"); }); + + test("keeps the refresh token and leaves the account id unset without previous tokens", async () => { + const refreshed = await refreshCodexTokens( + "refresh-1", + 0, + undefined, + fetchResolving({ access_token: "new" }), + ); + expect(refreshed.refresh).toBe("refresh-1"); + expect(refreshed.accountId).toBeUndefined(); + }); }); diff --git a/src/oauth.ts b/src/oauth.ts index 21a80ff..16a3e9f 100644 --- a/src/oauth.ts +++ b/src/oauth.ts @@ -104,13 +104,14 @@ export async function exchangeCodexCode( * Refreshes a Codex access token, carrying the prior refresh token forward * when the response omits it. A refresh response frequently omits * `id_token` entirely, which would otherwise drop `chatgpt-account-id` from - * every request after the first refresh — the caller's `previous` tokens - * supply the account id to carry forward in that case. + * every request after the first refresh. Pass the caller's `previous` tokens + * to carry their account id forward in that case; without them, the caller + * keeps the account id some other way (for example, in credential metadata). */ export async function refreshCodexTokens( refreshToken: string, now: number, - previous: CodexTokens, + previous?: CodexTokens, fetchImpl: FetchLike = fetch, ): Promise { const refreshed = codexTokensFromResponse( @@ -120,7 +121,7 @@ export async function refreshCodexTokens( ); return { ...refreshed, - ...(refreshed.accountId === undefined && previous.accountId !== undefined + ...(refreshed.accountId === undefined && previous?.accountId !== undefined ? { accountId: previous.accountId } : {}), };