{"version":3,"sources":["/Users/ami/Documents/prometheus/x12i/ai-tools/dist/chunk-KLPSZJ7M.cjs","../src/sync/modelNameResolver/constants.ts","../src/sync/modelNameResolver/normalize.ts","../src/sync/openRouterRoutingEnv.ts","../src/sync/modelNameResolver/catalogIndexes.ts","../src/sync/modelNameResolver/stripVersionSuffix.ts","../src/sync/modelNameResolver/ModelNameResolver.ts"],"names":["stripped"],"mappings":"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","file":"/Users/ami/Documents/prometheus/x12i/ai-tools/dist/chunk-KLPSZJ7M.cjs","sourcesContent":[null,"export const DEFAULT_CONFIDENCE_THRESHOLD = 0.6;\n\nexport const PROVIDER_NORMALISATION_MAP: Record<string, string> = {\n  \"open-router\": \"openrouter\",\n  or: \"openrouter\",\n  oai: \"openai\",\n  \"open-ai\": \"openai\",\n  claude: \"anthropic\",\n  ant: \"anthropic\",\n  gemini: \"google\",\n  gcp: \"google\",\n  \"google-ai\": \"google\",\n  vertex: \"google\",\n  meta: \"meta-llama\",\n  llama: \"meta-llama\",\n  \"mistral-ai\": \"mistral\",\n  \"cohere-ai\": \"cohere\",\n};\n\nexport const LOCAL_PROVIDERS = new Set([\n  \"ollama\",\n  \"lmstudio\",\n  \"lm-studio\",\n  \"localai\",\n  \"local-ai\",\n  \"llamacpp\",\n  \"llama.cpp\",\n  \"llamafile\",\n  \"jan\",\n  \"koboldcpp\",\n]);\n\nexport const PROVIDER_INFERENCE_MAP: Array<{\n  pattern: RegExp;\n  provider: string;\n  notes?: string;\n}> = [\n  { pattern: /^gpt-/, provider: \"openai\" },\n  { pattern: /^o1($|-)/, provider: \"openai\" },\n  { pattern: /^o3($|-)/, provider: \"openai\" },\n  { pattern: /^o4($|-)/, provider: \"openai\" },\n  { pattern: /^chatgpt-/, provider: \"openai\" },\n  { pattern: /^text-davinci/, provider: \"openai\" },\n  { pattern: /^dall-e/, provider: \"openai\" },\n  { pattern: /^whisper/, provider: \"openai\" },\n  { pattern: /^tts-/, provider: \"openai\" },\n  { pattern: /^claude-/, provider: \"anthropic\" },\n  { pattern: /^gemini-/, provider: \"google\" },\n  { pattern: /^palm-/, provider: \"google\" },\n  { pattern: /^bison/, provider: \"google\" },\n  { pattern: /^llama-?[23]/, provider: \"meta-llama\" },\n  { pattern: /^llama/, provider: \"meta-llama\" },\n  { pattern: /^codellama/, provider: \"meta-llama\" },\n  { pattern: /^mistral-/, provider: \"mistral\" },\n  { pattern: /^mixtral-/, provider: \"mistral\" },\n  { pattern: /^codestral/, provider: \"mistral\" },\n  { pattern: /^ministral/, provider: \"mistral\" },\n  { pattern: /^command-/, provider: \"cohere\" },\n  { pattern: /^c4ai-/, provider: \"cohere\" },\n  { pattern: /^deepseek-/, provider: \"deepseek\" },\n  { pattern: /^grok-/, provider: \"x-ai\" },\n  { pattern: /^qwen/, provider: \"qwen\" },\n  { pattern: /^yi-/, provider: \"01-ai\" },\n  { pattern: /^sonar/, provider: \"perplexity\" },\n  { pattern: /^pplx-/, provider: \"perplexity\" },\n  { pattern: /^titan-/, provider: \"amazon\" },\n  { pattern: /^nova-/, provider: \"amazon\" },\n];\n","import { PROVIDER_NORMALISATION_MAP } from \"./constants.js\";\n\nexport function normalizeString(input: string): string {\n  let s = input.trim();\n  s = s.replace(/\\s+/g, \" \").replace(/ /g, \"-\");\n  s = s.toLowerCase();\n  s = s.replace(/^\\/+|\\/+$/g, \"\");\n  s = s.replace(/\\/+/g, \"/\");\n  if (\n    (s.startsWith('\"') && s.endsWith('\"')) ||\n    (s.startsWith(\"'\") && s.endsWith(\"'\"))\n  ) {\n    s = s.slice(1, -1);\n  }\n  return s;\n}\n\nexport function normalizeProvider(provider: string | undefined): string | undefined {\n  if (provider === undefined || provider === \"\") return undefined;\n  const n = normalizeString(provider);\n  return PROVIDER_NORMALISATION_MAP[n] ?? n;\n}\n\nexport function tokenise(s: string): string[] {\n  return s.split(/[-_./]+/).filter(Boolean);\n}\n\nexport function modelSlug(modelId: string): string {\n  const slash = modelId.indexOf(\"/\");\n  return slash >= 0 ? modelId.slice(slash + 1) : modelId;\n}\n","import { loadDotenv } from \"@x12i/env\";\nimport { parsePreferOpenRouter } from \"@x12i/ai-profiles\";\n\n/**\n * Env naming for vendor direct API keys: `{VENDOR}_API_KEY`\n * where VENDOR is the provider id in UPPER_SNAKE (hyphens → underscores).\n *\n * Examples: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `META_LLAMA_API_KEY`, `X_AI_API_KEY`\n */\nexport function providerIdToEnvKeyPrefix(providerId: string): string {\n  return providerId\n    .trim()\n    .toLowerCase()\n    .replace(/-/g, \"_\")\n    .toUpperCase();\n}\n\nexport function vendorApiKeyEnvName(providerId: string): string {\n  return `${providerIdToEnvKeyPrefix(providerId)}_API_KEY`;\n}\n\nexport type OpenRouterRoutingConfig = {\n  /** OPENROUTER_API_KEY is set and non-empty */\n  hasOpenRouterKey: boolean;\n  /** From PREFER_OPENROUTER env; defaults to true when unset */\n  preferOpenRouter: boolean;\n  /** Read `{PROVIDER}_API_KEY` for a catalog provider id */\n  getVendorApiKey(providerId: string): string | undefined;\n};\n\n/**\n * Load routing hints from process env and optional `.env` (via @x12/env).\n */\nexport function loadOpenRouterRoutingEnv(\n  env: Record<string, string | undefined> = process.env,\n): OpenRouterRoutingConfig {\n  const dotenv = loadDotenv();\n  const merged: Record<string, string> = {};\n  for (const [k, v] of Object.entries(dotenv)) {\n    if (v !== undefined) merged[k] = v;\n  }\n  for (const [k, v] of Object.entries(env)) {\n    if (v !== undefined) merged[k] = v;\n  }\n\n  const openRouterKey = merged.OPENROUTER_API_KEY?.trim();\n  const preferOpenRouter = parsePreferOpenRouter(merged.PREFER_OPENROUTER) ?? true;\n\n  return {\n    hasOpenRouterKey: Boolean(openRouterKey),\n    preferOpenRouter,\n    getVendorApiKey(providerId: string) {\n      const name = vendorApiKeyEnvName(providerId);\n      const val = merged[name]?.trim();\n      return val || undefined;\n    },\n  };\n}\n\n/**\n * Default route via OpenRouter when:\n * 1. OPENROUTER_API_KEY is set AND PREFER_OPENROUTER is true (default), or\n * 2. OPENROUTER_API_KEY is set AND the vendor's `{VENDOR}_API_KEY` is missing.\n */\nexport function shouldDefaultRouteViaOpenRouter(\n  providerId: string | undefined,\n  config: OpenRouterRoutingConfig,\n): boolean {\n  if (!config.hasOpenRouterKey) return false;\n  if (providerId && providerId !== \"openrouter\") {\n    const vendorKey = config.getVendorApiKey(providerId);\n    if (!vendorKey) return true;\n    return config.preferOpenRouter;\n  }\n  return config.preferOpenRouter;\n}\n\nexport type EffectiveOpenRouterTransportInput = {\n  provider?: string;\n  modelId?: string;\n  routeViaOpenRouter?: boolean;\n};\n\n/**\n * Whether the effective runtime transport is OpenRouter.\n * Honors explicit gateway override, then env defaults from {@link loadOpenRouterRoutingEnv}.\n */\nexport function isEffectiveOpenRouterTransport(\n  config: OpenRouterRoutingConfig,\n  input: EffectiveOpenRouterTransportInput = {},\n): boolean {\n  if (input.routeViaOpenRouter === true) return true;\n  if (input.routeViaOpenRouter === false) return false;\n\n  const provider = input.provider?.trim().toLowerCase();\n  if (provider === \"openrouter\") return true;\n\n  const vendor =\n    input.modelId?.includes(\"/\") === true\n      ? input.modelId.split(\"/\")[0]!.trim().toLowerCase()\n      : provider;\n\n  if (vendor && vendor !== \"openrouter\") {\n    return shouldDefaultRouteViaOpenRouter(vendor, config);\n  }\n\n  if (config.hasOpenRouterKey && config.preferOpenRouter) return true;\n  return false;\n}\n","import type { AiModelRecord } from \"../../models/types.js\";\nimport { normalizeString } from \"./normalize.js\";\nimport type { CatalogIndexes } from \"./types.js\";\n\nexport function buildCatalogIndexes(catalog: Map<string, AiModelRecord>): CatalogIndexes {\n  const aliasIndex = new Map<string, string>();\n  const slugIndex = new Map<string, string>();\n  const prefixCounts = new Map<string, number>();\n\n  for (const record of catalog.values()) {\n    for (const alias of record.aliases) {\n      aliasIndex.set(normalizeString(alias), record.modelId);\n    }\n    if (record.canonicalSlug) {\n      slugIndex.set(normalizeString(record.canonicalSlug), record.modelId);\n    }\n    const pid = record.providerId;\n    prefixCounts.set(pid, (prefixCounts.get(pid) ?? 0) + 1);\n  }\n\n  const providerPrefixesBySize = [...prefixCounts.entries()]\n    .sort((a, b) => b[1] - a[1])\n    .map(([p]) => p);\n\n  return { aliasIndex, slugIndex, providerPrefixesBySize };\n}\n","import { normalizeString } from \"./normalize.js\";\n\n/** Patterns stripped when resolving versioned runtime model ids to catalog entries. */\nexport const VERSION_STRIP_PATTERNS: ReadonlyArray<{ re: RegExp; label: string }> = [\n  { re: /-\\d{4}-\\d{2}-\\d{2}$/, label: \"date YYYY-MM-DD\" },\n  { re: /-\\d{8}$/, label: \"date YYYYMMDD\" },\n  { re: /-v\\d+$/, label: \"version -vN\" },\n  { re: /-\\d+\\.\\d+\\.\\d+$/, label: \"semver\" },\n  { re: /:\\d+[a-z]?$/i, label: \"colon version\" },\n  { re: /-\\d{4,}$/, label: \"build number\" },\n  { re: /@\\d+$/, label: \"epoch @NNNN\" },\n];\n\n/** Strip version/date suffix from a bare slug (no `provider/` prefix). */\nexport function stripModelSlugSuffix(slug: string): string | null {\n  for (const { re } of VERSION_STRIP_PATTERNS) {\n    if (re.test(slug)) {\n      return slug.replace(re, \"\");\n    }\n  }\n  return null;\n}\n\n/**\n * Strip a version/date suffix from a model id (`gpt-5.5-2026-04-23` → `gpt-5.5`,\n * `deepseek/deepseek-v3.2-2025-06-01` → `deepseek/deepseek-v3.2`). Returns null when nothing was stripped.\n */\nexport function stripModelVersionSuffix(model: string): string | null {\n  const normalized = normalizeString(model);\n  if (!normalized) return null;\n\n  if (normalized.includes(\"/\")) {\n    const slash = normalized.indexOf(\"/\");\n    const prefix = normalized.slice(0, slash);\n    const slug = normalized.slice(slash + 1);\n    const stripped = stripModelSlugSuffix(slug);\n    if (stripped && stripped !== slug) {\n      return `${prefix}/${stripped}`;\n    }\n    return null;\n  }\n\n  const stripped = stripModelSlugSuffix(normalized);\n  return stripped && stripped !== normalized ? stripped : null;\n}\n","import { ModelResolutionError } from \"../../errors.js\";\nimport type { AiModelRecord } from \"../../models/types.js\";\nimport {\n  loadOpenRouterRoutingEnv,\n  shouldDefaultRouteViaOpenRouter,\n} from \"../openRouterRoutingEnv.js\";\nimport { buildCatalogIndexes } from \"./catalogIndexes.js\";\nimport {\n  DEFAULT_CONFIDENCE_THRESHOLD,\n  LOCAL_PROVIDERS,\n  PROVIDER_INFERENCE_MAP,\n} from \"./constants.js\";\nimport { modelSlug, normalizeProvider, normalizeString, tokenise } from \"./normalize.js\";\nimport { stripModelSlugSuffix, stripModelVersionSuffix } from \"./stripVersionSuffix.js\";\nimport type {\n  CatalogIndexes,\n  ModelResolutionInput,\n  ModelResolutionNotFound,\n  ModelResolutionResult,\n  ModelResolutionSuccess,\n  ModelResolverOptions,\n  ResolutionStrategy,\n  ResolverContext,\n} from \"./types.js\";\n\ntype MatchCandidate = {\n  modelId: string;\n  record: AiModelRecord | null;\n  confidence: number;\n  strategy: ResolutionStrategy;\n  reason: string;\n};\n\nexport class ModelNameResolver {\n  private readonly catalog: Map<string, AiModelRecord>;\n  private readonly indexes: CatalogIndexes;\n  private readonly ctx: ResolverContext;\n\n  constructor(\n    catalog: Map<string, AiModelRecord>,\n    options: ModelResolverOptions = {},\n  ) {\n    this.catalog = catalog;\n    this.indexes = buildCatalogIndexes(catalog);\n    this.ctx = {\n      catalog,\n      indexes: this.indexes,\n      options: {\n        confidenceThreshold: options.confidenceThreshold ?? DEFAULT_CONFIDENCE_THRESHOLD,\n        additionalProviderPatterns: options.additionalProviderPatterns ?? [],\n        additionalLocalProviders: options.additionalLocalProviders ?? [],\n        routingEnv: options.routingEnv ?? loadOpenRouterRoutingEnv(),\n        routeViaOpenRouter: options.routeViaOpenRouter,\n      },\n    };\n  }\n\n  resolve(input: ModelResolutionInput): ModelResolutionResult {\n    const attempted: ResolutionStrategy[] = [];\n    const threshold = this.ctx.options.confidenceThreshold;\n\n    const provider = normalizeProvider(input.provider);\n    const model = normalizeString(input.model);\n    const normalisedInput = model;\n\n    // S10 — local provider (before catalog strategies)\n    const localProviders = new Set([\n      ...LOCAL_PROVIDERS,\n      ...this.ctx.options.additionalLocalProviders.map((p) => normalizeString(p)),\n    ]);\n    if (provider && localProviders.has(provider)) {\n      attempted.push(\"local-provider-passthrough\");\n      return this.success({\n        modelId: model,\n        record: null,\n        confidence: 0.99,\n        strategy: \"local-provider-passthrough\",\n        reason: `Local provider \"${provider}\" — model not looked up in catalog`,\n        resolvedVia: attempted,\n        normalisedInput,\n        provider,\n        originalProvider: input.provider,\n      });\n    }\n\n    const { match, rejected } = this.runPipeline(model, provider, attempted, threshold, []);\n    if (match) {\n      return this.success({\n        modelId: match.modelId,\n        record: match.record,\n        confidence: match.confidence,\n        strategy: match.strategy,\n        reason: match.reason,\n        resolvedVia: match.resolvedVia,\n        normalisedInput,\n        provider,\n        originalProvider: input.provider,\n      });\n    }\n\n    const notFound: ModelResolutionNotFound = {\n      found: false,\n      modelId: null,\n      record: null,\n      attemptedStrategies: attempted,\n      reason: \"No catalog entry matched after all strategies.\",\n      bestRejectedCandidate: rejected,\n    };\n    return notFound;\n  }\n\n  resolveMany(inputs: ModelResolutionInput[]): ModelResolutionResult[] {\n    return inputs.map((i) => this.resolve(i));\n  }\n\n  resolveOrThrow(input: ModelResolutionInput): ModelResolutionSuccess {\n    const result = this.resolve(input);\n    if (result.found) return result;\n    throw new ModelResolutionError(input, result);\n  }\n\n  private runPipeline(\n    model: string,\n    provider: string | undefined,\n    attempted: ResolutionStrategy[],\n    threshold: number,\n    resolvedVia: ResolutionStrategy[],\n    depth = 0,\n  ): {\n    match: (MatchCandidate & { resolvedVia: ResolutionStrategy[] }) | null;\n    rejected?: ModelResolutionNotFound[\"bestRejectedCandidate\"];\n  } {\n    if (depth > 12) return { match: null };\n\n    const tryMatch = (\n      strategy: ResolutionStrategy,\n      fn: () => MatchCandidate | null,\n    ): (MatchCandidate & { resolvedVia: ResolutionStrategy[] }) | null => {\n      if (!attempted.includes(strategy)) attempted.push(strategy);\n      const m = fn();\n      if (m && m.confidence >= threshold) {\n        return { ...m, resolvedVia: [...resolvedVia, strategy] };\n      }\n      return null;\n    };\n\n    let hit = tryMatch(\"exact-match\", () => this.exactMatch(model));\n    if (hit) return { match: hit };\n\n    hit = tryMatch(\"catalog-alias-match\", () => this.catalogAliasMatch(model));\n    if (hit) return { match: hit };\n\n    hit = tryMatch(\"canonical-slug-match\", () => this.slugMatch(model));\n    if (hit) return { match: hit };\n\n    hit = tryMatch(\"provider-prefix-injection\", () =>\n      this.providerPrefixInjection(model, provider),\n    );\n    if (hit) return { match: hit };\n\n    hit = tryMatch(\"cross-provider-correction\", () => this.crossProviderCorrection(model));\n    if (hit) return { match: hit };\n\n    const stripped = stripModelVersionSuffix(model);\n    if (stripped && stripped !== model) {\n      if (!attempted.includes(\"version-suffix-strip\")) attempted.push(\"version-suffix-strip\");\n      const inner = this.runPipeline(\n        stripped,\n        provider,\n        attempted,\n        threshold,\n        [...resolvedVia, \"version-suffix-strip\"],\n        depth + 1,\n      );\n      if (inner.match) {\n        return {\n          match: {\n            ...inner.match,\n            confidence: 0.85,\n            strategy: \"version-suffix-strip\",\n            reason: `Stripped version suffix from \"${model}\" → \"${stripped}\" then ${inner.match.reason}`,\n            resolvedVia: [...resolvedVia, \"version-suffix-strip\", ...inner.match.resolvedVia],\n          },\n        };\n      }\n    }\n\n    hit = tryMatch(\"date-suffix-strip\", () => this.dateSuffixMatch(model));\n    if (hit) return { match: hit };\n\n    const partial = this.partialMatch(model, provider);\n    if (partial) {\n      if (!attempted.includes(\"partial-name-match\")) attempted.push(\"partial-name-match\");\n      if (partial.confidence >= threshold) {\n        return {\n          match: { ...partial, resolvedVia: [...resolvedVia, \"partial-name-match\"] },\n        };\n      }\n      return {\n        match: null,\n        rejected: {\n          modelId: partial.modelId,\n          confidence: partial.confidence,\n          reason: partial.reason,\n        },\n      };\n    }\n\n    return { match: null };\n  }\n\n  private exactMatch(model: string): MatchCandidate | null {\n    const record = this.catalog.get(model);\n    if (!record) return null;\n    return {\n      modelId: model,\n      record,\n      confidence: 1,\n      strategy: \"exact-match\",\n      reason: `Exact catalog match for \"${model}\"`,\n    };\n  }\n\n  private catalogAliasMatch(model: string): MatchCandidate | null {\n    const modelId = this.indexes.aliasIndex.get(model);\n    if (!modelId) return null;\n    const record = this.catalog.get(modelId);\n    if (!record) return null;\n    return {\n      modelId,\n      record,\n      confidence: 1,\n      strategy: \"catalog-alias-match\",\n      reason: `Catalog alias \"${model}\" → \"${modelId}\"`,\n    };\n  }\n\n  private slugMatch(model: string): MatchCandidate | null {\n    const modelId = this.indexes.slugIndex.get(model);\n    if (!modelId) return null;\n    const record = this.catalog.get(modelId);\n    if (!record) return null;\n    return {\n      modelId,\n      record,\n      confidence: 0.95,\n      strategy: \"canonical-slug-match\",\n      reason: `Canonical slug match \"${model}\" → \"${modelId}\"`,\n    };\n  }\n\n  private inferProviderFromSlug(slug: string): string | undefined {\n    const patterns = [\n      ...this.ctx.options.additionalProviderPatterns,\n      ...PROVIDER_INFERENCE_MAP,\n    ];\n    for (const { pattern, provider } of patterns) {\n      if (pattern.test(slug)) return provider;\n    }\n    return undefined;\n  }\n\n  private providerPrefixInjection(\n    model: string,\n    provider: string | undefined,\n  ): MatchCandidate | null {\n    if (model.includes(\"/\")) return null;\n\n    const prefixes: string[] = [];\n    if (provider && provider !== \"openrouter\") {\n      prefixes.push(provider);\n    }\n    const inferred = this.inferProviderFromSlug(model);\n    if (inferred && !prefixes.includes(inferred)) prefixes.push(inferred);\n    for (const p of this.indexes.providerPrefixesBySize) {\n      if (!prefixes.includes(p)) prefixes.push(p);\n    }\n\n    for (const prefix of prefixes) {\n      const candidate = `${prefix}/${model}`;\n      let record = this.catalog.get(candidate);\n      let modelId = candidate;\n      if (!record) {\n        const latest = this.findLatestVersioned(candidate);\n        if (latest) {\n          record = latest.record;\n          modelId = latest.modelId;\n        }\n      }\n      if (record) {\n        const viaInference = inferred === prefix;\n        return {\n          modelId,\n          record,\n          confidence: 0.95,\n          strategy: \"provider-prefix-injection\",\n          reason: viaInference\n            ? `Injected provider prefix \"${prefix}/\" inferred from model name pattern`\n            : `Injected provider prefix \"${prefix}/\"`,\n        };\n      }\n    }\n    return null;\n  }\n\n  private crossProviderCorrection(model: string): MatchCandidate | null {\n    if (!model.includes(\"/\")) return null;\n    const [wrongPrefix, ...rest] = model.split(\"/\");\n    const slug = rest.join(\"/\");\n    const inferred = this.inferProviderFromSlug(slug);\n    if (!inferred || inferred === wrongPrefix) return null;\n    const candidate = `${inferred}/${slug}`;\n    let record = this.catalog.get(candidate);\n    let modelId = candidate;\n    if (!record) {\n      const latest = this.findLatestVersioned(candidate);\n      if (!latest) return null;\n      record = latest.record;\n      modelId = latest.modelId;\n    }\n    return {\n      modelId,\n      record,\n      confidence: 0.9,\n      strategy: \"cross-provider-correction\",\n      reason: `Corrected provider prefix from \"${wrongPrefix}\" to \"${inferred}\" based on model name pattern`,\n    };\n  }\n\n  private dateSuffixMatch(model: string): MatchCandidate | null {\n    if (model.includes(\"/\")) {\n      const [prefix, slug] = model.split(\"/\", 2);\n      const stripped = stripModelSlugSuffix(slug);\n      if (stripped && stripped !== slug) {\n        const candidate = `${prefix}/${stripped}`;\n        const record = this.catalog.get(candidate);\n        if (record) {\n          return {\n            modelId: candidate,\n            record,\n            confidence: 0.85,\n            strategy: \"date-suffix-strip\",\n            reason: `Stripped date from slug \"${slug}\" → \"${stripped}\"`,\n          };\n        }\n      }\n    }\n\n    const latest = this.findLatestVersioned(model);\n    if (latest) {\n      return {\n        modelId: latest.modelId,\n        record: latest.record,\n        confidence: 0.85,\n        strategy: \"date-suffix-strip\",\n        reason: `Matched \"${model}\" to versioned catalog entry \"${latest.modelId}\" (latest)`,\n      };\n    }\n    return null;\n  }\n\n  private findLatestVersioned(\n    baseModelId: string,\n  ): { modelId: string; record: AiModelRecord } | null {\n    const candidates: Array<{ modelId: string; record: AiModelRecord; suffix: string }> = [];\n    for (const [modelId, record] of this.catalog) {\n      if (modelId === baseModelId) continue;\n      if (modelId.startsWith(`${baseModelId}-`)) {\n        candidates.push({\n          modelId,\n          record,\n          suffix: modelId.slice(baseModelId.length + 1),\n        });\n      }\n    }\n    if (candidates.length === 0) return null;\n    candidates.sort((a, b) => b.suffix.localeCompare(a.suffix));\n    const best = candidates[0]!;\n    return { modelId: best.modelId, record: best.record };\n  }\n\n  private partialMatch(\n    model: string,\n    provider: string | undefined,\n  ): (MatchCandidate & { resolvedVia?: ResolutionStrategy[] }) | null {\n    let best: { modelId: string; record: AiModelRecord; score: number } | null = null;\n    const inputTokens = tokenise(model);\n    if (inputTokens.length === 0) return null;\n\n    for (const record of this.catalog.values()) {\n      const recordTokens = new Set([\n        ...tokenise(record.modelId),\n        ...tokenise(record.name ?? \"\"),\n      ]);\n      const intersection = inputTokens.filter((t) => recordTokens.has(t));\n      const overlapRatio = intersection.length / inputTokens.length;\n      let score = overlapRatio * 0.6;\n\n      if (record.modelId.includes(model)) score += 0.3;\n      const slug = modelSlug(record.modelId);\n      if (model.includes(slug)) score += 0.2;\n\n      if (provider && provider === record.providerId) score += 0.15;\n\n      const lenPenalty =\n        (Math.abs(model.length - slug.length) / Math.max(model.length, slug.length, 1)) * 0.1;\n      score -= lenPenalty;\n\n      if (!best || score > best.score) {\n        best = { modelId: record.modelId, record, score };\n      }\n    }\n\n    if (!best || best.score < 0.65) return null;\n    return {\n      modelId: best.modelId,\n      record: best.record,\n      confidence: 0.65,\n      strategy: \"partial-name-match\",\n      reason: `Partial token match (score ${best.score.toFixed(2)})`,\n    };\n  }\n\n  private computeRoutedViaOpenRouter(\n    modelId: string,\n    record: AiModelRecord | null,\n    provider: string | undefined,\n    originalProvider: string | undefined,\n  ): boolean {\n    const override = this.ctx.options.routeViaOpenRouter;\n    if (override === true) return true;\n    if (override === false) return false;\n\n    const norm = normalizeProvider(originalProvider) ?? provider;\n    if (norm === \"openrouter\") return true;\n    if (norm && norm !== \"openrouter\") {\n      if (shouldDefaultRouteViaOpenRouter(norm, this.ctx.options.routingEnv)) return true;\n      return false;\n    }\n\n    const recordProvider = record?.providerId;\n    if (recordProvider && shouldDefaultRouteViaOpenRouter(recordProvider, this.ctx.options.routingEnv)) {\n      return true;\n    }\n\n    if (record?.availableOnOpenRouter && modelId.includes(\"/\")) return true;\n    return false;\n  }\n\n  private toOpenRouterSlug(\n    modelId: string,\n    provider: string | undefined,\n    record: AiModelRecord | null,\n  ): string {\n    if (modelId.includes(\"/\")) return modelId;\n    const vendor =\n      record?.providerId ??\n      (provider && provider !== \"openrouter\" ? provider : undefined);\n    if (!vendor || vendor === \"openrouter\") return modelId;\n    return `${vendor}/${modelId}`;\n  }\n\n  private success(args: {\n    modelId: string;\n    record: AiModelRecord | null;\n    confidence: number;\n    strategy: ResolutionStrategy;\n    reason: string;\n    resolvedVia: ResolutionStrategy[];\n    normalisedInput: string;\n    provider?: string;\n    originalProvider?: string;\n  }): ModelResolutionSuccess {\n    const routedViaOpenRouter = this.computeRoutedViaOpenRouter(\n      args.modelId,\n      args.record,\n      args.provider,\n      args.originalProvider,\n    );\n    const modelId = routedViaOpenRouter\n      ? this.toOpenRouterSlug(args.modelId, args.provider, args.record)\n      : args.modelId;\n\n    return {\n      found: true,\n      modelId,\n      record: args.record,\n      routedViaOpenRouter,\n      confidence: args.confidence,\n      resolvedVia: args.resolvedVia,\n      resolvedReason: args.reason,\n      normalisedInput: args.normalisedInput,\n    };\n  }\n}\n"]}