From eb73e15e5f0d072648d9fa4b88c87450f4edcb4a Mon Sep 17 00:00:00 2001 From: Theo Luu Date: Thu, 17 Sep 2026 01:06:45 -0700 Subject: [PATCH] Fix DeepSeek V4.1 reasoning effort catalog --- codex-model-proxy/README.md | 7 ++++++ codex-model-proxy/proxy.mjs | 35 ++++++++++++++++++++++----- codex-model-proxy/test/proxy.test.mjs | 31 ++++++++++++++++++++++++ codex-setup.sh | 6 +++++ 4 files changed, 73 insertions(+), 6 deletions(-) diff --git a/codex-model-proxy/README.md b/codex-model-proxy/README.md index 97c46f2..a84e16a 100644 --- a/codex-model-proxy/README.md +++ b/codex-model-proxy/README.md @@ -11,6 +11,13 @@ model metadata. Routing and Codex catalog generation use that discovered list, so keys with different access receive different pickers and future models are picked up on the next re-run. +Webster's `/v1/models` response does not currently advertise reasoning-effort +capabilities. Catalog generation therefore uses a shared default profile and a +centralized override only for models with a verified incompatible contract. Do +not rewrite Responses request bodies to hide a catalog mismatch. If Webster +adds capability metadata, prefer that owned contract over adding more local +profiles. + ## Security - The server binds to `127.0.0.1` by default. diff --git a/codex-model-proxy/proxy.mjs b/codex-model-proxy/proxy.mjs index 0c442b0..e6356c5 100755 --- a/codex-model-proxy/proxy.mjs +++ b/codex-model-proxy/proxy.mjs @@ -209,19 +209,42 @@ function modelTemplate(catalog) { ); } +const DEFAULT_WEBSTER_REASONING_PROFILE = { + defaultLevel: "medium", + levels: [ + { effort: "low", description: "Fast responses with lighter reasoning" }, + { effort: "medium", description: "Balanced reasoning depth" }, + { effort: "high", description: "Greater reasoning depth for complex tasks" }, + ], +}; + +const WEBSTER_REASONING_PROFILE_OVERRIDES = new Map([ + [ + "deepseek-v4.1-flash", + { + defaultLevel: "high", + levels: [ + { effort: "low", description: "Reasoning budget 25" }, + { effort: "high", description: "Reasoning budget 50" }, + { effort: "xhigh", description: "Reasoning budget 75" }, + { effort: "max", description: "Reasoning budget 100" }, + ], + }, + ], +]); + function websterCatalogEntry(definition, template, priority) { const entry = structuredClone(template); const contextWindow = definition.contextWindow ?? template.context_window; + const reasoningProfile = + WEBSTER_REASONING_PROFILE_OVERRIDES.get(definition.id) ?? + DEFAULT_WEBSTER_REASONING_PROFILE; Object.assign(entry, { slug: definition.id, display_name: definition.displayName, description: definition.description, - default_reasoning_level: "medium", - supported_reasoning_levels: [ - { effort: "low", description: "Fast responses with lighter reasoning" }, - { effort: "medium", description: "Balanced reasoning depth" }, - { effort: "high", description: "Greater reasoning depth for complex tasks" }, - ], + default_reasoning_level: reasoningProfile.defaultLevel, + supported_reasoning_levels: structuredClone(reasoningProfile.levels), shell_type: "default", visibility: "list", supported_in_api: true, diff --git a/codex-model-proxy/test/proxy.test.mjs b/codex-model-proxy/test/proxy.test.mjs index dfb6761..b8067b6 100644 --- a/codex-model-proxy/test/proxy.test.mjs +++ b/codex-model-proxy/test/proxy.test.mjs @@ -269,6 +269,37 @@ test("merges Webster models into the Codex model catalog", async (t) => { assert.equal(seen.chatGpt[0].url, "/v1/models?client_version=0.148.0"); }); +test("publishes model-specific reasoning levels while preserving generic defaults", async (t) => { + const websterModels = normalizeWebsterModels({ + data: [ + { id: "future-model-7b" }, + { id: "deepseek-v4.1-flash" }, + ], + }); + const { proxyBaseUrl } = await fixture(t, { websterModels }); + const response = await fetch(`${proxyBaseUrl}/models`, { + headers: { + authorization: "Bearer chatgpt-secret", + "chatgpt-account-id": "account-123", + }, + }); + + assert.equal(response.status, 200); + const body = await response.json(); + const deepSeek = body.models.find((model) => model.slug === "deepseek-v4.1-flash"); + const future = body.models.find((model) => model.slug === "future-model-7b"); + assert.equal(deepSeek.default_reasoning_level, "high"); + assert.deepEqual( + deepSeek.supported_reasoning_levels.map((level) => level.effort), + ["low", "high", "xhigh", "max"], + ); + assert.equal(future.default_reasoning_level, "medium"); + assert.deepEqual( + future.supported_reasoning_levels.map((level) => level.effort), + ["low", "medium", "high"], + ); +}); + test("rejects unauthenticated and unsupported requests", async (t) => { const { proxyBaseUrl } = await fixture(t); const missingAuth = await fetch(`${proxyBaseUrl}/responses`, { diff --git a/codex-setup.sh b/codex-setup.sh index be38044..db3a35d 100755 --- a/codex-setup.sh +++ b/codex-setup.sh @@ -658,6 +658,12 @@ assert_catalog() { for (const model of models) { if (!required.has(model.slug)) continue; if (model.visibility !== "list" || typeof model.supports_reasoning_summaries !== "boolean") process.exit(1); + const levels = model.supported_reasoning_levels; + if ( + typeof model.default_reasoning_level !== "string" || + !Array.isArray(levels) || + !levels.some((level) => level?.effort === model.default_reasoning_level) + ) process.exit(1); required.delete(model.slug); } const websterIds = new Set(config.models.map((model) => model.id));