feat(codex): add GPT 5.3, fix API translation, add thinking levels

- Add GPT 5.3 Codex model with thinking level variants (none/low/medium/high/xhigh)
- Extract thinking level from model name suffix (e.g., gpt-5.3-codex-high)
- Fix Codex translation: preserve openai-responses format for Droid CLI
- Add effort level logging in request logs

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Hellodebasishsahu
2026-02-06 09:44:41 +07:00
committed by decolua
parent a7a52be2d4
commit 127475df84
4 changed files with 47 additions and 11 deletions

View File

@@ -10,11 +10,20 @@ export const PROVIDER_MODELS = {
{ id: "claude-haiku-4-5-20251001", name: "Claude 4.5 Haiku" },
],
cx: [ // OpenAI Codex
// GPT 5.3 Codex - all thinking levels
{ id: "gpt-5.3-codex", name: "GPT 5.3 Codex" },
{ id: "gpt-5.3-codex-xhigh", name: "GPT 5.3 Codex (xHigh)" },
{ id: "gpt-5.3-codex-high", name: "GPT 5.3 Codex (High)" },
{ id: "gpt-5.3-codex-low", name: "GPT 5.3 Codex (Low)" },
{ id: "gpt-5.3-codex-none", name: "GPT 5.3 Codex (None)" },
// Mini - medium and high only
{ id: "gpt-5.1-codex-mini", name: "GPT 5.1 Codex Mini" },
{ id: "gpt-5.1-codex-mini-high", name: "GPT 5.1 Codex Mini (High)" },
// Other models
{ id: "gpt-5.2-codex", name: "GPT 5.2 Codex" },
{ id: "gpt-5.2", name: "GPT 5.2" },
{ id: "gpt-5.1-codex-max", name: "GPT 5.1 Codex Max" },
{ id: "gpt-5.1-codex", name: "GPT 5.1 Codex" },
{ id: "gpt-5.1-codex-mini", name: "GPT 5.1 Codex Mini" },
{ id: "gpt-5.1", name: "GPT 5.1" },
{ id: "gpt-5-codex", name: "GPT 5 Codex" },
{ id: "gpt-5-codex-mini", name: "GPT 5 Codex Mini" },

View File

@@ -23,6 +23,26 @@ export class CodexExecutor extends BaseExecutor {
// Ensure store is false (Codex requirement)
body.store = false;
// Extract thinking level from model name suffix
// e.g., gpt-5.3-codex-high → high, gpt-5.3-codex → medium (default)
const effortLevels = ['none', 'low', 'medium', 'high', 'xhigh'];
let modelEffort = null;
for (const level of effortLevels) {
if (model.endsWith(`-${level}`)) {
modelEffort = level;
// Strip suffix from model name for actual API call
body.model = body.model.replace(`-${level}`, '');
break;
}
}
// Priority: explicit reasoning.effort > reasoning_effort param > model suffix > default (medium)
if (!body.reasoning) {
const effort = body.reasoning_effort || modelEffort || 'medium';
body.reasoning = { effort };
}
delete body.reasoning_effort;
// Remove unsupported parameters for Codex API
delete body.temperature;
delete body.top_p;

View File

@@ -468,16 +468,22 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
// Create transform stream with logger for streaming response
let transformStream;
// For Codex provider, always translate response from openai-responses to openai format
// This ensures clients like Cursor get the expected chat completions format
// BUT: skip translation if client already sent in openai-responses format (like Droid CLI)
const needsCodexTranslation = (provider === 'codex' || provider === 'openai') && targetFormat === 'openai-responses' && sourceFormat !== 'openai-responses';
if (needsCodexTranslation || needsTranslation(targetFormat, sourceFormat)) {
// For Codex, translate FROM openai-responses TO openai (client's expected format)
const responseSourceFormat = needsCodexTranslation ? 'openai-responses' : targetFormat;
const responseTargetFormat = needsCodexTranslation ? 'openai' : sourceFormat;
transformStream = createSSETransformStreamWithLogger(responseSourceFormat, responseTargetFormat, provider, reqLogger, toolNameMap, model, connectionId, body);
// For Codex provider, translate response from openai-responses to openai (Chat Completions) format
// UNLESS client originally sent in openai-responses format (like Droid CLI) - they expect same format back
const needsCodexTranslation = provider === 'codex'
&& targetFormat === 'openai-responses'
&& sourceFormat !== 'openai-responses';
if (needsCodexTranslation) {
// Codex returns openai-responses, translate to openai (Chat Completions) that clients expect
log?.debug?.("STREAM", `Codex translation mode: openai-responses → openai`);
transformStream = createSSETransformStreamWithLogger('openai-responses', 'openai', provider, reqLogger, toolNameMap, model, connectionId, body);
} else if (needsTranslation(targetFormat, sourceFormat)) {
// Standard translation for other providers
log?.debug?.("STREAM", `Translation mode: ${targetFormat}${sourceFormat}`);
transformStream = createSSETransformStreamWithLogger(targetFormat, sourceFormat, provider, reqLogger, toolNameMap, model, connectionId, body);
} else {
log?.debug?.("STREAM", `Standard passthrough mode`);
transformStream = createPassthroughStreamWithLogger(provider, reqLogger, model, connectionId, body);
}

View File

@@ -38,7 +38,8 @@ export async function handleChat(request, clientRawRequest = null) {
// Count messages (support both messages[] and input[] formats)
const msgCount = body.messages?.length || body.input?.length || 0;
const toolCount = body.tools?.length || 0;
log.request("POST", `${url.pathname} | ${modelStr} | ${msgCount} msgs${toolCount ? ` | ${toolCount} tools` : ""}`);
const effort = body.reasoning_effort || body.reasoning?.effort || null;
log.request("POST", `${url.pathname} | ${modelStr} | ${msgCount} msgs${toolCount ? ` | ${toolCount} tools` : ""}${effort ? ` | effort=${effort}` : ""}`);
// Log API key (masked)
const apiKey = request.headers.get("Authorization");