fix: make provider-lock catalog check case-insensitive
The matcher compares selector ids case-insensitively, but the lock's
bundled-catalog lookup was exact-case: Anthropic/Claude-Opus-5 exact-
matched OpenRouter's flat id while getBundledModel("Anthropic", ...)
missed, silently re-enabling the aggregator shadow the lock exists to
prevent. Scan the named provider's bundled ids case-insensitively.
This commit is contained in:
@@ -22,7 +22,7 @@ import { modelMatchesHost } from "@oh-my-pi/pi-catalog/hosts";
|
||||
import { buildModelProviderPriorityRank } from "@oh-my-pi/pi-catalog/identity";
|
||||
import { stripThinkingVariantToken } from "@oh-my-pi/pi-catalog/identity/family";
|
||||
import { clampThinkingLevelForModel } from "@oh-my-pi/pi-catalog/model-thinking";
|
||||
import { type GeneratedProvider, getBundledModel, modelsAreEqual } from "@oh-my-pi/pi-catalog/models";
|
||||
import { type GeneratedProvider, getBundledModels, modelsAreEqual } from "@oh-my-pi/pi-catalog/models";
|
||||
import { DEFAULT_MODEL_PER_PROVIDER } from "@oh-my-pi/pi-catalog/provider-models";
|
||||
import { resolveBareVariantAlias, resolveVariantAlias } from "@oh-my-pi/pi-catalog/variant-collapse";
|
||||
import { fuzzyMatch } from "@oh-my-pi/pi-tui";
|
||||
@@ -619,12 +619,15 @@ function isProviderLockedCrossMatch(pattern: string, matchedModel: Model<Api>):
|
||||
if (slashIdx <= 0) {
|
||||
return false;
|
||||
}
|
||||
const provider = pattern.slice(0, slashIdx);
|
||||
const modelId = pattern.slice(slashIdx + 1);
|
||||
if (matchedModel.provider.toLowerCase() === provider.toLowerCase()) {
|
||||
const provider = pattern.slice(0, slashIdx).toLowerCase();
|
||||
const modelId = pattern.slice(slashIdx + 1).toLowerCase();
|
||||
if (matchedModel.provider.toLowerCase() === provider) {
|
||||
return false;
|
||||
}
|
||||
return getBundledModel(provider as GeneratedProvider, modelId) !== undefined;
|
||||
// Case-insensitive on both halves: the surrounding matcher lowercases the
|
||||
// selector before comparing ids, so the lock must not evaporate on case
|
||||
// variance (catalog provider keys are lowercase; model ids may not be).
|
||||
return getBundledModels(provider as GeneratedProvider).some(m => m.id.toLowerCase() === modelId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -704,6 +704,13 @@ describe("parseModelPattern", () => {
|
||||
expect(result.model).toBeUndefined();
|
||||
});
|
||||
|
||||
test("the provider lock is case-insensitive (Anthropic/Claude-Opus-5 still fails closed)", () => {
|
||||
// The matcher compares ids case-insensitively, so the lock must too —
|
||||
// otherwise case variance silently re-enables the aggregator shadow.
|
||||
const result = parseModelPattern("Anthropic/Claude-Opus-5", [openRouterOpus5]);
|
||||
expect(result.model).toBeUndefined();
|
||||
});
|
||||
|
||||
test("anthropic/claude-opus-5 resolves to the anthropic provider when it is available", () => {
|
||||
const result = parseModelPattern("anthropic/claude-opus-5", [anthropicOpus5, openRouterOpus5]);
|
||||
expect(result.model?.provider).toBe("anthropic");
|
||||
|
||||
Reference in New Issue
Block a user