import {
  LLM_CACHED_TOKEN_ATTR_KEYS,
  LLM_CACHE_WRITE_TOKEN_ATTR_KEYS,
  LLM_COST_ATTR_KEYS,
  LLM_INPUT_TOKEN_ATTR_KEYS,
  LLM_OUTPUT_TOKEN_ATTR_KEYS,
  LLM_REASONING_TOKEN_ATTR_KEYS,
  RUN_COST_ATTR_KEYS
} from "./chunk-K4DBDHLK.js";

// src/trace/execution-measurements.ts
var TOKEN_MEASUREMENT_KEY_GROUPS = [
  LLM_INPUT_TOKEN_ATTR_KEYS,
  LLM_OUTPUT_TOKEN_ATTR_KEYS,
  LLM_REASONING_TOKEN_ATTR_KEYS,
  LLM_CACHED_TOKEN_ATTR_KEYS,
  LLM_CACHE_WRITE_TOKEN_ATTR_KEYS
];
var EXECUTION_MEASUREMENT_KEY_GROUPS = [
  ...TOKEN_MEASUREMENT_KEY_GROUPS,
  LLM_COST_ATTR_KEYS
];
function summarizeExecutionMeasurements(spans) {
  const byId = /* @__PURE__ */ new Map();
  for (const span of spans) {
    if (byId.has(span.id)) {
      throw new Error(`summarizeExecutionMeasurements: duplicate span id "${span.id}"`);
    }
    byId.set(span.id, span);
  }
  const tokenMeasurementKeys = TOKEN_MEASUREMENT_KEY_GROUPS.flat();
  const candidates = spans.filter(
    (span) => span.modelCall || !span.aggregate && readNumber(span.attributes, tokenMeasurementKeys) !== void 0
  );
  const candidateIds = new Set(candidates.map((span) => span.id));
  const candidateChildren = /* @__PURE__ */ new Map();
  for (const candidate of candidates) {
    const parentId = nearestCandidateParent(candidate, byId, candidateIds);
    if (!parentId) continue;
    const children = candidateChildren.get(parentId) ?? [];
    children.push(candidate);
    candidateChildren.set(parentId, children);
  }
  const aggregateIds = classifyAggregateSpans(candidates, candidateChildren);
  const untypedRunCostIds = new Set(
    spans.filter(
      (span) => !span.modelCall && !span.aggregate && !candidateIds.has(span.id) && readNumber(span.attributes, RUN_COST_ATTR_KEYS) !== void 0
    ).map((span) => span.id)
  );
  const aggregateSourceIds = new Set(
    spans.filter(
      (span) => span.aggregate || aggregateIds.has(span.id) || untypedRunCostIds.has(span.id)
    ).map((span) => span.id)
  );
  const calls = candidates.filter((span) => !aggregateIds.has(span.id));
  const input = reconcileMeasurement(calls, byId, aggregateSourceIds, LLM_INPUT_TOKEN_ATTR_KEYS);
  const reasoning = reconcileMeasurement(
    calls,
    byId,
    aggregateSourceIds,
    LLM_REASONING_TOKEN_ATTR_KEYS
  );
  const output = reconcileMeasurement(
    calls,
    byId,
    aggregateSourceIds,
    LLM_OUTPUT_TOKEN_ATTR_KEYS,
    LLM_REASONING_TOKEN_ATTR_KEYS
  );
  const cached = reconcileMeasurement(calls, byId, aggregateSourceIds, LLM_CACHED_TOKEN_ATTR_KEYS);
  const cacheWrite = reconcileMeasurement(
    calls,
    byId,
    aggregateSourceIds,
    LLM_CACHE_WRITE_TOKEN_ATTR_KEYS
  );
  const aggregate = summarizeAggregateMeasurements(
    spans,
    byId,
    /* @__PURE__ */ new Set([...aggregateIds, ...untypedRunCostIds])
  );
  return {
    tokenUsage: {
      input: input.value ?? 0,
      output: Math.max(output.value ?? 0, reasoning.value ?? 0),
      ...reasoning.value !== void 0 ? { reasoning: reasoning.value } : {},
      ...cached.value !== void 0 ? { cached: cached.value } : {},
      ...cacheWrite.value !== void 0 ? { cacheWrite: cacheWrite.value } : {}
    },
    modelCallCount: calls.length,
    callSpanIds: calls.map((span) => span.id),
    cost: reconcileMeasurement(calls, byId, aggregateSourceIds, LLM_COST_ATTR_KEYS),
    ...aggregate ? { aggregate } : {}
  };
}
function recordAggregateMeasurements(raw, aggregate) {
  if (!aggregate) return;
  raw.aggregate_prompt_tokens = aggregate.tokenUsage.input;
  raw.aggregate_completion_tokens = aggregate.tokenUsage.output;
  if (aggregate.tokenUsage.reasoning !== void 0)
    raw.aggregate_reasoning_tokens = aggregate.tokenUsage.reasoning;
  if (aggregate.tokenUsage.cached !== void 0)
    raw.aggregate_cached_tokens = aggregate.tokenUsage.cached;
  if (aggregate.tokenUsage.cacheWrite !== void 0)
    raw.aggregate_cache_write_tokens = aggregate.tokenUsage.cacheWrite;
  if (aggregate.costUsd !== void 0) raw.aggregate_cost_usd = aggregate.costUsd;
}
function summarizeAggregateMeasurements(spans, byId, aggregateIds) {
  const aggregates = spans.filter((span) => span.aggregate || aggregateIds.has(span.id));
  const input = reconcileTopLevelMeasurement(aggregates, byId, LLM_INPUT_TOKEN_ATTR_KEYS);
  const output = reconcileTopLevelMeasurement(aggregates, byId, LLM_OUTPUT_TOKEN_ATTR_KEYS);
  const reasoning = reconcileTopLevelMeasurement(aggregates, byId, LLM_REASONING_TOKEN_ATTR_KEYS);
  const cached = reconcileTopLevelMeasurement(aggregates, byId, LLM_CACHED_TOKEN_ATTR_KEYS);
  const cacheWrite = reconcileTopLevelMeasurement(aggregates, byId, LLM_CACHE_WRITE_TOKEN_ATTR_KEYS);
  const costUsd = reconcileTopLevelMeasurement(aggregates, byId, LLM_COST_ATTR_KEYS);
  if (input === void 0 && output === void 0 && reasoning === void 0 && cached === void 0 && cacheWrite === void 0 && costUsd === void 0)
    return void 0;
  return {
    tokenUsage: {
      input: input ?? 0,
      output: output ?? reasoning ?? 0,
      ...reasoning !== void 0 ? { reasoning } : {},
      ...cached !== void 0 ? { cached } : {},
      ...cacheWrite !== void 0 ? { cacheWrite } : {}
    },
    ...costUsd !== void 0 ? { costUsd } : {}
  };
}
function reconcileTopLevelMeasurement(spans, byId, keys) {
  const selected = /* @__PURE__ */ new Map();
  for (const span of spans) {
    const value = readNumber(span.attributes, keys);
    if (value !== void 0) selected.set(span.id, value);
  }
  for (const spanId of [...selected.keys()]) {
    const span = byId.get(spanId);
    if (!span) continue;
    if (ancestorIds(span, byId).some((ancestorId) => selected.has(ancestorId))) {
      selected.delete(spanId);
    }
  }
  return selected.size > 0 ? [...selected.values()].reduce((total, value) => total + value, 0) : void 0;
}
function reconcileMeasurement(calls, byId, aggregateSourceIds, keys, fallbackKeys) {
  const selected = /* @__PURE__ */ new Map();
  const callIds = new Set(calls.map((call) => call.id));
  let reportingCalls = 0;
  for (const call of calls) {
    const primary = nearestMeasurement(call, byId, callIds, aggregateSourceIds, keys);
    const fallback = fallbackKeys ? nearestMeasurement(call, byId, callIds, aggregateSourceIds, fallbackKeys) : void 0;
    const source = primary && fallback && primary.span.id === fallback.span.id ? { span: primary.span, value: Math.max(primary.value, fallback.value) } : primary ?? fallback;
    if (!source) continue;
    reportingCalls += 1;
    selected.set(source.span.id, source.value);
  }
  for (const spanId of [...selected.keys()]) {
    const span = byId.get(spanId);
    if (!span) continue;
    if (ancestorIds(span, byId).some(
      (ancestorId) => selected.has(ancestorId) && !callIds.has(ancestorId)
    )) {
      selected.delete(spanId);
    }
  }
  return {
    ...selected.size > 0 ? { value: [...selected.values()].reduce((total, value) => total + value, 0) } : {},
    reportingCalls,
    complete: calls.length > 0 && reportingCalls === calls.length
  };
}
function nearestMeasurement(call, byId, callIds, aggregateSourceIds, keys) {
  let current = call;
  const seen = /* @__PURE__ */ new Set();
  while (current && !seen.has(current.id)) {
    seen.add(current.id);
    const value = readNumber(current.attributes, keys);
    if (value !== void 0 && (current.id === call.id || !callIds.has(current.id) && aggregateSourceIds.has(current.id))) {
      return { span: current, value };
    }
    current = current.parentId ? byId.get(current.parentId) : void 0;
  }
  return void 0;
}
function classifyAggregateSpans(candidates, childrenById) {
  const aggregateIds = /* @__PURE__ */ new Set();
  const summaries = /* @__PURE__ */ new Map();
  const visiting = /* @__PURE__ */ new Set();
  const visit = (span) => {
    const cached = summaries.get(span.id);
    if (cached) return cached;
    if (visiting.has(span.id)) return emptyRetainedCallSummary();
    visiting.add(span.id);
    const descendants = emptyRetainedCallSummary();
    for (const child of childrenById.get(span.id) ?? []) {
      const childDescendants = visit(child);
      if (!aggregateIds.has(child.id)) addRetainedCall(descendants, child);
      addRetainedCallSummary(descendants, childDescendants);
    }
    if (descendants.callCount > 0 && (!span.modelCall || hasCompatibleDescendantMeasurements(span, descendants))) {
      aggregateIds.add(span.id);
    }
    visiting.delete(span.id);
    summaries.set(span.id, descendants);
    return descendants;
  };
  for (const candidate of candidates) visit(candidate);
  return aggregateIds;
}
function emptyRetainedCallSummary() {
  return {
    callCount: 0,
    measurements: EXECUTION_MEASUREMENT_KEY_GROUPS.map(() => ({
      total: 0,
      reportingCalls: 0
    }))
  };
}
function addRetainedCall(summary, span) {
  summary.callCount += 1;
  for (let index = 0; index < EXECUTION_MEASUREMENT_KEY_GROUPS.length; index += 1) {
    const value = readNumber(span.attributes, EXECUTION_MEASUREMENT_KEY_GROUPS[index]);
    if (value === void 0) continue;
    const measurement = summary.measurements[index];
    measurement.total += value;
    measurement.reportingCalls += 1;
  }
}
function addRetainedCallSummary(target, source) {
  target.callCount += source.callCount;
  for (let index = 0; index < target.measurements.length; index += 1) {
    const measurement = target.measurements[index];
    const sourceMeasurement = source.measurements[index];
    measurement.total += sourceMeasurement.total;
    measurement.reportingCalls += sourceMeasurement.reportingCalls;
  }
}
function hasCompatibleDescendantMeasurements(span, descendants) {
  let parentMeasurements = 0;
  let descendantMeasurements = 0;
  for (let index = 0; index < EXECUTION_MEASUREMENT_KEY_GROUPS.length; index += 1) {
    const keys = EXECUTION_MEASUREMENT_KEY_GROUPS[index];
    const parentValue = readNumber(span.attributes, keys);
    const measurement = descendants.measurements[index];
    if (parentValue !== void 0) parentMeasurements += 1;
    if (measurement.reportingCalls > 0) descendantMeasurements += 1;
    if (parentValue === void 0 || measurement.reportingCalls === 0) continue;
    if (measurement.reportingCalls !== descendants.callCount) continue;
    if (Math.abs(parentValue - measurement.total) > 1e-12) return false;
  }
  return parentMeasurements === 0 || descendantMeasurements > 0;
}
function ancestorIds(span, byId) {
  const ids = [];
  const seen = /* @__PURE__ */ new Set();
  let parentId = span.parentId;
  while (parentId && !seen.has(parentId)) {
    ids.push(parentId);
    seen.add(parentId);
    parentId = byId.get(parentId)?.parentId;
  }
  return ids;
}
function nearestCandidateParent(span, byId, candidateIds) {
  const seen = /* @__PURE__ */ new Set();
  let parentId = span.parentId;
  while (parentId && !seen.has(parentId)) {
    if (candidateIds.has(parentId)) return parentId;
    seen.add(parentId);
    parentId = byId.get(parentId)?.parentId;
  }
  return void 0;
}
function readNumber(attributes, keys) {
  for (const key of keys) {
    const value = attributes[key];
    const parsed = typeof value === "number" ? value : typeof value === "string" && value.length > 0 ? Number(value) : Number.NaN;
    if (Number.isFinite(parsed) && parsed >= 0) return parsed;
  }
  return void 0;
}

export {
  summarizeExecutionMeasurements,
  recordAggregateMeasurements
};
//# sourceMappingURL=chunk-H5UD2323.js.map