refactor(aliases): migrate to pattern-based model alias resolution

Move from hardcoded exact aliases to pattern-based canonicalization:

- Populate PATTERN_ALIAS_RULES with regex patterns for:
  - Claude thinking variants (claude-opus-4-6-thinking → claude-opus-4-6)
  - Gemini tier suffixes (gemini-3.1-pro-{high,low} → gemini-3.1-pro)
- Add stripProviderPrefixForAliasLookup() for provider-prefixed models
  (anthropic/claude-sonnet-4-6 → claude-sonnet-4-6 for capability lookup)
- Preserve requestedModelID (with prefix) for API transport
- Reduce EXACT_ALIAS_RULES to exceptional cases only
  (gemini-3-pro-{high,low} → gemini-3-pro-preview)
- Comprehensive test coverage for patterns, prefix stripping, negatives

Addresses Discussion #2835 (pattern matching architecture)
Related to PR #2834 (alias guardrails)

41 targeted tests pass, 4467 full suite tests pass, tsc clean.
This commit is contained in:
YeonGyu-Kim
2026-03-26 12:04:50 +09:00
parent 23df6bd255
commit 829c58ccb0
5 changed files with 231 additions and 42 deletions
+55 -4
View File
@@ -178,8 +178,8 @@ describe("getModelCapabilities", () => {
expect(result.diagnostics).toMatchObject({
resolutionMode: "alias-backed",
canonicalization: {
source: "exact-alias",
ruleID: "claude-opus-4-6-thinking-legacy-alias",
source: "pattern-alias",
ruleID: "claude-thinking-legacy-alias",
},
snapshot: { source: "bundled-snapshot" },
})
@@ -202,13 +202,63 @@ describe("getModelCapabilities", () => {
expect(result.diagnostics).toMatchObject({
resolutionMode: "alias-backed",
canonicalization: {
source: "exact-alias",
source: "pattern-alias",
ruleID: "gemini-3.1-pro-tier-alias",
},
snapshot: { source: "bundled-snapshot" },
})
})
test("canonicalizes provider-prefixed gemini aliases without changing the transport-facing request", () => {
const result = getModelCapabilities({
providerID: "google",
modelID: "google/gemini-3.1-pro-high",
bundledSnapshot,
})
expect(result).toMatchObject({
requestedModelID: "google/gemini-3.1-pro-high",
canonicalModelID: "gemini-3.1-pro",
family: "gemini",
supportsThinking: true,
supportsTemperature: true,
maxOutputTokens: 65_000,
})
expect(result.diagnostics).toMatchObject({
resolutionMode: "alias-backed",
canonicalization: {
source: "pattern-alias",
ruleID: "gemini-3.1-pro-tier-alias",
},
snapshot: { source: "bundled-snapshot" },
})
})
test("canonicalizes provider-prefixed Claude thinking aliases to bare snapshot IDs", () => {
const result = getModelCapabilities({
providerID: "anthropic",
modelID: "anthropic/claude-opus-4-6-thinking",
bundledSnapshot,
})
expect(result).toMatchObject({
requestedModelID: "anthropic/claude-opus-4-6-thinking",
canonicalModelID: "claude-opus-4-6",
family: "claude-opus",
supportsThinking: true,
supportsTemperature: true,
maxOutputTokens: 128_000,
})
expect(result.diagnostics).toMatchObject({
resolutionMode: "alias-backed",
canonicalization: {
source: "pattern-alias",
ruleID: "claude-thinking-legacy-alias",
},
snapshot: { source: "bundled-snapshot" },
})
})
test("prefers runtime models.dev cache over bundled snapshot", () => {
const runtimeSnapshot: ModelCapabilitiesSnapshot = {
...bundledSnapshot,
@@ -272,7 +322,8 @@ describe("getModelCapabilities", () => {
})
expect(result).toMatchObject({
canonicalModelID: "openai/o3-mini",
requestedModelID: "openai/o3-mini",
canonicalModelID: "o3-mini",
family: "openai-reasoning",
variants: ["low", "medium", "high"],
reasoningEfforts: ["none", "minimal", "low", "medium", "high"],