import {
  recordAggregateMeasurements,
  summarizeExecutionMeasurements
} from "./chunk-H5UD2323.js";
import {
  extractUsage,
  extractUsageFromSse
} from "./chunk-PXE2VKMX.js";
import {
  analyzeTraces
} from "./chunk-NUKSVU3W.js";
import {
  applyToolSpanOtlpAttributes,
  compareSpanTime,
  firstStringAttr,
  projectOtlpFlatLine,
  spanEpochMillis,
  traceSpanKindToOpenInferenceKind
} from "./chunk-IR3KBHOY.js";
import {
  defaultProviderRedactor,
  providerFromBaseUrl
} from "./chunk-PC4UYEBM.js";
import {
  validateRunRecord
} from "./chunk-IILEIWGW.js";
import {
  canonicalize,
  hashJson
} from "./chunk-VSMTAMNK.js";
import {
  ReplayError
} from "./chunk-ONWEPEDO.js";
import {
  LLM_INPUT_TOKENS,
  LLM_MODEL_ATTR_KEYS,
  LLM_MODEL_NAME,
  LLM_OUTPUT_TOKENS,
  OPENINFERENCE_SPAN_KIND,
  TOOL_NAME,
  applyLlmSpanOtlpAttributes
} from "./chunk-K4DBDHLK.js";

// src/trace-analyst/hook.ts
var DEFAULT_QUESTION = "Summarise what happened in this run. Surface any failure modes, surprising findings, or evidence that the run's verdict is wrong.";
function traceAnalystOnRunComplete(opts) {
  return async (ctx) => {
    if (opts.shouldRun && !opts.shouldRun(ctx)) return;
    const source = opts.analyze.source;
    if (source === void 0) {
      await ctx.store.appendEvent({
        eventId: `analyst-skip-${ctx.runId}`,
        runId: ctx.runId,
        kind: "log",
        timestamp: Date.now(),
        payload: { source: "trace_analyst_hook", reason: "no source configured" }
      });
      return;
    }
    const result = await analyzeTraces({ question: opts.question ?? DEFAULT_QUESTION }, {
      ...opts.analyze,
      source
    });
    if (opts.save) await opts.save(result, ctx);
    if (opts.gateOn && !opts.gateOn(result, ctx)) {
      await ctx.store.appendEvent({
        eventId: `analyst-gate-${ctx.runId}`,
        runId: ctx.runId,
        kind: "log",
        timestamp: Date.now(),
        payload: {
          source: "trace_analyst_hook",
          reason: "analyst_gate_failed",
          findings: result.findings
        }
      });
    }
  };
}

