feat(openai): honor custom base URL in model discovery + complete openai/codex pricing (#4005)

Integrated into release/v3.8.27 — openai model-discovery honors custom base URL (SSRF-guarded) + pricing rows for new openai/codex models. Tested + baselines bumped.
This commit is contained in:
NOXX - Commiter
2026-06-16 19:08:37 +03:00
committed by GitHub
parent e3ec10bc2a
commit 0bc670e414
4 changed files with 115 additions and 3 deletions

View File

@@ -93,7 +93,7 @@
"src/app/(dashboard)/dashboard/usage/components/EvalsTab.tsx": 2148,
"src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx": 1069,
"src/app/api/oauth/[provider]/[action]/route.ts": 918,
"src/app/api/providers/[id]/models/route.ts": 2494,
"src/app/api/providers/[id]/models/route.ts": 2512,
"src/app/api/providers/[id]/test/route.ts": 842,
"src/app/api/usage/analytics/route.ts": 941,
"src/app/api/v1/models/catalog.ts": 1435,
@@ -118,7 +118,7 @@
"src/shared/components/RequestLoggerV2.tsx": 1287,
"src/shared/components/analytics/charts.tsx": 1558,
"src/shared/constants/cliTools.ts": 875,
"src/shared/constants/pricing.ts": 1529,
"src/shared/constants/pricing.ts": 1581,
"src/shared/constants/providers.ts": 3147,
"src/shared/constants/sidebarVisibility.ts": 1100,
"src/shared/services/cliRuntime.ts": 1090,
@@ -155,5 +155,6 @@
"_rebaseline_2026_06_14_3861_gitlab_duo": "PR #3861 own growth: oauth/[provider]/[action]/route.ts 903→916 (+13 = gitlab-duo authorize guard mirroring the existing qoder guard — returns a clear 'register an OAuth app + set GITLAB_DUO_OAUTH_CLIENT_ID' message instead of letting buildAuthUrl's throw become an opaque 500). Cohesive with the qoder branch right above it; not separately extractable.",
"_rebaseline_2026_06_15_3941_provider_request_capture": "PR #3941 own growth: chatCore.ts 5823->5830 (+7 by check-file-size counting = run executor attempts inside the unified provider request capture scope), antigravity.ts 1649->1664 (+15) and codex.ts 1439->1447 (+8) = bridge hand-written upstream transports that bypass normal fetch/BaseExecutor capture. Cohesive logging-fidelity refactor; not extractable without hiding the actual dispatch boundary.",
"_rebaseline_2026_06_16_3958_qwen_body_check": "PR #3958 own growth: validation.ts 4407->4428 (+21 = validateQwenWebProvider now parses the /api/v2/user 200 body and requires a real user object, since Qwen returns HTTP 200 even for invalid tokens — fixes the validation false-positive, #3931). Cohesive with the existing qwen-web validation branch; not separately extractable.",
"_rebaseline_2026_06_16_4001_perplexity_diff_block": "PR #4001 own growth: perplexity-web.ts 939->1013 (+74 = parse the schematized API's RFC-6902 diff_block JSON-patch frames — applyMarkdownDiff + isAnswerTextUsage primary-usage lock + stop only on COMPLETED, not on a still-PENDING final flag — so streamed answers aren't empty, #3938 follow-up). Cohesive single-executor SSE-parsing logic; not separately extractable."
"_rebaseline_2026_06_16_4001_perplexity_diff_block": "PR #4001 own growth: perplexity-web.ts 939->1013 (+74 = parse the schematized API's RFC-6902 diff_block JSON-patch frames — applyMarkdownDiff + isAnswerTextUsage primary-usage lock + stop only on COMPLETED, not on a still-PENDING final flag — so streamed answers aren't empty, #3938 follow-up). Cohesive single-executor SSE-parsing logic; not separately extractable.",
"_rebaseline_2026_06_16_4005_openai_dynamic_models": "PR #4005 own growth: models/route.ts 2494->2512 (+18 = openai model-discovery derives {customBaseUrl}/v1/models from providerSpecificData.baseUrl, SSRF-guarded via safeOutboundFetch+public-only) and pricing.ts 1529->1581 (+52 = pure-data pricing rows closing $0 gaps for registry-exposed ids: openai gpt-5.4/-mini/-nano, gpt-4.1, gpt-4o-2024-11-20, o3 + codex(cx) gpt-5.4-{xhigh,high,medium,low}, gpt-5.3-codex-spark). Cohesive; pricing is data, route change mirrors the anthropic-compat discovery path."
}

View File

