fix(openai): scope websocket header timeout override
This commit is contained in:
parent
44208fcd09
commit
95252f66a6
4 changed files with 51 additions and 30 deletions
|
|
@ -358,13 +358,6 @@ export async function CodexAuthPlugin(input: PluginInput, options: CodexAuthPlug
|
|||
const websocketFetches: Array<ReturnType<typeof OpenAIWebSocketPool.createWebSocketFetch>> = []
|
||||
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -190,13 +190,13 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
|||
options: ok ? {} : { apiKey: "public" },
|
||||
}
|
||||
}),
|
||||
openai: () =>
|
||||
openai: (input) =>
|
||||
Effect.succeed({
|
||||
autoload: false,
|
||||
async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
|
||||
return sdk.responses(modelID)
|
||||
},
|
||||
options: { headerTimeout: OPENAI_HEADER_TIMEOUT_DEFAULT },
|
||||
options: { headerTimeout: input.options.headerTimeout ?? OPENAI_HEADER_TIMEOUT_DEFAULT },
|
||||
}),
|
||||
xai: () =>
|
||||
Effect.succeed({
|
||||
|
|
|
|||
|
|
@ -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<NonNullable<typeof hooks.config>>[0]
|
||||
const config = {} as Parameters<NonNullable<typeof hooks.config>>[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<typeof hooks.config>
|
||||
>[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 () => {
|
||||
|
|
|
|||
|
|
@ -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<string, unknown> = {}) {
|
||||
const config = testProviderConfig(url)
|
||||
return {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue