mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 22:32:12 +03:00
@@ -140,6 +140,16 @@ const KIMI_CODING_SHARED = {
|
||||
const buildModels = (ids: readonly string[]): RegistryModel[] =>
|
||||
ids.map((id) => ({ id, name: id }));
|
||||
|
||||
const GPT_5_5_CONTEXT_LENGTH = 1050000;
|
||||
const GPT_5_5_CODEX_CAPABILITIES = {
|
||||
targetFormat: "openai-responses",
|
||||
toolCalling: true,
|
||||
supportsReasoning: true,
|
||||
supportsVision: true,
|
||||
supportsXHighEffort: true,
|
||||
contextLength: GPT_5_5_CONTEXT_LENGTH,
|
||||
} as const;
|
||||
|
||||
const CHAT_OPENAI_COMPAT_MODELS: Record<string, RegistryModel[]> = {
|
||||
deepinfra: buildModels([
|
||||
"Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
||||
@@ -373,10 +383,10 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
},
|
||||
models: [
|
||||
{ id: "codex-auto-review", name: "Codex Auto Review", targetFormat: "openai-responses" },
|
||||
{ id: "gpt-5.5-xhigh", name: "GPT 5.5 (xHigh)", targetFormat: "openai-responses" },
|
||||
{ id: "gpt-5.5-high", name: "GPT 5.5 (High)", targetFormat: "openai-responses" },
|
||||
{ id: "gpt-5.5", name: "GPT 5.5 (Medium)", targetFormat: "openai-responses" },
|
||||
{ id: "gpt-5.5-low", name: "GPT 5.5 (Low)", targetFormat: "openai-responses" },
|
||||
{ id: "gpt-5.5-xhigh", name: "GPT 5.5 (xHigh)", ...GPT_5_5_CODEX_CAPABILITIES },
|
||||
{ id: "gpt-5.5-high", name: "GPT 5.5 (High)", ...GPT_5_5_CODEX_CAPABILITIES },
|
||||
{ id: "gpt-5.5", name: "GPT 5.5 (Medium)", ...GPT_5_5_CODEX_CAPABILITIES },
|
||||
{ id: "gpt-5.5-low", name: "GPT 5.5 (Low)", ...GPT_5_5_CODEX_CAPABILITIES },
|
||||
{ id: "gpt-5.5-mini", name: "GPT 5.5 Mini", targetFormat: "openai-responses" },
|
||||
{ id: "gpt-5.4", name: "GPT 5.4", targetFormat: "openai-responses" },
|
||||
{ id: "gpt-5.4-mini", name: "GPT 5.4 Mini", targetFormat: "openai-responses" },
|
||||
|
||||
@@ -202,6 +202,26 @@ type EffortLevel = (typeof EFFORT_ORDER)[number];
|
||||
const CODEX_FAST_WIRE_VALUE = "priority";
|
||||
const CODEX_RESPONSES_WS_URL = "wss://chatgpt.com/backend-api/codex/responses";
|
||||
|
||||
function splitCodexReasoningSuffix(model: unknown): {
|
||||
baseModel: string;
|
||||
effort: EffortLevel | null;
|
||||
} {
|
||||
const modelId = typeof model === "string" ? model : "";
|
||||
for (const level of EFFORT_ORDER) {
|
||||
if (modelId.endsWith(`-${level}`)) {
|
||||
return {
|
||||
baseModel: modelId.slice(0, -`-${level}`.length),
|
||||
effort: level,
|
||||
};
|
||||
}
|
||||
}
|
||||
return { baseModel: modelId, effort: null };
|
||||
}
|
||||
|
||||
export function getCodexUpstreamModel(model: unknown): string {
|
||||
return splitCodexReasoningSuffix(model).baseModel;
|
||||
}
|
||||
|
||||
function stringifyCodexInstructionContent(content: unknown): string {
|
||||
if (typeof content === "string") {
|
||||
return content.trim();
|
||||
@@ -469,10 +489,8 @@ function consumeResponsesStoreMarker(body: Record<string, unknown>): unknown {
|
||||
return marker;
|
||||
}
|
||||
|
||||
function isCodexResponsesWebSocketRequired(model: string, credentials: unknown): boolean {
|
||||
const normalizedModel = String(model || "")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
export function isCodexResponsesWebSocketRequired(model: string, credentials: unknown): boolean {
|
||||
const normalizedModel = getCodexUpstreamModel(model).trim().toLowerCase();
|
||||
if (normalizedModel === "gpt-5.5") return true;
|
||||
const providerSpecificData =
|
||||
credentials && typeof credentials === "object"
|
||||
@@ -626,7 +644,7 @@ export class CodexExecutor extends BaseExecutor {
|
||||
true,
|
||||
input.credentials
|
||||
)) as Record<string, unknown>;
|
||||
transformedBody.model = input.model;
|
||||
transformedBody.model = getCodexUpstreamModel(transformedBody.model || input.model);
|
||||
delete transformedBody.stream;
|
||||
delete transformedBody.stream_options;
|
||||
|
||||
@@ -988,16 +1006,13 @@ export class CodexExecutor extends BaseExecutor {
|
||||
delete body.messages;
|
||||
delete body.prompt;
|
||||
|
||||
const effortLevels = ["none", "low", "medium", "high", "xhigh"];
|
||||
let modelEffort: string | null = null;
|
||||
let cleanModel = typeof body.model === "string" ? body.model : model;
|
||||
for (const level of effortLevels) {
|
||||
if (typeof cleanModel === "string" && cleanModel.endsWith(`-${level}`)) {
|
||||
modelEffort = level;
|
||||
body.model = cleanModel.slice(0, -`-${level}`.length);
|
||||
cleanModel = body.model;
|
||||
break;
|
||||
}
|
||||
const splitModel = splitCodexReasoningSuffix(cleanModel);
|
||||
if (splitModel.effort) {
|
||||
modelEffort = splitModel.effort;
|
||||
body.model = splitModel.baseModel;
|
||||
cleanModel = body.model;
|
||||
}
|
||||
|
||||
const explicitReasoning = normalizeEffortValue(body?.reasoning?.effort);
|
||||
|
||||
@@ -18,6 +18,23 @@ export interface ModelSpec {
|
||||
}
|
||||
|
||||
export const MODEL_SPECS: Record<string, ModelSpec> = {
|
||||
"gpt-5.5-mini": {
|
||||
maxOutputTokens: 128000,
|
||||
contextWindow: 400000,
|
||||
supportsThinking: true,
|
||||
supportsTools: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
|
||||
"gpt-5.5": {
|
||||
maxOutputTokens: 128000,
|
||||
contextWindow: 1050000,
|
||||
supportsThinking: true,
|
||||
supportsTools: true,
|
||||
supportsVision: true,
|
||||
aliases: ["gpt-5.5-xhigh", "gpt-5.5-high", "gpt-5.5-medium", "gpt-5.5-low", "gpt-5.5-none"],
|
||||
},
|
||||
|
||||
// ── Gemini 3 Flash series ───────────────────────────────────────
|
||||
"gemini-3-flash": {
|
||||
maxOutputTokens: 65536,
|
||||
|
||||
@@ -11,6 +11,14 @@ const GPT_5_3_CODEX_PRICING = {
|
||||
cache_creation: 5.0,
|
||||
};
|
||||
|
||||
const GPT_5_5_PRICING = {
|
||||
input: 5.0,
|
||||
output: 30.0,
|
||||
cached: 0.5,
|
||||
reasoning: 30.0,
|
||||
cache_creation: 5.0,
|
||||
};
|
||||
|
||||
const CLAUDE_OPUS_4_PRICING = {
|
||||
input: 15.0,
|
||||
output: 75.0,
|
||||
@@ -167,6 +175,14 @@ export const DEFAULT_PRICING = {
|
||||
|
||||
// OpenAI Codex (cx)
|
||||
cx: {
|
||||
// GPT 5.5
|
||||
"gpt-5.5": GPT_5_5_PRICING,
|
||||
"gpt5.5": GPT_5_5_PRICING,
|
||||
"gpt-5.5-xhigh": GPT_5_5_PRICING,
|
||||
"gpt-5.5-high": GPT_5_5_PRICING,
|
||||
"gpt-5.5-medium": GPT_5_5_PRICING,
|
||||
"gpt-5.5-low": GPT_5_5_PRICING,
|
||||
"gpt-5.5-none": GPT_5_5_PRICING,
|
||||
// GPT 5.4
|
||||
"gpt-5.4": {
|
||||
input: 5.0,
|
||||
@@ -584,6 +600,7 @@ export const DEFAULT_PRICING = {
|
||||
|
||||
// OpenAI
|
||||
openai: {
|
||||
"gpt-5.5": GPT_5_5_PRICING,
|
||||
"gpt-4o": {
|
||||
input: 2.5,
|
||||
output: 10.0,
|
||||
|
||||
@@ -26,6 +26,10 @@ test("getTokenLimit: detects gemini", () => {
|
||||
assert.equal(getTokenLimit("gemini", "gemini-2.5-pro"), 1048576);
|
||||
});
|
||||
|
||||
test("getTokenLimit: uses GPT-5.5 Codex model context", () => {
|
||||
assert.equal(getTokenLimit("codex", "gpt-5.5"), 1050000);
|
||||
});
|
||||
|
||||
test("getTokenLimit: default fallback", () => {
|
||||
assert.equal(getTokenLimit("unknown"), 128000);
|
||||
});
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
getCodexModelScope,
|
||||
getCodexRateLimitKey,
|
||||
getCodexResetTime,
|
||||
getCodexUpstreamModel,
|
||||
isCodexResponsesWebSocketRequired,
|
||||
parseCodexQuotaHeaders,
|
||||
} from "../../open-sse/executors/codex.ts";
|
||||
import {
|
||||
@@ -58,6 +60,10 @@ test("Codex helper functions isolate rate-limit scopes and parse quota headers",
|
||||
|
||||
assert.equal(getCodexModelScope("codex-spark-mini"), "spark");
|
||||
assert.equal(getCodexModelScope("gpt-5.3-codex"), "codex");
|
||||
assert.equal(getCodexModelScope("gpt-5.5-xhigh"), "codex");
|
||||
assert.equal(getCodexUpstreamModel("gpt-5.5-xhigh"), "gpt-5.5");
|
||||
assert.equal(isCodexResponsesWebSocketRequired("gpt-5.5-xhigh", {}), true);
|
||||
assert.equal(isCodexResponsesWebSocketRequired("gpt-5.5-mini", {}), false);
|
||||
assert.equal(getCodexRateLimitKey("acct-1", "codex-spark-mini"), "acct-1:spark");
|
||||
assert.equal(quota.usage5h, 100);
|
||||
assert.equal(quota.limit7d, 5000);
|
||||
@@ -276,8 +282,8 @@ test("CodexExecutor.transformRequest lets model suffix beat connection reasoning
|
||||
test("CodexExecutor.transformRequest keeps gpt-5.5 as the model and applies xhigh reasoning", () => {
|
||||
const executor = new CodexExecutor();
|
||||
const result = executor.transformRequest(
|
||||
"gpt-5.5",
|
||||
{ model: "gpt-5.5", input: [], reasoning_effort: "xhigh" },
|
||||
"gpt-5.5-xhigh",
|
||||
{ model: "gpt-5.5-xhigh", input: [] },
|
||||
false,
|
||||
{}
|
||||
);
|
||||
|
||||
@@ -102,6 +102,12 @@ test("canonical model capability resolver merges models.dev data and keeps stati
|
||||
modelCapabilities.capThinkingBudget("antigravity/gemini-3.1-pro-high", 40000),
|
||||
32768
|
||||
);
|
||||
|
||||
const codexGpt55 = modelCapabilities.getResolvedModelCapabilities("codex/gpt-5.5");
|
||||
assert.equal(codexGpt55.contextWindow, 1050000);
|
||||
assert.equal(codexGpt55.maxOutputTokens, 128000);
|
||||
assert.equal(codexGpt55.supportsThinking, true);
|
||||
assert.equal(codexGpt55.supportsVision, true);
|
||||
});
|
||||
|
||||
test("GPT OSS and DeepSeek Reasoner models support tool calling", () => {
|
||||
|
||||
@@ -7,6 +7,12 @@ import { REGISTRY } from "../../open-sse/config/providerRegistry.ts";
|
||||
test("T12: pricing table includes MiniMax, GLM, Kimi and gpt-5.4 mini entries", () => {
|
||||
const pricing = getDefaultPricing();
|
||||
|
||||
assert.ok(pricing.cx["gpt-5.5"], "missing cx/gpt-5.5");
|
||||
assert.ok(pricing.cx["gpt-5.5-xhigh"], "missing cx/gpt-5.5-xhigh");
|
||||
assert.equal(pricing.cx["gpt-5.5"].input, 5.0);
|
||||
assert.equal(pricing.cx["gpt-5.5"].cached, 0.5);
|
||||
assert.equal(pricing.cx["gpt-5.5"].output, 30.0);
|
||||
|
||||
assert.ok(pricing.cx["gpt-5.4"], "missing cx/gpt-5.4");
|
||||
assert.ok(pricing.cx["gpt-5.4-mini"], "missing cx/gpt-5.4-mini");
|
||||
|
||||
@@ -30,9 +36,12 @@ test("T12: pricing table includes MiniMax, GLM, Kimi and gpt-5.4 mini entries",
|
||||
});
|
||||
|
||||
test("T12: codex catalog includes GPT 5.5 entries", () => {
|
||||
const codexModels = REGISTRY.codex.models.map((m) => m.id);
|
||||
assert.ok(codexModels.includes("gpt-5.5"), "missing codex/gpt-5.5");
|
||||
assert.ok(codexModels.includes("gpt-5.5-mini"), "missing codex/gpt-5.5-mini");
|
||||
const codexModels = new Map(REGISTRY.codex.models.map((m) => [m.id, m]));
|
||||
assert.ok(codexModels.has("gpt-5.5"), "missing codex/gpt-5.5");
|
||||
assert.ok(codexModels.has("gpt-5.5-mini"), "missing codex/gpt-5.5-mini");
|
||||
assert.equal(codexModels.get("gpt-5.5")?.contextLength, 1050000);
|
||||
assert.equal(codexModels.get("gpt-5.5")?.supportsXHighEffort, true);
|
||||
assert.equal(codexModels.get("gpt-5.5-xhigh")?.targetFormat, "openai-responses");
|
||||
});
|
||||
|
||||
test("T12: minimax default model list starts with M2.7", () => {
|
||||
|
||||
@@ -42,6 +42,8 @@ test("T33: thinkingLevel string is converted into numeric thinkingBudget", () =>
|
||||
});
|
||||
|
||||
test("T34: max output tokens are capped by model spec", () => {
|
||||
assert.equal(capMaxOutputTokens("gpt-5.5", 200000), 128000);
|
||||
assert.equal(capMaxOutputTokens("gpt-5.5-xhigh", 200000), 128000);
|
||||
assert.equal(capMaxOutputTokens("gemini-3-flash", 131072), 65536);
|
||||
assert.equal(capMaxOutputTokens("gemini-3-flash"), 65536);
|
||||
assert.equal(capMaxOutputTokens("gemini-3.1-pro-high", 131072), 65535);
|
||||
@@ -49,6 +51,9 @@ test("T34: max output tokens are capped by model spec", () => {
|
||||
});
|
||||
|
||||
test("T38: modelSpecs exposes centralized helpers with alias and prefix lookup", () => {
|
||||
assert.equal(getModelSpec("gpt-5.5").contextWindow, 1050000);
|
||||
assert.equal(getModelSpec("gpt-5.5-high").maxOutputTokens, 128000);
|
||||
assert.equal(getModelSpec("gpt-5.5-mini").contextWindow, 400000);
|
||||
assert.equal(typeof MODEL_SPECS["gemini-3.1-pro-high"], "object");
|
||||
assert.equal(getModelSpec("gemini-3-pro-high").maxOutputTokens, 65535);
|
||||
assert.equal(getModelSpec("gemini-3-pro-preview").maxOutputTokens, 65535);
|
||||
|
||||
Reference in New Issue
Block a user