fix(codex): address @daniel-lxs review — i18n settings, auth.json size guard, remove previous_response_id, avoid as-any reasoning mutation

This commit is contained in:
Hannes Rudolph 2025-09-26 00:02:34 -06:00
parent 297956a170
commit ccaed3debf
4 changed files with 59 additions and 34 deletions

View file

@ -1,6 +1,7 @@
import type { Anthropic } from "@anthropic-ai/sdk"
import { promises as fs } from "node:fs"
import os from "node:os"
import path from "node:path"
import type { ApiHandlerCreateMessageMetadata } from "../index"
import { BaseProvider } from "./base-provider"
@ -64,7 +65,6 @@ export class OpenAiNativeCodexHandler extends BaseProvider {
protected options: ApiHandlerOptions
private chatgptAccessToken!: string
private chatgptAccountId?: string
private lastResponseId: string | undefined
private lastServiceTier: ServiceTier | undefined
// Inline-loaded provider prompt (via esbuild text loader for .md files)
@ -134,18 +134,50 @@ export class OpenAiNativeCodexHandler extends BaseProvider {
const expandHome = (p: string) => p.replace(/^~(?=\/|\\|$)/, os.homedir())
const pathToUse = configured && configured.trim() ? configured.trim() : defaultPath
const explicitPath = expandHome(pathToUse)
const resolvedPath = path.resolve(explicitPath)
// Guard file size before reading to prevent loading unexpectedly large files
const MAX_OAUTH_SIZE = 1_000_000 // 1 MB
try {
const stat = await fs.stat(resolvedPath)
if (stat.size > MAX_OAUTH_SIZE) {
throw new Error(
t("common:errors.openaiNativeCodex.oauthFileTooLarge", {
path: resolvedPath,
size: stat.size,
max: MAX_OAUTH_SIZE,
}),
)
}
} catch (e: any) {
// Surface read failure with localized error (e.g., file missing or inaccessible)
const base = t("common:errors.openaiNativeCodex.oauthReadFailed", {
path: resolvedPath,
error: e?.message || String(e),
})
throw new Error(base)
}
let raw: string
try {
raw = await fs.readFile(explicitPath, "utf8")
raw = await fs.readFile(resolvedPath, "utf8")
} catch (e: any) {
const base = t("common:errors.openaiNativeCodex.oauthReadFailed", {
path: explicitPath,
path: resolvedPath,
error: e?.message || String(e),
})
const tip =
" Tip: Authenticate with the Codex CLI to generate auth.json (defaults to ~/.codex/auth.json), then retry."
throw new Error(base + tip)
throw new Error(base)
}
// Post-read size check using byte length
if (Buffer.byteLength(raw, "utf8") > MAX_OAUTH_SIZE) {
throw new Error(
t("common:errors.openaiNativeCodex.oauthFileTooLarge", {
path: resolvedPath,
size: Buffer.byteLength(raw, "utf8"),
max: MAX_OAUTH_SIZE,
}),
)
}
let j: AuthJson
@ -153,12 +185,10 @@ export class OpenAiNativeCodexHandler extends BaseProvider {
j = JSON.parse(raw) as AuthJson
} catch (e: any) {
const base = t("common:errors.openaiNativeCodex.oauthParseFailed", {
path: explicitPath,
path: resolvedPath,
error: e?.message || String(e),
})
const tip =
" Tip: Ensure the file is valid JSON or re-authenticate via the Codex CLI to regenerate auth.json."
throw new Error(base + tip)
throw new Error(base)
}
const tokens: AuthTokens = j?.tokens ?? {}
@ -210,21 +240,10 @@ export class OpenAiNativeCodexHandler extends BaseProvider {
settings: this.options,
})
const effort =
(this.options.reasoningEffort as ReasoningEffortWithMinimal | undefined) ??
(info.reasoningEffort as ReasoningEffortWithMinimal | undefined)
if (effort) {
;(params.reasoning as any) = { reasoning_effort: effort }
}
// Reasoning effort is computed by getModelParams based on model + settings
return { id: id as string, info, ...params, verbosity: params.verbosity }
}
// Expose last response id for conversation continuity consumers (e.g., Task.persistGpt5Metadata)
getLastResponseId(): string | undefined {
return this.lastResponseId
}
override async *createMessage(
systemPrompt: string,
messages: Anthropic.Messages.MessageParam[],
@ -434,10 +453,7 @@ export class OpenAiNativeCodexHandler extends BaseProvider {
}
try {
const parsed = JSON.parse(data)
// Persist ids/tier when available (parity with openai-native)
if (parsed.response?.id) {
this.lastResponseId = parsed.response.id
}
// Persist tier when available (parity with openai-native)
if (parsed.response?.service_tier) {
this.lastServiceTier = parsed.response.service_tier as ServiceTier
}

View file

@ -126,6 +126,7 @@
"openaiNativeCodex": {
"oauthReadFailed": "Failed to load ChatGPT OAuth credentials at {{path}}: {{error}}. Tip: authenticate with the Codex CLI (e.g., \"codex login\") to create auth.json.",
"oauthParseFailed": "Failed to parse ChatGPT OAuth credentials JSON at {{path}}: {{error}}. Tip: ensure the file is valid JSON or re-authenticate with \"codex login\" to regenerate it.",
"oauthFileTooLarge": "OAuth credentials file at {{path}} is too large ({{size}} bytes). Maximum allowed is {{max}} bytes.",
"missingAccessToken": "ChatGPT OAuth credentials are missing tokens.access_token.",
"httpError": "Codex HTTP {{status}} (req: {{requestId}}) model={{modelId}}: {{message}}",
"noResponseBody": "ChatGPT Responses error: No response body",

View file

@ -1,6 +1,7 @@
import React from "react"
import { VSCodeTextField, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { type ProviderSettings } from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
interface OpenAiNativeCodexProps {
apiConfiguration: ProviderSettings
@ -8,6 +9,7 @@ interface OpenAiNativeCodexProps {
}
export const OpenAiNativeCodex: React.FC<OpenAiNativeCodexProps> = ({ apiConfiguration, setApiConfigurationField }) => {
const { t } = useAppTranslation()
const defaultPath = "~/.codex/auth.json"
const handleInputChange = (e: Event | React.FormEvent<HTMLElement>) => {
@ -24,28 +26,27 @@ export const OpenAiNativeCodex: React.FC<OpenAiNativeCodexProps> = ({ apiConfigu
type="text"
onInput={handleInputChange}
placeholder={defaultPath}>
OAuth Credentials Path
<label className="block font-medium mb-1">
{t("settings:providers.openAiNativeCodex.oauthPathLabel")}
</label>
</VSCodeTextField>
<p className="text-xs mt-1 text-vscode-descriptionForeground">
Path to your ChatGPT Codex auth.json credentials. Defaults to ~/.codex/auth.json if left empty
(Windows: C:\\Users\\USERNAME\\.codex\\auth.json).
{t("settings:providers.openAiNativeCodex.oauthPathDescription", { defaultPath })}
</p>
<div className="text-xs text-vscode-descriptionForeground mt-3">
ChatGPT Codex uses your ChatGPT web credentials via the official Codex CLI. Authenticate with the
Codex CLI so that auth.json is created. If you use a custom location, set the full file path here.
{t("settings:providers.openAiNativeCodex.oauthCliDescription")}
</div>
<div className="text-xs text-vscode-descriptionForeground mt-2">
After authentication, Roo will read the access token from auth.json and connect to ChatGPT Responses
(Codex).
{t("settings:providers.openAiNativeCodex.oauthConnectDescription")}
</div>
<VSCodeLink
href="https://chatgpt.com"
className="text-vscode-textLink-foreground mt-2 inline-block text-xs">
Learn more about ChatGPT
{t("settings:providers.openAiNativeCodex.learnMoreLinkText")}
</VSCodeLink>
</div>
</div>

View file

@ -449,6 +449,13 @@
"authenticatedMessage": "Securely authenticated through your Roo Code Cloud account.",
"connectButton": "Connect to Roo Code Cloud"
},
"openAiNativeCodex": {
"oauthPathLabel": "OAuth Credentials Path",
"oauthPathDescription": "Path to your ChatGPT Codex auth.json credentials. Defaults to {{defaultPath}} if left empty (Windows: C:\\\\Users\\\\USERNAME\\\\.codex\\\\auth.json).",
"oauthCliDescription": "ChatGPT Codex uses your ChatGPT web credentials via the official Codex CLI. Authenticate with the Codex CLI so that auth.json is created. If you use a custom location, set the full file path here.",
"oauthConnectDescription": "After authentication, Roo will read the access token from auth.json and connect to ChatGPT Responses (Codex).",
"learnMoreLinkText": "Learn more about ChatGPT"
},
"openRouter": {
"providerRouting": {
"title": "OpenRouter Provider Routing",