diff --git a/packages/opencode/src/plugin/openai/codex.ts b/packages/opencode/src/plugin/openai/codex.ts index d16b7495654c..99b8b400286f 100644 --- a/packages/opencode/src/plugin/openai/codex.ts +++ b/packages/opencode/src/plugin/openai/codex.ts @@ -340,6 +340,16 @@ export async function CodexAuthPlugin(input: PluginInput, options: CodexAuthPlug return { apiKey: OAUTH_DUMMY_KEY, async fetch(requestInput: RequestInfo | URL, init?: RequestInit) { + const currentAuth = await getAuth() + if (currentAuth?.type === "api") { + const headers = new Headers(init?.headers) + headers.set("authorization", `Bearer ${currentAuth.key}`) + const requestInit = { ...init, headers } + return websocketFetch ? websocketFetch(requestInput, requestInit) : fetch(requestInput, requestInit) + } + if (currentAuth?.type !== "oauth") + return websocketFetch ? websocketFetch(requestInput, init) : fetch(requestInput, init) + if (init?.headers) { if (init.headers instanceof Headers) { init.headers.delete("authorization") @@ -352,10 +362,6 @@ export async function CodexAuthPlugin(input: PluginInput, options: CodexAuthPlug } } - const currentAuth = await getAuth() - if (currentAuth.type !== "oauth") - return websocketFetch ? websocketFetch(requestInput, init) : fetch(requestInput, init) - const authWithAccount = currentAuth as typeof currentAuth & { accountId?: string } if (!currentAuth.access || currentAuth.expires < Date.now()) { diff --git a/packages/opencode/test/plugin/codex.test.ts b/packages/opencode/test/plugin/codex.test.ts index 1381c4ee8adb..8edc524607e2 100644 --- a/packages/opencode/test/plugin/codex.test.ts +++ b/packages/opencode/test/plugin/codex.test.ts @@ -7,6 +7,7 @@ import { renderOAuthError, type IdTokenClaims, } from "../../src/plugin/openai/codex" +import { OAUTH_DUMMY_KEY } from "../../src/auth" function createTestJwt(payload: object): string { const header = Buffer.from(JSON.stringify({ alg: "none" })).toString("base64url") @@ -149,6 +150,68 @@ describe("plugin.codex", () => { await enabled.dispose?.() }) + test("passes requests through when OAuth auth is removed after loading", async () => { + let auth: + | { + type: "oauth" + refresh: string + access: string + expires: number + } + | undefined = { + type: "oauth", + refresh: "refresh", + access: "access", + expires: Date.now() + 60_000, + } + using server = Bun.serve({ + port: 0, + fetch(request) { + return Response.json({ authorization: request.headers.get("authorization") }) + }, + }) + const hooks = await CodexAuthPlugin({} as never) + const loaded = await hooks.auth!.loader!(async () => auth as never, {} as never) + auth = undefined + + const response = await loaded.fetch!(server.url, { + headers: { authorization: `Bearer ${OAUTH_DUMMY_KEY}` }, + }) + + expect(await response.json()).toEqual({ authorization: `Bearer ${OAUTH_DUMMY_KEY}` }) + }) + + test("uses current API key when OAuth auth is replaced after loading", async () => { + let auth: + | { + type: "oauth" + refresh: string + access: string + expires: number + } + | { type: "api"; key: string } = { + type: "oauth", + refresh: "refresh", + access: "access", + expires: Date.now() + 60_000, + } + using server = Bun.serve({ + port: 0, + fetch(request) { + return Response.json({ authorization: request.headers.get("authorization") }) + }, + }) + const hooks = await CodexAuthPlugin({} as never) + const loaded = await hooks.auth!.loader!(async () => auth as never, {} as never) + auth = { type: "api", key: "sk-current" } + + const response = await loaded.fetch!(server.url, { + headers: { authorization: `Bearer ${OAUTH_DUMMY_KEY}` }, + }) + + expect(await response.json()).toEqual({ authorization: "Bearer sk-current" }) + }) + test("filters unsupported modes and uses Codex context limits for OAuth GPT models", async () => { const hooks = await CodexAuthPlugin({} as never) const limit = { context: 1_050_000, input: 922_000, output: 128_000 }