From faeca3bbac691c51dfc71293cb74dd7ed5f3f210 Mon Sep 17 00:00:00 2001 From: Xiangzhe <32761048+xz-dev@users.noreply.github.com> Date: Mon, 17 Aug 2026 18:01:09 +0800 Subject: [PATCH] fix(providers): scope model target formats to providers (#10072) Co-authored-by: xz-dev Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: adevwithpurpose --- open-sse/config/providerModels.ts | 26 +++++++++-------------- tests/unit/chatcore-target-format.test.ts | 12 +++++++++++ 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/open-sse/config/providerModels.ts b/open-sse/config/providerModels.ts index afffd4429b..ee0028116f 100644 --- a/open-sse/config/providerModels.ts +++ b/open-sse/config/providerModels.ts @@ -173,14 +173,11 @@ export function getModelTargetFormat(aliasOrId: string, modelId: string): string // Accept either the public alias ("cmd") or the raw provider id ("command-code"), // mirroring getProviderModels (same pattern as #2798/#3870). const alias = PROVIDER_ID_TO_ALIAS[aliasOrId] || aliasOrId; - const models = PROVIDER_MODELS[alias]; // Strip provider prefix if present: "openai/gpt-5.6-luna" → "gpt-5.6-luna" - const prefix = alias + "/"; - const bareModelId = - typeof modelId === "string" && modelId.startsWith(prefix) - ? modelId.slice(prefix.length) - : modelId; - const found = models?.find((m) => m.id === bareModelId); + const prefixes = [`${aliasOrId}/`, `${alias}/`]; + const prefix = prefixes.find((value) => modelId.startsWith(value)); + const bareModelId = prefix ? modelId.slice(prefix.length) : modelId; + const found = PROVIDER_MODELS[alias]?.find((m) => m.id === bareModelId); if (found?.targetFormat) return found.targetFormat; // #5842: OpenAI "*-pro" reasoning models (o1-pro, gpt-5.x-pro) are only served by // the native /v1/responses endpoint — /v1/chat/completions 404s ("only supported @@ -188,16 +185,13 @@ export function getModelTargetFormat(aliasOrId: string, modelId: string): string // covers dynamically-synced ids that post-date the catalog (same spirit as the gh // executor's /codex/i routing, 9router#102). Scoped to the openai alias so other // providers shipping *-pro ids keep their own endpoint semantics. - if (alias === "openai" && /-pro$/i.test(modelId)) return "openai-responses"; + if (alias === "openai" && /-pro$/i.test(bareModelId)) return "openai-responses"; // Model-level targetFormat is provider-scoped: a catalog entry declares how THIS - // provider's endpoint serves the model. When the provider has its own catalog but - // the model is not in it, do NOT import the global entry's tag — it encodes the - // DECLARING provider's endpoint semantics (e.g. ghe-copilot tags gpt-5.6-* as - // openai-responses, which must not hijack command-code's chat-shaped - // /alpha/generate → 502 "Invalid prompt: messages must not be empty"). Providers - // with no catalog at all keep the global fallback as their only metadata source. - if (models) return null; - return getGlobalModel(bareModelId)?.targetFormat ?? null; + // provider's endpoint serves the model — do NOT import another provider's tag. + // #9994 scoped this for providers WITH a catalog; #10072 extends it to catalogless + // providers (openai-compatible-chat-*), which previously inherited the declaring + // provider's endpoint semantics via the global fallback. + return null; } export function getModelStripTypes(aliasOrId: string, modelId: string): string[] { const models = PROVIDER_MODELS[aliasOrId]; diff --git a/tests/unit/chatcore-target-format.test.ts b/tests/unit/chatcore-target-format.test.ts index b820b245fe..480ae01ca6 100644 --- a/tests/unit/chatcore-target-format.test.ts +++ b/tests/unit/chatcore-target-format.test.ts @@ -58,6 +58,18 @@ test("delegates byte-identically for a normal model (no apiFormat / no custom ov assert.deepEqual(r, expected("openai", "gpt-4o", undefined, undefined, undefined)); }); +test("provider-local target format does not leak from another provider", () => { + const r = resolveChatCoreTargetFormat({ + provider: "openai-compatible-chat-example", + resolvedModel: "gpt-5.6-sol", + apiFormat: undefined, + sourceFormat: FORMATS.OPENAI_RESPONSES, + customModelTargetFormat: undefined, + providerSpecificData: undefined, + }); + assert.equal(r.targetFormat, FORMATS.OPENAI); +}); + test("customModelTargetFormat is used when the model has no registry target format", () => { const customModel = "totally-unknown-custom-model-xyz"; // precondition: the registry has no target format for this unknown model