mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 18:40:44 +00:00
fix: preserve OpenAI Codex OAuth transport (#75111)
Preserve the existing wrapped OpenAI Codex stream so PI OAuth bearer injection reaches ChatGPT/Codex Responses, and scope native Codex payload sanitization to the ChatGPT backend.\n\nThanks @keshavbotagent.
This commit is contained in:
@@ -24,6 +24,27 @@ describe("resolveCodexAuthIdentity", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("extracts account and plan metadata from the JWT auth claim", () => {
|
||||
const identity = resolveCodexAuthIdentity({
|
||||
accessToken: createJwt({
|
||||
"https://api.openai.com/profile": {
|
||||
email: "jwt-user@example.com",
|
||||
},
|
||||
"https://api.openai.com/auth": {
|
||||
chatgpt_account_id: "acct-123",
|
||||
chatgpt_plan_type: "prolite",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(identity).toEqual({
|
||||
accountId: "acct-123",
|
||||
chatgptPlanType: "prolite",
|
||||
email: "jwt-user@example.com",
|
||||
profileName: "jwt-user@example.com",
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to credential email before synthetic ids", () => {
|
||||
const identity = resolveCodexAuthIdentity({
|
||||
accessToken: createJwt({}),
|
||||
|
||||
@@ -10,6 +10,7 @@ type CodexJwtPayload = {
|
||||
"https://api.openai.com/auth"?: {
|
||||
chatgpt_account_id?: unknown;
|
||||
chatgpt_account_user_id?: unknown;
|
||||
chatgpt_plan_type?: unknown;
|
||||
chatgpt_user_id?: unknown;
|
||||
user_id?: unknown;
|
||||
};
|
||||
@@ -67,23 +68,33 @@ export function resolveCodexAccessTokenExpiry(accessToken: string): number | und
|
||||
}
|
||||
|
||||
export function resolveCodexAuthIdentity(params: { accessToken: string; email?: string | null }): {
|
||||
accountId?: string;
|
||||
chatgptPlanType?: string;
|
||||
email?: string;
|
||||
profileName?: string;
|
||||
} {
|
||||
const payload = decodeCodexJwtPayload(params.accessToken);
|
||||
const auth = payload?.["https://api.openai.com/auth"];
|
||||
const accountId = trimNonEmptyString(auth?.chatgpt_account_id);
|
||||
const chatgptPlanType = trimNonEmptyString(auth?.chatgpt_plan_type);
|
||||
const email =
|
||||
trimNonEmptyString(payload?.["https://api.openai.com/profile"]?.email) ??
|
||||
trimNonEmptyString(params.email);
|
||||
const metadata = {
|
||||
...(accountId ? { accountId } : {}),
|
||||
...(chatgptPlanType ? { chatgptPlanType } : {}),
|
||||
};
|
||||
if (email) {
|
||||
return { email, profileName: email };
|
||||
return { ...metadata, email, profileName: email };
|
||||
}
|
||||
|
||||
const stableSubject = resolveCodexStableSubject(payload);
|
||||
if (!stableSubject) {
|
||||
return {};
|
||||
return metadata;
|
||||
}
|
||||
|
||||
return {
|
||||
...metadata,
|
||||
profileName: `id-${Buffer.from(stableSubject).toString("base64url")}`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -225,13 +225,13 @@ describe("openai codex provider", () => {
|
||||
access:
|
||||
"eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJodHRwczovL2FwaS5vcGVuYWkuY29tL2F1dGgiOnsiY2hhdGdwdF9hY2NvdW50X2lkIjoiYWNjdC1kZXZpY2UtMTIzIn19.signature",
|
||||
refresh: "device-refresh-token",
|
||||
accountId: "acct-device-123",
|
||||
},
|
||||
},
|
||||
],
|
||||
defaultModel: "openai-codex/gpt-5.5",
|
||||
});
|
||||
expect(result?.profiles[0]?.credential).not.toHaveProperty("idToken");
|
||||
expect(result?.profiles[0]?.credential).not.toHaveProperty("accountId");
|
||||
});
|
||||
|
||||
it("does not log the device pairing code in remote mode", async () => {
|
||||
|
||||
@@ -304,17 +304,33 @@ function withDefaultCodexContextMetadata(params: {
|
||||
};
|
||||
}
|
||||
|
||||
function buildCodexCredentialExtra(identity: {
|
||||
accountId?: string;
|
||||
chatgptPlanType?: string;
|
||||
}): Record<string, unknown> | undefined {
|
||||
const extra = {
|
||||
...(identity.accountId ? { accountId: identity.accountId } : {}),
|
||||
...(identity.chatgptPlanType ? { chatgptPlanType: identity.chatgptPlanType } : {}),
|
||||
};
|
||||
return Object.keys(extra).length > 0 ? extra : undefined;
|
||||
}
|
||||
|
||||
async function refreshOpenAICodexOAuthCredential(cred: OAuthCredential) {
|
||||
try {
|
||||
const { refreshOpenAICodexToken } = await import("./openai-codex-provider.runtime.js");
|
||||
const refreshed = await refreshOpenAICodexToken(cred.refresh);
|
||||
const identity = resolveCodexAuthIdentity({
|
||||
accessToken: refreshed.access,
|
||||
email: cred.email,
|
||||
});
|
||||
return {
|
||||
...cred,
|
||||
...refreshed,
|
||||
type: "oauth" as const,
|
||||
provider: PROVIDER_ID,
|
||||
email: cred.email,
|
||||
email: identity.email ?? cred.email,
|
||||
displayName: cred.displayName,
|
||||
...buildCodexCredentialExtra(identity),
|
||||
};
|
||||
} catch (error) {
|
||||
const message = formatErrorMessage(error);
|
||||
@@ -359,6 +375,7 @@ async function runOpenAICodexOAuth(ctx: ProviderAuthContext) {
|
||||
expires: creds.expires,
|
||||
email: identity.email,
|
||||
profileName: identity.profileName,
|
||||
credentialExtra: buildCodexCredentialExtra(identity),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -409,6 +426,7 @@ async function runOpenAICodexDeviceCode(ctx: ProviderAuthContext) {
|
||||
expires: creds.expires,
|
||||
email: identity.email,
|
||||
profileName: identity.profileName,
|
||||
credentialExtra: buildCodexCredentialExtra(identity),
|
||||
});
|
||||
} catch (error) {
|
||||
spin.stop("OpenAI device code failed");
|
||||
|
||||
Reference in New Issue
Block a user