Files
OmniRoute/tests/unit/provider-registry-github-copilot-targetformat.test.ts
Praveen K Palaniswamy 65e81158ab fix(ollama): route models by advertised capability (#11088)
Landed with the design call resolved per the owner's pick — **option 1**: the synced store is now endpoint-agnostic (persistDiscoveredModels and managedModelImport no longer drop non-chat models at write time), and chat selectability moved to read time (auto-pool expansion in autoStrategy applies filterChatSelectableModels; the models-route projection already had its chatOnly filter). Your discovery test now passes end-to-end (3/3): /api/show capabilities persist per connection and image/embedding requests route through the advertising host.

Reconciliation notes: conflicted areas merged onto the current tip (adobe discovery import, requestedModel preflight signature, resolvedProvider fast-path coexists with the synced-route override — explicit resolution wins); carried base-red drains (#10055 memoization, #11071 test variants) dropped as already-landed; the managed-model-import exclusion test was propagated to the new contract (image/video models persist; the read filter still hides them from chat pickers — pinned by a new assertion). Full battery: 205/206 focused (the one red is a confirmed periodic-timer timing flake on the loaded devbox — 20/20 isolated), autoCombo vitest 30/30, combo suites 46/46, gates + typecheck clean.

Thank you @yourspraveen — the capability probe + routing design was right; it just needed the store contract opened up. Fixes #11087.
2026-08-23 11:45:01 -03:00

95 lines
3.4 KiB
TypeScript

/**
* Issue #2911 — Built-in GitHub Copilot Claude Opus / Gemini models fail.
*
* The `github` provider has `format: "openai"` (baseUrl .../chat/completions)
* and a separate `responsesBaseUrl` (.../responses). A model only routes to the
* Responses API when it sets `targetFormat: "openai-responses"`.
*
* GitHub Copilot's Responses API does NOT serve the Claude/Gemini models, so
* `claude-opus-4.7`, `gemini-3.1-pro-preview` and other non-OpenAI models
* failed with a 400. The working `claude-sonnet-4.6`
* carries no `targetFormat` and goes through chat/completions.
*
* Fix: drop `targetFormat: "openai-responses"` from the Claude/Gemini entries so
* they use the provider default (chat/completions). The native OpenAI `gpt-*`
* models legitimately keep the Responses API and must NOT be touched.
*/
import test from "node:test";
import assert from "node:assert/strict";
const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts");
const { getModelsByProviderId } = await import("../../open-sse/config/providerModels.ts");
type ModelEntry = { id: string; targetFormat?: string; [k: string]: unknown };
function githubModel(id: string): ModelEntry | undefined {
const provider = (REGISTRY as Record<string, { models?: ModelEntry[] }>)["github"];
return provider?.models?.find((m) => m.id === id);
}
// Claude/Gemini models that must NOT route through the Responses API.
const MUST_NOT_BE_RESPONSES = [
"claude-fable-5",
"claude-opus-5",
"claude-opus-4.7",
"claude-opus-4.8",
"claude-opus-4.8-fast",
"claude-opus-4.5",
"claude-sonnet-4.6",
"claude-sonnet-5",
"claude-sonnet-4.5",
"claude-haiku-4.5",
"gemini-3.1-pro-preview",
"gemini-3.7-flash",
];
for (const id of MUST_NOT_BE_RESPONSES) {
test(`#2911 github/${id} must not use openai-responses targetFormat`, () => {
const model = githubModel(id);
assert.ok(model, `${id} must be registered under github`);
assert.notEqual(
model.targetFormat,
"openai-responses",
`github/${id} must route via chat/completions (Copilot Responses API rejects it)`
);
});
}
test("#2911 github/claude-sonnet-4.6 baseline stays on chat/completions (no targetFormat)", () => {
const model = githubModel("claude-sonnet-4.6");
assert.ok(model, "claude-sonnet-4.6 must be registered");
assert.notEqual(model.targetFormat, "openai-responses");
});
// Regression guard: models with `/responses` in the curated Copilot catalog keep the Responses API.
for (const id of [
"gpt-5.3-codex",
"gpt-5.4-mini",
"gpt-5.4",
"gpt-5.5",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
"mai-code-1-flash",
"gpt-5-mini",
"oswe-vscode-prime",
]) {
test(`#2911 github/${id} (OpenAI-native) still uses openai-responses`, () => {
const model = githubModel(id);
assert.ok(model, `${id} must be registered`);
assert.equal(
model.targetFormat,
"openai-responses",
`github/${id} is OpenAI-native and must keep the Responses API`
);
});
}
// Sanity: lookup-by-id helper resolves the same entries.
test("#2911 getModelsByProviderId(github) reflects the targetFormat changes", () => {
const models = getModelsByProviderId("github") as ModelEntry[];
const opus47 = models.find((m) => m.id === "claude-opus-4.7");
assert.ok(opus47, "claude-opus-4.7 resolvable via getModelsByProviderId");
assert.notEqual(opus47.targetFormat, "openai-responses");
});