From 95252f66a61d0fa034bd4955a1520b5a4179a847 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 3 Jun 2026 16:15:59 -0500 Subject: [PATCH] fix(openai): scope websocket header timeout override --- packages/opencode/src/plugin/openai/codex.ts | 10 +---- packages/opencode/src/provider/provider.ts | 4 +- packages/opencode/test/plugin/codex.test.ts | 30 +++++---------- .../test/provider/header-timeout.test.ts | 37 +++++++++++++++++++ 4 files changed, 51 insertions(+), 30 deletions(-) diff --git a/packages/opencode/src/plugin/openai/codex.ts b/packages/opencode/src/plugin/openai/codex.ts index ac8610f36e..b55fa3dda8 100644 --- a/packages/opencode/src/plugin/openai/codex.ts +++ b/packages/opencode/src/plugin/openai/codex.ts @@ -358,13 +358,6 @@ export async function CodexAuthPlugin(input: PluginInput, options: CodexAuthPlug const websocketFetches: Array> = [] return { - async config(config) { - if (!options.experimentalWebSockets) return - config.provider ??= {} - config.provider.openai ??= {} - config.provider.openai.options ??= {} - config.provider.openai.options.headerTimeout ??= false - }, async dispose() { for (const websocketFetch of websocketFetches) websocketFetch.close() websocketFetches.length = 0 @@ -417,7 +410,7 @@ export async function CodexAuthPlugin(input: PluginInput, options: CodexAuthPlug websocketFetches.push(websocketFetch) websocketFetchInstalled = true } - if (auth.type !== "oauth") return websocketFetch ? { fetch: websocketFetch } : {} + if (auth.type !== "oauth") return websocketFetch ? { fetch: websocketFetch, headerTimeout: false } : {} let refreshPromise: | Promise<{ @@ -428,6 +421,7 @@ export async function CodexAuthPlugin(input: PluginInput, options: CodexAuthPlug return { apiKey: OAUTH_DUMMY_KEY, + ...(websocketFetch ? { headerTimeout: false } : {}), async fetch(requestInput: RequestInfo | URL, init?: RequestInit) { if (init?.headers) { if (init.headers instanceof Headers) { diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 2c50a9a60d..8620e1cc9f 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -190,13 +190,13 @@ function custom(dep: CustomDep): Record { options: ok ? {} : { apiKey: "public" }, } }), - openai: () => + openai: (input) => Effect.succeed({ autoload: false, async getModel(sdk: any, modelID: string, _options?: Record) { return sdk.responses(modelID) }, - options: { headerTimeout: OPENAI_HEADER_TIMEOUT_DEFAULT }, + options: { headerTimeout: input.options.headerTimeout ?? OPENAI_HEADER_TIMEOUT_DEFAULT }, }), xai: () => Effect.succeed({ diff --git a/packages/opencode/test/plugin/codex.test.ts b/packages/opencode/test/plugin/codex.test.ts index 0db149662a..02a83e8107 100644 --- a/packages/opencode/test/plugin/codex.test.ts +++ b/packages/opencode/test/plugin/codex.test.ts @@ -137,31 +137,21 @@ describe("plugin.codex", () => { expect(disabledOptions.fetch).toBeUndefined() expect(enabledOptions.fetch).toBeFunction() + expect(disabledOptions.headerTimeout).toBeUndefined() + expect(enabledOptions.headerTimeout).toBe(false) await enabled.dispose?.() }) - test("disables the HTTP header timeout when websocket transport is enabled", async () => { - const disabled = await CodexAuthPlugin({} as never) + test("disables the HTTP header timeout for websocket OAuth transport", async () => { const hooks = await CodexAuthPlugin({} as never, { experimentalWebSockets: true }) - const disabledConfig = {} as Parameters>[0] - const config = {} as Parameters>[0] + const options = await hooks.auth!.loader!( + async () => ({ type: "oauth", refresh: "refresh", access: "access", expires: Date.now() + 60_000 }) as never, + {} as never, + ) - await disabled.config!(disabledConfig) - await hooks.config!(config) - - expect(disabledConfig.provider).toBeUndefined() - expect(config.provider?.openai?.options?.headerTimeout).toBe(false) - }) - - test("preserves explicit header timeout configuration when websocket transport is enabled", async () => { - const hooks = await CodexAuthPlugin({} as never, { experimentalWebSockets: true }) - const config = { provider: { openai: { options: { headerTimeout: 30_000 } } } } as Parameters< - NonNullable - >[0] - - await hooks.config!(config) - - expect(config.provider.openai.options.headerTimeout).toBe(30_000) + expect(options.fetch).toBeFunction() + expect(options.headerTimeout).toBe(false) + await hooks.dispose?.() }) test("deduplicates concurrent Codex token refreshes", async () => { diff --git a/packages/opencode/test/provider/header-timeout.test.ts b/packages/opencode/test/provider/header-timeout.test.ts index a3caf4fd8a..7b8c81166c 100644 --- a/packages/opencode/test/provider/header-timeout.test.ts +++ b/packages/opencode/test/provider/header-timeout.test.ts @@ -11,6 +11,7 @@ import { Env } from "@/env" import { Plugin } from "@/plugin" import { Provider } from "@/provider/provider" import { ProviderError } from "@/provider/error" +import { RuntimeFlags } from "@/effect/runtime-flags" afterEach(async () => { await disposeAllInstances() @@ -169,6 +170,42 @@ it.live("OpenAI API auth gets default headerTimeout", () => }), ) +it.live("OpenAI Codex websocket transport disables default headerTimeout", () => + Effect.gen(function* () { + yield* withAuthContent( + Effect.gen(function* () { + yield* provideTmpdirInstance(() => + Effect.gen(function* () { + const provider = yield* Provider.Service + const openai = yield* provider.getProvider(ProviderV2.ID.openai) + expect(openai.options.headerTimeout).toBe(false) + }), + ) + }), + { openai: { type: "api", key: "sk-test" } }, + ) + }).pipe(Effect.provide(RuntimeFlags.layer({ experimentalWebSockets: true }))), +) + +it.live("OpenAI Codex websocket transport preserves configured headerTimeout", () => + Effect.gen(function* () { + yield* withAuthContent( + Effect.gen(function* () { + yield* provideTmpdirInstance( + () => + Effect.gen(function* () { + const provider = yield* Provider.Service + const openai = yield* provider.getProvider(ProviderV2.ID.openai) + expect(openai.options.headerTimeout).toBe(30_000) + }), + { config: { provider: { openai: { options: { headerTimeout: 30_000 } } } } }, + ) + }), + { openai: { type: "api", key: "sk-test" } }, + ) + }).pipe(Effect.provide(RuntimeFlags.layer({ experimentalWebSockets: true }))), +) + function providerConfig(url: string, options: Record = {}) { const config = testProviderConfig(url) return {