Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 11cddf8

Browse files
committed
feat(openai-codex): profile-scoped oauth + email/limits
1 parent fa93109 commit 11cddf8

8 files changed

Lines changed: 321 additions & 106 deletions

File tree

packages/types/src/vscode-extension-host.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ export type ExtensionState = Pick<
403403
taskSyncEnabled: boolean
404404
featureRoomoteControlEnabled: boolean
405405
openAiCodexIsAuthenticated?: boolean
406+
openAiCodexAccountEmail?: string | null
406407
debug?: boolean
407408
}
408409

src/api/providers/openai-codex.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { isMcpTool } from "../../utils/mcp-name"
2626
import { sanitizeOpenAiCallId } from "../../utils/tool-id"
2727
import { openAiCodexOAuthManager } from "../../integrations/openai-codex/oauth"
2828
import { t } from "../../i18n"
29+
import { ContextProxy } from "../../core/config/ContextProxy"
2930

3031
export type OpenAiCodexModel = ReturnType<OpenAiCodexHandler["getModel"]>
3132

@@ -64,6 +65,20 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
6465
*/
6566
private pendingToolCallId: string | undefined
6667
private pendingToolCallName: string | undefined
68+
private resolveProfileId(): string | undefined {
69+
try {
70+
const contextProxy = ContextProxy.instance
71+
const currentApiConfigName = contextProxy.getValue("currentApiConfigName")
72+
const listApiConfigMeta = contextProxy.getValue("listApiConfigMeta")
73+
if (!Array.isArray(listApiConfigMeta)) {
74+
return undefined
75+
}
76+
const match = listApiConfigMeta.find((profile) => profile?.name === currentApiConfigName)
77+
return typeof match?.id === "string" ? match.id : undefined
78+
} catch {
79+
return undefined
80+
}
81+
}
6782

