From d7d5dc90bccdcdf80eaa94a26254f148a65bca68 Mon Sep 17 00:00:00 2001 From: apple-techie <46746496+apple-techie@users.noreply.github.com> Date: Thu, 12 Feb 2026 18:12:17 +0700 Subject: [PATCH] fix: update Codex executor for gpt-5.3-codex support Co-authored-by: Cursor --- open-sse/config/constants.js | 5 ++- open-sse/executors/codex.js | 26 +++++++++++++- .../translator/response/openai-responses.js | 36 ++++++++++++++++--- open-sse/utils/stream.js | 2 +- src/app/api/providers/[id]/test/route.js | 5 +-- 5 files changed, 60 insertions(+), 14 deletions(-) diff --git a/open-sse/config/constants.js b/open-sse/config/constants.js index 2da1f6b..f1dde12 100644 --- a/open-sse/config/constants.js +++ b/open-sse/config/constants.js @@ -39,9 +39,8 @@ export const PROVIDERS = { baseUrl: "https://chatgpt.com/backend-api/codex/responses", format: "openai-responses", // Use OpenAI Responses API format (reuse translator) headers: { - "Version": "0.92.0", - "Openai-Beta": "responses=experimental", - "User-Agent": "codex-cli/0.92.0 (Windows 10.0.26100; x64)" + "originator": "codex-cli", + "User-Agent": "codex-cli/1.0.18 (macOS; arm64)" }, // OpenAI OAuth configuration clientId: "app_EMoamEEZ73f0CkXaXp7hrann", diff --git a/open-sse/executors/codex.js b/open-sse/executors/codex.js index dd65fd4..0814e4f 100644 --- a/open-sse/executors/codex.js +++ b/open-sse/executors/codex.js @@ -11,10 +11,27 @@ export class CodexExecutor extends BaseExecutor { super("codex", PROVIDERS.codex); } + /** + * Override headers to add session_id per request + */ + buildHeaders(credentials, stream = true) { + const headers = super.buildHeaders(credentials, stream); + headers["session_id"] = `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`; + return headers; + } + /** * Transform request before sending - inject default instructions if missing */ transformRequest(model, body, stream, credentials) { + // Ensure input is present and non-empty (Codex API rejects empty input) + if (!body.input || (Array.isArray(body.input) && body.input.length === 0)) { + body.input = [{ type: "message", role: "user", content: [{ type: "input_text", text: "..." }] }]; + } + + // Ensure streaming is enabled (Codex API requires it) + body.stream = true; + // If no instructions provided, inject default Codex instructions if (!body.instructions || body.instructions.trim() === "") { body.instructions = CODEX_DEFAULT_INSTRUCTIONS; @@ -39,10 +56,17 @@ export class CodexExecutor extends BaseExecutor { // Priority: explicit reasoning.effort > reasoning_effort param > model suffix > default (medium) if (!body.reasoning) { const effort = body.reasoning_effort || modelEffort || 'medium'; - body.reasoning = { effort }; + body.reasoning = { effort, summary: "auto" }; + } else if (!body.reasoning.summary) { + body.reasoning.summary = "auto"; } delete body.reasoning_effort; + // Include reasoning encrypted content (required by Codex backend for reasoning models) + if (body.reasoning && body.reasoning.effort && body.reasoning.effort !== 'none') { + body.include = ["reasoning.encrypted_content"]; + } + // Remove unsupported parameters for Codex API delete body.temperature; delete body.top_p; diff --git a/open-sse/translator/response/openai-responses.js b/open-sse/translator/response/openai-responses.js index e1455d6..e0985e5 100644 --- a/open-sse/translator/response/openai-responses.js +++ b/open-sse/translator/response/openai-responses.js @@ -368,7 +368,7 @@ export function openaiResponsesToOpenAIResponse(chunk, state) { id: state.chatId || `chatcmpl-${Date.now()}`, object: "chat.completion.chunk", created: state.created || Math.floor(Date.now() / 1000), - model: state.model || "gpt-4", + model: state.model || "unknown", choices: [{ index: 0, delta: {}, @@ -401,7 +401,7 @@ export function openaiResponsesToOpenAIResponse(chunk, state) { id: state.chatId, object: "chat.completion.chunk", created: state.created, - model: state.model || "gpt-4", + model: state.model || "unknown", choices: [{ index: 0, delta: { content: delta }, @@ -424,7 +424,7 @@ export function openaiResponsesToOpenAIResponse(chunk, state) { id: state.chatId, object: "chat.completion.chunk", created: state.created, - model: state.model || "gpt-4", + model: state.model || "unknown", choices: [{ index: 0, delta: { @@ -452,7 +452,7 @@ export function openaiResponsesToOpenAIResponse(chunk, state) { id: state.chatId, object: "chat.completion.chunk", created: state.created, - model: state.model || "gpt-4", + model: state.model || "unknown", choices: [{ index: 0, delta: { @@ -511,7 +511,7 @@ export function openaiResponsesToOpenAIResponse(chunk, state) { id: state.chatId, object: "chat.completion.chunk", created: state.created, - model: state.model || "gpt-4", + model: state.model || "unknown", choices: [{ index: 0, delta: {}, @@ -529,6 +529,32 @@ export function openaiResponsesToOpenAIResponse(chunk, state) { return null; } + // Error events from Responses API (e.g. model_not_found) + if (eventType === "error" || eventType === "response.failed") { + // Avoid emitting duplicate errors (error + response.failed arrive back-to-back) + if (state.finishReasonSent) return null; + + const error = data.error || data.response?.error; + if (error) { + state.error = error; + state.finishReasonSent = true; + + // Surface the error as an OpenAI-compatible error chunk + return { + id: state.chatId || `chatcmpl-${Date.now()}`, + object: "chat.completion.chunk", + created: state.created || Math.floor(Date.now() / 1000), + model: state.model || "unknown", + choices: [{ + index: 0, + delta: { content: `[Error] ${error.message || JSON.stringify(error)}` }, + finish_reason: "stop" + }] + }; + } + return null; + } + // Reasoning events (convert to content or skip) if (eventType === "response.reasoning_summary_text.delta") { // Optionally include reasoning as content, or skip diff --git a/open-sse/utils/stream.js b/open-sse/utils/stream.js index ed2a278..bcfff89 100644 --- a/open-sse/utils/stream.js +++ b/open-sse/utils/stream.js @@ -49,7 +49,7 @@ export function createSSEStream(options = {}) { let buffer = ""; let usage = null; - const state = mode === STREAM_MODE.TRANSLATE ? { ...initState(sourceFormat), provider, toolNameMap } : null; + const state = mode === STREAM_MODE.TRANSLATE ? { ...initState(sourceFormat), provider, toolNameMap, model } : null; let totalContentLength = 0; let accumulatedContent = ""; diff --git a/src/app/api/providers/[id]/test/route.js b/src/app/api/providers/[id]/test/route.js index cf4f90d..26abcf0 100644 --- a/src/app/api/providers/[id]/test/route.js +++ b/src/app/api/providers/[id]/test/route.js @@ -17,10 +17,7 @@ const OAUTH_TEST_CONFIG = { checkExpiry: true, }, codex: { - url: "https://api.openai.com/v1/models", - method: "GET", - authHeader: "Authorization", - authPrefix: "Bearer ", + checkExpiry: true, refreshable: true, }, "gemini-cli": {