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

This commit is contained in:
Hannes Rudolph 2025-09-25 17:27:06 -06:00
parent 43c9f576c7
commit 9408eb4556
3 changed files with 50 additions and 10 deletions

View file

@ -1,3 +1,12 @@
/**
* ChatGPT Codex system prompt (canonical/inert):
* - The Responses API applies an immutable default system prompt serverside.
* - We cannot replace it dynamically; this file supplies the canonical text used for the toplevel "instructions".
* Strategy:
* - We complement this with a separate system-role injection in the provider using
* <instructions_override> (to de-emphasize defaults) and <new_instructions> (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:
* - Codexs default system prompt cannot be removed.
* - This override clarifies Roos identity and precedence rules and asks the model to honor Roos tools and flow.
* - It is injected as <instructions_override> so the servers 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.

View file

@ -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 <instructions_override> and <new_instructions> 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) <instructions_override> — explains to the model how Roos rules supersede Codex defaults.
// 2) <new_instructions> — the current task/systemPrompt, asking Codex to prioritize these rules/tools.
// - This pattern reduces the impact of Codexs 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 {

View file

@ -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."
},