// src/spend.ts
function toGranularHttpBase(apiUrl) {
  const url = new URL(apiUrl);
  if (url.protocol === "ws:") {
    url.protocol = "http:";
  } else if (url.protocol === "wss:") {
    url.protocol = "https:";
  }
  url.pathname = url.pathname.replace(/\/ws\/connect$/, "").replace(/\/ws$/, "");
  if (!url.pathname || url.pathname === "/") {
    url.pathname = "/granular";
  }
  url.search = "";
  url.hash = "";
  return url.toString().replace(/\/$/, "");
}
function cleanIdPart(value) {
  return value.replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
}
function buildOpenAISpendEventId(usage, context = {}) {
  const requestId = usage.requestId?.trim();
  if (!requestId) return void 0;
  const scope = context.sessionId || context.environmentId || context.subjectId || context.sandboxId || "global";
  return ["spend", "openai", scope, requestId].map(cleanIdPart).join("_");
}
function pricingEffectiveAtSeconds(value) {
  if (!value) return null;
  const parsed = Date.parse(value);
  return Number.isFinite(parsed) ? Math.floor(parsed / 1e3) : null;
}
function compactContext(context) {
  return Object.fromEntries(
    Object.entries(context).filter(
      ([, value]) => value != null && value !== ""
    )
  );
}
function omitTenantId(context) {
  const scopedContext = { ...context };
  delete scopedContext.tenantId;
  return scopedContext;
}
async function recordOpenAIUsageSpend(options) {
  const usageContext = compactContext({
    ...options.usage.usageContext || {},
    ...options.context || {}
  });
  const context = omitTenantId(usageContext);
  const spendEventId = options.usage.spendEventId || buildOpenAISpendEventId(options.usage, context);
  const metadata = {
    ...options.metadata || {},
    ...options.usage.rawUsage !== void 0 ? { openaiUsage: options.usage.rawUsage } : {},
    usageContext: context
  };
  const response = await fetch(
    `${toGranularHttpBase(options.apiUrl)}/control/spend/events`,
    {
      method: "POST",
      cache: "no-store",
      headers: {
        Authorization: `Bearer ${options.token}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        ...spendEventId ? { spendEventId } : {},
        sandboxId: context.sandboxId || null,
        environmentId: context.environmentId || null,
        sessionId: context.sessionId || null,
        subjectId: context.subjectId || null,
        permissionProfileId: context.permissionProfileId || null,
        source: "openai",
        lineItemType: "llm_tokens",
        provider: options.usage.provider,
        model: options.usage.model,
        operation: options.usage.operation || "chat.completions",
        requestId: options.usage.requestId || null,
        inputTokens: options.usage.inputTokens,
        outputTokens: options.usage.outputTokens,
        cachedInputTokens: options.usage.cachedInputTokens,
        reasoningTokens: options.usage.reasoningTokens,
        quantity: options.usage.totalTokens,
        quantityUnit: "tokens",
        inputPricePerMillionMicros: options.usage.inputPricePerMillionMicros,
        cachedInputPricePerMillionMicros: options.usage.cachedInputPricePerMillionMicros,
        outputPricePerMillionMicros: options.usage.outputPricePerMillionMicros,
        amountMicros: options.usage.amountMicros,
        currency: options.usage.currency,
        pricingSource: options.usage.pricingSource,
        pricingEffectiveAt: pricingEffectiveAtSeconds(
          options.usage.pricingEffectiveAt
        ),
        estimated: false,
        metadata
      })
    }
  );
  if (!response.ok) {
    throw new Error(
      `Granular spend event failed (${response.status}): ${await response.text()}`
    );
  }
  return response.json();
}

export { buildOpenAISpendEventId, recordOpenAIUsageSpend, toGranularHttpBase };
//# sourceMappingURL=spend.mjs.map
//# sourceMappingURL=spend.mjs.map