From 43c9f576c70f1ddb602608cca068b1c616468d81 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Thu, 25 Sep 2025 17:06:00 -0600 Subject: [PATCH] feat(openai-native-codex): remove service tier logic; stop forcing minimal effort; add request timeout; improve auth.json error guidance; update ChatGPT link; clear reasoningEffort on model change\n\n- Remove service_tier handling entirely for Codex (server decides tier)\n- Do not auto-default GPT-5 to minimal for Codex; use model/user defaults only\n- Add AbortController using getApiRequestTimeout() to prevent hanging requests\n- Improve auth.json error messages with Codex CLI guidance\n- Update settings link to chatgpt.com\n- Clear reasoningEffort on model change for Codex like native OpenAI --- src/api/providers/openai-native-codex.ts | 30 +++++++++++-------- .../src/components/settings/ApiOptions.tsx | 7 +++-- .../components/settings/ThinkingBudget.tsx | 15 +++------- .../settings/providers/OpenAiNativeCodex.tsx | 2 +- 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/api/providers/openai-native-codex.ts b/src/api/providers/openai-native-codex.ts index 3e2228b357..565e909e5d 100644 --- a/src/api/providers/openai-native-codex.ts +++ b/src/api/providers/openai-native-codex.ts @@ -9,6 +9,7 @@ import { ApiStream, ApiStreamUsageChunk } from "../transform/stream" import { getModelParams } from "../transform/model-params" // Provider prompt content as a TS string module (no loader required) import codexPromptContent, { overridePrompt } from "./openai-native-codex.prompt" +import { getApiRequestTimeout } from "./utils/timeout-config" import { type ModelInfo, @@ -110,14 +111,18 @@ export class OpenAiNativeCodexHandler extends BaseProvider { try { raw = await fs.readFile(explicitPath, "utf8") } catch (e: any) { - throw new Error(`Failed to load ChatGPT OAuth credentials at ${explicitPath}: ${e?.message || e}`) + throw new Error( + `Failed to load ChatGPT OAuth credentials at ${explicitPath}: ${e?.message || e}. Tip: authenticate with the Codex CLI (e.g., "codex login") to create auth.json.`, + ) } let j: any try { j = JSON.parse(raw) } catch (e: any) { - throw new Error(`Failed to parse ChatGPT OAuth credentials JSON at ${explicitPath}: ${e?.message || e}`) + throw new Error( + `Failed to parse ChatGPT OAuth credentials JSON at ${explicitPath}: ${e?.message || e}. Tip: ensure the file is valid JSON or re-authenticate with "codex login" to regenerate it.`, + ) } const tokens = (j?.tokens as any) || {} @@ -263,13 +268,7 @@ export class OpenAiNativeCodexHandler extends BaseProvider { // - Regular "gpt-5" should default to minimal reasoning unless explicitly overridden in settings. // - The "gpt-5-codex" variant should NOT force minimal; use provided/default effort. let effectiveEffort: ReasoningEffortWithMinimal | undefined = reasoningEffort - const explicitEffortProvided = typeof (this.options.reasoningEffort as any) === "string" - if (!explicitEffortProvided && model.id === "gpt-5") { - effectiveEffort = "minimal" - } - const requestedTier = (this.options.openAiNativeServiceTier as ServiceTier | undefined) || undefined - const allowedTierNames = new Set(model.info.tiers?.map((t) => t.name).filter(Boolean) || []) const body: any = { model: model.id, input: formattedInput, @@ -286,9 +285,6 @@ export class OpenAiNativeCodexHandler extends BaseProvider { }), // ChatGPT codex/responses does not support previous_response_id (stateless). // Preserve continuity by sending curated prior items in `input`. - ...(requestedTier && (requestedTier === "default" || allowedTierNames.has(requestedTier)) - ? { service_tier: requestedTier } - : {}), } if (model.info.supportsVerbosity === true) { body.text = { verbosity: (verbosity || "medium") as VerbosityLevel } @@ -307,11 +303,16 @@ export class OpenAiNativeCodexHandler extends BaseProvider { } if (this.chatgptAccountId) headers["chatgpt-account-id"] = this.chatgptAccountId + let timeoutId: ReturnType | undefined try { + const timeoutMs = getApiRequestTimeout() + const controller = new AbortController() + timeoutId = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : undefined const response = await fetch(url, { method: "POST", headers, body: JSON.stringify(requestBody), + signal: controller.signal, }) if (!response.ok) { @@ -468,7 +469,12 @@ export class OpenAiNativeCodexHandler extends BaseProvider { } catch (err) { throw err as Error } finally { - // no-op + // Clear timeout if set + try { + if (typeof timeoutId !== "undefined") { + clearTimeout(timeoutId as any) + } + } catch {} } } } diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 28c21b1c1b..3b93bf4700 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -788,8 +788,11 @@ const ApiOptions = ({ } // Clear reasoning effort when switching models to allow the new model's default to take effect - // This is especially important for GPT-5 models which default to "medium" - if (selectedProvider === "openai-native") { + // Applies to both OpenAI Native and ChatGPT Codex providers + if ( + selectedProvider === "openai-native" || + selectedProvider === "openai-native-codex" + ) { setApiConfigurationField("reasoningEffort", undefined) } }}> diff --git a/webview-ui/src/components/settings/ThinkingBudget.tsx b/webview-ui/src/components/settings/ThinkingBudget.tsx index 6edaaceb24..65bb57c9c9 100644 --- a/webview-ui/src/components/settings/ThinkingBudget.tsx +++ b/webview-ui/src/components/settings/ThinkingBudget.tsx @@ -116,18 +116,11 @@ export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, mod ? (["disable", ...baseAvailableOptions] as ReasoningEffortOption[]) : (baseAvailableOptions as ReadonlyArray) - // Default reasoning effort - use model's default if available - // Special-case for ChatGPT Codex "gpt-5": default to "minimal" unless user overrides + // Default reasoning effort - use model's default if available (no special-case overrides) const modelDefaultReasoningEffort = modelInfo?.reasoningEffort as ReasoningEffortWithMinimal | undefined - // Special-case for ChatGPT Codex "gpt-5": default to "minimal" unless user overrides - const defaultReasoningEffort: ReasoningEffortOption = - apiConfiguration.apiProvider === "openai-native-codex" && - selectedModelId === "gpt-5" && - isReasoningEffortSupported - ? "minimal" - : modelInfo?.requiredReasoningEffort - ? modelDefaultReasoningEffort || "medium" - : "disable" + const defaultReasoningEffort: ReasoningEffortOption = modelInfo?.requiredReasoningEffort + ? modelDefaultReasoningEffort || "medium" + : "disable" // Current reasoning effort from settings, or fall back to default const storedReasoningEffort = apiConfiguration.reasoningEffort as ReasoningEffortOption | undefined const currentReasoningEffort: ReasoningEffortOption = storedReasoningEffort || defaultReasoningEffort diff --git a/webview-ui/src/components/settings/providers/OpenAiNativeCodex.tsx b/webview-ui/src/components/settings/providers/OpenAiNativeCodex.tsx index 13de146fd2..1ab874ad0a 100644 --- a/webview-ui/src/components/settings/providers/OpenAiNativeCodex.tsx +++ b/webview-ui/src/components/settings/providers/OpenAiNativeCodex.tsx @@ -43,7 +43,7 @@ export const OpenAiNativeCodex: React.FC = ({ apiConfigu Learn more about ChatGPT