Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions codex-model-proxy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
35 changes: 29 additions & 6 deletions codex-model-proxy/proxy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
31 changes: 31 additions & 0 deletions codex-model-proxy/test/proxy.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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`, {
Expand Down
6 changes: 6 additions & 0 deletions codex-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down