mirror of
https://github.com/decolua/9router.git
synced 2026-05-08 12:01:28 +00:00
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:
committed by
decolua
parent
a7a52be2d4
commit
127475df84
@@ -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" },
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user