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
+32 -27
View File
@@ -20,18 +20,6 @@ export type ModelIDAliasResolution = {
}
const EXACT_ALIAS_RULES: ReadonlyArray<ExactAliasRule> = [
{
aliasModelID: "gemini-3.1-pro-high",
ruleID: "gemini-3.1-pro-tier-alias",
canonicalModelID: "gemini-3.1-pro",
rationale: "OmO historically encoded Gemini tier selection in the model name instead of variant metadata.",
},
{
aliasModelID: "gemini-3.1-pro-low",
ruleID: "gemini-3.1-pro-tier-alias",
canonicalModelID: "gemini-3.1-pro",
rationale: "OmO historically encoded Gemini tier selection in the model name instead of variant metadata.",
},
{
aliasModelID: "gemini-3-pro-high",
ruleID: "gemini-3-pro-tier-alias",
@@ -44,30 +32,47 @@ const EXACT_ALIAS_RULES: ReadonlyArray<ExactAliasRule> = [
canonicalModelID: "gemini-3-pro-preview",
rationale: "Legacy Gemini 3 tier suffixes still need to land on the canonical preview model.",
},
{
aliasModelID: "claude-opus-4-6-thinking",
ruleID: "claude-opus-4-6-thinking-legacy-alias",
canonicalModelID: "claude-opus-4-6",
rationale: "OmO historically used a legacy compatibility suffix before models.dev shipped canonical thinking variants for newer Claude families.",
},
]
const EXACT_ALIAS_RULES_BY_MODEL: ReadonlyMap<string, ExactAliasRule> = new Map(
EXACT_ALIAS_RULES.map((rule) => [rule.aliasModelID, rule]),
)
const PATTERN_ALIAS_RULES: ReadonlyArray<PatternAliasRule> = []
const PATTERN_ALIAS_RULES: ReadonlyArray<PatternAliasRule> = [
{
ruleID: "claude-thinking-legacy-alias",
description: "Normalizes the legacy Claude Opus 4.6 thinking suffix to the canonical snapshot ID.",
match: (normalizedModelID) => /^claude-opus-4-6-thinking$/.test(normalizedModelID),
canonicalize: () => "claude-opus-4-6",
},
{
ruleID: "gemini-3.1-pro-tier-alias",
description: "Normalizes Gemini 3.1 Pro tier suffixes to the canonical snapshot ID.",
match: (normalizedModelID) => /^gemini-3\.1-pro-(?:high|low)$/.test(normalizedModelID),
canonicalize: () => "gemini-3.1-pro",
},
]
function normalizeLookupModelID(modelID: string): string {
return modelID.trim().toLowerCase()
}
function stripProviderPrefixForAliasLookup(normalizedModelID: string): string {
const slashIndex = normalizedModelID.indexOf("/")
if (slashIndex <= 0 || slashIndex === normalizedModelID.length - 1) {
return normalizedModelID
}
return normalizedModelID.slice(slashIndex + 1)
}
export function resolveModelIDAlias(modelID: string): ModelIDAliasResolution {
const normalizedModelID = normalizeLookupModelID(modelID)
const exactRule = EXACT_ALIAS_RULES_BY_MODEL.get(normalizedModelID)
const requestedModelID = normalizeLookupModelID(modelID)
const aliasLookupModelID = stripProviderPrefixForAliasLookup(requestedModelID)
const exactRule = EXACT_ALIAS_RULES_BY_MODEL.get(aliasLookupModelID)
if (exactRule) {
return {
requestedModelID: normalizedModelID,
requestedModelID,
canonicalModelID: exactRule.canonicalModelID,
source: "exact-alias",
ruleID: exactRule.ruleID,
@@ -75,21 +80,21 @@ export function resolveModelIDAlias(modelID: string): ModelIDAliasResolution {
}
for (const rule of PATTERN_ALIAS_RULES) {
if (!rule.match(normalizedModelID)) {
if (!rule.match(aliasLookupModelID)) {
continue
}
return {
requestedModelID: normalizedModelID,
canonicalModelID: rule.canonicalize(normalizedModelID),
requestedModelID,
canonicalModelID: rule.canonicalize(aliasLookupModelID),
source: "pattern-alias",
ruleID: rule.ruleID,
}
}
return {
requestedModelID: normalizedModelID,
canonicalModelID: normalizedModelID,
requestedModelID,
canonicalModelID: aliasLookupModelID,
source: "canonical",
}
}