Files
OmniRoute/tests/unit/github-copilot-custom-model-target-format.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

91 lines
4.1 KiB
TypeScript

// tests/unit/github-copilot-custom-model-target-format.test.ts
// GitHub Copilot custom models (custom-model dropdown, #2905) can carry a
// per-model targetFormat override resolving to "openai-responses" — e.g. a
// Codex-family custom model (gpt-5.6-terra/gpt-5.6-luna) that the operator
// wants routed through Copilot's native /responses endpoint instead of
// /chat/completions. GithubExecutor.buildUrl() only reads the static
// PROVIDER_MODELS registry via getModelTargetFormat("gh", model) and has no
// other way to see a custom model's override, so every custom Copilot model
// silently hit /chat/completions regardless of the dashboard's Target Format
// setting and got rejected upstream with "not accessible via the
// /chat/completions endpoint". Mirrors the zai/glm-coding-apikey fix (#7364)
// for the same class of bug.
import { test } from "node:test";
import assert from "node:assert/strict";
import { GithubExecutor } from "../../open-sse/executors/github.ts";
import { resolveExecutionCredentials } from "../../open-sse/handlers/chatCore/executionCredentials.ts";
// NOTE: the "custom model" id must stay ABSENT from the gh registry for these
// tests to exercise the override path — #9050 (ea4bbdf7c0) promoted the original
// gpt-5.6-terra/luna ids into the curated registry with
// targetFormat:"openai-responses", so we use a fictional gpt-5.7-nova instead.
test("BUG: GithubExecutor.buildUrl ignores a per-model targetFormat:'openai-responses' override and still returns the chat/completions URL", () => {
const executor = new GithubExecutor();
const credentialsWithoutOverride = { apiKey: "test-token" };
const url = executor.buildUrl("gpt-5.7-nova", false, 0, credentialsWithoutOverride);
assert.ok(
!url.endsWith("/responses"),
"sanity check: with no override and a non-codex custom model id, buildUrl falls back to chat/completions"
);
});
test("FIX: GithubExecutor.buildUrl honors providerSpecificData.targetFormat:'openai-responses' for a custom model", () => {
const executor = new GithubExecutor();
const credentialsWithOverride = {
apiKey: "test-token",
providerSpecificData: { targetFormat: "openai-responses" },
};
const url = executor.buildUrl("gpt-5.7-nova", false, 0, credentialsWithOverride);
assert.ok(
url.endsWith("/responses"),
`expected the /responses endpoint when the override is set, got: ${url}`
);
});
test("FIX: a Gemini/Claude custom model is never routed to /responses even with the override set (supportsResponsesEndpoint gate)", () => {
const executor = new GithubExecutor();
const credentialsWithOverride = {
apiKey: "test-token",
providerSpecificData: { targetFormat: "openai-responses" },
};
const url = executor.buildUrl("gemini-2.5-pro", false, 0, credentialsWithOverride);
assert.ok(
!url.endsWith("/responses"),
"9router#1536 invariant: Gemini/Claude models must never route to /responses, even with a targetFormat override"
);
});
const base = {
credentials: { providerSpecificData: { foo: "bar" } } as Record<string, unknown>,
nativeCodexPassthrough: false,
endpointPath: "/v1/messages",
ccSessionId: null,
};
test("github + resolved openai-responses targetFormat threads providerSpecificData.targetFormat", () => {
const out = resolveExecutionCredentials({
...base,
provider: "github",
targetFormat: "openai-responses",
}) as Record<string, unknown>;
assert.deepEqual(out.providerSpecificData, { foo: "bar", targetFormat: "openai-responses" });
});
test("github + default (non-responses) targetFormat does NOT inject a targetFormat override", () => {
const out = resolveExecutionCredentials({
...base,
provider: "github",
targetFormat: "openai",
}) as Record<string, unknown>;
assert.deepEqual(out.providerSpecificData, { foo: "bar" });
});
test("unrelated provider (openai) with targetFormat=openai-responses is untouched by the github branch", () => {
const out = resolveExecutionCredentials({
...base,
provider: "openai",
targetFormat: "openai-responses",
}) as Record<string, unknown>;
assert.deepEqual(out.providerSpecificData, { foo: "bar" });
});