{"version":3,"sources":["../src/parse.ts","../src/provider-core.ts"],"sourcesContent":["import { resolveHostAlias } from \"./provider-core.js\";\nimport type { HostAlias } from \"./provider-core.js\";\n\nexport interface LlmConnectionConfig {\n  /** The original connection string */\n  raw: string;\n  /** Provider's API host (e.g. \"api.openai.com\") */\n  host: string;\n  /** Short provider alias that was expanded to host, if any. */\n  hostAlias?: HostAlias;\n  /** Model name (e.g. \"gpt-5.2\") */\n  model: string;\n  /** Optional label or app name */\n  label?: string;\n  /** Optional API key or password */\n  apiKey?: string;\n  /** Additional config parameters (temp, max_tokens, etc.) */\n  params: Record<string, string>;\n}\n\n/**\n * Parse an LLM connection string into its component parts.\n *\n * Format: `llm://[label[:apiKey]@]host/model[?key=value&...]`\n *\n * @example\n * ```ts\n * parse(\"llm://api.openai.com/gpt-5.2?temp=0.7&max_tokens=1500\")\n * parse(\"llm://app-name:sk-proj-123456@api.openai.com/gpt-5.2?temp=0.7\")\n * ```\n */\nexport function parse(connectionString: string): LlmConnectionConfig {\n  const url = new URL(connectionString);\n\n  if (url.protocol !== \"llm:\") {\n    throw new Error(\n      `Invalid scheme: expected \"llm://\", got \"${url.protocol}//\"`,\n    );\n  }\n\n  const { host, alias: hostAlias } = resolveHostAlias(url.host);\n  const model = url.pathname.replace(/^\\//, \"\");\n  const label = url.username || undefined;\n  const apiKey = url.password || undefined;\n\n  const params: Record<string, string> = {};\n  for (const [key, value] of url.searchParams) {\n    params[key] = value;\n  }\n\n  return {\n    raw: connectionString,\n    host,\n    hostAlias,\n    model,\n    label,\n    apiKey,\n    params,\n  };\n}\n\n/**\n * Build an LLM connection string from a config object.\n */\nexport function build(config: Omit<LlmConnectionConfig, \"raw\">): string {\n  const { host } = resolveHostAlias(config.host);\n  const auth =\n    config.label || config.apiKey\n      ? `${config.label ?? \"\"}${config.apiKey ? `:${config.apiKey}` : \"\"}@`\n      : \"\";\n\n  const query = new URLSearchParams(config.params).toString();\n  const qs = query ? `?${query}` : \"\";\n\n  return `llm://${auth}${host}/${config.model}${qs}`;\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":[]}