// src/trace-analyst/insights.ts
var DOMAIN_STOP_WORDS = /* @__PURE__ */ new Set([
  "and",
  "advanced",
  "app",
  "build",
  "create",
  "easy",
  "expert",
  "extreme",
  "for",
  "from",
  "hard",
  "implementation",
  "integrate",
  "medium",
  "project",
  "task",
  "the",
  "this",
  "with",
  "workflow"
]);
function tokenizeDomainWords(value) {
  return [...value.matchAll(/[A-Za-z][A-Za-z0-9.+#-]{2,}/g)].map((match) => match[0].toLowerCase()).filter((word) => !DOMAIN_STOP_WORDS.has(word));
}
function inferDomainKeywords(suite) {
  const suiteWords = new Set(tokenizeDomainWords(`${suite.name} ${suite.collectionId ?? ""}`));
  const source = [
    suite.name,
    suite.collectionId ?? "",
    ...suite.tasks.flatMap((task) => [
      task.id,
      task.name,
      task.prompt ?? "",
      task.difficulty ?? "",
      ...task.tags ?? [],
      ...task.gaps ?? []
    ])
  ].join(" ");
  const counts = /* @__PURE__ */ new Map();
  for (const word of tokenizeDomainWords(source)) counts.set(word, (counts.get(word) ?? 0) + 1);
  return [...counts.entries()].filter(([word, count]) => count >= 2 || suiteWords.has(word)).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([word]) => word).slice(0, 18);
}
function domainEvidencePattern(keywords) {
  const escaped = keywords.filter((keyword) => keyword.length >= 3).map((keyword) => keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
  return escaped.length > 0 ? new RegExp(`(?<![A-Za-z0-9])(?:${escaped.join("|")})(?![A-Za-z0-9])`, "i") : /(?<![A-Za-z0-9])(?:sdk|api|css|dns|xml|provider|client|service|integration|webhook|transaction|auth|oauth|graphql|rest)(?![A-Za-z0-9])/i;
}
function describeTraceInsightScope(suite) {
  const taskLabel = suite.tasks.length === 1 ? "1 implementation task" : `${suite.tasks.length} implementation tasks`;
  const tags = /* @__PURE__ */ new Map();
  for (const task of suite.tasks) {
    for (const tag of task.tags ?? []) tags.set(tag, (tags.get(tag) ?? 0) + 1);
  }
  const topTags = [...tags.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 8).map(([tag]) => tag);
  if (topTags.length > 0) return `${taskLabel} across ${topTags.join(", ")}.`;
  const difficulties = [
    ...new Set(
      suite.tasks.map((task) => task.difficulty).filter((value) => Boolean(value))
    )
  ].join(", ");
  return `${taskLabel} across ${difficulties || "the selected benchmark scope"}.`;
}
function planTraceInsightQuestions(input) {
  const hasFailures = input.suite.tasks.some((task) => task.outcome && task.outcome !== "satisfied");
  const hasMultipleShots = input.suite.tasks.some(
    (task) => (task.gaps ?? []).some((gap) => /shot|review|retry|continue/i.test(gap))
  );
  const questions = [
    {
      id: "execution-path",
      question: "What did the worker actually do before the first meaningful implementation edit?",
      why: "Separates grounded execution from polished but shallow output."
    },
    {
      id: "research-grounding",
      question: "Did the worker inspect docs, source, examples, or package references before committing to an implementation path?",
      why: "Identifies whether failures came from weak retrieval, weak examples, or premature coding."
    },
    {
      id: "domain-proof",
      question: "Which tasks produced executable domain proof versus UI copy, placeholders, or inferred behavior?",
      why: "Keeps product-quality claims tied to concrete evidence."
    },
    {
      id: "root-cause",
      question: "For each major failure cluster, is the likely root cause prompt/scaffold, docs/examples, SDK/API ergonomics, evaluator, runtime, or model behavior?",
      why: "Turns trace observations into actionable ownership."
    },
    {
      id: "evidence-quality",
      question: "Which external-facing claims are directly supported by trace ids, span ids, verifier findings, reviewer notes, or generated code?",
      why: "Prevents unsupported customer-report conclusions."
    }
  ];
  if (hasMultipleShots) {
    questions.push({
      id: "reviewer-lift",
      question: "Where did reviewer feedback improve score, stall, or regress across shots?",
      why: "Shows whether the driver loop is learning or merely repeating work."
    });
  }
  if (hasFailures) {
    questions.push({
      id: "optimization-targets",
      question: "Which prompt, evaluator, scaffold, or workflow changes should feed the next GEPA/autoresearch optimization run?",
      why: "Connects benchmark evidence to the optimization loop."
    });
  }
  return questions;
}
function buildTraceInsightContext(input) {
  return {
    suite: input.suite,
    scope: describeTraceInsightScope(input.suite),
    keywords: inferDomainKeywords(input.suite),
    questions: planTraceInsightQuestions(input),
    panel: defaultTraceInsightPanel(),
    findings: input.findings ?? [],
    agent: input.agent ?? null,
    totals: input.totals ?? null
  };
}
function scoreTraceInsightReadiness(context) {
  const failedTasks = context.suite.tasks.filter(
    (task) => task.outcome && task.outcome !== "satisfied"
  );
  const findingTaskIds = new Set(context.findings.flatMap((finding) => finding.taskIds));
  const failedTasksWithFindings = failedTasks.filter((task) => findingTaskIds.has(task.id));
  const tasksWithGaps = context.suite.tasks.filter((task) => (task.gaps ?? []).length > 0);
  const gates = [
    {
      id: "domain-context",
      label: "Domain context inferred",
      passed: context.keywords.length > 0,
      severity: "high",
      detail: context.keywords.length > 0 ? `${context.keywords.length} domain terms inferred: ${context.keywords.slice(0, 8).join(", ")}` : "No domain terms were inferred from suite, tasks, prompts, tags, or gaps."
    },
    {
      id: "panel-coverage",
      label: "Analyst panel planned",
      passed: context.panel.length >= 4 && context.questions.length >= 5,
      severity: "high",
      detail: `${context.panel.length} panel roles and ${context.questions.length} investigation questions planned.`
    },
    {
      id: "failure-coverage",
      label: "Failures mapped to findings",
      passed: failedTasks.length === 0 || failedTasksWithFindings.length / failedTasks.length >= 0.5,
      severity: "critical",
      detail: failedTasks.length === 0 ? "No failed tasks in suite." : `${failedTasksWithFindings.length}/${failedTasks.length} failed tasks appear in finding clusters.`
    },
    {
      id: "gap-evidence",
      label: "Task gaps captured",
      passed: failedTasks.length === 0 || tasksWithGaps.length / failedTasks.length >= 0.5,
      severity: "medium",
      detail: `${tasksWithGaps.length} tasks include explicit evaluator or analyst gaps.`
    }
  ];
  const penalty = gates.reduce((sum, gate) => {
    if (gate.passed) return sum;
    if (gate.severity === "critical") return sum + 35;
    if (gate.severity === "high") return sum + 20;
    if (gate.severity === "medium") return sum + 10;
    return sum + 5;
  }, 0);
  const score = Math.max(0, Math.min(1, 1 - penalty / 100));
  return {
    score,
    grade: score >= 0.9 ? "external-ready" : score >= 0.7 ? "internal-review" : "raw-analysis",
    gates
  };
}
function defaultTraceInsightPanel() {
  return [
    {
      id: "trace-forensics",
      name: "Trace Forensics",
      responsibility: "Reconstruct what the worker did in order, including research, edits, reviewer interventions, verifier feedback, and stop reason."
    },
    {
      id: "root-cause",
      name: "Root Cause",
      responsibility: "Map failures to prompt/scaffold, docs/examples, SDK/API/product ergonomics, evaluator, runtime, or model behavior."
    },
    {
      id: "optimization",
      name: "Optimization",
      responsibility: "Identify prompt, reviewer, evaluator, scaffold, and GEPA/autoresearch changes that should be tested next."
    },
    {
      id: "external-evidence",
      name: "External Evidence",
      responsibility: "Separate customer-safe claims from internal harness findings and reject conclusions without task, trace, span, code, reviewer, or verifier evidence."
    }
  ];
}
function buildTraceInsightPrompt(input) {
  const context = buildTraceInsightContext(input);
  const maxRepresentativeTraces = input.maxRepresentativeTraces ?? 6;
  return `Analyze this benchmark run and produce evidence-backed trace intelligence.

Audience:
- internal AI/product leadership
- possible customer-facing report for ${input.suite.name}

Investigation plan:
${context.questions.map((item, index) => `${index + 1}. ${item.question} (${item.why})`).join("\n")}

Analyst panel:
${context.panel.map((role) => `- ${role.name}: ${role.responsibility}`).join("\n")}

If the task branches are independent, use subagents for the panel roles above and aggregate their findings. Do not run a panel role unless its answer will change the final report.

Required output:
1. Executive verdict: what this run proves and does not prove.
2. The investigation questions you answered and the evidence used.
3. Failure taxonomy: agent prompting, evaluator/harness, docs/examples, SDK/API/product integration, infra.
4. Evidence-backed examples with trace ids/task ids and concrete verifier findings.
5. Highest-ROI fixes for the benchmark harness, prompt/GEPA optimization, and customer-facing product/docs surface.
6. What is safe for an external report versus what must stay internal.
7. One rerun plan that would validate lift after optimization.

Budget:
- Inspect the dataset overview, the failure summary, and at most ${maxRepresentativeTraces} representative traces.
- Prefer traces named in the failure summary over broad exploration.
- Do not do exhaustive trace sweeps.
- Return the final report as soon as the taxonomy and examples are supported.

Run summary:
${JSON.stringify(
    {
      suite: input.suite.name,
      scope: context.scope,
      inferredKeywords: context.keywords,
      agent: context.agent,
      totals: context.totals,
      findings: context.findings.map((finding) => ({
        kind: finding.kind,
        severity: finding.severity,
        taskCount: finding.taskIds.length,
        proposedFixClass: finding.proposedFixClass
      })),
      failures: input.suite.tasks.filter((task) => task.outcome && task.outcome !== "satisfied").map((task) => ({
        task: task.id,
        difficulty: task.difficulty,
        outcome: task.outcome,
        score: task.score,
        gaps: task.gaps ?? []
      }))
    },
    null,
    2
  )}

Use the trace tools. Do not invent facts. Cite task ids. Separate customer-facing claims from internal harness/model findings.`;
}

// src/trace-analyst/otlp-flatten.ts
var DEFAULT_KIND_MAP = {
  0: "SPAN_KIND_UNSPECIFIED",
  1: "SPAN_KIND_INTERNAL",
  2: "SPAN_KIND_SERVER",
  3: "SPAN_KIND_CLIENT",
  4: "SPAN_KIND_PRODUCER",
  5: "SPAN_KIND_CONSUMER"
};
var STATUS_MAP = {
  0: "STATUS_CODE_UNSET",
  1: "STATUS_CODE_OK",
  2: "STATUS_CODE_ERROR"
};
function attrValue(v) {
  if (v.stringValue !== void 0) return v.stringValue;
  if (v.intValue !== void 0) return Number(v.intValue);
  if (v.doubleValue !== void 0) return v.doubleValue;
  if (v.boolValue !== void 0) return v.boolValue;
  return "";
}
function attrsToRecord(attrs) {
  const out = {};
  for (const a of attrs) out[a.key] = attrValue(a.value);
  return out;
}
function nanoToIso(nano) {
  const ms = Number(nano) / 1e6;
  return Number.isFinite(ms) ? new Date(ms).toISOString() : (/* @__PURE__ */ new Date(0)).toISOString();
}
function applyOpenInference(attrs) {
  if ("llm.model" in attrs && !(LLM_MODEL_NAME in attrs)) {
    attrs[LLM_MODEL_NAME] = attrs["llm.model"];
  }
  if ("llm.input_tokens" in attrs && !(LLM_INPUT_TOKENS in attrs)) {
    attrs[LLM_INPUT_TOKENS] = attrs["llm.input_tokens"];
  }
  if ("inference.llm.input_tokens" in attrs && !(LLM_INPUT_TOKENS in attrs)) {
    attrs[LLM_INPUT_TOKENS] = attrs["inference.llm.input_tokens"];
  }
  if ("llm.output_tokens" in attrs && !(LLM_OUTPUT_TOKENS in attrs)) {
    attrs[LLM_OUTPUT_TOKENS] = attrs["llm.output_tokens"];
  }
  if ("inference.llm.output_tokens" in attrs && !(LLM_OUTPUT_TOKENS in attrs)) {
    attrs[LLM_OUTPUT_TOKENS] = attrs["inference.llm.output_tokens"];
  }
  if (TOOL_NAME in attrs && !("inference.tool.name" in attrs)) {
    attrs["inference.tool.name"] = attrs[TOOL_NAME];
  }
  if ("span.kind" in attrs && !(OPENINFERENCE_SPAN_KIND in attrs)) {
    attrs[OPENINFERENCE_SPAN_KIND] = String(attrs["span.kind"]).toUpperCase();
  }
}
function flattenOtlpExportToNdjson(otlpExport, opts = {}) {
  const vocab = opts.attributeVocabulary ?? "openinference";
  const kindMap = { ...DEFAULT_KIND_MAP, ...opts.kindMap };
  const lines = [];
  for (const rs of otlpExport.resourceSpans ?? []) {
    const resource = { attributes: attrsToRecord(rs.resource?.attributes ?? []) };
    for (const scope of rs.scopeSpans ?? []) {
      for (const span of scope.spans ?? []) {
        const attributes = attrsToRecord(span.attributes ?? []);
        if (vocab === "openinference") applyOpenInference(attributes);
        const line = {
          trace_id: span.traceId,
          span_id: span.spanId,
          parent_span_id: span.parentSpanId ?? null,
          name: span.name,
          kind: kindMap[span.kind] ?? "SPAN_KIND_UNSPECIFIED",
          start_time: nanoToIso(span.startTimeUnixNano),
          end_time: nanoToIso(span.endTimeUnixNano),
          status: {
            code: STATUS_MAP[span.status?.code ?? 0] ?? "STATUS_CODE_UNSET",
            ...span.status?.message ? { message: span.status.message } : {}
          },
          resource,
          attributes
        };
        if (span.events && span.events.length > 0) {
          line.events = span.events.map((e) => ({
            name: e.name,
            timeUnixNano: e.timeUnixNano,
            ...e.attributes ? { attributes: attrsToRecord(e.attributes) } : {}
          }));
        }
        lines.push(line);
      }
    }
  }
  return lines;
}

// src/trace-analyst/otlp-to-run-records.ts
function otlpToRunRecords(otlpJsonl, opts) {
  return otlpToTraceRunRecords(otlpJsonl, opts).map((r) => r.record);
}
function otlpRowsToRunRecords(rows, opts) {
  return otlpRowsToTraceRunRecords(rows, opts).map((row) => row.record);
}
function otlpToTraceRunRecords(otlpJsonl, opts) {
  return traceRunRecordsFromSpans(
    groupSpansByLogicalRun(groupJsonlSpansByTrace(otlpJsonl), opts.logicalRunIdForTrace),
    opts
  );
}
function otlpRowsToTraceRunRecords(rows, opts) {
  return traceRunRecordsFromSpans(
    groupSpansByLogicalRun(groupRowsByTrace(rows), opts.logicalRunIdForTrace),
    opts
  );
}
function traceRunRecordsFromSpans(byTrace, opts) {
  const splitTag = opts.splitTag ?? "holdout";
  const commitSha = opts.commitSha ?? "unknown";
  const promptHash = opts.promptHash ?? "unknown";
  const configHash = opts.configHash ?? "unknown";
  const seed = opts.seed ?? 0;
  const fallbackModel = opts.fallbackModel ?? "unknown@otlp";
  if (byTrace.size === 0) {
    throw new Error(
      "otlpToRunRecords: OTLP input produced zero valid spans \u2014 every row was empty, malformed, or missing trace_id/span_id"
    );
  }
  const traceIds = [...byTrace.keys()].sort();
  const out = [];
  for (const traceId of traceIds) {
    const spans = byTrace.get(traceId);
    const agg = aggregateTrace(traceId, spans, fallbackModel);
    const score = resolveScore(opts, traceId, agg);
    const { costUsd, costProvenance } = resolveCost(opts, agg);
    const raw = {
      source_trace_count: agg.sourceTraceCount,
      span_count: agg.spanCount,
      llm_span_count: agg.llmSpanCount,
      tool_span_count: agg.toolSpanCount,
      agent_span_count: agg.agentSpanCount,
      error_span_count: agg.errorSpanCount,
      prompt_tokens: agg.tokenUsage.input,
      completion_tokens: agg.tokenUsage.output
    };
    if (agg.tokenUsage.reasoning !== void 0) raw.reasoning_tokens = agg.tokenUsage.reasoning;
    if (agg.tokenUsage.cached !== void 0) raw.cached_tokens = agg.tokenUsage.cached;
    if (agg.tokenUsage.cacheWrite !== void 0) {
      raw.cache_write_tokens = agg.tokenUsage.cacheWrite;
    }
    if (agg.costMeasurement.value !== void 0 && !agg.costMeasurement.complete) {
      raw.partial_observed_cost_usd = agg.costMeasurement.value;
    }
    recordAggregateMeasurements(raw, agg.aggregateMeasurement);
    if (costProvenance.kind === "uncaptured") raw.cost_unpriced = 1;
    const outcome = splitTag === "holdout" ? { holdoutScore: score, raw } : { searchScore: score, raw };
    const { promptText, completionText } = extractPromptCompletion(spans, agg.callSpanIds);
    const judgeMetadata = opts.judgeMetadataForTrace?.(traceId);
    const record = validateRunRecord({
      runId: `otlp:${opts.experimentId}:${opts.candidateId}:${traceId}`,
      experimentId: opts.experimentId,
      candidateId: opts.candidateId,
      seed,
      model: ensureSnapshot(agg.model, fallbackModel),
      promptHash,
      configHash,
      commitSha,
      wallMs: agg.wallMs,
      costUsd,
      costProvenance,
      tokenUsage: agg.tokenUsage,
      ...judgeMetadata ? { judgeMetadata } : {},
      outcome,
      ...agg.firstErrorMessage ? { failureMode: agg.firstErrorMessage } : {},
      splitTag,
      scenarioId: traceId
    });
    out.push({
      record,
      ...promptText !== void 0 ? { promptText } : {},
      ...completionText !== void 0 ? { completionText } : {}
    });
  }
  return out;
}
function* yieldJsonlRows(otlpJsonl) {
  for (const line of otlpJsonl.split("\n")) {
    const trimmed = line.trim();
    if (trimmed.length === 0) continue;
    let parsed;
    try {
      parsed = JSON.parse(trimmed);
    } catch {
      continue;
    }
    if (parsed && typeof parsed === "object") yield parsed;
  }
}
function groupJsonlSpansByTrace(otlpJsonl) {
  return groupRowsByTrace(yieldJsonlRows(otlpJsonl));
}
function groupRowsByTrace(rows) {
  const byTrace = /* @__PURE__ */ new Map();
  for (const row of rows) {
    if (!row || typeof row !== "object") continue;
    const span = projectOtlpFlatLine(row);
    if (!span) continue;
    const arr = byTrace.get(span.trace_id);
    if (arr) arr.push(span);
    else byTrace.set(span.trace_id, [span]);
  }
  return byTrace;
}
function groupSpansByLogicalRun(byTrace, logicalRunIdForTrace) {
  if (!logicalRunIdForTrace) return byTrace;
  const byRun = /* @__PURE__ */ new Map();
  for (const [traceId, spans] of byTrace) {
    const suppliedRunId = logicalRunIdForTrace(traceId);
    if (typeof suppliedRunId !== "string" || suppliedRunId.trim().length === 0) {
      throw new Error(
        `otlpToRunRecords: logicalRunIdForTrace('${traceId}') returned an empty run id`
      );
    }
    const runId = suppliedRunId.trim();
    const target = byRun.get(runId) ?? [];
    for (const span of spans) {
      target.push({
        ...span,
        span_id: qualifySpanId(traceId, span.span_id),
        parent_span_id: span.parent_span_id ? qualifySpanId(traceId, span.parent_span_id) : null
      });
    }
    byRun.set(runId, target);
  }
  return byRun;
}
function qualifySpanId(traceId, spanId) {
  const prefix = `${traceId}:`;
  return spanId.startsWith(prefix) ? spanId : `${prefix}${spanId}`;
}
function aggregateTrace(traceId, spans, fallbackModel) {
  const ordered = [...spans].sort(
    (a2, b2) => compareSpanTime(a2.start_time, b2.start_time) || a2.span_id.localeCompare(b2.span_id)
  );
  const measurements = summarizeExecutionMeasurements(
    ordered.map((span) => ({
      id: span.span_id,
      ...span.parent_span_id ? { parentId: span.parent_span_id } : {},
      attributes: span.attributes,
      modelCall: span.kind === "LLM" || span.kind === "UNKNOWN" && (span.model_name !== null || typeof span.attributes["gen_ai.operation.name"] === "string"),
      aggregate: span.kind !== "LLM" && span.kind !== "UNKNOWN"
    }))
  );
  let toolSpanCount = 0;
  let agentSpanCount = 0;
  let errorSpanCount = 0;
  let firstErrorMessage;
  const modelVotes = /* @__PURE__ */ new Map();
  let earliest = ordered[0]?.start_time ?? "";
  let latest = ordered[0]?.end_time ?? "";
  for (const s of ordered) {
    if (s.start_time && (!earliest || compareSpanTime(s.start_time, earliest) < 0))
      earliest = s.start_time;
    if (s.end_time && (!latest || compareSpanTime(s.end_time, latest) > 0)) latest = s.end_time;
    if (s.kind === "TOOL") {
      toolSpanCount += 1;
    } else if (s.kind === "AGENT") {
      agentSpanCount += 1;
    }
    if (s.status === "ERROR") {
      errorSpanCount += 1;
      if (firstErrorMessage === void 0) {
        firstErrorMessage = (s.status_message ?? `${s.name} \u2014 STATUS_CODE_ERROR`).slice(0, 500);
      }
    }
  }
  const callSpanIds = new Set(measurements.callSpanIds);
  for (const span of ordered) {
    if (!callSpanIds.has(span.span_id)) continue;
    const model2 = firstStringAttr(span.attributes, LLM_MODEL_ATTR_KEYS) ?? span.model_name;
    if (model2) modelVotes.set(model2, (modelVotes.get(model2) ?? 0) + 1);
  }
  const model = topVote(modelVotes) ?? firstModelAttr(ordered) ?? fallbackModel;
  let wallMs = 0;
  const a = spanEpochMillis(earliest);
  const b = spanEpochMillis(latest);
  if (a !== null && b !== null) wallMs = Math.max(0, b - a);
  const sourceTraceIds = [...new Set(spans.map((span) => span.trace_id))].sort();
  return {
    traceId,
    sourceTraceCount: sourceTraceIds.length,
    sourceTraceIds,
    spanCount: spans.length,
    llmSpanCount: measurements.modelCallCount,
    toolSpanCount,
    agentSpanCount,
    errorSpanCount,
    tokenUsage: measurements.tokenUsage,
    firstErrorMessage,
    model,
    startTime: earliest,
    endTime: latest,
    wallMs,
    callSpanIds: measurements.callSpanIds,
    costMeasurement: measurements.cost,
    ...measurements.aggregate ? { aggregateMeasurement: measurements.aggregate } : {}
  };
}
function resolveScore(opts, traceId, agg) {
  const supplied = opts.scoreForTrace?.(traceId, agg);
  if (supplied !== void 0) {
    if (!Number.isFinite(supplied)) {
      throw new Error(
        `otlpToRunRecords: scoreForTrace('${traceId}') returned non-finite ${supplied}`
      );
    }
    return supplied;
  }
  return agg.errorSpanCount > 0 ? 0 : 1;
}
function resolveCost(opts, agg) {
  const observedCost = agg.costMeasurement;
  if (observedCost.complete && observedCost.value !== void 0) {
    return {
      costUsd: observedCost.value,
      costProvenance: { kind: "observed", usd: observedCost.value }
    };
  }
  if (agg.aggregateMeasurement?.costUsd !== void 0) {
    return {
      costUsd: agg.aggregateMeasurement.costUsd,
      costProvenance: { kind: "observed", usd: agg.aggregateMeasurement.costUsd }
    };
  }
  if (opts.priceUsdPerToken !== void 0) {
    const totalTokens = agg.tokenUsage.input + agg.tokenUsage.output;
    const costUsd = totalTokens * opts.priceUsdPerToken;
    return { costUsd, costProvenance: { kind: "estimated", usd: costUsd } };
  }
  return { costUsd: 0, costProvenance: { kind: "uncaptured", usd: null } };
}
function extractPromptCompletion(spans, callSpanIds) {
  const callIds = new Set(callSpanIds);
  const measuredCalls = spans.filter((span) => callIds.has(span.span_id));
  const llm = (measuredCalls.length > 0 ? measuredCalls : spans.filter((s) => s.kind === "LLM")).sort(
    (a, b) => compareSpanTime(a.start_time, b.start_time) || a.span_id.localeCompare(b.span_id)
  );
  if (llm.length === 0) return {};
  const promptText = firstStringAttr(llm[0].attributes, ["input.value", "llm.input_messages", "gen_ai.prompt"]) ?? void 0;
  const last = llm[llm.length - 1];
  const completionText = firstStringAttr(last.attributes, [
    "output.value",
    "llm.output_messages",
    "gen_ai.completion"
  ]) ?? void 0;
  return {
    ...promptText !== void 0 ? { promptText } : {},
    ...completionText !== void 0 ? { completionText } : {}
  };
}
function topVote(votes) {
  let best = null;
  let bestN = 0;
  for (const [k, n] of votes) {
    if (n > bestN || n === bestN && best !== null && k < best) {
      best = k;
      bestN = n;
    }
  }
  return best;
}
function firstModelAttr(spans) {
  for (const s of spans) {
    const m = firstStringAttr(s.attributes, LLM_MODEL_ATTR_KEYS) ?? s.model_name;
    if (m) return m;
  }
  return null;
}
function ensureSnapshot(model, fallbackModel) {
  if (modelHasSnapshot(model)) return model;
  const fallbackTag = fallbackModel.includes("@") ? fallbackModel.slice(fallbackModel.indexOf("@")) : "@otlp";
  return `${model}${fallbackTag}`;
}
function modelHasSnapshot(model) {
  if (model.includes("@")) return true;
  if (/-\d{8}$/.test(model)) return true;
  if (/-\d{4}-\d{2}-\d{2}$/.test(model)) return true;
  if (/:date-/.test(model)) return true;
  return false;
}

// src/trace/store.ts
var InMemoryTraceStore = class {
  runs = /* @__PURE__ */ new Map();
  allSpans = [];
  allEvents = [];
  allArtifacts = [];
  allBudget = [];
  async appendRun(run) {
    if (this.runs.has(run.runId)) throw new Error(`run ${run.runId} already exists`);
    this.runs.set(run.runId, { ...run });
  }
  async updateRun(runId, patch) {
    const existing = this.runs.get(runId);
    if (!existing) throw new Error(`run ${runId} not found`);
    this.runs.set(runId, { ...existing, ...patch });
  }
  async appendSpan(span) {
    this.allSpans.push({ ...span });
  }
  async updateSpan(spanId, patch) {
    const idx = this.allSpans.findIndex((s) => s.spanId === spanId);
    if (idx < 0) throw new Error(`span ${spanId} not found`);
    this.allSpans[idx] = { ...this.allSpans[idx], ...patch };
  }
  async appendEvent(event) {
    this.allEvents.push({ ...event });
  }
  async appendArtifact(artifact) {
    this.allArtifacts.push({ ...artifact });
  }
  async appendBudgetEntry(entry) {
    this.allBudget.push({ ...entry });
  }
  async getRun(runId) {
    const r = this.runs.get(runId);
    return r ? { ...r } : void 0;
  }
  async listRuns(filter = {}) {
    return [...this.runs.values()].filter((r) => matchesRun(r, filter));
  }
  async spans(filter = {}) {
    return this.allSpans.filter((s) => matchesSpan(s, filter)).map((s) => ({ ...s }));
  }
  async events(filter = {}) {
    return this.allEvents.filter((e) => matchesEvent(e, filter)).map((e) => ({ ...e }));
  }
  async budget(runId) {
    return this.allBudget.filter((b) => b.runId === runId).map((b) => ({ ...b }));
  }
  async artifacts(runId) {
    return this.allArtifacts.filter((a) => a.runId === runId).map((a) => ({ ...a }));
  }
};
function matchesRun(r, f) {
  if (f.scenarioId && r.scenarioId !== f.scenarioId) return false;
  if (f.variantId && r.variantId !== f.variantId) return false;
  if (f.status && r.status !== f.status) return false;
  if (f.since !== void 0 && r.startedAt < f.since) return false;
  if (f.until !== void 0 && r.startedAt > f.until) return false;
  if (f.tag && r.tags?.[f.tag.key] !== f.tag.value) return false;
  if (f.parentRunId && r.parentRunId !== f.parentRunId) return false;
  if (f.projectId && r.projectId !== f.projectId) return false;
  if (f.chatId && r.chatId !== f.chatId) return false;
  if (f.layer && r.layer !== f.layer) return false;
  return true;
}
function matchesSpan(s, f) {
  if (f.runId && s.runId !== f.runId) return false;
  if (f.parentSpanId && s.parentSpanId !== f.parentSpanId) return false;
  if (f.kind && s.kind !== f.kind) return false;
  if (f.name && s.name !== f.name) return false;
  if (f.toolName && (s.kind !== "tool" || s.toolName !== f.toolName)) return false;
  if (f.judgeId && (s.kind !== "judge" || s.judgeId !== f.judgeId)) return false;
  if (f.since !== void 0 && s.startedAt < f.since) return false;
  if (f.until !== void 0 && s.startedAt > f.until) return false;
  return true;
}
function matchesEvent(e, f) {
  if (f.runId && e.runId !== f.runId) return false;
  if (f.spanId && e.spanId !== f.spanId) return false;
  if (f.kind && e.kind !== f.kind) return false;
  if (f.since !== void 0 && e.timestamp < f.since) return false;
  if (f.until !== void 0 && e.timestamp > f.until) return false;
  return true;
}
var FileSystemTraceStore = class {
  dir;
  maxBytes;
  /** Lazy in-memory index for queries — populated on first read. */
  index;
  /** Memoized index build — concurrent first reads share one build, and an
   *  append racing an in-flight load awaits this so its row isn't lost. */
  indexPromise;
  /** Strictly-increasing rollover stamp. Date.now() alone collides when two
   *  rollovers land in the same millisecond, overwriting a rolled file. */
  lastRolloverStamp = 0;
  /**
   * Per-file append serialization. stat → conditional-rename → appendFile is a
   * read-modify-write on the active file; without a lock two concurrent appends
   * to the same `name` can both pass the size check (exceeding maxBytes) or one
   * can append to a file the other just renamed away. Each entry chains the
   * next append behind the prior one for that file.
   */
  appendLocks = /* @__PURE__ */ new Map();
  constructor(options) {
    this.dir = options.dir;
    this.maxBytes = options.maxBytes ?? 32 * 1024 * 1024;
  }
  async ensureDir() {
    const fs = await import("fs/promises");
    await fs.mkdir(this.dir, { recursive: true });
  }
  async append(name, record) {
    if (this.indexPromise) await this.indexPromise;
    const prior = this.appendLocks.get(name) ?? Promise.resolve();
    const result = prior.then(() => this.appendLocked(name, record));
    const tail = result.then(
      () => {
      },
      () => {
      }
    );
    this.appendLocks.set(name, tail);
    try {
      await result;
    } finally {
      if (this.appendLocks.get(name) === tail) this.appendLocks.delete(name);
    }
  }
  async appendLocked(name, record) {
    await this.ensureDir();
    const fs = await import("fs/promises");
    const path = await import("path");
    const active = path.join(this.dir, `${name}.ndjson`);
    try {
      const stat = await fs.stat(active);
      if (stat.size >= this.maxBytes) {
        const stamp = Math.max(Date.now(), this.lastRolloverStamp + 1);
        this.lastRolloverStamp = stamp;
        const rolled = path.join(this.dir, `${name}.${stamp}.ndjson`);
        await fs.rename(active, rolled);
      }
    } catch {
    }
    await fs.appendFile(active, `${JSON.stringify(record)}
`, "utf8");
    if (this.index && !record?._update) {
      await this.insertInto(name, record);
    }
  }
  async insertInto(name, record) {
    if (!this.index) return;
    switch (name) {
      case "runs":
        await this.index.appendRun(record);
        break;
      case "spans":
        await this.index.appendSpan(record);
        break;
      case "events":
        await this.index.appendEvent(record);
        break;
      case "artifacts":
        await this.index.appendArtifact(record);
        break;
      case "budget":
        await this.index.appendBudgetEntry(record);
        break;
    }
  }
  load() {
    this.indexPromise ??= this.buildIndex();
    return this.indexPromise;
  }
  async buildIndex() {
    const fs = await import("fs/promises");
    const path = await import("path");
    const store = new InMemoryTraceStore();
    let entries;
    try {
      entries = await fs.readdir(this.dir);
    } catch {
      this.index = store;
      return store;
    }
    entries.sort((a, b) => {
      const baseA = a.split(".")[0];
      const baseB = b.split(".")[0];
      if (baseA !== baseB) return baseA < baseB ? -1 : 1;
      const ta = a.match(/^[^.]+\.(\d+)\.ndjson$/);
      const tb = b.match(/^[^.]+\.(\d+)\.ndjson$/);
      const tsA = ta ? Number(ta[1]) : Number.POSITIVE_INFINITY;
      const tsB = tb ? Number(tb[1]) : Number.POSITIVE_INFINITY;
      return tsA - tsB;
    });
    {
      const runUpdates = [];
      const spanUpdates = [];
      for (const file of entries) {
        if (!file.endsWith(".ndjson")) continue;
        const full = path.join(this.dir, file);
        const content = await fs.readFile(full, "utf8");
        const base = file.split(".")[0];
        const lines = content.split("\n");
        for (let ln = 0; ln < lines.length; ln++) {
          const line = lines[ln];
          if (!line.trim()) continue;
          let record;
          try {
            record = JSON.parse(line);
          } catch (err) {
            throw new Error(
              `FileSystemTraceStore: corrupt NDJSON in ${file} line ${ln + 1}: ${err instanceof Error ? err.message : String(err)} \u2014 ${line.slice(0, 120)}`
            );
          }
          if (base === "runs") {
            if (record?._update) {
              runUpdates.push(record);
              continue;
            }
            try {
              await store.appendRun(record);
            } catch {
              await store.updateRun(record.runId, record);
            }
          } else if (base === "spans") {
            if (record?._update) {
              spanUpdates.push(record);
              continue;
            }
            await store.appendSpan(record);
          } else if (base === "events") {
            await store.appendEvent(record);
          } else if (base === "artifacts") {
            await store.appendArtifact(record);
          } else if (base === "budget") {
            await store.appendBudgetEntry(record);
          }
        }
      }
      for (const record of runUpdates) {
        await store.updateRun(record.runId, record);
      }
      for (const record of spanUpdates) {
        await store.updateSpan(record.spanId, record);
      }
    }
    this.index = store;
    return store;
  }
  async appendRun(run) {
    await this.append("runs", run);
  }
  async updateRun(runId, patch) {
    await this.append("runs", { runId, ...patch, _update: true });
    if (this.index) await this.index.updateRun(runId, patch);
  }
  async appendSpan(span) {
    await this.append("spans", span);
  }
  async updateSpan(spanId, patch) {
    await this.append("spans", { spanId, ...patch, _update: true });
    if (this.index) await this.index.updateSpan(spanId, patch);
  }
  async appendEvent(event) {
    await this.append("events", event);
  }
  async appendArtifact(artifact) {
    await this.append("artifacts", artifact);
  }
  async appendBudgetEntry(entry) {
    await this.append("budget", entry);
  }
  async getRun(runId) {
    return (await this.load()).getRun(runId);
  }
  async listRuns(filter) {
    return (await this.load()).listRuns(filter);
  }
  async spans(filter) {
    return (await this.load()).spans(filter);
  }
  async events(filter) {
    return (await this.load()).events(filter);
  }
  async budget(runId) {
    return (await this.load()).budget(runId);
  }
  async artifacts(runId) {
    return (await this.load()).artifacts(runId);
  }
};

// src/trace/capture-fetch.ts
var DEFAULT_BODY_CAP = 2 * 1024 * 1024;
function headersToRecord(headers) {
  if (!headers) return void 0;
  const out = {};
  headers.forEach((value, key) => {
    out[key.toLowerCase()] = value;
  });
  return Object.keys(out).length > 0 ? out : void 0;
}
function parseMaybeJson(text) {
  if (text.length === 0) return void 0;
  try {
    return JSON.parse(text);
  } catch {
    return text;
  }
}
async function readRequestBody(input, init) {
  if (typeof init?.body === "string") return parseMaybeJson(init.body);
  if (init?.body != null) return void 0;
  if (input instanceof Request) {
    try {
      return parseMaybeJson(await input.clone().text());
    } catch {
      return void 0;
    }
  }
  return void 0;
}
function endpointFromUrl(url, baseUrl) {
  const normalisedBase = baseUrl.replace(/\/+$/, "");
  if (url.startsWith(normalisedBase)) return url.slice(normalisedBase.length) || "/";
  try {
    return new URL(url).pathname;
  } catch {
    return url;
  }
}
function captureFetchToRawSink(fetch2, sink, ctx, opts = {}) {
  const provider = ctx.provider ?? providerFromBaseUrl(ctx.baseUrl);
  const redactor = opts.redactor ?? defaultProviderRedactor;
  const bodyCap = opts.responseBodyByteCap ?? DEFAULT_BODY_CAP;
  let warned = false;
  const baseEvent = (direction, endpoint) => ({
    eventId: crypto.randomUUID(),
    runId: ctx.runId,
    spanId: ctx.spanId,
    provider,
    model: ctx.model,
    endpoint,
    baseUrl: ctx.baseUrl,
    attemptIndex: 0,
    // retries are re-invocations one layer up; documented in 0.x
    direction,
    timestamp: Date.now(),
    redactedFields: []
  });
  const record = async (event) => {
    try {
      await sink.record(redactor(event));
    } catch (err) {
      if (opts.failClosed) throw err;
      if (!warned) {
        warned = true;
        console.warn(
          `captureFetchToRawSink: sink.record failed (capture is best-effort) \u2014 ${err instanceof Error ? err.message : String(err)}`
        );
      }
    }
  };
  return async (input, init) => {
    const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
    const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
    const endpoint = endpointFromUrl(url, ctx.baseUrl);
    const reqHeaders = new Headers(
      init?.headers ?? (input instanceof Request ? input.headers : void 0)
    );
    await record({
      ...baseEvent("request", endpoint),
      requestHeaders: { ...headersToRecord(reqHeaders), "x-http-method": method },
      requestBody: await readRequestBody(input, init)
    });
    const start = Date.now();
    let response;
    try {
      response = await fetch2(input, init);
    } catch (err) {
      await record({
        ...baseEvent("error", endpoint),
        durationMs: Date.now() - start,
        errorMessage: err instanceof Error ? err.message : String(err)
      });
      throw err;
    }
    let responseBody;
    let rawText;
    const redactedFields = [];
    try {
      rawText = await response.clone().text();
      if (rawText.length > bodyCap) {
        responseBody = rawText.slice(0, bodyCap);
        redactedFields.push("body_truncated");
      } else {
        responseBody = parseMaybeJson(rawText);
      }
    } catch {
      responseBody = void 0;
    }
    if (opts.onUsage && rawText !== void 0) {
      try {
        const parsedForUsage = parseMaybeJson(rawText);
        const usage = extractUsage(parsedForUsage) ?? (typeof parsedForUsage === "string" ? extractUsageFromSse(rawText, { mode: opts.sseUsageMode }) : null);
        if (usage) opts.onUsage(usage, ctx);
      } catch (err) {
        if (opts.failClosed) throw err;
      }
    }
    await record({
      ...baseEvent("response", endpoint),
      durationMs: Date.now() - start,
      statusCode: response.status,
      responseHeaders: headersToRecord(response.headers),
      responseBody,
      redactedFields
    });
    return response;
  };
}

// src/trace/otel.ts
var OTEL_AGENT_EVAL_SCOPE = { name: "@tangle-network/agent-eval", version: "0.3.0" };
async function exportRunAsOtlp(store, runId, resourceAttrs = {}) {
  const run = await store.getRun(runId);
  if (!run) throw new Error(`run ${runId} not found`);
  const spans = await store.spans({ runId });
  const events = await store.events({ runId });
  const eventsBySpan = /* @__PURE__ */ new Map();
  for (const e of events) {
    if (!e.spanId) continue;
    const arr = eventsBySpan.get(e.spanId) ?? [];
    arr.push(e);
    eventsBySpan.set(e.spanId, arr);
  }
  const traceId = runToTraceId(run);
  const otlpSpans = spans.map(
    (s) => spanToOtlp(s, traceId, eventsBySpan.get(s.spanId) ?? [])
  );
  return {
    resourceSpans: [
      {
        resource: {
          attributes: toAttributes({
            "service.name": "agent-eval",
            "run.id": run.runId,
            "run.scenario_id": run.scenarioId,
            "run.variant_id": run.variantId ?? "",
            "run.dataset_version": run.datasetVersion ?? "",
            "run.code_sha": run.codeSha ?? "",
            "run.model_fingerprint": run.modelFingerprint ?? "",
            ...resourceAttrs
          })
        },
        scopeSpans: [{ scope: OTEL_AGENT_EVAL_SCOPE, spans: otlpSpans }]
      }
    ]
  };
}
function spanToOtlp(span, traceId, events) {
  const endedAt = span.endedAt ?? span.startedAt;
  return {
    traceId,
    spanId: padSpanId(span.spanId),
    parentSpanId: span.parentSpanId ? padSpanId(span.parentSpanId) : void 0,
    name: span.name,
    kind: 1,
    // SPAN_KIND_INTERNAL
    startTimeUnixNano: msToNs(span.startedAt),
    endTimeUnixNano: msToNs(endedAt),
    attributes: toAttributes(flattenSpanAttributes(span)),
    events: events.map((e) => ({
      timeUnixNano: msToNs(e.timestamp),
      name: e.kind,
      attributes: toAttributes(flattenPayload(e.payload))
    })),
    status: span.status === "error" ? { code: 2, message: span.error } : { code: 1 }
  };
}
function flattenSpanAttributes(span) {
  const base = {};
  if (span.attributes) {
    for (const [k, v] of Object.entries(span.attributes)) {
      if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") base[k] = v;
    }
  }
  base[OPENINFERENCE_SPAN_KIND] = traceSpanKindToOpenInferenceKind(span.kind);
  if (span.kind === "llm") {
    applyLlmSpanOtlpAttributes(base, span);
  } else if (span.kind === "tool") {
    applyToolSpanOtlpAttributes(base, span);
  } else if (span.kind === "retrieval") {
    base["retrieval.query"] = span.query;
    base["retrieval.hits"] = span.hits.length;
  } else if (span.kind === "judge") {
    base["judge.id"] = span.judgeId;
    base["judge.dimension"] = span.dimension;
    base["judge.score"] = span.score;
    base["judge.target_span_id"] = span.targetSpanId;
  } else if (span.kind === "sandbox") {
    if (span.image) base["sandbox.image"] = span.image;
    if (span.exitCode !== void 0) base["sandbox.exit_code"] = span.exitCode;
    if (span.testsPassed !== void 0) base["sandbox.tests_passed"] = span.testsPassed;
    if (span.testsTotal !== void 0) base["sandbox.tests_total"] = span.testsTotal;
  }
  return base;
}
function flattenPayload(payload) {
  const out = {};
  for (const [k, v] of Object.entries(payload)) {
    if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") out[k] = v;
    else out[k] = JSON.stringify(v);
  }
  return out;
}
function toAttributes(record) {
  return Object.entries(record).map(([key, value]) => ({
    key,
    value: typeof value === "number" ? Number.isInteger(value) ? { intValue: value.toString() } : { doubleValue: value } : typeof value === "boolean" ? { boolValue: value } : { stringValue: value }
  }));
}
function msToNs(ms) {
  return (BigInt(Math.floor(ms)) * 1000000n).toString();
}
function padSpanId(id) {
  const cleaned = id.replace(/-/g, "");
  return cleaned.slice(0, 16).padEnd(16, "0");
}
function runToTraceId(run) {
  const cleaned = run.runId.replace(/-/g, "");
  return cleaned.slice(0, 32).padEnd(32, "0");
}

// src/trace/otel-bridge.ts
function otelRunCompleteHook(exporter) {
  return async (ctx) => {
    const spans = await ctx.store.spans({ runId: ctx.runId });
    for (const span of spans) {
      if (span.endedAt) {
        exporter.exportSpan(storeSpanToExportable(span, ctx.runId));
      }
    }
    await exporter.flush();
  };
}
function createOtelTracingStore(inner, exporter, traceId) {
  return {
    async appendRun(run) {
      return inner.appendRun(run);
    },
    async updateRun(runId, patch) {
      return inner.updateRun(runId, patch);
    },
    async appendSpan(span) {
      if (span.endedAt) {
        exporter.exportSpan(storeSpanToExportable(span, traceId));
      }
      return inner.appendSpan(span);
    },
    async updateSpan(spanId, patch) {
      await inner.updateSpan(spanId, patch);
      if (patch.endedAt) {
        const spans = await inner.spans({ runId: traceId });
        const found = spans.find((s) => s.spanId === spanId);
        if (found) {
          exporter.exportSpan(storeSpanToExportable(found, traceId));
        }
      }
    },
    async appendEvent(event) {
      return inner.appendEvent(event);
    },
    async appendBudgetEntry(entry) {
      return inner.appendBudgetEntry(entry);
    },
    async appendArtifact(artifact) {
      return inner.appendArtifact(artifact);
    },
    getRun: inner.getRun.bind(inner),
    listRuns: inner.listRuns.bind(inner),
    spans: inner.spans.bind(inner),
    events: inner.events.bind(inner),
    budget: inner.budget.bind(inner),
    artifacts: inner.artifacts.bind(inner)
  };
}
function storeSpanToExportable(span, traceId) {
  const llm = span.kind === "llm" ? span : void 0;
  const tool = span.kind === "tool" ? span : void 0;
  return {
    traceId,
    spanId: span.spanId,
    parentSpanId: span.parentSpanId,
    name: span.name,
    kind: span.kind,
    startedAt: span.startedAt,
    endedAt: span.endedAt,
    status: span.status,
    error: span.error,
    model: llm?.model,
    inputTokens: llm?.inputTokens,
    outputTokens: llm?.outputTokens,
    reasoningTokens: llm?.reasoningTokens,
    cachedTokens: llm?.cachedTokens,
    cacheWriteTokens: llm?.cacheWriteTokens,
    costUsd: llm?.costUsd,
    tool: tool ? {
      toolName: tool.toolName,
      args: tool.args,
      argsCaptured: tool.argsCaptured,
      result: tool.result,
      latencyMs: tool.latencyMs
    } : void 0,
    attributes: span.attributes
  };
}

// src/trace/otel-export.ts
function createOtelExporter(config) {
  const resolvedEndpoint = config?.endpoint ?? (typeof process !== "undefined" ? process.env.OTEL_EXPORTER_OTLP_ENDPOINT : void 0);
  if (!resolvedEndpoint) return void 0;
  const endpoint = resolvedEndpoint;
  const headers = config?.headers ?? parseHeadersFromEnv();
  const batchSize = config?.batchSize ?? 64;
  const flushIntervalMs = config?.flushIntervalMs ?? 5e3;
  const serviceName = config?.serviceName ?? "agent-eval";
  const resourceAttrs = config?.resourceAttributes ?? {};
  const pending = [];
  let timer;
  let stopped = false;
  const exporter = {
    exportSpan(span) {
      if (stopped) return;
      pending.push(toOtlpSpan(span));
      if (pending.length >= batchSize) {
        void doFlush();
      }
    },
    async flush() {
      await doFlush();
    },
    async shutdown() {
      stopped = true;
      if (timer !== void 0) {
        clearInterval(timer);
        timer = void 0;
      }
      await doFlush();
    }
  };
  timer = setInterval(() => {
    if (pending.length > 0) void doFlush();
  }, flushIntervalMs);
  if (typeof timer === "object" && "unref" in timer) {
    ;
    timer.unref();
  }
  async function doFlush() {
    if (pending.length === 0) return;
    const batch = pending.splice(0);
    const body = {
      resourceSpans: [
        {
          resource: {
            attributes: toAttributes2({
              "service.name": serviceName,
              ...resourceAttrs
            })
          },
          scopeSpans: [{ scope: OTEL_AGENT_EVAL_SCOPE, spans: batch }]
        }
      ]
    };
    const url = `${endpoint.replace(/\/+$/, "")}/v1/traces`;
    try {
      await fetch(url, {
        method: "POST",
        headers: {
          "content-type": "application/json",
          ...headers
        },
        body: JSON.stringify(body)
      });
    } catch {
    }
  }
  return exporter;
}
function parseHeadersFromEnv() {
  if (typeof process === "undefined") return {};
  const raw = process.env.OTEL_EXPORTER_OTLP_HEADERS;
  if (!raw) return {};
  const out = {};
  for (const pair of raw.split(",")) {
    const eq = pair.indexOf("=");
    if (eq < 0) continue;
    const key = pair.slice(0, eq).trim();
    const value = pair.slice(eq + 1).trim();
    if (key) out[key] = value;
  }
  return out;
}
function toOtlpSpan(span) {
  const endedAt = span.endedAt ?? span.startedAt;
  const attrs = {};
  if (span.attributes) {
    for (const [k, v] of Object.entries(span.attributes)) {
      if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") attrs[k] = v;
    }
  }
  attrs[OPENINFERENCE_SPAN_KIND] = traceSpanKindToOpenInferenceKind(span.kind);
  applyLlmSpanOtlpAttributes(attrs, span);
  if (span.tool) applyToolSpanOtlpAttributes(attrs, span.tool);
  return {
    traceId: padTraceId(span.traceId),
    spanId: padSpanId2(span.spanId),
    parentSpanId: span.parentSpanId ? padSpanId2(span.parentSpanId) : void 0,
    name: span.name,
    kind: 1,
    // SPAN_KIND_INTERNAL
    startTimeUnixNano: msToNs2(span.startedAt),
    endTimeUnixNano: msToNs2(endedAt),
    attributes: toAttributes2(attrs),
    status: span.status === "error" ? { code: 2, message: span.error } : { code: 1 }
  };
}
function toAttributes2(record) {
  return Object.entries(record).map(([key, value]) => ({
    key,
    value: typeof value === "number" ? Number.isInteger(value) ? { intValue: value.toString() } : { doubleValue: value } : typeof value === "boolean" ? { boolValue: value } : { stringValue: value }
  }));
}
function msToNs2(ms) {
  return (BigInt(Math.floor(ms)) * 1000000n).toString();
}
function padSpanId2(id) {
  const cleaned = id.replace(/-/g, "");
  return cleaned.slice(0, 16).padEnd(16, "0");
}
function padTraceId(id) {
  const cleaned = id.replace(/-/g, "");
  return cleaned.slice(0, 32).padEnd(32, "0");
}

// src/trace/store-to-otlp.ts
import { readdirSync, readFileSync, statSync, writeFileSync } from "fs";
import { join } from "path";
function convertTraceStoresToOtlp(source, outPath, opts = {}) {
  const sources = Array.isArray(source) ? [...source] : typeof source === "string" ? [{ root: source, layout: "celled" }] : [source];
  const defaultServiceName = opts.serviceName ?? "agent-eval";
  const resourceAttributes = opts.resourceAttributes ?? (() => ({}));
  const runAttributes = opts.runAttributes ?? (() => ({}));
  const lines = [];
  let spanCount = 0;
  let runCount = 0;
  let cellCount = 0;
  let cellErrorCount = 0;
  for (const src of sources) {
    const serviceName = src.serviceName ?? defaultServiceName;
    const cellDirs = src.layout === "flat" ? [{ label: "<root>", dir: src.root }] : listCells(src.root).map((name) => ({ label: name, dir: join(src.root, name) }));
    for (const cell of cellDirs) {
      try {
        const result = projectCell({
          cellDir: cell.dir,
          serviceName,
          resourceAttributes,
          runAttributes
        });
        for (const line of result.lines) lines.push(line);
        spanCount += result.spanCount;
        runCount += result.runCount;
        cellCount += 1;
      } catch (err) {
        console.warn(
          `[traces-to-otlp] cell ${cell.label} (${cell.dir}) skipped: ${err instanceof Error ? err.message : String(err)}`
        );
        cellErrorCount += 1;
      }
    }
  }
  writeFileSync(outPath, lines.join("\n") + (lines.length > 0 ? "\n" : ""));
  return { spanCount, runCount, cellCount, cellErrorCount };
}
function projectCell(args) {
  const { cellDir, serviceName, resourceAttributes, runAttributes } = args;
  const lines = [];
  let runCount = 0;
  let spanCount = 0;
  const runs = readMergedShards(cellDir, "runs", "runId");
  const spans = readMergedShards(cellDir, "spans", "spanId");
  const events = readShards(cellDir, "events");
  const runByRunId = /* @__PURE__ */ new Map();
  for (const r of runs) runByRunId.set(r.runId, r);
  const spanBySpanId = /* @__PURE__ */ new Map();
  for (const s of spans) spanBySpanId.set(s.spanId, s);
  const eventsBySpanId = /* @__PURE__ */ new Map();
  for (const e of events) {
    if (e.kind === "state_mutation" && e.payload && typeof e.payload === "object") {
      const entity = e.payload.entity;
      if (entity === "run") {
        const run = e.payload.run;
        if (run?.runId) runByRunId.set(run.runId, run);
        continue;
      }
      if (entity === "run.update") {
        const patch = e.payload.patch;
        if (patch && e.runId) {
          const prior = runByRunId.get(e.runId);
          if (prior) runByRunId.set(e.runId, { ...prior, ...patch });
        }
        continue;
      }
      if (entity === "span") {
        const span = e.payload.span;
        if (span?.spanId) spanBySpanId.set(span.spanId, span);
        continue;
      }
      if (entity === "span.update") {
        const spanId = e.payload.spanId;
        const patch = e.payload.patch;
        if (spanId && patch) {
          const prior = spanBySpanId.get(spanId);
          if (prior) spanBySpanId.set(spanId, { ...prior, ...patch });
        }
        continue;
      }
    }
    if (!e.spanId) continue;
    const arr = eventsBySpanId.get(e.spanId) ?? [];
    arr.push(e);
    eventsBySpanId.set(e.spanId, arr);
  }
  for (const run of runByRunId.values()) {
    const traceId = padTraceId2(run.runId);
    const agentName = run.variantId ?? run.scenarioId;
    const sharedResource = {
      attributes: {
        "service.name": serviceName,
        "agent.name": agentName,
        "run.id": run.runId,
        "run.status": run.status,
        ...resourceAttributes(run)
      }
    };
    const runSpanId = padSpanId3(`run-${run.runId}`);
    const runStart = msToIso(run.startedAt);
    const runEnd = msToIso(run.endedAt ?? run.startedAt);
    const runStatus = run.outcome?.failureClass && run.outcome.failureClass !== "success" ? "STATUS_CODE_ERROR" : "STATUS_CODE_OK";
    const runAttrs = {
      [OPENINFERENCE_SPAN_KIND]: "AGENT",
      "agent.name": agentName,
      "agent.workflow.name": serviceName,
      ...runAttributes(run)
    };
    lines.push(
      JSON.stringify(
        toLine({
          traceId,
          spanId: runSpanId,
          parentSpanId: "",
          name: `run.${agentName}`,
          kind: "SPAN_KIND_INTERNAL",
          startTime: runStart,
          endTime: runEnd,
          statusCode: runStatus,
          statusMessage: run.outcome?.notes ?? "",
          resource: sharedResource,
          attributes: runAttrs
        })
      )
    );
    runCount += 1;
    for (const span of spanBySpanId.values()) {
      if (span.runId !== run.runId) continue;
      const spanAttrs = spanToAttributes(span, eventsBySpanId.get(span.spanId) ?? []);
      const statusCode = span.status === "error" ? "STATUS_CODE_ERROR" : "STATUS_CODE_OK";
      lines.push(
        JSON.stringify(
          toLine({
            traceId,
            spanId: padSpanId3(span.spanId),
            parentSpanId: span.parentSpanId ? padSpanId3(span.parentSpanId) : runSpanId,
            name: span.name,
            kind: spanKindToOtlpKind(span.kind),
            startTime: msToIso(span.startedAt),
            endTime: msToIso(span.endedAt ?? span.startedAt),
            statusCode,
            statusMessage: span.error ?? "",
            resource: sharedResource,
            attributes: spanAttrs
          })
        )
      );
      spanCount += 1;
    }
  }
  return { lines, runCount, spanCount };
}
function listCells(root) {
  try {
    return readdirSync(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
  } catch {
    return [];
  }
}
function readShards(cellDir, name) {
  let entries;
  try {
    entries = readdirSync(cellDir);
  } catch {
    return [];
  }
  const shards = entries.filter((f) => (f === `${name}.ndjson` || f.startsWith(`${name}.`)) && f.endsWith(".ndjson")).map((f) => ({ file: f, path: join(cellDir, f) })).map((s) => {
    let mtime = 0;
    try {
      mtime = statSync(s.path).mtimeMs;
    } catch {
    }
    return { ...s, mtime };
  }).sort((a, b) => a.mtime - b.mtime || a.file.localeCompare(b.file));
  const rows = [];
  for (const shard of shards) {
    let text;
    try {
      text = readFileSync(shard.path, "utf-8");
    } catch {
      continue;
    }
    for (const line of text.split("\n")) {
      const trimmed = line.trim();
      if (!trimmed) continue;
      try {
        rows.push(JSON.parse(trimmed));
      } catch {
      }
    }
  }
  return rows;
}
function readMergedShards(cellDir, name, idKey) {
  const rows = readShards(cellDir, name);
  const byId = /* @__PURE__ */ new Map();
  for (const row of rows) {
    const id = row[idKey];
    if (!id) continue;
    const prior = byId.get(id);
    if (prior && row._update) {
      byId.set(id, { ...prior, ...row, _update: void 0 });
    } else {
      byId.set(id, row);
    }
  }
  return [...byId.values()];
}
function spanToAttributes(span, events) {
  const attrs = {
    [OPENINFERENCE_SPAN_KIND]: traceSpanKindToOpenInferenceKind(span.kind)
  };
  if (span.kind === "llm") {
    applyLlmSpanOtlpAttributes(attrs, span);
    if (Array.isArray(span.messages)) {
      attrs["llm.input_messages"] = JSON.stringify(span.messages.slice(-6));
    }
    if (typeof span.output === "string") {
      attrs["llm.output_messages"] = JSON.stringify([{ role: "assistant", content: span.output }]);
    }
  } else if (span.kind === "tool") {
    applyToolSpanOtlpAttributes(attrs, span);
  } else if (span.kind === "judge") {
    attrs["judge.id"] = span.judgeId;
    attrs["judge.dimension"] = span.dimension;
    attrs["judge.score"] = span.score;
    attrs["judge.target_span_id"] = span.targetSpanId;
  }
  if (span.attributes) {
    for (const [k, v] of Object.entries(span.attributes)) {
      attrs[`agent_eval.${k}`] = v;
    }
  }
  if (events.length > 0) {
    attrs["agent_eval.event_count"] = events.length;
    attrs["agent_eval.event_kinds"] = JSON.stringify(events.map((e) => e.kind));
  }
  return attrs;
}
function spanKindToOtlpKind(kind) {
  switch (kind) {
    case "llm":
      return "SPAN_KIND_CLIENT";
    case "retrieval":
      return "SPAN_KIND_CLIENT";
    default:
      return "SPAN_KIND_INTERNAL";
  }
}
function toLine(args) {
  return {
    trace_id: args.traceId,
    span_id: args.spanId,
    parent_span_id: args.parentSpanId,
    name: args.name,
    kind: args.kind,
    start_time: args.startTime,
    end_time: args.endTime,
    status: { code: args.statusCode, message: args.statusMessage },
    resource: args.resource,
    attributes: args.attributes
  };
}
function msToIso(ms) {
  if (!Number.isFinite(ms) || ms <= 0) return (/* @__PURE__ */ new Date(0)).toISOString();
  return new Date(ms).toISOString();
}
function padSpanId3(id) {
  const cleaned = id.replace(/[^a-f0-9]/gi, "").toLowerCase();
  if (cleaned.length >= 16) return cleaned.slice(0, 16);
  return foldTo16Hex(id);
}
function padTraceId2(id) {
  const cleaned = id.replace(/[^a-f0-9]/gi, "").toLowerCase();
  if (cleaned.length >= 32) return cleaned.slice(0, 32);
  return foldTo32Hex(id);
}
function foldTo16Hex(s) {
  let h1 = 2166136261;
  for (const ch of s) {
    h1 ^= ch.charCodeAt(0);
    h1 = Math.imul(h1, 16777619) >>> 0;
  }
  const part = h1.toString(16).padStart(8, "0");
  return (part + part).slice(0, 16);
}
function foldTo32Hex(s) {
  return foldTo16Hex(s) + foldTo16Hex(`${s}::trace`).slice(0, 16);
}

// src/replay.ts
var ReplayCacheMissError = class extends ReplayError {
  constructor(url, requestKey2, message) {
    super(message ?? `replay cache miss for ${url} (key=${requestKey2})`);
    this.url = url;
    this.requestKey = requestKey2;
  }
  url;
  requestKey;
};
var ReplayCache = class _ReplayCache {
  byKey = /* @__PURE__ */ new Map();
  orphans = 0;
  byProvider = {};
  byModel = {};
  /**
   * Build a cache from a sink's events. The sink must implement `list()`.
   * Filter by `runId` / `spanId` to scope to a specific replay.
   */
  static async fromSink(sink, filter = {}) {
    if (!sink.list) {
      throw new ReplayError("ReplayCache.fromSink: sink must implement list() to be replayable.");
    }
    const events = await sink.list(filter);
    return _ReplayCache.fromEvents(events);
  }
  /** Build a cache from an in-memory event list. */
  static async fromEvents(events) {
    const cache = new _ReplayCache();
    const groups = /* @__PURE__ */ new Map();
    for (const e of events) {
      const k = `${e.runId ?? ""}::${e.spanId ?? ""}::${e.attemptIndex}`;
      const g = groups.get(k) ?? {};
      if (e.direction === "request") g.req = e;
      else g.res = e;
      groups.set(k, g);
    }
    for (const g of groups.values()) {
      if (!g.req) continue;
      if (!g.res) {
        cache.orphans += 1;
        continue;
      }
      const key = await requestKey(g.req);
      cache.byKey.set(key, { request: g.req, response: g.res });
      cache.byProvider[g.req.provider] = (cache.byProvider[g.req.provider] ?? 0) + 1;
      cache.byModel[g.req.model] = (cache.byModel[g.req.model] ?? 0) + 1;
    }
    return cache;
  }
  /** Number of cacheable (request, response) pairs in the cache. */
  size() {
    return this.byKey.size;
  }
  stats() {
    return {
      total: this.byKey.size,
      byProvider: { ...this.byProvider },
      byModel: { ...this.byModel },
      orphanRequests: this.orphans
    };
  }
  /** Iterate every cached `(request, response)` pair in insertion order. */
  *entries() {
    for (const entry of this.byKey.values()) yield entry;
  }
  /**
   * Look up a cached response by hashing the (model, messages, temperature,
   * maxTokens, response_format) shape. Returns `undefined` on miss; the
   * caller decides whether to throw, fall back to the network, or skip.
   */
  async lookup(requestBody) {
    const key = await keyFromBody(requestBody);
    return this.byKey.get(key);
  }
};
function createReplayFetch(cache, opts = {}) {
  const onMiss = opts.onMiss ?? "throw";
  const fallback = opts.fallbackFetch ?? globalThis.fetch?.bind(globalThis);
  return (async (input, init) => {
    const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
    if (!/\/chat\/completions(?:[?#].*)?$/.test(url)) {
      if (!fallback)
        throw new ReplayError(
          `replay fetch: non-completions URL ${url} but no fallbackFetch configured`
        );
      return fallback(input, init);
    }
    let bodyParsed;
    if (init?.body && typeof init.body === "string") {
      try {
        bodyParsed = JSON.parse(init.body);
      } catch {
      }
    }
    const hit = bodyParsed === void 0 ? void 0 : await cache.lookup(bodyParsed);
    if (hit) {
      opts.onHit?.({ url, provider: hit.request.provider, model: hit.request.model });
      const status = hit.response.statusCode ?? 200;
      const headers = new Headers(
        Object.entries(hit.response.responseHeaders ?? { "Content-Type": "application/json" })
      );
      const bodyText = typeof hit.response.responseBody === "string" ? hit.response.responseBody : JSON.stringify(hit.response.responseBody ?? {});
      return new Response(bodyText, { status, headers });
    }
    opts.onMissNotify?.({ url, requestBody: bodyParsed });
    if (onMiss === "throw") {
      const key = bodyParsed === void 0 ? "<unparseable>" : await keyFromBody(bodyParsed);
      throw new ReplayCacheMissError(url, key);
    }
    if (onMiss === "fail-closed") {
      return new Response(JSON.stringify({ error: "replay_cache_miss" }), { status: 599 });
    }
    if (!fallback)
      throw new ReplayError("replay fetch: onMiss=fallback but no fallbackFetch configured");
    return fallback(input, init);
  });
}
async function* iterateRawCalls(sink, filter = {}) {
  if (!sink.list) {
    throw new ReplayError("iterateRawCalls: sink must implement list().");
  }
  const events = await sink.list(filter);
  const cache = await ReplayCache.fromEvents(events);
  for (const entry of cache.entries()) yield entry;
}
async function requestKey(event) {
  return keyFromBody(event.requestBody);
}
async function keyFromBody(body) {
  if (body == null || typeof body !== "object") return hashJson({ raw: String(body) });
  const b = body;
  const reduced = canonicalize({
    model: b.model ?? null,
    messages: b.messages ?? null,
    temperature: b.temperature ?? null,
    max_tokens: b.max_tokens ?? null,
    max_completion_tokens: b.max_completion_tokens ?? null,
    response_format: b.response_format ?? null
  });
  return hashJson(reduced);
}

export {
  traceAnalystOnRunComplete,
  tokenizeDomainWords,
  inferDomainKeywords,
  domainEvidencePattern,
  describeTraceInsightScope,
  planTraceInsightQuestions,
  buildTraceInsightContext,
  scoreTraceInsightReadiness,
  defaultTraceInsightPanel,
  buildTraceInsightPrompt,
  flattenOtlpExportToNdjson,
  otlpToRunRecords,
  otlpRowsToRunRecords,
  otlpToTraceRunRecords,
  otlpRowsToTraceRunRecords,
  InMemoryTraceStore,
  FileSystemTraceStore,
  captureFetchToRawSink,
  OTEL_AGENT_EVAL_SCOPE,
  exportRunAsOtlp,
  otelRunCompleteHook,
  createOtelTracingStore,
  createOtelExporter,
  convertTraceStoresToOtlp,
  ReplayCacheMissError,
  ReplayCache,
  createReplayFetch,
  iterateRawCalls
};
//# sourceMappingURL=chunk-OCFJACJU.js.map