6883
// Event types handled by the shared event processor
6984
private readonly coreHandledEventTypes = new Set<string>([
@@ -151,7 +166,8 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
151166
this.pendingToolCallName = undefined
152167

153168
// Get access token from OAuth manager
154-
let accessToken = await openAiCodexOAuthManager.getAccessToken()
169+
const profileId = this.resolveProfileId()
170+
let accessToken = await openAiCodexOAuthManager.getAccessToken(profileId)
155171
if (!accessToken) {
156172
throw new Error(
157173
t("common:errors.openAiCodex.notAuthenticated", {
@@ -183,7 +199,7 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
183199

184200
if (attempt === 0 && isAuthFailure) {
185201
// Force refresh the token for retry
186-
const refreshed = await openAiCodexOAuthManager.forceRefreshAccessToken()
202+
const refreshed = await openAiCodexOAuthManager.forceRefreshAccessToken(profileId)
187203
if (!refreshed) {
188204
throw new Error(
189205
t("common:errors.openAiCodex.notAuthenticated", {
@@ -341,7 +357,7 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
341357
// is consistent across providers.
342358
try {
343359
// Get ChatGPT account ID for organization subscriptions
344-
const accountId = await openAiCodexOAuthManager.getAccountId()
360+
const accountId = await openAiCodexOAuthManager.getAccountId(this.resolveProfileId())
345361

346362
// Build Codex-specific headers. Authorization is provided by the SDK apiKey.
347363
const codexHeaders: Record<string, string> = {
@@ -481,7 +497,7 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
481497
const url = `${CODEX_API_BASE_URL}/responses`
482498

483499
// Get ChatGPT account ID for organization subscriptions
484-
const accountId = await openAiCodexOAuthManager.getAccountId()
500+
const accountId = await openAiCodexOAuthManager.getAccountId(this.resolveProfileId())
485501

486502
// Build headers with required Codex-specific fields
487503
const headers: Record<string, string> = {
@@ -1008,7 +1024,7 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
10081024
const model = this.getModel()
10091025

10101026
// Get access token
1011-
const accessToken = await openAiCodexOAuthManager.getAccessToken()
1027+
const accessToken = await openAiCodexOAuthManager.getAccessToken(this.resolveProfileId())
10121028
if (!accessToken) {
10131029
throw new Error(
10141030
t("common:errors.openAiCodex.notAuthenticated", {
@@ -1043,7 +1059,7 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
10431059
const url = `${CODEX_API_BASE_URL}/responses`
10441060

10451061
// Get ChatGPT account ID for organization subscriptions
1046-
const accountId = await openAiCodexOAuthManager.getAccountId()
1062+
const accountId = await openAiCodexOAuthManager.getAccountId(this.resolveProfileId())
10471063

10481064
// Build headers with required Codex-specific fields
10491065
const headers: Record<string, string> = {

src/core/webview/ClineProvider.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2105,6 +2105,21 @@ export class ClineProvider
21052105
const currentMode = mode ?? defaultModeSlug
21062106
const hasSystemPromptOverride = await this.hasFileBasedSystemPromptOverride(currentMode)
21072107

2108+
const openAiCodexProfileId = Array.isArray(listApiConfigMeta)
2109+
? listApiConfigMeta.find((profile) => profile.name === currentApiConfigName)?.id
2110+
: undefined
2111+
let openAiCodexIsAuthenticated = false
2112+
let openAiCodexAccountEmail: string | null = null
2113+
2114+
try {
2115+
const { openAiCodexOAuthManager } = await import("../../integrations/openai-codex/oauth")
2116+
openAiCodexIsAuthenticated = await openAiCodexOAuthManager.isAuthenticated(openAiCodexProfileId)
2117+
openAiCodexAccountEmail = await openAiCodexOAuthManager.getEmail(openAiCodexProfileId)
2118+
} catch {
2119+
openAiCodexIsAuthenticated = false
2120+
openAiCodexAccountEmail = null
2121+
}
2122+
21082123
return {
21092124
version: this.context.extension?.packageJSON?.version ?? "",
21102125
apiConfiguration,
@@ -2232,14 +2247,8 @@ export class ClineProvider
22322247
openRouterImageApiKey,
22332248
openRouterImageGenerationSelectedModel,
22342249
featureRoomoteControlEnabled,
2235-
openAiCodexIsAuthenticated: await (async () => {
2236-
try {
2237-
const { openAiCodexOAuthManager } = await import("../../integrations/openai-codex/oauth")
2238-
return await openAiCodexOAuthManager.isAuthenticated()
2239-
} catch {
2240-
return false
2241-
}
2242-
})(),
2250+
openAiCodexIsAuthenticated,
2251+
openAiCodexAccountEmail,
22432252
debug: vscode.workspace.getConfiguration(Package.name).get<boolean>("debug", false),
22442253
}
22452254
}

src/core/webview/webviewMessageHandler.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2385,14 +2385,16 @@ export const webviewMessageHandler = async (
23852385
case "openAiCodexSignIn": {
23862386
try {
23872387
const { openAiCodexOAuthManager } = await import("../../integrations/openai-codex/oauth")
2388-
const authUrl = openAiCodexOAuthManager.startAuthorizationFlow()
2388+
const { currentApiConfigName, listApiConfigMeta } = await provider.getState()
2389+
const profileId = listApiConfigMeta?.find((profile) => profile.name === currentApiConfigName)?.id
2390+
const authUrl = openAiCodexOAuthManager.startAuthorizationFlow(profileId)
23892391

23902392
// Open the authorization URL in the browser
23912393
await vscode.env.openExternal(vscode.Uri.parse(authUrl))
23922394

23932395
// Wait for the callback in a separate promise (non-blocking)
23942396
openAiCodexOAuthManager
2395-
.waitForCallback()
2397+
.waitForCallback(profileId)
23962398
.then(async () => {
23972399
vscode.window.showInformationMessage("Successfully signed in to OpenAI Codex")
23982400
await provider.postStateToWebview()
@@ -2412,7 +2414,9 @@ export const webviewMessageHandler = async (
24122414
case "openAiCodexSignOut": {
24132415
try {
24142416
const { openAiCodexOAuthManager } = await import("../../integrations/openai-codex/oauth")
2415-
await openAiCodexOAuthManager.clearCredentials()
2417+
const { currentApiConfigName, listApiConfigMeta } = await provider.getState()
2418+
const profileId = listApiConfigMeta?.find((profile) => profile.name === currentApiConfigName)?.id
2419+
await openAiCodexOAuthManager.clearCredentials(profileId)
24162420
vscode.window.showInformationMessage("Signed out from OpenAI Codex")
24172421
await provider.postStateToWebview()
24182422
} catch (error) {
@@ -3244,7 +3248,9 @@ export const webviewMessageHandler = async (
32443248
case "requestOpenAiCodexRateLimits": {
32453249
try {
32463250
const { openAiCodexOAuthManager } = await import("../../integrations/openai-codex/oauth")
3247-
const accessToken = await openAiCodexOAuthManager.getAccessToken()
3251+
const { currentApiConfigName, listApiConfigMeta } = await provider.getState()
3252+
const profileId = listApiConfigMeta?.find((profile) => profile.name === currentApiConfigName)?.id
3253+
const accessToken = await openAiCodexOAuthManager.getAccessToken(profileId)
32483254

32493255
if (!accessToken) {
32503256
provider.postMessageToWebview({
@@ -3254,7 +3260,7 @@ export const webviewMessageHandler = async (
32543260
break
32553261
}
32563262

3257-
const accountId = await openAiCodexOAuthManager.getAccountId()
3263+
const accountId = await openAiCodexOAuthManager.getAccountId(profileId)
32583264
const { fetchOpenAiCodexRateLimitInfo } = await import("../../integrations/openai-codex/rate-limits")
32593265
const rateLimits = await fetchOpenAiCodexRateLimitInfo(accessToken, { accountId })
32603266

0 commit comments

Comments
 (0)