65a7444e94
mergeDiscoveredModel re-applied baseUrl, headers, and compat from the
provider override when an existing bundled record matched, but dropped
`transport` because the raw /v1/models payload never carries one. With
`transport: pi-native` set for an auth-gateway-routed openrouter
provider in models.yml, the next /model switch after the background
catalog refresh picked the now-transport-less entry and routed through
the default openai-completions client to ${baseUrl}/chat/completions —
a path the auth-gateway never serves, so the gateway returned
"404 No route: POST /chat/completions".
Propagate transport with the same priority as baseUrl: provider
override wins, otherwise preserve the existing (boot-time override-
applied) value, otherwise the discovered value. Cover the regression
both at the merge unit level (xiaomi-tp-discovery-merge) and at the
ModelRegistry refresh level with a mock /models fetch.
Fixes #2555
114 lines
4.3 KiB
TypeScript
114 lines
4.3 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import type { Model } from "@oh-my-pi/pi-ai";
|
|
import { buildModel } from "@oh-my-pi/pi-catalog/build";
|
|
import { mergeDiscoveredModel } from "@oh-my-pi/pi-coding-agent/config/model-registry";
|
|
|
|
/**
|
|
* Regression for v15.2.4 tp- key bug: when Xiaomi `tp-` token-plan keys hit
|
|
* discovery, models came back with `baseUrl: token-plan-sgp.xiaomimimo.com/v1`,
|
|
* but the bundled `xiaomi/*` entries in `models.json` carry the standard
|
|
* `api.xiaomimimo.com/v1` host. The old merge forced `existing.baseUrl` over
|
|
* the discovered value, sending stream calls to the wrong host → 401.
|
|
*/
|
|
|
|
const STANDARD = "https://api.xiaomimimo.com/v1";
|
|
const TOKEN_PLAN = "https://token-plan-sgp.xiaomimimo.com/v1";
|
|
|
|
function bundled(baseUrl: string): Model<"openai-completions"> {
|
|
return buildModel({
|
|
id: "mimo-v2.5",
|
|
name: "MiMo v2.5",
|
|
api: "openai-completions",
|
|
provider: "xiaomi",
|
|
baseUrl,
|
|
reasoning: false,
|
|
input: ["text"],
|
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
contextWindow: 128000,
|
|
maxTokens: 8192,
|
|
});
|
|
}
|
|
|
|
describe("mergeDiscoveredModel", () => {
|
|
test("prefers discovered baseUrl over bundled baseUrl (xiaomi tp- regression)", () => {
|
|
const discovered = bundled(TOKEN_PLAN);
|
|
const existing = bundled(STANDARD);
|
|
const merged = mergeDiscoveredModel(discovered, existing);
|
|
expect(merged.baseUrl).toBe(TOKEN_PLAN);
|
|
});
|
|
|
|
test("falls back to existing baseUrl when discovery did not supply one", () => {
|
|
const discovered = { ...bundled(STANDARD), baseUrl: undefined as unknown as string };
|
|
const existing = bundled(STANDARD);
|
|
const merged = mergeDiscoveredModel(discovered, existing);
|
|
expect(merged.baseUrl).toBe(STANDARD);
|
|
});
|
|
|
|
test("merges headers: existing first, discovered overrides per-key", () => {
|
|
const discovered: Model<"openai-completions"> = {
|
|
...bundled(TOKEN_PLAN),
|
|
headers: { "x-tp": "1", "x-shared": "discovered" },
|
|
};
|
|
const existing: Model<"openai-completions"> = {
|
|
...bundled(STANDARD),
|
|
headers: { "x-bundled": "1", "x-shared": "existing" },
|
|
};
|
|
const merged = mergeDiscoveredModel(discovered, existing);
|
|
expect(merged.headers).toEqual({
|
|
"x-bundled": "1",
|
|
"x-shared": "discovered",
|
|
"x-tp": "1",
|
|
});
|
|
});
|
|
|
|
test("provider override path: override baseUrl wins when no bundled entry", () => {
|
|
const discovered = bundled(STANDARD);
|
|
const merged = mergeDiscoveredModel(discovered, undefined, { baseUrl: TOKEN_PLAN });
|
|
expect(merged.baseUrl).toBe(TOKEN_PLAN);
|
|
});
|
|
|
|
test("user providerOverride baseUrl wins over discovered baseUrl even when bundled entry exists", () => {
|
|
const discovered = bundled(STANDARD);
|
|
const existing = bundled(STANDARD);
|
|
const merged = mergeDiscoveredModel(discovered, existing, { baseUrl: "https://my-proxy.example.com/v1" });
|
|
expect(merged.baseUrl).toBe("https://my-proxy.example.com/v1");
|
|
});
|
|
|
|
test("preserves provider override transport on rediscovery (#2555 openrouter gateway regression)", () => {
|
|
// Bundled openrouter entry carries transport=pi-native after
|
|
// applying providerOverride at boot (#loadBuiltInModels). Discovery
|
|
// refetched the same model from /v1/models — provider catalogs
|
|
// never set transport in defaults, so the discovered model has no
|
|
// transport hint of its own.
|
|
const existing: Model<"openai-completions"> = {
|
|
...bundled("http://localhost:4000"),
|
|
transport: "pi-native",
|
|
headers: { Authorization: "Bearer gateway-token" },
|
|
};
|
|
const discovered = bundled("http://localhost:4000");
|
|
const merged = mergeDiscoveredModel(discovered, existing, {
|
|
baseUrl: "http://localhost:4000",
|
|
transport: "pi-native",
|
|
headers: { Authorization: "Bearer gateway-token" },
|
|
});
|
|
expect(merged.transport).toBe("pi-native");
|
|
expect(merged.baseUrl).toBe("http://localhost:4000");
|
|
expect(merged.headers).toEqual({ Authorization: "Bearer gateway-token" });
|
|
});
|
|
|
|
test("provider override path (no bundled entry): transport flows through", () => {
|
|
const discovered = bundled("http://localhost:4000");
|
|
const merged = mergeDiscoveredModel(discovered, undefined, {
|
|
baseUrl: "http://localhost:4000",
|
|
transport: "pi-native",
|
|
});
|
|
expect(merged.transport).toBe("pi-native");
|
|
});
|
|
|
|
test("returns model untouched when no existing entry and no override", () => {
|
|
const discovered = bundled(TOKEN_PLAN);
|
|
const merged = mergeDiscoveredModel(discovered, undefined);
|
|
expect(merged).toEqual(discovered);
|
|
});
|
|
});
|