{"version":3,"sources":["/Users/ami/Documents/prometheus/x12i/ai-tools/dist/chunk-AT4JAVDK.cjs","../src/cost/constants.ts","../src/cost/enrichCostResult.ts","../src/cost/extractUsageInput.ts","../src/cost/resolveUsageModel.ts","../src/cost/profilePricing.ts","../src/cost/CostCalculator.ts"],"names":["routedViaOpenRouter","pricing","cost","breakdown"],"mappings":"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","file":"/Users/ami/Documents/prometheus/x12i/ai-tools/dist/chunk-AT4JAVDK.cjs","sourcesContent":[null,"/** Default OpenRouter platform surcharge when catalog markup fields are absent (5%). */\nexport const DEFAULT_OPENROUTER_MARKUP_RATE = 0.05;\n\n/** ai-profiles tiered pricing threshold (tokens). */\nexport const PROFILE_TIERED_CONTEXT_THRESHOLD = 200_000;\n","import type { ExtractUsageInputResult } from \"./extractUsageInput.js\";\nimport type { AiCostExtraction, AiCostResult, AiUsageInput } from \"./types.js\";\n\nexport function toCostExtraction(\n  provenance: ExtractUsageInputResult[\"provenance\"],\n): AiCostExtraction {\n  return {\n    model: { field: provenance.model.field, path: provenance.model.path },\n    tokens: {\n      source: provenance.tokens.source,\n      path: provenance.tokens.path,\n      estimated: provenance.tokens.estimated,\n    },\n    ...(provenance.modelUsed\n      ? {\n          modelUsed: {\n            field: provenance.modelUsed.field,\n            path: provenance.modelUsed.path,\n          },\n        }\n      : {}),\n    ...(provenance.provider\n      ? {\n          provider: {\n            field: provenance.provider.field,\n            path: provenance.provider.path,\n          },\n        }\n      : {}),\n  };\n}\n\n/** Attach usage, provider, and model fields callers expect on every cost response. */\nexport function enrichCostResult(\n  result: Omit<\n    AiCostResult,\n    \"usage\" | \"provider\" | \"usedModel\" | \"model\" | \"extraction\"\n  >,\n  input: AiUsageInput,\n  extraction?: AiCostExtraction,\n): AiCostResult {\n  /** Original model id from the caller — never replaced by catalog resolution. */\n  const receivedModelId = input.usedModel ?? input.modelUsed ?? input.model;\n\n  return {\n    ...result,\n    provider: input.provider,\n    usage: { ...input.tokens },\n    ...(receivedModelId ? { usedModel: receivedModelId } : {}),\n    ...(input.model && input.model !== receivedModelId ? { model: input.model } : {}),\n    ...(extraction ? { extraction } : {}),\n  };\n}\n","import type { AiUsageInput } from \"./types.js\";\n\n/** Model id field priority (higher wins). Runtime ids beat request/config aliases. */\nexport const MODEL_FIELD_PRIORITY: readonly { key: string; score: number }[] = [\n  { key: \"usedModel\", score: 1000 },\n  { key: \"modelUsed\", score: 990 },\n  { key: \"resolvedModel\", score: 980 },\n  { key: \"model\", score: 500 },\n  { key: \"xynthesisModel\", score: 120 },\n  { key: \"skillModel\", score: 110 },\n] as const;\n\n/** Path segments that boost or penalize a model candidate. */\nconst MODEL_PATH_BONUS: readonly { pattern: RegExp; bonus: number }[] = [\n  { pattern: /(^|\\.)response(\\.|$)/i, bonus: 40 },\n  { pattern: /(^|\\.)outer\\.input(\\.|$)/i, bonus: 35 },\n  { pattern: /(^|\\.)config(?!\\.rawConfig)(\\.|$)/i, bonus: 30 },\n  { pattern: /(^|\\.)outer\\.input\\.config(\\.|$)/i, bonus: 28 },\n  { pattern: /(^|\\.)usage(\\.|$)/i, bonus: 15 },\n  { pattern: /(^|\\.)rawConfig(\\.|$)/i, bonus: -40 },\n  { pattern: /workingMemory/i, bonus: -50 },\n  { pattern: /requestExample/i, bonus: -80 },\n  { pattern: /graphsStudio/i, bonus: -80 },\n];\n\nconst PROVIDER_FIELD_PRIORITY: readonly { key: string; score: number }[] = [\n  { key: \"provider\", score: 500 },\n  { key: \"providerId\", score: 480 },\n] as const;\n\nconst PROVIDER_PATH_BONUS: readonly { pattern: RegExp; bonus: number }[] = [\n  { pattern: /(^|\\.)config(?!\\.rawConfig)(\\.|$)/i, bonus: 30 },\n  { pattern: /(^|\\.)outer\\.input(\\.|$)/i, bonus: 20 },\n  { pattern: /(^|\\.)rawConfig(\\.|$)/i, bonus: -20 },\n];\n\nconst PROMPT_TOKEN_KEYS = new Set([\n  \"prompt\",\n  \"prompt_tokens\",\n  \"promptTokens\",\n  \"input_tokens\",\n  \"inputTokens\",\n  \"estimatedInputTokens\",\n  \"input\",\n]);\n\nconst COMPLETION_TOKEN_KEYS = new Set([\n  \"completion\",\n  \"completion_tokens\",\n  \"completionTokens\",\n  \"output_tokens\",\n  \"outputTokens\",\n  \"estimatedOutputTokens\",\n  \"output\",\n]);\n\nconst OPTIONAL_TOKEN_KEYS: Record<string, keyof AiUsageInput[\"tokens\"]> = {\n  total: \"total\",\n  total_tokens: \"total\",\n  totalTokens: \"total\",\n  cached: \"cached\",\n  cached_tokens: \"cached\",\n  cache_read_tokens: \"cached\",\n  cacheReadTokens: \"cached\",\n  cacheWrite: \"cacheWrite\",\n  cache_write_tokens: \"cacheWrite\",\n  cacheWriteTokens: \"cacheWrite\",\n  reasoning: \"reasoning\",\n  reasoning_tokens: \"reasoning\",\n  reasoningTokens: \"reasoning\",\n  audio: \"audio\",\n  audio_tokens: \"audio\",\n  image: \"image\",\n  image_tokens: \"image\",\n};\n\nexport type FieldProvenance = {\n  field: string;\n  path: string;\n  score: number;\n};\n\nexport type TokenExtractionProvenance = {\n  source: \"usage\" | \"tokens\" | \"diagnostics\" | \"estimated\";\n  path: string;\n  estimated?: boolean;\n};\n\nexport type ExtractUsageInputResult = {\n  input: AiUsageInput;\n  provenance: {\n    model: FieldProvenance;\n    modelUsed?: FieldProvenance;\n    provider?: FieldProvenance;\n    tokens: TokenExtractionProvenance;\n  };\n};\n\nexport type ExtractUsageInputOptions = {\n  /** Default when no provider field is found (default: `openrouter`). */\n  defaultProvider?: string;\n};\n\ntype ModelCandidate = FieldProvenance & { value: string };\ntype ProviderCandidate = FieldProvenance & { value: string };\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n  return v !== null && typeof v === \"object\" && !Array.isArray(v);\n}\n\nfunction modelKeyScore(key: string): number {\n  return MODEL_FIELD_PRIORITY.find((e) => e.key === key)?.score ?? 0;\n}\n\nfunction providerKeyScore(key: string): number {\n  return PROVIDER_FIELD_PRIORITY.find((e) => e.key === key)?.score ?? 0;\n}\n\nfunction pathBonus(path: string, rules: readonly { pattern: RegExp; bonus: number }[]): number {\n  let bonus = 0;\n  for (const { pattern, bonus: b } of rules) {\n    if (pattern.test(path)) bonus += b;\n  }\n  return bonus;\n}\n\nfunction normalizeModelValue(v: unknown): string | undefined {\n  if (typeof v !== \"string\") return undefined;\n  const t = v.trim();\n  return t.length > 0 ? t : undefined;\n}\n\nfunction normalizeProviderValue(v: unknown): string | undefined {\n  if (typeof v !== \"string\") return undefined;\n  const t = v.trim().toLowerCase();\n  if (!t || t === \"null\") return undefined;\n  return t;\n}\n\nfunction collectModelCandidates(\n  node: unknown,\n  path: string,\n  out: ModelCandidate[],\n  depth: number,\n  maxDepth: number,\n): void {\n  if (depth > maxDepth || node === null || node === undefined) return;\n\n  if (isPlainObject(node)) {\n    for (const [key, value] of Object.entries(node)) {\n      const childPath = path ? `${path}.${key}` : key;\n      const base = modelKeyScore(key);\n      if (base > 0) {\n        const model = normalizeModelValue(value);\n        if (model) {\n          out.push({\n            field: key,\n            path: childPath,\n            value: model,\n            score: base + pathBonus(childPath, MODEL_PATH_BONUS),\n          });\n        }\n      }\n      collectModelCandidates(value, childPath, out, depth + 1, maxDepth);\n    }\n    return;\n  }\n\n  if (Array.isArray(node)) {\n    for (let i = 0; i < node.length; i++) {\n      collectModelCandidates(node[i], `${path}[${i}]`, out, depth + 1, maxDepth);\n    }\n  }\n}\n\nfunction collectProviderCandidates(\n  node: unknown,\n  path: string,\n  out: ProviderCandidate[],\n  depth: number,\n  maxDepth: number,\n): void {\n  if (depth > maxDepth || node === null || node === undefined) return;\n\n  if (isPlainObject(node)) {\n    for (const [key, value] of Object.entries(node)) {\n      const childPath = path ? `${path}.${key}` : key;\n      const base = providerKeyScore(key);\n      if (base > 0) {\n        const provider = normalizeProviderValue(value);\n        if (provider) {\n          out.push({\n            field: key,\n            path: childPath,\n            value: provider,\n            score: base + pathBonus(childPath, PROVIDER_PATH_BONUS),\n          });\n        }\n      }\n      collectProviderCandidates(value, childPath, out, depth + 1, maxDepth);\n    }\n    return;\n  }\n\n  if (Array.isArray(node)) {\n    for (let i = 0; i < node.length; i++) {\n      collectProviderCandidates(node[i], `${path}[${i}]`, out, depth + 1, maxDepth);\n    }\n  }\n}\n\nfunction readNumber(obj: Record<string, unknown>, keys: Set<string>): number | undefined {\n  for (const [k, v] of Object.entries(obj)) {\n    if (!keys.has(k)) continue;\n    if (typeof v === \"number\" && Number.isFinite(v) && v >= 0) return v;\n  }\n  return undefined;\n}\n\nfunction readOptionalNumbers(\n  obj: Record<string, unknown>,\n): Partial<AiUsageInput[\"tokens\"]> {\n  const extra: Partial<AiUsageInput[\"tokens\"]> = {};\n  for (const [k, v] of Object.entries(obj)) {\n    const target = OPTIONAL_TOKEN_KEYS[k];\n    if (!target || typeof v !== \"number\" || !Number.isFinite(v) || v < 0) continue;\n    extra[target] = v;\n  }\n  return extra;\n}\n\nfunction looksLikeUsageObject(obj: Record<string, unknown>): boolean {\n  const hasPrompt = readNumber(obj, PROMPT_TOKEN_KEYS) !== undefined;\n  const hasCompletion = readNumber(obj, COMPLETION_TOKEN_KEYS) !== undefined;\n  return hasPrompt && hasCompletion;\n}\n\nfunction looksLikeDiagnosticsObject(obj: Record<string, unknown>): boolean {\n  return (\n    typeof obj.estimatedInputTokens === \"number\" &&\n    typeof obj.estimatedOutputTokens === \"number\"\n  );\n}\n\ntype TokenCandidate = {\n  tokens: AiUsageInput[\"tokens\"];\n  provenance: TokenExtractionProvenance;\n  score: number;\n};\n\nfunction tokensFromObject(\n  obj: Record<string, unknown>,\n  source: TokenExtractionProvenance[\"source\"],\n  path: string,\n  estimated: boolean,\n  score: number,\n): TokenCandidate | null {\n  const prompt = readNumber(obj, PROMPT_TOKEN_KEYS);\n  const completion = readNumber(obj, COMPLETION_TOKEN_KEYS);\n  if (prompt === undefined || completion === undefined) return null;\n\n  const extra = readOptionalNumbers(obj);\n  const total =\n    typeof extra.total === \"number\" ? extra.total : prompt + completion;\n\n  return {\n    tokens: {\n      prompt,\n      completion,\n      total,\n      ...extra,\n    },\n    provenance: { source, path, estimated },\n    score,\n  };\n}\n\nfunction collectTokenCandidates(\n  node: unknown,\n  path: string,\n  out: TokenCandidate[],\n  depth: number,\n  maxDepth: number,\n): void {\n  if (depth > maxDepth || node === null || node === undefined) return;\n\n  if (isPlainObject(node)) {\n    if (path.endsWith(\"diagnostics\") || looksLikeDiagnosticsObject(node)) {\n      const cand = tokensFromObject(node, \"diagnostics\", path, true, 200);\n      if (cand) out.push(cand);\n    } else if (path.endsWith(\"usage\") || path.endsWith(\"tokens\") || looksLikeUsageObject(node)) {\n      const source = path.endsWith(\"tokens\") ? \"tokens\" : \"usage\";\n      const estimated = false;\n      const score = path.endsWith(\"usage\") ? 500 : 450;\n      const cand = tokensFromObject(node, source, path, estimated, score);\n      if (cand) out.push(cand);\n    }\n\n    for (const [key, value] of Object.entries(node)) {\n      const childPath = path ? `${path}.${key}` : key;\n      if (key === \"usage\" || key === \"tokens\" || key === \"diagnostics\") {\n        if (isPlainObject(value)) {\n          collectTokenCandidates(value, childPath, out, depth + 1, maxDepth);\n        }\n      } else {\n        collectTokenCandidates(value, childPath, out, depth + 1, maxDepth);\n      }\n    }\n    return;\n  }\n\n  if (Array.isArray(node)) {\n    for (let i = 0; i < node.length; i++) {\n      collectTokenCandidates(node[i], `${path}[${i}]`, out, depth + 1, maxDepth);\n    }\n  }\n}\n\nfunction pickBestModel(candidates: ModelCandidate[]): ModelCandidate | null {\n  if (candidates.length === 0) return null;\n  return [...candidates].sort((a, b) => b.score - a.score)[0]!;\n}\n\nfunction pickBestProvider(candidates: ProviderCandidate[]): ProviderCandidate | null {\n  if (candidates.length === 0) return null;\n  return [...candidates].sort((a, b) => b.score - a.score)[0]!;\n}\n\nfunction pickBestTokens(candidates: TokenCandidate[]): TokenCandidate | null {\n  if (candidates.length === 0) return null;\n  return [...candidates].sort((a, b) => b.score - a.score)[0]!;\n}\n\nfunction inferProviderFromModel(model: string, fallback: string): string {\n  const slash = model.indexOf(\"/\");\n  if (slash > 0) {\n    const prefix = model.slice(0, slash).toLowerCase();\n    if (prefix !== \"openrouter\") return prefix;\n  }\n  return fallback;\n}\n\n/**\n * Extract {@link AiUsageInput} from activity logs, gateway records, MongoDB exports,\n * or any nested JSON shape (e.g. `outer.input`, `config`, `metadata.diagnostics`).\n */\nexport function extractUsageInput(\n  record: unknown,\n  options: ExtractUsageInputOptions = {},\n): ExtractUsageInputResult {\n  const defaultProvider = options.defaultProvider ?? \"openrouter\";\n  const maxDepth = 24;\n\n  const modelCandidates: ModelCandidate[] = [];\n  const providerCandidates: ProviderCandidate[] = [];\n  const tokenCandidates: TokenCandidate[] = [];\n\n  collectModelCandidates(record, \"\", modelCandidates, 0, maxDepth);\n  collectProviderCandidates(record, \"\", providerCandidates, 0, maxDepth);\n  collectTokenCandidates(record, \"\", tokenCandidates, 0, maxDepth);\n\n  const bestModel = pickBestModel(modelCandidates);\n  if (!bestModel) {\n    throw new Error(\n      \"[ai-tools] Could not extract a model from the record (looked for usedModel, modelUsed, model, …).\",\n    );\n  }\n\n  const runtimeFields = new Set([\"usedModel\", \"modelUsed\", \"resolvedModel\"]);\n  const isRuntimeModel = runtimeFields.has(bestModel.field);\n  const requestModel = modelCandidates.find(\n    (c) => c.field === \"model\" && c.path !== bestModel.path,\n  );\n\n  const bestProvider = pickBestProvider(providerCandidates);\n  const provider =\n    bestProvider?.value ??\n    inferProviderFromModel(bestModel.value, defaultProvider);\n\n  const bestTokens = pickBestTokens(tokenCandidates);\n  if (!bestTokens) {\n    throw new Error(\n      \"[ai-tools] Could not extract token usage from the record (usage, tokens, or metadata.diagnostics).\",\n    );\n  }\n\n  const input: AiUsageInput = {\n    tokens: bestTokens.tokens,\n    provider,\n    ...(isRuntimeModel\n      ? { modelUsed: bestModel.value, model: requestModel?.value }\n      : { model: bestModel.value }),\n  };\n\n  const provenance: ExtractUsageInputResult[\"provenance\"] = {\n    model: {\n      field: bestModel.field,\n      path: bestModel.path,\n      score: bestModel.score,\n    },\n    tokens: bestTokens.provenance,\n  };\n\n  if (bestProvider) {\n    provenance.provider = {\n      field: bestProvider.field,\n      path: bestProvider.path,\n      score: bestProvider.score,\n    };\n  }\n\n  if (isRuntimeModel && requestModel) {\n    provenance.modelUsed = {\n      field: bestModel.field,\n      path: bestModel.path,\n      score: bestModel.score,\n    };\n  } else if (input.modelUsed) {\n    provenance.modelUsed = provenance.model;\n  }\n\n  return { input, provenance };\n}\n","import type { AiUsageInput } from \"./types.js\";\n\n/** Prefer runtime id (`usedModel` / `modelUsed`); fall back to `model`. */\nexport function resolveUsageModel(input: AiUsageInput): string {\n  const value = input.usedModel ?? input.modelUsed ?? input.model;\n  if (!value?.trim()) {\n    throw new Error(\n      \"[ai-tools] Cost calculation requires `usedModel`, `modelUsed`, or `model` on usage input.\",\n    );\n  }\n  return value.trim();\n}\n","import type { AIModelPricing } from \"@x12i/ai-profiles\";\nimport {\n  DEFAULT_OPENROUTER_MARKUP_RATE,\n  PROFILE_TIERED_CONTEXT_THRESHOLD,\n} from \"./constants.js\";\nimport type { AiModelPricing, AiUsageInput } from \"./types.js\";\n\nconst TOKENS_PER_UNIT = 1_000_000;\n\nfunction usdPerToken(perMillion?: number): number {\n  return (perMillion ?? 0) / TOKENS_PER_UNIT;\n}\n\nfunction pickInputRatePerMillion(\n  pricing: AIModelPricing,\n  promptTokens: number,\n): number | undefined {\n  if (\n    pricing.inputBelow200k !== undefined &&\n    pricing.inputAbove200k !== undefined\n  ) {\n    return promptTokens <= PROFILE_TIERED_CONTEXT_THRESHOLD\n      ? pricing.inputBelow200k\n      : pricing.inputAbove200k;\n  }\n  return pricing.input ?? pricing.inputBelow200k ?? pricing.inputAbove200k;\n}\n\nfunction pickOutputRatePerMillion(\n  pricing: AIModelPricing,\n  promptTokens: number,\n): number | undefined {\n  if (\n    pricing.outputBelow200k !== undefined &&\n    pricing.outputAbove200k !== undefined\n  ) {\n    return promptTokens <= PROFILE_TIERED_CONTEXT_THRESHOLD\n      ? pricing.outputBelow200k\n      : pricing.outputAbove200k;\n  }\n  return pricing.output ?? pricing.outputBelow200k ?? pricing.outputAbove200k;\n}\n\nexport type AiProfilesPricingOptions = {\n  promptTokens?: number;\n  routedViaOpenRouter?: boolean;\n};\n\nexport function aiProfilesPricingToCatalogPricing(\n  pricing: AIModelPricing,\n  pricedAt: string,\n  options: AiProfilesPricingOptions = {},\n): AiModelPricing {\n  const promptTokens = options.promptTokens ?? 0;\n  const promptUsdPerToken = usdPerToken(pickInputRatePerMillion(pricing, promptTokens));\n  const completionUsdPerToken = usdPerToken(\n    pickOutputRatePerMillion(pricing, promptTokens),\n  );\n\n  const snapshot: AiModelPricing = {\n    promptUsdPerToken,\n    completionUsdPerToken,\n    cacheReadUsdPerToken: usdPerToken(pricing.cachedInput ?? pricing.cacheHit),\n    cacheWriteUsdPerToken: usdPerToken(pricing.cacheWrite5m ?? pricing.cacheWrite1h),\n    imageUsdPerUnit: 0,\n    requestUsdPerRequest: 0,\n    pricedAt,\n    source: \"manual\",\n  };\n\n  if (options.routedViaOpenRouter) {\n    snapshot.openRouterMarkupUsdPerInputToken =\n      promptUsdPerToken * DEFAULT_OPENROUTER_MARKUP_RATE;\n    snapshot.openRouterMarkupUsdPerOutputToken =\n      completionUsdPerToken * DEFAULT_OPENROUTER_MARKUP_RATE;\n  }\n\n  return snapshot;\n}\n\nexport function computeCostFromPricing(\n  input: AiUsageInput,\n  pricing: AiModelPricing,\n  routedViaOpenRouter: boolean,\n  includeBreakdown: boolean,\n): { cost: number; breakdown?: import(\"./types.js\").AiCostResult[\"breakdown\"] } {\n  const { tokens } = input;\n\n  let promptCost = tokens.prompt * pricing.promptUsdPerToken;\n  let completionCost = tokens.completion * pricing.completionUsdPerToken;\n\n  const cachingCost =\n    (tokens.cacheWrite ?? 0) * (pricing.cacheWriteUsdPerToken ?? 0) +\n    (tokens.cached ?? 0) * (pricing.cacheReadUsdPerToken ?? 0);\n\n  const reasoningCost =\n    (tokens.reasoning ?? 0) *\n    (pricing.reasoningUsdPerToken ?? pricing.promptUsdPerToken);\n\n  const audioCost = (tokens.audio ?? 0) * pricing.promptUsdPerToken;\n  const imageCost = (tokens.image ?? 0) * (pricing.imageUsdPerUnit ?? 0);\n  const requestFlat = pricing.requestUsdPerRequest;\n\n  if (routedViaOpenRouter) {\n    promptCost +=\n      tokens.prompt * (pricing.openRouterMarkupUsdPerInputToken ?? 0);\n    completionCost +=\n      tokens.completion * (pricing.openRouterMarkupUsdPerOutputToken ?? 0);\n  }\n\n  const cost =\n    promptCost +\n    completionCost +\n    cachingCost +\n    reasoningCost +\n    audioCost +\n    imageCost +\n    requestFlat;\n\n  if (!includeBreakdown) {\n    return { cost };\n  }\n\n  return {\n    cost,\n    breakdown: {\n      promptCostUsd: promptCost,\n      completionCostUsd: completionCost,\n      cachingCostUsd: cachingCost || undefined,\n      reasoningCostUsd: reasoningCost || undefined,\n      audioCostUsd: audioCost || undefined,\n      imageCostUsd: imageCost || undefined,\n      requestFlatCostUsd: requestFlat || undefined,\n    },\n  };\n}\n","import type { AiModelsCatalogClient } from \"../catalog/AiModelsCatalogClient.js\";\nimport { UnknownModelCostError } from \"../errors.js\";\nimport { normalizeString } from \"../sync/modelNameResolver/normalize.js\";\nimport { stripModelVersionSuffix } from \"../sync/modelNameResolver/stripVersionSuffix.js\";\nimport type {\n  ModelResolutionResult,\n  ModelResolverOptions,\n} from \"../sync/modelNameResolver/types.js\";\nimport { matchModelInAiProfiles } from \"./aiProfilesMatch.js\";\nimport {\n  buildCatalogResolveAttempts,\n  isLocalProvider,\n  isLocalProviderResolution,\n  resolveFromCatalogAttempts,\n  resolveRoutedViaOpenRouter,\n} from \"./costModelResolution.js\";\nimport {\n  aiProfilesPricingToCatalogPricing,\n  computeCostFromPricing,\n} from \"./profilePricing.js\";\nimport { enrichCostResult, toCostExtraction } from \"./enrichCostResult.js\";\nimport {\n  extractUsageInput,\n  type ExtractUsageInputOptions,\n} from \"./extractUsageInput.js\";\nimport { resolveUsageModel } from \"./resolveUsageModel.js\";\nimport type { AiCostResult, AiCostWarning, AiModelPricing, AiUsageInput } from \"./types.js\";\n\nexport type CostCalculatorOptions = {\n  includeBreakdown?: boolean;\n  resolverOptions?: ModelResolverOptions;\n  /** When true, throw {@link UnknownModelCostError} instead of zero-cost fallback. */\n  throwOnUnknownModel?: boolean;\n};\n\nfunction emptyPricing(): AiModelPricing {\n  return {\n    promptUsdPerToken: 0,\n    completionUsdPerToken: 0,\n    imageUsdPerUnit: 0,\n    requestUsdPerRequest: 0,\n    pricedAt: new Date().toISOString(),\n    source: \"manual\",\n  };\n}\n\nfunction mergeAttempts(\n  ...groups: ReturnType<typeof buildCatalogResolveAttempts>[]\n): ReturnType<typeof buildCatalogResolveAttempts> {\n  const seen = new Set<string>();\n  const out: ReturnType<typeof buildCatalogResolveAttempts> = [];\n  for (const group of groups) {\n    for (const a of group) {\n      const key = `${a.provider ?? \"\"}\\0${a.model}`;\n      if (seen.has(key)) continue;\n      seen.add(key);\n      out.push(a);\n    }\n  }\n  return out;\n}\n\nexport class CostCalculator {\n  private readonly includeBreakdown: boolean;\n  private readonly resolverOptions?: ModelResolverOptions;\n  private readonly throwOnUnknownModel: boolean;\n\n  constructor(\n    private readonly catalog: AiModelsCatalogClient,\n    options: CostCalculatorOptions = {},\n  ) {\n    this.includeBreakdown = options.includeBreakdown ?? true;\n    this.resolverOptions = options.resolverOptions;\n    this.throwOnUnknownModel = options.throwOnUnknownModel ?? false;\n  }\n\n  /**\n   * Extract model, provider, and token usage from an activity / gateway record\n   * (MongoDB export, nested `outer`, `config`, `metadata.diagnostics`, etc.)\n   * then run {@link calculate}.\n   */\n  async calculateFromRecord(\n    record: unknown,\n    extractOptions?: ExtractUsageInputOptions,\n  ): Promise<AiCostResult> {\n    const { input, provenance } = extractUsageInput(record, extractOptions);\n    const warnings: AiCostWarning[] = [];\n    if (provenance.tokens.estimated) {\n      warnings.push({\n        code: \"ESTIMATED_TOKEN_USAGE\",\n        message: `Token counts are estimates from ${provenance.tokens.path} (not billed usage).`,\n      });\n    }\n    const result = await this.calculate(input);\n    const enriched = enrichCostResult(result, input, toCostExtraction(provenance));\n    if (warnings.length > 0) {\n      enriched.warnings = [...(enriched.warnings ?? []), ...warnings];\n    }\n    return enriched;\n  }\n\n  async calculate(input: AiUsageInput): Promise<AiCostResult> {\n    const modelInput = resolveUsageModel(input);\n    const resolverOpts = this.resolverOptions;\n\n    let resolved = await resolveFromCatalogAttempts(\n      this.catalog,\n      buildCatalogResolveAttempts(modelInput, input.provider),\n      resolverOpts,\n    );\n\n    if (isLocalProviderResolution(resolved, input.provider)) {\n      return this.localProviderResult(modelInput, input, resolved);\n    }\n\n    let profileMatch = null as Awaited<ReturnType<typeof matchModelInAiProfiles>>;\n    const warnings: AiCostWarning[] = [];\n\n    if (!resolved?.found || !resolved.record) {\n      if (!isLocalProvider(input.provider)) {\n        profileMatch = await matchModelInAiProfiles(modelInput, input.provider, {\n          catalogLane: this.resolverOptions?.catalogLane,\n        });\n      }\n      if (profileMatch) {\n        resolved = await resolveFromCatalogAttempts(\n          this.catalog,\n          mergeAttempts(\n            buildCatalogResolveAttempts(\n              profileMatch.canonicalModelId,\n              input.provider ?? profileMatch.provider,\n            ),\n            buildCatalogResolveAttempts(\n              profileMatch.canonicalModelId,\n              profileMatch.provider,\n            ),\n          ),\n          resolverOpts,\n        );\n      }\n\n      if (isLocalProviderResolution(resolved, input.provider)) {\n        return this.localProviderResult(modelInput, input, resolved);\n      }\n\n      if ((!resolved?.found || !resolved.record) && profileMatch?.pricing) {\n        const routedViaOpenRouter = resolveRoutedViaOpenRouter(\n          input.provider,\n          resolved,\n          profileMatch.canonicalModelId,\n          resolverOpts?.routingEnv,\n        );\n        const pricing = aiProfilesPricingToCatalogPricing(\n          profileMatch.pricing,\n          new Date().toISOString(),\n          {\n            promptTokens: input.tokens.prompt,\n            routedViaOpenRouter,\n          },\n        );\n        const { cost, breakdown } = computeCostFromPricing(\n          input,\n          pricing,\n          routedViaOpenRouter,\n          this.includeBreakdown,\n        );\n\n        warnings.push({\n          code: \"AI_PROFILES_ESTIMATE\",\n          message: `Catalog has no pricing for \"${modelInput}\"; estimated from ai-profiles (${profileMatch.matchedVia} → ${profileMatch.canonicalModelId}).`,\n        });\n        if (\n          routedViaOpenRouter &&\n          pricing.openRouterMarkupUsdPerInputToken !== undefined\n        ) {\n          warnings.push({\n            code: \"OPENROUTER_MARKUP_ESTIMATED\",\n            message:\n              \"OpenRouter markup estimated at 5% of base token rates (catalog markup fields absent).\",\n          });\n        }\n\n        console.warn(`[ai-tools] ${warnings.map((w) => w.message).join(\" \")}`);\n\n        return this.finish(\n          {\n            cost,\n            breakdown,\n            resolvedModelId: profileMatch.canonicalModelId,\n            routedViaOpenRouter,\n            isAuthoritative: false,\n            pricingSnapshot: pricing,\n            source: \"ai-profiles\",\n            warnings,\n          },\n          input,\n          resolved,\n        );\n      }\n    }\n\n    if (!resolved?.found || !resolved.record) {\n      return this.unknownModelResult(modelInput, input, resolved);\n    }\n\n    const { record, modelId } = resolved;\n    const routedViaOpenRouter = resolveRoutedViaOpenRouter(\n      input.provider,\n      resolved,\n      modelId,\n      resolverOpts?.routingEnv,\n    );\n    const pricing = record.pricing;\n    const { cost, breakdown } = computeCostFromPricing(\n      input,\n      pricing,\n      routedViaOpenRouter,\n      this.includeBreakdown,\n    );\n\n    return this.finish(\n      {\n        cost,\n        breakdown,\n        resolvedModelId: modelId,\n        routedViaOpenRouter,\n        isAuthoritative: true,\n        pricingSnapshot: pricing,\n        source: \"catalog\",\n      },\n      input,\n      resolved,\n    );\n  }\n\n  private versionSuffixWarnings(\n    input: AiUsageInput,\n    pricedModelId: string,\n    resolved?: ModelResolutionResult | null,\n  ): AiCostWarning[] {\n    const received = input.usedModel ?? input.modelUsed ?? input.model;\n    if (!received) return [];\n\n    const normalizedReceived = normalizeString(received);\n    const normalizedPriced = normalizeString(pricedModelId);\n    if (normalizedReceived === normalizedPriced) return [];\n\n    const stripped = stripModelVersionSuffix(received);\n    const viaResolver =\n      resolved?.found === true &&\n      resolved.resolvedVia.some(\n        (s) => s === \"version-suffix-strip\" || s === \"date-suffix-strip\",\n      );\n    const pricedViaStrip =\n      stripped !== null && normalizeString(stripped) === normalizedPriced;\n\n    if (!viaResolver && !pricedViaStrip) return [];\n\n    return [\n      {\n        code: \"VERSION_SUFFIX_PRICING\",\n        message: `Priced using catalog model \"${pricedModelId}\" (no exact pricing for \"${received}\").`,\n      },\n    ];\n  }\n\n  private finish(\n    partial: Omit<\n      AiCostResult,\n      \"usage\" | \"provider\" | \"usedModel\" | \"model\" | \"extraction\"\n    >,\n    input: AiUsageInput,\n    resolved?: ModelResolutionResult | null,\n  ): AiCostResult {\n    const enriched = enrichCostResult(partial, input);\n    const suffixWarnings = this.versionSuffixWarnings(\n      input,\n      partial.resolvedModelId,\n      resolved,\n    );\n    if (suffixWarnings.length > 0) {\n      enriched.warnings = [...(enriched.warnings ?? []), ...suffixWarnings];\n    }\n    return enriched;\n  }\n\n  private localProviderResult(\n    modelInput: string,\n    input: AiUsageInput,\n    resolved: Awaited<ReturnType<typeof resolveFromCatalogAttempts>>,\n  ): AiCostResult {\n    const warnings: AiCostWarning[] = [\n      {\n        code: \"LOCAL_PROVIDER_NO_PRICING\",\n        message: `Local provider \"${input.provider}\" has no catalog pricing for \"${modelInput}\".`,\n      },\n    ];\n    console.warn(`[ai-tools] ${warnings[0]!.message}`);\n    return this.finish(\n      {\n        cost: 0,\n        resolvedModelId: resolved?.found ? resolved.modelId : modelInput,\n        routedViaOpenRouter: false,\n        isAuthoritative: false,\n        pricingSnapshot: emptyPricing(),\n        source: \"local\",\n        warnings,\n      },\n      input,\n    );\n  }\n\n  private unknownModelResult(\n    modelInput: string,\n    input: AiUsageInput,\n    resolved: Awaited<ReturnType<typeof resolveFromCatalogAttempts>>,\n  ): AiCostResult {\n    if (this.throwOnUnknownModel) {\n      throw new UnknownModelCostError(modelInput, input.provider);\n    }\n\n    const warnings: AiCostWarning[] = [\n      {\n        code: \"UNKNOWN_MODEL\",\n        message: `Unknown model \"${modelInput}\" (provider: \"${input.provider}\") — returning zero-cost fallback.`,\n      },\n    ];\n    console.warn(`[ai-tools] ${warnings[0]!.message}`);\n\n    return this.finish(\n      {\n        cost: 0,\n        resolvedModelId: modelInput,\n        routedViaOpenRouter: resolveRoutedViaOpenRouter(\n          input.provider,\n          resolved,\n          modelInput,\n          this.resolverOptions?.routingEnv,\n        ),\n        isAuthoritative: false,\n        pricingSnapshot: emptyPricing(),\n        source: \"estimate-fallback\",\n        unknownModel: true,\n        warnings,\n      },\n      input,\n    );\n  }\n}\n"]}