{"version":3,"sources":["../src/normalize.ts","../src/provider-core.ts"],"sourcesContent":["import type { LlmConnectionConfig } from \"./parse.js\";\nimport {\n  ALIASES,\n  CACHE_TTLS,\n  CACHE_VALUES,\n  DURATION_RE,\n  PROVIDER_PARAMS,\n  bedrockSupportsCaching,\n  canHostOpenAIModels,\n  detectGatewaySubProvider,\n  detectProvider,\n  isGatewayProvider,\n  isReasoningModel,\n  providerFromHostAlias,\n  type Provider,\n} from \"./provider-core.js\";\n\nexport interface NormalizeChange {\n  from: string;\n  to: string;\n  value: string;\n  reason: string;\n}\n\nexport interface NormalizeResult {\n  config: LlmConnectionConfig;\n  provider: Provider | undefined;\n  /** Underlying provider extracted from gateway model prefix (e.g. \"anthropic\" from \"anthropic/claude-sonnet-4-5\"). */\n  subProvider: Provider | undefined;\n  changes: NormalizeChange[];\n}\n\nexport interface NormalizeOptions {\n  /** Include detailed change log in the result. */\n  verbose?: boolean;\n}\n\n/**\n * Normalize an LLM connection config's params for its target provider.\n *\n * 1. Expands shorthand aliases (e.g. `temp` → `temperature`)\n * 2. Maps canonical param names to provider-specific names\n *    (e.g. `max_tokens` → `maxOutputTokens` for Google)\n * 3. Normalizes special values (e.g. `cache=true` → `cache_control=ephemeral` for Anthropic)\n * 4. For OpenAI reasoning models, remaps `max_tokens` → `max_completion_tokens`\n *    and warns about unsupported sampling params\n */\nexport function normalize(\n  config: LlmConnectionConfig,\n  options: NormalizeOptions = {},\n): NormalizeResult {\n  const provider =\n    (config.hostAlias ? providerFromHostAlias(config.hostAlias) : undefined) ??\n    detectProvider(config.host);\n  const subProvider =\n    provider && isGatewayProvider(provider)\n      ? detectGatewaySubProvider(config.model)\n      : undefined;\n  const changes: NormalizeChange[] = [];\n  const params: Record<string, string> = {};\n\n  for (const [rawKey, value] of Object.entries(config.params)) {\n    let key = rawKey;\n\n    // Step 1: Expand aliases to canonical name\n    if (ALIASES[key]) {\n      const canonical = ALIASES[key];\n      if (options.verbose) {\n        changes.push({\n          from: key,\n          to: canonical,\n          value,\n          reason: `alias: \"${key}\" → \"${canonical}\"`,\n        });\n      }\n      key = canonical;\n    }\n\n    // Step 2: Handle special \"cache\" param\n    if (key === \"cache\" && provider) {\n      let cacheValue = CACHE_VALUES[provider];\n\n      // Bedrock supports cache for Anthropic Claude and Amazon Nova models\n      if (provider === \"bedrock\" && !bedrockSupportsCaching(config.model)) {\n        cacheValue = undefined;\n      }\n\n      // Provider/model doesn't support cache — drop it\n      if (!cacheValue) {\n        if (options.verbose) {\n          changes.push({\n            from: \"cache\",\n            to: \"(dropped)\",\n            value,\n            reason: `${provider} does not use a cache param for this model (caching is automatic or unsupported)`,\n          });\n        }\n        continue;\n      }\n\n      const isBool = value === \"true\" || value === \"1\" || value === \"yes\";\n      const isDuration = DURATION_RE.test(value);\n\n      if (isBool || isDuration) {\n        const providerKey = PROVIDER_PARAMS[provider]?.[\"cache\"] ?? \"cache\";\n        if (options.verbose) {\n          changes.push({\n            from: \"cache\",\n            to: providerKey,\n            value: cacheValue,\n            reason: `cache=${value} → ${providerKey}=${cacheValue} for ${provider}`,\n          });\n        }\n        params[providerKey] = cacheValue;\n\n        // Emit cache_ttl when a duration is specified\n        if (isDuration && CACHE_TTLS[provider]) {\n          if (options.verbose) {\n            changes.push({\n              from: \"cache\",\n              to: \"cache_ttl\",\n              value,\n              reason: `cache=${value} → cache_ttl=${value} for ${provider}`,\n            });\n          }\n          params[\"cache_ttl\"] = value;\n        }\n        continue;\n      }\n    }\n\n    // Step 3: Map canonical → provider-specific param name\n    if (provider && PROVIDER_PARAMS[provider]) {\n      const providerKey = PROVIDER_PARAMS[provider][key];\n      if (providerKey && providerKey !== key) {\n        if (options.verbose) {\n          changes.push({\n            from: key,\n            to: providerKey,\n            value,\n            reason: `${provider} uses \"${providerKey}\" instead of \"${key}\"`,\n          });\n        }\n        key = providerKey;\n      }\n    }\n\n    // Step 4: OpenAI reasoning model adjustments (direct or via gateway)\n    if (\n      provider &&\n      canHostOpenAIModels(provider) &&\n      isReasoningModel(config.model) &&\n      key === \"max_tokens\"\n    ) {\n      if (options.verbose) {\n        changes.push({\n          from: \"max_tokens\",\n          to: \"max_completion_tokens\",\n          value,\n          reason:\n            \"OpenAI reasoning models use max_completion_tokens instead of max_tokens\",\n        });\n      }\n      key = \"max_completion_tokens\";\n    }\n\n    params[key] = value;\n  }\n\n  return {\n    config: { ...config, params },\n    provider,\n    subProvider,\n    changes,\n  };\n}\n","export type Provider =\n  | \"openai\"\n  | \"anthropic\"\n  | \"google\"\n  | \"mistral\"\n  | \"cohere\"\n  | \"bedrock\"\n  | \"openrouter\"\n  | \"vercel\";\n\nexport type HostAlias =\n  | Provider\n  | \"aistudio\"\n  | \"alibaba\"\n  | \"alibabacloud\"\n  | \"atlascloud\"\n  | \"baidu\"\n  | \"dashscope\"\n  | \"deepinfra\"\n  | \"fireworks\"\n  | \"fireworksai\"\n  | \"grok\"\n  | \"minimax\"\n  | \"novita\"\n  | \"novitaai\"\n  | \"parasail\"\n  | \"qianfan\"\n  | \"vertex\"\n  | \"venice\"\n  | \"wandb\"\n  | \"weightsandbiases\"\n  | \"xai\"\n  | \"xiaomi\";\n\ntype Env = Record<string, string | undefined>;\n\ndeclare const process:\n  | {\n      env?: Env;\n    }\n  | undefined;\n\nfunction hasOwn<T extends object>(object: T, key: PropertyKey): key is keyof T {\n  return Object.prototype.hasOwnProperty.call(object, key);\n}\n\nexport const HOST_ALIASES: Record<HostAlias, string> = {\n  openai: \"api.openai.com\",\n  anthropic: \"api.anthropic.com\",\n  google: \"generativelanguage.googleapis.com\",\n  aistudio: \"generativelanguage.googleapis.com\",\n  mistral: \"api.mistral.ai\",\n  cohere: \"api.cohere.com\",\n  bedrock: \"bedrock-runtime.us-east-1.amazonaws.com\",\n  openrouter: \"openrouter.ai\",\n  vercel: \"gateway.ai.vercel.app\",\n  alibaba: \"dashscope-intl.aliyuncs.com\",\n  alibabacloud: \"dashscope-intl.aliyuncs.com\",\n  dashscope: \"dashscope-intl.aliyuncs.com\",\n  fireworks: \"api.fireworks.ai\",\n  fireworksai: \"api.fireworks.ai\",\n  venice: \"api.venice.ai\",\n  parasail: \"api.parasail.io\",\n  deepinfra: \"api.deepinfra.com\",\n  atlascloud: \"api.atlascloud.ai\",\n  novita: \"api.novita.ai\",\n  novitaai: \"api.novita.ai\",\n  grok: \"api.x.ai\",\n  xai: \"api.x.ai\",\n  wandb: \"api.inference.wandb.ai\",\n  weightsandbiases: \"api.inference.wandb.ai\",\n  baidu: \"qianfan.baidubce.com\",\n  qianfan: \"qianfan.baidubce.com\",\n  vertex: \"aiplatform.googleapis.com\",\n  xiaomi: \"api.xiaomimimo.com\",\n  minimax: \"api.minimax.io\",\n};\n\nexport interface HostResolution {\n  /** The hostname/host to use for requests. */\n  host: string;\n  /** The provider alias that was expanded, if any. */\n  alias?: HostAlias;\n}\n\nfunction readProcessEnv(): Env {\n  return typeof process !== \"undefined\" && process.env ? process.env : {};\n}\n\nfunction normalizeHostValue(value: string): string {\n  const trimmed = value.trim();\n  if (!trimmed) return trimmed;\n\n  try {\n    if (trimmed.includes(\"://\")) {\n      return new URL(trimmed).host;\n    }\n  } catch {\n    // Fall through and treat it as a host-ish string.\n  }\n\n  return trimmed.replace(/^\\/\\//, \"\").split(\"/\")[0] ?? trimmed;\n}\n\nfunction envHostOverride(alias: HostAlias, env: Env): string | undefined {\n  const upper = alias.toUpperCase();\n  const override =\n    env[`LLM_STRINGS_${upper}_HOST`] ?? env[`LLM_STRINGS_HOST_${upper}`];\n  return override?.trim() ? override : undefined;\n}\n\n/**\n * Resolve short provider host aliases (`openai`, `anthropic`, etc.) to their\n * canonical hostnames. Per-provider environment overrides can redirect aliases\n * to regional or private endpoints:\n *\n * - `LLM_STRINGS_OPENAI_HOST`\n * - `LLM_STRINGS_HOST_OPENAI`\n */\nexport function resolveHostAlias(\n  host: string,\n  env: Env = readProcessEnv(),\n): HostResolution {\n  const normalizedHost = host.toLowerCase();\n  if (!hasOwn(HOST_ALIASES, normalizedHost)) {\n    return { host };\n  }\n\n  const alias = normalizedHost as HostAlias;\n  const override = envHostOverride(alias, env);\n  return {\n    host: normalizeHostValue(override ?? HOST_ALIASES[alias]),\n    alias,\n  };\n}\n\nexport function providerFromHostAlias(alias: string): Provider | undefined {\n  const normalizedAlias = alias.toLowerCase();\n  if (hasOwn(PROVIDER_PARAMS, normalizedAlias)) {\n    return normalizedAlias as Provider;\n  }\n  return undefined;\n}\n\nexport function detectProvider(host: string): Provider | undefined {\n  host = host.toLowerCase();\n\n  // Gateways and aggregators first — they proxy to other providers\n  if (host.includes(\"openrouter\")) return \"openrouter\";\n  if (host.includes(\"gateway.ai.vercel\")) return \"vercel\";\n  // Bedrock before native providers since it hosts models from multiple vendors\n  if (host.includes(\"amazonaws\") || host.includes(\"bedrock\")) return \"bedrock\";\n  if (host.includes(\"openai\")) return \"openai\";\n  if (host.includes(\"anthropic\") || host.includes(\"claude\")) return \"anthropic\";\n  if (host.includes(\"googleapis\") || host.includes(\"google\")) return \"google\";\n  if (host.includes(\"mistral\")) return \"mistral\";\n  if (host.includes(\"cohere\")) return \"cohere\";\n  return undefined;\n}\n\n/**\n * Shorthand aliases → canonical param name.\n * Canonical names use snake_case and follow OpenAI conventions where possible.\n */\nexport const ALIASES: Record<string, string> = {\n  // temperature\n  temp: \"temperature\",\n\n  // max_tokens\n  max: \"max_tokens\",\n  max_out: \"max_tokens\",\n  max_output: \"max_tokens\",\n  max_output_tokens: \"max_tokens\",\n  max_completion_tokens: \"max_tokens\",\n  maxOutputTokens: \"max_tokens\",\n  maxTokens: \"max_tokens\",\n\n  // top_p\n  topp: \"top_p\",\n  topP: \"top_p\",\n  nucleus: \"top_p\",\n\n  // top_k\n  topk: \"top_k\",\n  topK: \"top_k\",\n\n  // frequency_penalty\n  freq: \"frequency_penalty\",\n  freq_penalty: \"frequency_penalty\",\n  frequencyPenalty: \"frequency_penalty\",\n  repetition_penalty: \"frequency_penalty\",\n\n  // presence_penalty\n  pres: \"presence_penalty\",\n  pres_penalty: \"presence_penalty\",\n  presencePenalty: \"presence_penalty\",\n\n  // stop\n  stop_sequences: \"stop\",\n  stopSequences: \"stop\",\n  stop_sequence: \"stop\",\n\n  // seed\n  random_seed: \"seed\",\n  randomSeed: \"seed\",\n\n  // n (completions count)\n  candidateCount: \"n\",\n  candidate_count: \"n\",\n  num_completions: \"n\",\n\n  // effort / reasoning\n  reasoning_effort: \"effort\",\n  reasoning: \"effort\",\n\n  // cache\n  cache_control: \"cache\",\n  cacheControl: \"cache\",\n  cachePoint: \"cache\",\n  cache_point: \"cache\",\n};\n\n/**\n * Canonical param name → provider-specific API param name.\n * Only includes params the provider actually supports.\n */\nexport const PROVIDER_PARAMS: Record<Provider, Record<string, string>> = {\n  openai: {\n    temperature: \"temperature\",\n    max_tokens: \"max_tokens\",\n    top_p: \"top_p\",\n    frequency_penalty: \"frequency_penalty\",\n    presence_penalty: \"presence_penalty\",\n    stop: \"stop\",\n    n: \"n\",\n    seed: \"seed\",\n    stream: \"stream\",\n    effort: \"reasoning_effort\",\n  },\n  anthropic: {\n    temperature: \"temperature\",\n    max_tokens: \"max_tokens\",\n    top_p: \"top_p\",\n    top_k: \"top_k\",\n    stop: \"stop_sequences\",\n    stream: \"stream\",\n    effort: \"effort\",\n    cache: \"cache_control\",\n    cache_ttl: \"cache_ttl\",\n  },\n  google: {\n    temperature: \"temperature\",\n    max_tokens: \"maxOutputTokens\",\n    top_p: \"topP\",\n    top_k: \"topK\",\n    frequency_penalty: \"frequencyPenalty\",\n    presence_penalty: \"presencePenalty\",\n    stop: \"stopSequences\",\n    n: \"candidateCount\",\n    stream: \"stream\",\n    seed: \"seed\",\n    responseMimeType: \"responseMimeType\",\n    responseSchema: \"responseSchema\",\n  },\n  mistral: {\n    temperature: \"temperature\",\n    max_tokens: \"max_tokens\",\n    top_p: \"top_p\",\n    frequency_penalty: \"frequency_penalty\",\n    presence_penalty: \"presence_penalty\",\n    stop: \"stop\",\n    n: \"n\",\n    seed: \"random_seed\",\n    stream: \"stream\",\n    safe_prompt: \"safe_prompt\",\n    min_tokens: \"min_tokens\",\n  },\n  cohere: {\n    temperature: \"temperature\",\n    max_tokens: \"max_tokens\",\n    top_p: \"p\",\n    top_k: \"k\",\n    frequency_penalty: \"frequency_penalty\",\n    presence_penalty: \"presence_penalty\",\n    stop: \"stop_sequences\",\n    stream: \"stream\",\n    seed: \"seed\",\n  },\n  bedrock: {\n    // Bedrock Converse API uses camelCase\n    temperature: \"temperature\",\n    max_tokens: \"maxTokens\",\n    top_p: \"topP\",\n    top_k: \"topK\", // Claude models via additionalModelRequestFields\n    stop: \"stopSequences\",\n    stream: \"stream\",\n    cache: \"cache_control\",\n    cache_ttl: \"cache_ttl\",\n  },\n  openrouter: {\n    // OpenAI-compatible API with extra routing params\n    temperature: \"temperature\",\n    max_tokens: \"max_tokens\",\n    top_p: \"top_p\",\n    top_k: \"top_k\",\n    frequency_penalty: \"frequency_penalty\",\n    presence_penalty: \"presence_penalty\",\n    stop: \"stop\",\n    n: \"n\",\n    seed: \"seed\",\n    stream: \"stream\",\n    effort: \"reasoning_effort\",\n  },\n  vercel: {\n    // OpenAI-compatible gateway\n    temperature: \"temperature\",\n    max_tokens: \"max_tokens\",\n    top_p: \"top_p\",\n    top_k: \"top_k\",\n    frequency_penalty: \"frequency_penalty\",\n    presence_penalty: \"presence_penalty\",\n    stop: \"stop\",\n    n: \"n\",\n    seed: \"seed\",\n    stream: \"stream\",\n    effort: \"reasoning_effort\",\n  },\n};\n\n/**\n * Validation specs per provider, keyed by provider-specific param name.\n */\nexport interface ParamSpec {\n  type: \"number\" | \"string\" | \"boolean\";\n  min?: number;\n  max?: number;\n  values?: string[];\n  default?: string | number | boolean;\n  description?: string;\n}\n\nexport const PARAM_SPECS: Record<Provider, Record<string, ParamSpec>> = {\n  openai: {\n    temperature: {\n      type: \"number\",\n      min: 0,\n      max: 2,\n      default: 0.7,\n      description: \"Controls randomness\",\n    },\n    max_tokens: {\n      type: \"number\",\n      min: 1,\n      default: 4096,\n      description: \"Maximum output tokens\",\n    },\n    top_p: {\n      type: \"number\",\n      min: 0,\n      max: 1,\n      default: 1,\n      description: \"Nucleus sampling\",\n    },\n    frequency_penalty: {\n      type: \"number\",\n      min: -2,\n      max: 2,\n      default: 0,\n      description: \"Penalize frequent tokens\",\n    },\n    presence_penalty: {\n      type: \"number\",\n      min: -2,\n      max: 2,\n      default: 0,\n      description: \"Penalize repeated topics\",\n    },\n    stop: { type: \"string\", description: \"Stop sequences\" },\n    n: { type: \"number\", min: 1, default: 1, description: \"Completions count\" },\n    seed: { type: \"number\", description: \"Random seed\" },\n    stream: { type: \"boolean\", default: false, description: \"Stream response\" },\n    reasoning_effort: {\n      type: \"string\",\n      values: [\"none\", \"minimal\", \"low\", \"medium\", \"high\", \"xhigh\"],\n      default: \"medium\",\n      description: \"Reasoning effort\",\n    },\n  },\n  anthropic: {\n    temperature: {\n      type: \"number\",\n      min: 0,\n      max: 1,\n      default: 0.7,\n      description: \"Controls randomness\",\n    },\n    max_tokens: {\n      type: \"number\",\n      min: 1,\n      default: 4096,\n      description: \"Maximum output tokens\",\n    },\n    top_p: {\n      type: \"number\",\n      min: 0,\n      max: 1,\n      default: 1,\n      description: \"Nucleus sampling\",\n    },\n    top_k: {\n      type: \"number\",\n      min: 0,\n      default: 40,\n      description: \"Top-K sampling\",\n    },\n    stop_sequences: { type: \"string\", description: \"Stop sequences\" },\n    stream: { type: \"boolean\", default: false, description: \"Stream response\" },\n    effort: {\n      type: \"string\",\n      values: [\"low\", \"medium\", \"high\", \"max\"],\n      default: \"medium\",\n      description: \"Thinking effort\",\n    },\n    cache_control: {\n      type: \"string\",\n      values: [\"ephemeral\"],\n      default: \"ephemeral\",\n      description: \"Cache control\",\n    },\n    cache_ttl: {\n      type: \"string\",\n      values: [\"5m\", \"1h\"],\n      default: \"5m\",\n      description: \"Cache TTL\",\n    },\n  },\n  google: {\n    temperature: {\n      type: \"number\",\n      min: 0,\n      max: 2,\n      default: 0.7,\n      description: \"Controls randomness\",\n    },\n    maxOutputTokens: {\n      type: \"number\",\n      min: 1,\n      default: 4096,\n      description: \"Maximum output tokens\",\n    },\n    topP: {\n      type: \"number\",\n      min: 0,\n      max: 1,\n      default: 1,\n      description: \"Nucleus sampling\",\n    },\n    topK: {\n      type: \"number\",\n      min: 0,\n      default: 40,\n      description: \"Top-K sampling\",\n    },\n    frequencyPenalty: {\n      type: \"number\",\n      min: -2,\n      max: 2,\n      default: 0,\n      description: \"Penalize frequent tokens\",\n    },\n    presencePenalty: {\n      type: \"number\",\n      min: -2,\n      max: 2,\n      default: 0,\n      description: \"Penalize repeated topics\",\n    },\n    stopSequences: { type: \"string\", description: \"Stop sequences\" },\n    candidateCount: {\n      type: \"number\",\n      min: 1,\n      default: 1,\n      description: \"Candidate count\",\n    },\n    stream: { type: \"boolean\", default: false, description: \"Stream response\" },\n    seed: { type: \"number\", description: \"Random seed\" },\n    responseMimeType: { type: \"string\", description: \"Response MIME type\" },\n    responseSchema: { type: \"string\", description: \"Response schema\" },\n  },\n  mistral: {\n    temperature: {\n      type: \"number\",\n      min: 0,\n      max: 1,\n      default: 0.7,\n      description: \"Controls randomness\",\n    },\n    max_tokens: {\n      type: \"number\",\n      min: 1,\n      default: 4096,\n      description: \"Maximum output tokens\",\n    },\n    top_p: {\n      type: \"number\",\n      min: 0,\n      max: 1,\n      default: 1,\n      description: \"Nucleus sampling\",\n    },\n    frequency_penalty: {\n      type: \"number\",\n      min: -2,\n      max: 2,\n      default: 0,\n      description: \"Penalize frequent tokens\",\n    },\n    presence_penalty: {\n      type: \"number\",\n      min: -2,\n      max: 2,\n      default: 0,\n      description: \"Penalize repeated topics\",\n    },\n    stop: { type: \"string\", description: \"Stop sequences\" },\n    n: { type: \"number\", min: 1, default: 1, description: \"Completions count\" },\n    random_seed: { type: \"number\", description: \"Random seed\" },\n    stream: { type: \"boolean\", default: false, description: \"Stream response\" },\n    safe_prompt: {\n      type: \"boolean\",\n      default: false,\n      description: \"Enable safe prompt\",\n    },\n    min_tokens: {\n      type: \"number\",\n      min: 0,\n      default: 0,\n      description: \"Minimum tokens\",\n    },\n  },\n  cohere: {\n    temperature: {\n      type: \"number\",\n      min: 0,\n      max: 1,\n      default: 0.7,\n      description: \"Controls randomness\",\n    },\n    max_tokens: {\n      type: \"number\",\n      min: 1,\n      default: 4096,\n      description: \"Maximum output tokens\",\n    },\n    p: {\n      type: \"number\",\n      min: 0,\n      max: 1,\n      default: 1,\n      description: \"Nucleus sampling (p)\",\n    },\n    k: {\n      type: \"number\",\n      min: 0,\n      max: 500,\n      default: 40,\n      description: \"Top-K sampling (k)\",\n    },\n    frequency_penalty: {\n      type: \"number\",\n      min: 0,\n      max: 1,\n      default: 0,\n      description: \"Penalize frequent tokens\",\n    },\n    presence_penalty: {\n      type: \"number\",\n      min: 0,\n      max: 1,\n      default: 0,\n      description: \"Penalize repeated topics\",\n    },\n    stop_sequences: { type: \"string\", description: \"Stop sequences\" },\n    stream: { type: \"boolean\", default: false, description: \"Stream response\" },\n    seed: { type: \"number\", description: \"Random seed\" },\n  },\n  bedrock: {\n    // Converse API inferenceConfig params\n    temperature: {\n      type: \"number\",\n      min: 0,\n      max: 1,\n      default: 0.7,\n      description: \"Controls randomness\",\n    },\n    maxTokens: {\n      type: \"number\",\n      min: 1,\n      default: 4096,\n      description: \"Maximum output tokens\",\n    },\n    topP: {\n      type: \"number\",\n      min: 0,\n      max: 1,\n      default: 1,\n      description: \"Nucleus sampling\",\n    },\n    topK: {\n      type: \"number\",\n      min: 0,\n      default: 40,\n      description: \"Top-K sampling\",\n    },\n    stopSequences: { type: \"string\", description: \"Stop sequences\" },\n    stream: { type: \"boolean\", default: false, description: \"Stream response\" },\n    cache_control: {\n      type: \"string\",\n      values: [\"ephemeral\"],\n      default: \"ephemeral\",\n      description: \"Cache control\",\n    },\n    cache_ttl: {\n      type: \"string\",\n      values: [\"5m\", \"1h\"],\n      default: \"5m\",\n      description: \"Cache TTL\",\n    },\n  },\n  openrouter: {\n    // Loose validation — proxies to many providers with varying ranges\n    temperature: {\n      type: \"number\",\n      min: 0,\n      max: 2,\n      default: 0.7,\n      description: \"Controls randomness\",\n    },\n    max_tokens: {\n      type: \"number\",\n      min: 1,\n      default: 4096,\n      description: \"Maximum output tokens\",\n    },\n    top_p: {\n      type: \"number\",\n      min: 0,\n      max: 1,\n      default: 1,\n      description: \"Nucleus sampling\",\n    },\n    top_k: {\n      type: \"number\",\n      min: 0,\n      default: 40,\n      description: \"Top-K sampling\",\n    },\n    frequency_penalty: {\n      type: \"number\",\n      min: -2,\n      max: 2,\n      default: 0,\n      description: \"Penalize frequent tokens\",\n    },\n    presence_penalty: {\n      type: \"number\",\n      min: -2,\n      max: 2,\n      default: 0,\n      description: \"Penalize repeated topics\",\n    },\n    stop: { type: \"string\", description: \"Stop sequences\" },\n    n: { type: \"number\", min: 1, default: 1, description: \"Completions count\" },\n    seed: { type: \"number\", description: \"Random seed\" },\n    stream: { type: \"boolean\", default: false, description: \"Stream response\" },\n    reasoning_effort: {\n      type: \"string\",\n      values: [\"none\", \"minimal\", \"low\", \"medium\", \"high\", \"xhigh\"],\n      default: \"medium\",\n      description: \"Reasoning effort\",\n    },\n  },\n  vercel: {\n    // Loose validation — proxies to many providers with varying ranges\n    temperature: {\n      type: \"number\",\n      min: 0,\n      max: 2,\n      default: 0.7,\n      description: \"Controls randomness\",\n    },\n    max_tokens: {\n      type: \"number\",\n      min: 1,\n      default: 4096,\n      description: \"Maximum output tokens\",\n    },\n    top_p: {\n      type: \"number\",\n      min: 0,\n      max: 1,\n      default: 1,\n      description: \"Nucleus sampling\",\n    },\n    top_k: {\n      type: \"number\",\n      min: 0,\n      default: 40,\n      description: \"Top-K sampling\",\n    },\n    frequency_penalty: {\n      type: \"number\",\n      min: -2,\n      max: 2,\n      default: 0,\n      description: \"Penalize frequent tokens\",\n    },\n    presence_penalty: {\n      type: \"number\",\n      min: -2,\n      max: 2,\n      default: 0,\n      description: \"Penalize repeated topics\",\n    },\n    stop: { type: \"string\", description: \"Stop sequences\" },\n    n: { type: \"number\", min: 1, default: 1, description: \"Completions count\" },\n    seed: { type: \"number\", description: \"Random seed\" },\n    stream: { type: \"boolean\", default: false, description: \"Stream response\" },\n    reasoning_effort: {\n      type: \"string\",\n      values: [\"none\", \"minimal\", \"low\", \"medium\", \"high\", \"xhigh\"],\n      default: \"medium\",\n      description: \"Reasoning effort\",\n    },\n  },\n};\n\n/** OpenAI reasoning models don't support standard sampling params. */\nexport function isReasoningModel(model: string): boolean {\n  // Strip gateway prefix: \"openai/o3\" → \"o3\"\n  const name = model.includes(\"/\") ? model.split(\"/\").pop()! : model;\n  return /^o[134]/.test(name);\n}\n\n/** Providers that can route to OpenAI models (and need reasoning-model checks). */\nexport function canHostOpenAIModels(provider: Provider): boolean {\n  return (\n    provider === \"openai\" || provider === \"openrouter\" || provider === \"vercel\"\n  );\n}\n\n/** Whether this provider is a gateway/router that proxies to other providers. */\nexport function isGatewayProvider(provider: Provider): boolean {\n  return provider === \"openrouter\" || provider === \"vercel\";\n}\n\n/**\n * Extract the underlying provider from a gateway model string.\n * e.g. \"anthropic/claude-sonnet-4-5\" → \"anthropic\"\n * Returns undefined for unknown prefixes (qwen, deepseek, etc.) or models without \"/\".\n */\nexport function detectGatewaySubProvider(model: string): Provider | undefined {\n  const slash = model.indexOf(\"/\");\n  if (slash < 1) return undefined;\n  const prefix = model.slice(0, slash);\n  const direct: Provider[] = [\n    \"openai\",\n    \"anthropic\",\n    \"google\",\n    \"mistral\",\n    \"cohere\",\n  ];\n  return direct.find((p) => p === prefix);\n}\n\nexport const REASONING_MODEL_UNSUPPORTED = new Set([\n  \"temperature\",\n  \"top_p\",\n  \"frequency_penalty\",\n  \"presence_penalty\",\n  \"n\",\n]);\n\n/**\n * Bedrock model IDs are prefixed with the vendor name.\n * e.g. \"anthropic.claude-sonnet-4-5-20250929-v1:0\"\n */\nexport type BedrockModelFamily =\n  | \"anthropic\"\n  | \"meta\"\n  | \"amazon\"\n  | \"mistral\"\n  | \"cohere\"\n  | \"ai21\";\n\nexport function detectBedrockModelFamily(\n  model: string,\n): BedrockModelFamily | undefined {\n  // Handle cross-region inference profiles (e.g. \"us.anthropic.claude-sonnet-4-5...\")\n  // and global inference profiles (e.g. \"global.anthropic.claude-sonnet-4-5...\")\n  const parts = model.split(\".\");\n\n  // If first part is a region prefix (us, eu, apac) or global, skip it\n  let prefix = parts[0];\n  if ([\"us\", \"eu\", \"apac\", \"global\"].includes(prefix) && parts.length > 1) {\n    prefix = parts[1];\n  }\n\n  const families: BedrockModelFamily[] = [\n    \"anthropic\",\n    \"meta\",\n    \"amazon\",\n    \"mistral\",\n    \"cohere\",\n    \"ai21\",\n  ];\n  return families.find((f) => prefix === f);\n}\n\n/** Whether a Bedrock model supports prompt caching (Claude and Nova only). */\nexport function bedrockSupportsCaching(model: string): boolean {\n  const family = detectBedrockModelFamily(model);\n  if (family === \"anthropic\") return true;\n  if (family === \"amazon\" && model.includes(\"nova\")) return true;\n  return false;\n}\n\n/** Cache value normalization per provider. */\nexport const CACHE_VALUES: Record<Provider, string | undefined> = {\n  openai: undefined, // OpenAI auto-caches; no explicit param\n  anthropic: \"ephemeral\",\n  google: undefined, // Google uses explicit caching API, not a param\n  mistral: undefined,\n  cohere: undefined,\n  bedrock: \"ephemeral\", // Supported for Claude models on Bedrock\n  openrouter: undefined, // Depends on underlying provider\n  vercel: undefined, // Depends on underlying provider\n};\n\n/** Valid cache TTL values per provider. */\nexport const CACHE_TTLS: Record<Provider, string[] | undefined> = {\n  openai: undefined,\n  anthropic: [\"5m\", \"1h\"],\n  google: undefined,\n  mistral: undefined,\n  cohere: undefined,\n  bedrock: [\"5m\", \"1h\"], // Claude on Bedrock uses same TTLs as direct Anthropic\n  openrouter: undefined,\n  vercel: undefined,\n};\n\n/** Match a duration expression like \"5m\", \"1h\", \"30m\". */\nexport const DURATION_RE = /^\\d+[mh]$/;\n"],"mappings":"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","names":[]}