From e26fce0c8d3e3c903c5691dbdaa20acaf1cf8f29 Mon Sep 17 00:00:00 2001 From: Juan de Urtubey Date: Fri, 16 Oct 2026 12:29:33 -0700 Subject: [PATCH] fix(catalog): preserve OpenAI API display name overrides --- .../docs/reference/configuration/providers.md | 4 ++- src/codex/catalog/routed-gather.ts | 4 ++- ...model-display-names-management-api.test.ts | 33 +++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/docs-site/src/content/docs/reference/configuration/providers.md b/docs-site/src/content/docs/reference/configuration/providers.md index 700e060ca9b..a8e4499e9a6 100644 --- a/docs-site/src/content/docs/reference/configuration/providers.md +++ b/docs-site/src/content/docs/reference/configuration/providers.md @@ -672,7 +672,9 @@ The effective label order is operator `modelDisplayNames`, then provider catalog normal `provider/model` fallback. The routed selector remains `xai/grok-4.6`, while the upstream wire model remains `grok-4.6`. Labels are display only. They do not change authentication, adapter behavior, routing, billing, or upstream request construction. Removing a map entry resets only its -label. A management client can set or reset one label with +label. For built-in OpenAI API models, use +`providers.openai-apikey.modelDisplayNames`; the label survives catalog refreshes. +A management client can set or reset one label with `PUT /api/providers/:provider/model-display-names` and a body of `{ "modelId": "grok-4.6", "displayName": "Grok 4.6" }`; send `displayName: null` to reset it. Provider `PATCH` does not edit this map. Use this dedicated `PUT` endpoint to change or remove labels. diff --git a/src/codex/catalog/routed-gather.ts b/src/codex/catalog/routed-gather.ts index 99f7e64d3db..13bf24a7064 100644 --- a/src/codex/catalog/routed-gather.ts +++ b/src/codex/catalog/routed-gather.ts @@ -102,7 +102,7 @@ import type { CatalogTrustedOpenAiApiPolicySnapshot, } from "../convergence-types"; import type { CatalogGatherProviderAuthOutcome, CatalogGatherProviderModelOutcome, GatherFlightCapture, ModelsAuthResolverFactory } from "./gather-capture"; -import { applyProviderConfigHints, configuredAutoCompactTokenLimit, configuredMaxInputTokens, configuredReasoningSummarySupport, modelInputModalities, routedMaxOutputTokens, withVisionSidecarInput } from "./model-hints"; +import { applyProviderConfigHints, configuredAutoCompactTokenLimit, configuredMaxInputTokens, configuredModelDisplayName, configuredReasoningSummarySupport, modelInputModalities, routedMaxOutputTokens, withVisionSidecarInput } from "./model-hints"; import { resolveComboCatalogMember } from "./combo-member"; import { captureGatherFlight, captureTrustedOpenAiApiPolicy, gatherFlightKey, keyedGatherBytesIdentity, withCanonicalOpenAiForwardAuthDefault } from "./gather-capture"; import { fetchProviderModelsWithAuth, observedModelsAuthResolver, refreshingModelsAuthResolver } from "./provider-models"; @@ -886,10 +886,12 @@ function augmentRoutedModelsWithCapturedOpenAiApiRows( undefined, metadataConfigDir, ); + const displayName = configuredModelDisplayName(configured, id); return { provider: OPENAI_API_PROVIDER_ID, id, owned_by: OPENAI_API_PROVIDER_ID, + ...(displayName ? { displayName } : {}), ...(contextWindow ? { contextWindow } : {}), ...(maxInputTokens ? { maxInputTokens } : {}), ...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}), diff --git a/tests/codex-integration/model-display-names-management-api.test.ts b/tests/codex-integration/model-display-names-management-api.test.ts index ea11600e833..95819bb6316 100644 --- a/tests/codex-integration/model-display-names-management-api.test.ts +++ b/tests/codex-integration/model-display-names-management-api.test.ts @@ -1,4 +1,5 @@ import { afterEach, describe, expect, test } from "bun:test"; +import { buildCatalogEntries, gatherRoutedModels } from "../../src/codex/catalog"; import { clearModelCache, getFreshCached, setCached } from "../../src/codex/model-cache"; import type { CatalogDisposition } from "../../src/codex/convergence-types"; import { listManagementModelRows, toExportModel } from "../../src/server/management/model-rows"; @@ -32,6 +33,38 @@ afterEach(() => { }); describe("model display name management rows", () => { + test("OpenAI API labels survive trusted reconstruction into the Codex catalog", async () => { + const apiConfig: OcxConfig = { + port: 10100, + defaultProvider: "openai-apikey", + providers: { + "openai-apikey": { + adapter: "openai-responses", + baseUrl: "https://api.openai.com/v1", + liveModels: false, + modelDisplayNames: { "gpt-6-astra": "6 Astra", "gpt-6.1-sol": "6.1 Sol" }, + }, + }, + }; + const catalog = async () => buildCatalogEntries(null, [], await gatherRoutedModels(apiConfig)); + const labeled = await catalog(); + for (const [slug, displayName] of [ + ["openai-apikey/gpt-6-astra", "6 Astra"], + ["openai-apikey/gpt-6.1-sol", "6.1 Sol"], + ]) { + expect(labeled.filter(entry => entry.slug === slug)).toHaveLength(1); + expect(labeled.find(entry => entry.slug === slug)?.display_name).toBe(displayName); + } + + delete apiConfig.providers["openai-apikey"]!.modelDisplayNames; + const original = await catalog(); + for (const entry of labeled) { + const baseline = original.find(row => row.slug === entry.slug); + expect(baseline).toBeDefined(); + expect({ ...entry, display_name: baseline!.display_name }).toEqual(baseline); + } + }); + test("reports operator, provider, and fallback display name sources without provider secrets", async () => { const operatorConfig = config({ "model-a": "Operator Name" }); operatorConfig.providers[DISPLAY_PROVIDER].apiKey = "sk-secret-not-for-rows";