@@ -2383,6 +2383,26 @@ export async function GET(
// Build request URL
let url = config.url;
// VibeProxy: honor a user-configured custom base URL for the built-in
// `openai` provider (e.g. an OpenAI-compatible gateway / proxy). Without
// this, model discovery always hit the hardcoded api.openai.com and ignored
// the configured endpoint — returning the wrong catalog (or failing auth)
// for gateway users, and preventing instant access to gateway-served models.
// Falls back to config.url (api.openai.com) when no custom base URL is set.
if (provider === "openai") {
const customBaseUrl = getProviderBaseUrl(connection.providerSpecificData);
if (customBaseUrl) {
let base = customBaseUrl.replace(/\/$/, "");
if (base.endsWith("/chat/completions")) {
base = base.slice(0, -"/chat/completions".length);
} else if (base.endsWith("/completions")) {
base = base.slice(0, -"/completions".length);
} else if (base.endsWith("/v1")) {
base = base.slice(0, -"/v1".length);
}
url = `${base}/v1/models`;
}
}
if (provider === "cloudflare-ai") {
const pData = asRecord(connection.providerSpecificData);
const accountId =

View File

@@ -257,7 +257,14 @@ export const DEFAULT_PRICING = {
reasoning: 9.0,
cache_creation: 1.5,
},
// gpt-5.4 reasoning-effort variants share the gpt-5.4 tier (registry exposes
// -xhigh/-high/-medium/-low; without these rows they resolved to $0).
"gpt-5.4-xhigh": GPT_5_3_CODEX_PRICING,
"gpt-5.4-high": GPT_5_3_CODEX_PRICING,
"gpt-5.4-medium": GPT_5_3_CODEX_PRICING,
"gpt-5.4-low": GPT_5_3_CODEX_PRICING,
// GPT 5.3 Codex family (all same pricing tier)
"gpt-5.3-codex-spark": GPT_5_3_CODEX_PRICING,
"gpt-5.3-codex": GPT_5_3_CODEX_PRICING,
"gpt-5.3-codex-xhigh": GPT_5_3_CODEX_PRICING,
"gpt-5.3-codex-high": GPT_5_3_CODEX_PRICING,
@@ -645,6 +652,37 @@ export const DEFAULT_PRICING = {
// OpenAI
openai: {
"gpt-5.5": GPT_5_5_PRICING,
// gpt-5.4 family (public API tier; mirrors the codex 5.4 tier for the
// base/mini, with a lower nano tier). Without these rows the openai
// provider's gpt-5.4* models resolved to $0.
"gpt-5.4": {
input: 5.0,
output: 20.0,
cached: 2.5,
reasoning: 30.0,
cache_creation: 5.0,
},
"gpt-5.4-mini": {
input: 1.5,
output: 6.0,
cached: 0.75,
reasoning: 9.0,
cache_creation: 1.5,
},
"gpt-5.4-nano": {
input: 0.4,
output: 1.6,
cached: 0.2,
reasoning: 2.4,
cache_creation: 0.4,
},
"gpt-4.1": {
input: 2.0,
output: 8.0,
cached: 0.5,
reasoning: 12.0,
cache_creation: 2.0,
},
"gpt-4o": {
input: 2.5,
output: 10.0,
@@ -652,6 +690,13 @@ export const DEFAULT_PRICING = {
reasoning: 15.0,
cache_creation: 2.5,
},
"gpt-4o-2024-11-20": {
input: 2.5,
output: 10.0,
cached: 1.25,
reasoning: 15.0,
cache_creation: 2.5,
},
"gpt-4o-mini": {
input: 0.15,
output: 0.6,
@@ -659,6 +704,13 @@ export const DEFAULT_PRICING = {
reasoning: 0.9,
cache_creation: 0.15,
},
o3: {
input: 2.0,
output: 8.0,
cached: 0.5,
reasoning: 12.0,
cache_creation: 2.0,
},
"gpt-4-turbo": {
input: 10.0,
output: 30.0,

View File

@@ -110,3 +110,42 @@ test("Every Kiro registry model resolves a non-zero pricing row (no $0.00 usage)
assert.equal(sonnet46?.input, 3.0);
assert.equal(sonnet46?.output, 15.0);
});
test("Every OpenAI registry model resolves a non-zero pricing row (alias: openai)", async () => {
const { getPricingForModel } = await import("../../src/shared/constants/pricing.ts");
const models = getModelsByProviderId("openai");
assert.ok(models.length > 0, "openai must expose models");
for (const model of models) {
const pricing = getPricingForModel("openai", model.id) as {
input?: number;
output?: number;
} | null;
assert.ok(pricing, `openai pricing must include "${model.id}"`);
assert.equal(
typeof pricing?.input === "number" && typeof pricing?.output === "number",
true,
`openai pricing for "${model.id}" must have numeric input/output`
);
}
});
test("Every Codex registry model resolves a non-zero pricing row (alias: cx)", async () => {
const { getPricingForModel } = await import("../../src/shared/constants/pricing.ts");
const models = getModelsByProviderId("codex");
assert.ok(models.length > 0, "codex must expose models");
for (const model of models) {
// Codex pricing lives under the "cx" alias (its DEFAULT_PRICING key).
const pricing = getPricingForModel("cx", model.id) as {
input?: number;
output?: number;
} | null;
assert.ok(pricing, `cx pricing must include codex model "${model.id}"`);
assert.equal(
typeof pricing?.input === "number" && typeof pricing?.output === "number",
true,
`cx pricing for "${model.id}" must have numeric input/output`
);
}
});