{"version":3,"sources":["../src/ai.ts"],"sourcesContent":["import {\n  wrapLanguageModel,\n  type LanguageModel,\n  type LanguageModelMiddleware,\n} from \"ai\";\nimport type { BatchIngestResult, UsageEvent } from \"./generated/types.gen.js\";\n\ntype UsageEventsClient = {\n  usage: {\n    ingest: (body: UsageEvent | { events: UsageEvent[] }) => Promise<BatchIngestResult>;\n  };\n};\n\ntype JsonObject = Record<string, unknown>;\n\ntype AiTokenUsage = {\n  inputTokens?: {\n    total?: number;\n    noCache?: number;\n    cacheRead?: number;\n    cacheWrite?: number;\n  };\n  outputTokens?: {\n    total?: number;\n    text?: number;\n    reasoning?: number;\n  };\n  raw?: JsonObject;\n};\n\nexport type AiUsageQuantity =\n  | \"input_tokens\"\n  | \"input_tokens_no_cache\"\n  | \"cached_input_tokens\"\n  | \"cache_write_tokens\"\n  | \"output_tokens\"\n  | \"output_text_tokens\"\n  | \"output_reasoning_tokens\"\n  | \"total_tokens\"\n  | ((usage: AiMeterUsageContext) => number | undefined);\n\nexport interface AiMeterEventMapping {\n  name?: string;\n  meterId?: string;\n  quantity: AiUsageQuantity;\n  includeZero?: boolean;\n  metadata?: JsonObject;\n  idempotencyKeySuffix?: string;\n}\n\nexport interface AiMeterUsageContext {\n  usage: AiTokenUsage;\n  provider: string;\n  model: string;\n  operation: \"generate\" | \"stream\";\n  providerMetadata?: unknown;\n}\n\nexport interface RevKeenAiMeteringOptions {\n  revkeen: UsageEventsClient;\n  meters: AiMeterEventMapping[];\n  customerId?: string;\n  externalCustomerId?: string;\n  subscriptionId?: string;\n  provider?: string;\n  model?: string;\n  metadata?: JsonObject | ((context: AiMeterUsageContext) => JsonObject | undefined);\n  idempotencyKey?: string | ((context: AiMeterUsageContext) => string | undefined);\n  timestamp?: string | Date | ((context: AiMeterUsageContext) => string | Date | undefined);\n  awaitIngest?: boolean;\n  onIngestError?: (error: unknown, context: AiMeterUsageContext) => void | Promise<void>;\n}\n\nexport const AI_METER_ADAPTER_GUARDRAILS = [\n  \"Run only in merchant server-side or trusted edge code.\",\n  \"Emit raw usage only through /v2/usage-events.\",\n  \"Never compute or transmit RevKeen customer charge, provider cost, margin, tax, invoice, or accounting values.\",\n  \"Never write directly to RDS, ClickHouse, Redpanda, Kafka, or any analytics infrastructure.\",\n] as const;\n\nfunction isRecord(value: unknown): value is JsonObject {\n  return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction finiteNumber(value: unknown): number | undefined {\n  return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\nfunction sumDefined(values: Array<number | undefined>): number | undefined {\n  const defined = values.filter((value): value is number => value !== undefined);\n  if (defined.length === 0) return undefined;\n  return defined.reduce((total, value) => total + value, 0);\n}\n\nfunction quantityFor(selection: AiUsageQuantity, context: AiMeterUsageContext): number | undefined {\n  if (typeof selection === \"function\") {\n    return finiteNumber(selection(context));\n  }\n\n  const { usage } = context;\n  switch (selection) {\n    case \"input_tokens\":\n      return finiteNumber(usage.inputTokens?.total);\n    case \"input_tokens_no_cache\":\n      return finiteNumber(usage.inputTokens?.noCache);\n    case \"cached_input_tokens\":\n      return finiteNumber(usage.inputTokens?.cacheRead);\n    case \"cache_write_tokens\":\n      return finiteNumber(usage.inputTokens?.cacheWrite);\n    case \"output_tokens\":\n      return finiteNumber(usage.outputTokens?.total);\n    case \"output_text_tokens\":\n      return finiteNumber(usage.outputTokens?.text);\n    case \"output_reasoning_tokens\":\n      return finiteNumber(usage.outputTokens?.reasoning);\n    case \"total_tokens\":\n      return sumDefined([\n        usage.inputTokens?.total,\n        usage.outputTokens?.total,\n      ]);\n  }\n}\n\nfunction normalizeUsage(usage: AiTokenUsage) {\n  return {\n    input_tokens: {\n      total: usage.inputTokens?.total ?? null,\n      no_cache: usage.inputTokens?.noCache ?? null,\n      cache_read: usage.inputTokens?.cacheRead ?? null,\n      cache_write: usage.inputTokens?.cacheWrite ?? null,\n    },\n    output_tokens: {\n      total: usage.outputTokens?.total ?? null,\n      text: usage.outputTokens?.text ?? null,\n      reasoning: usage.outputTokens?.reasoning ?? null,\n    },\n  };\n}\n\nfunction eventNameFor(mapping: AiMeterEventMapping) {\n  if (mapping.name) return mapping.name;\n  if (mapping.meterId) return \"ai_usage\";\n  throw new Error(\"AI meter mapping requires either name or meterId\");\n}\n\nfunction assertServerSideRuntime() {\n  if (typeof window !== \"undefined\" && typeof document !== \"undefined\") {\n    throw new Error(\n      \"RevKeen AI Meter Adapter must run in trusted server-side or edge code, not in browser code.\"\n    );\n  }\n}\n\nfunction valueFromOption<T>(\n  option: T | ((context: AiMeterUsageContext) => T | undefined) | undefined,\n  context: AiMeterUsageContext\n): T | undefined {\n  return typeof option === \"function\"\n    ? (option as (context: AiMeterUsageContext) => T | undefined)(context)\n    : option;\n}\n\nexport function buildAiUsageEvents(\n  options: RevKeenAiMeteringOptions,\n  context: AiMeterUsageContext\n): UsageEvent[] {\n  if (options.meters.length === 0) {\n    throw new Error(\"RevKeen AI Meter Adapter requires at least one meter mapping\");\n  }\n\n  const baseMetadata = valueFromOption(options.metadata, context);\n  const timestamp = valueFromOption(options.timestamp, context);\n  const baseIdempotencyKey = valueFromOption(options.idempotencyKey, context);\n  // REV-6490: usage ingestion rejects unkeyed events — the adapter fails loud\n  // here rather than emitting a payload the API will 400. Derive the key from\n  // a stable per-response id (e.g. the completion id), never a timestamp.\n  if (!baseIdempotencyKey) {\n    throw new Error(\n      \"RevKeen AI Meter Adapter: idempotencyKey is required — every usage event carries one (REV-6490). Provide options.idempotencyKey (a stable per-response id such as the completion id).\"\n    );\n  }\n  const normalizedUsage = normalizeUsage(context.usage);\n\n  return options.meters.flatMap((mapping) => {\n    const quantity = quantityFor(mapping.quantity, context);\n    if (quantity === undefined || (!mapping.includeZero && quantity === 0)) {\n      return [];\n    }\n\n    const idempotencySuffix =\n      mapping.idempotencyKeySuffix ??\n      (typeof mapping.quantity === \"string\" ? mapping.quantity : eventNameFor(mapping));\n\n    return [{\n      name: eventNameFor(mapping),\n      meter_id: mapping.meterId,\n      customer_id: options.customerId,\n      external_customer_id: options.externalCustomerId,\n      subscription_id: options.subscriptionId,\n      quantity,\n      timestamp: timestamp instanceof Date ? timestamp.toISOString() : timestamp,\n      idempotency_key: `${baseIdempotencyKey}:${idempotencySuffix}`,\n      metadata: {\n        ...(isRecord(baseMetadata) ? baseMetadata : {}),\n        ...(isRecord(mapping.metadata) ? mapping.metadata : {}),\n        revkeen: {\n          adapter: \"@revkeen/sdk/ai\",\n          schema_version: 1,\n          source: \"ai_meter_adapter\",\n        },\n        ai: {\n          provider: context.provider,\n          model: context.model,\n          operation: context.operation,\n          usage: normalizedUsage,\n          provider_hints: {\n            usage_raw: context.usage.raw ?? null,\n            provider_metadata: context.providerMetadata ?? null,\n          },\n        },\n      },\n    }];\n  });\n}\n\nasync function emitUsage(options: RevKeenAiMeteringOptions, context: AiMeterUsageContext) {\n  const events = buildAiUsageEvents(options, context);\n  if (events.length === 0) return;\n\n  await options.revkeen.usage.ingest(events.length === 1 ? events[0] : { events });\n}\n\nfunction resolveProvider(model: LanguageModel, fallback?: string): string {\n  return fallback ?? (typeof model === \"string\" ? \"unknown\" : (model as { provider?: string }).provider ?? \"unknown\");\n}\n\nfunction resolveModel(model: LanguageModel, fallback?: string): string {\n  return fallback ?? (typeof model === \"string\" ? model : (model as { modelId?: string }).modelId ?? \"unknown\");\n}\n\nfunction createMiddleware(options: RevKeenAiMeteringOptions): LanguageModelMiddleware {\n  return {\n    specificationVersion: \"v3\",\n    wrapGenerate: async ({ doGenerate, model }) => {\n      const result = await doGenerate();\n      const context: AiMeterUsageContext = {\n        usage: result.usage,\n        provider: options.provider ?? model.provider ?? \"unknown\",\n        model: options.model ?? model.modelId ?? \"unknown\",\n        operation: \"generate\",\n        providerMetadata: result.providerMetadata,\n      };\n\n      const ingest = emitUsage(options, context).catch(async (error) => {\n        await options.onIngestError?.(error, context);\n        if (options.awaitIngest !== false) throw error;\n      });\n\n      if (options.awaitIngest !== false) {\n        await ingest;\n      }\n\n      return result;\n    },\n    wrapStream: async ({ doStream, model }) => {\n      const result = await doStream();\n      const stream = result.stream.pipeThrough(new TransformStream({\n        async transform(part, controller) {\n          controller.enqueue(part);\n\n          if (part.type !== \"finish\") return;\n\n          const context: AiMeterUsageContext = {\n            usage: part.usage,\n            provider: options.provider ?? model.provider ?? \"unknown\",\n            model: options.model ?? model.modelId ?? \"unknown\",\n            operation: \"stream\",\n            providerMetadata: part.providerMetadata,\n          };\n\n          const ingest = emitUsage(options, context).catch(async (error) => {\n            await options.onIngestError?.(error, context);\n            if (options.awaitIngest !== false) throw error;\n          });\n\n          if (options.awaitIngest !== false) {\n            await ingest;\n          }\n        },\n      }));\n\n      return {\n        ...result,\n        stream,\n      };\n    },\n  };\n}\n\nexport function withRevKeenAiMetering(\n  model: LanguageModel,\n  options: RevKeenAiMeteringOptions\n): LanguageModel {\n  assertServerSideRuntime();\n\n  if (typeof model === \"string\") {\n    throw new Error(\"RevKeen AI Meter Adapter requires an AI SDK language model object, not a model id string.\");\n  }\n\n  return wrapLanguageModel({\n    model: model as Parameters<typeof wrapLanguageModel>[0][\"model\"],\n    middleware: createMiddleware(options),\n    providerId: resolveProvider(model, options.provider),\n    modelId: resolveModel(model, options.model),\n  }) as LanguageModel;\n}\n"],"mappings":"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","names":[]}