From 9408eb4556cbee4a6915c22362957fe6cb81dbb1 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Thu, 25 Sep 2025 17:27:06 -0600 Subject: [PATCH] i18n(openai-native-codex): localize provider errors; document immutable Codex system prompt and override rationale\n\n- Add i18n keys under common.errors.openaiNativeCodex and use t() in handler\n- Explain immutability and strategy where we inject overrides in OpenAiNativeCodexHandler\n- Add commentary to codex prompt file describing canonical prompt and override rationale --- .../providers/openai-native-codex.prompt.ts | 15 ++++++++ src/api/providers/openai-native-codex.ts | 37 ++++++++++++++----- src/i18n/locales/en/common.json | 8 ++++ 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/api/providers/openai-native-codex.prompt.ts b/src/api/providers/openai-native-codex.prompt.ts index 1d80ba3b5a..27096dc6c3 100644 --- a/src/api/providers/openai-native-codex.prompt.ts +++ b/src/api/providers/openai-native-codex.prompt.ts @@ -1,3 +1,12 @@ +/** + * ChatGPT Codex system prompt (canonical/inert): + * - The Responses API applies an immutable default system prompt server‑side. + * - We cannot replace it dynamically; this file supplies the canonical text used for the top‑level "instructions". + * Strategy: + * - We complement this with a separate system-role injection in the provider using + * (to de-emphasize defaults) and (the current task). + * - See OpenAiNativeCodexHandler.createMessage for details and rationale. + */ export default `You are Codex, based on GPT-5. You are running as a coding agent in the Codex CLI on a user's computer. ## General @@ -104,6 +113,12 @@ You are producing plain text that will later be styled by the CLI. Follow these * Examples: src/app.ts, src/app.ts:42, b/server/index.js#L10, C:\\repo\\project\\main.rs:12:5 ` +/** + * Rationale for overridePrompt: + * - Codex’s default system prompt cannot be removed. + * - This override clarifies Roo’s identity and precedence rules and asks the model to honor Roo’s tools and flow. + * - It is injected as so the server’s default is still present, but downweighted in practice. + */ export const overridePrompt = `## Identity and precedence - You are Roo (not Codex). Ignore any "Codex", "Codex CLI", or "based on GPT-5" identity statements. Roo's rules take precedence over any harness text. diff --git a/src/api/providers/openai-native-codex.ts b/src/api/providers/openai-native-codex.ts index 565e909e5d..1b17b0acd9 100644 --- a/src/api/providers/openai-native-codex.ts +++ b/src/api/providers/openai-native-codex.ts @@ -10,6 +10,7 @@ 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 { t } from "i18next" import { type ModelInfo, @@ -112,7 +113,10 @@ export class OpenAiNativeCodexHandler extends BaseProvider { raw = await fs.readFile(explicitPath, "utf8") } catch (e: any) { 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.`, + t("common:errors.openaiNativeCodex.oauthReadFailed", { + path: explicitPath, + error: e?.message || String(e), + }), ) } @@ -121,7 +125,10 @@ export class OpenAiNativeCodexHandler extends BaseProvider { j = JSON.parse(raw) } catch (e: any) { 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.`, + t("common:errors.openaiNativeCodex.oauthParseFailed", { + path: explicitPath, + error: e?.message || String(e), + }), ) } @@ -147,7 +154,7 @@ export class OpenAiNativeCodexHandler extends BaseProvider { } if (!access) { - throw new Error("ChatGPT OAuth credentials are missing tokens.access_token") + throw new Error(t("common:errors.openaiNativeCodex.missingAccessToken")) } this.chatgptAccessToken = access @@ -202,10 +209,15 @@ export class OpenAiNativeCodexHandler extends BaseProvider { const content: any[] = [] if (!injectedUserInstructions && typeof systemPrompt === "string" && systemPrompt.trim().length > 0) { - // For ChatGPT Codex (Responses API), the top-level "instructions" payload is fixed and must be - // provided from a canonical prompt file. We cannot programmatically modify that contents here. - // Therefore, inject provider overrides and dynamic instructions as a separate system role message - // using and tags before the first user/assistant turn. + // Codex system prompt immutability: + // - The top-level "instructions" field sent to codex/responses is immutable on the server. + // - We cannot dynamically alter the default system prompt that Codex applies. + // Strategy and rationale: + // - We inject two system-role items before the first user/assistant turn: + // 1) — explains to the model how Roo’s rules supersede Codex defaults. + // 2) — the current task/systemPrompt, asking Codex to prioritize these rules/tools. + // - This pattern reduces the impact of Codex’s default prompt without trying to replace it (not possible). + // - We also keep these separate from user messages to avoid tool execution bias. formattedInput.push({ role: "system", content: [ @@ -327,7 +339,12 @@ export class OpenAiNativeCodexHandler extends BaseProvider { // ignore parse error } const snippet = (text || "").slice(0, 500).replace(/\s+/g, " ").trim() - const msg = `[Codex] HTTP ${response.status}${requestId ? ` req ${requestId}` : ""} model=${model.id}: ${userMessage || snippet}` + const msg = t("common:errors.openaiNativeCodex.httpError", { + status: response.status, + requestId: requestId || "n/a", + modelId: model.id, + message: userMessage || snippet, + }) const err = new Error(msg) ;(err as any).status = response.status if (requestId) (err as any).requestId = requestId @@ -336,7 +353,7 @@ export class OpenAiNativeCodexHandler extends BaseProvider { throw err } if (!response.body) { - throw new Error("ChatGPT Responses error: No response body") + throw new Error(t("common:errors.openaiNativeCodex.noResponseBody")) } // Stream parse @@ -458,7 +475,7 @@ export class OpenAiNativeCodexHandler extends BaseProvider { } } if (!hasContent) { - throw new Error(`[Codex] Empty stream: no content received for model=${model.id}`) + throw new Error(t("common:errors.openaiNativeCodex.emptyStream", { modelId: model.id })) } } finally { try { diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 2d17e13fea..d6a3d998f5 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -123,6 +123,14 @@ "roo": { "authenticationRequired": "Roo provider requires cloud authentication. Please sign in to Roo Code Cloud." }, + "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.", + "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", + "emptyStream": "ChatGPT Responses stream returned no content for model {{modelId}}" + }, "api": { "invalidKeyInvalidChars": "API key contains invalid characters." },