import {
  DEFAULT_RULES,
  classifyFailure,
  compareToBaseline,
  computeToolUseMetrics
} from "../chunk-QOTFXW5L.js";
import {
  buildTrajectory
} from "../chunk-RZTMDUO7.js";
import {
  executionTrackByLane
} from "../chunk-HHWE3POT.js";
import {
  interRaterReliability,
  pearsonR
} from "../chunk-PJQFMIOX.js";
import {
  aggregateLlm,
  argHash,
  hasCapturedToolArgs,
  llmSpans,
  runFailureClass,
  toolSpans
} from "../chunk-ZET2UAYW.js";
import {
  isToolSpan
} from "../chunk-MA6HLL3S.js";
import "../chunk-ONWEPEDO.js";
import "../chunk-PZ5AY32C.js";

// src/pipelines/budget-breach.ts
async function budgetBreachView(store, options = {}) {
  const runs = await store.listRuns({
    scenarioId: options.scenarioId,
    variantId: options.variantId
  });
  const findings = [];
  const byDimension = {};
  const byScenario = {};
  const byVariant = {};
  for (const run of runs) {
    const entries = await store.budget(run.runId);
    for (const e of entries) {
      if (!e.breached) continue;
      const excessRatio = e.limit > 0 ? e.consumed / e.limit : Infinity;
      findings.push({
        runId: run.runId,
        scenarioId: run.scenarioId,
        variantId: run.variantId,
        dimension: e.dimension,
        limit: e.limit,
        consumed: e.consumed,
        excessRatio,
        timestamp: e.timestamp
      });
      byDimension[e.dimension] = (byDimension[e.dimension] ?? 0) + 1;
      byScenario[run.scenarioId] = (byScenario[run.scenarioId] ?? 0) + 1;
      if (run.variantId) byVariant[run.variantId] = (byVariant[run.variantId] ?? 0) + 1;
    }
  }
  const breachedRuns = new Set(findings.map((f) => f.runId));
  return {
    findings,
    byDimension,
    byScenario,
    byVariant,
    totalRuns: runs.length,
    breachedRunRatio: runs.length > 0 ? breachedRuns.size / runs.length : 0
  };
}

// src/pipelines/failure-cluster.ts
async function failureClusterView(store, options = {}) {
  const rules = options.rules ?? DEFAULT_RULES;
  const minSize = options.minClusterSize ?? 1;
  const runs = await store.listRuns();
  const clusters = /* @__PURE__ */ new Map();
  let totalFailures = 0;
  for (const run of runs) {
    if (run.status === "completed" && run.outcome?.pass !== false) continue;
    totalFailures++;
    const spans = await store.spans({ runId: run.runId });
    const events = await store.events({ runId: run.runId });
    const cls = classifyFailure({ run, spans, events }, rules);
    let toolName;
    let argPrefix;
    let dimension;
    if (cls.triggerSpanId) {
      const trig = spans.find((s) => s.spanId === cls.triggerSpanId);
      if (trig?.kind === "tool") {
        toolName = trig.toolName;
        if (hasCapturedToolArgs(trig)) argPrefix = argHash(trig.args).slice(0, 16);
      } else if (trig?.kind === "judge") {
        dimension = trig.dimension;
      }
    }
    if (!toolName) {
      const ts = await toolSpans(store, run.runId);
      const errored = ts.filter((t) => t.status === "error").pop();
      if (errored) {
        toolName = errored.toolName;
        if (hasCapturedToolArgs(errored)) argPrefix = argHash(errored.args).slice(0, 16);
      }
    }
    if (!dimension) {
      const judge = spans.find((s) => s.kind === "judge" && typeof s.dimension === "string");
      if (judge?.kind === "judge") dimension = judge.dimension;
    }
    const key = `${cls.failureClass}|${toolName ?? ""}|${argPrefix ?? ""}|${dimension ?? ""}`;
    let cluster = clusters.get(key);
    if (!cluster) {
      cluster = {
        failureClass: cls.failureClass,
        toolName,
        argPrefix,
        dimension,
        runCount: 0,
        scenarioIds: [],
        exampleRunId: run.runId,
        exampleError: firstErrorMessage(spans) ?? cls.reason
      };
      clusters.set(key, cluster);
    }
    cluster.runCount++;
    if (!cluster.scenarioIds.includes(run.scenarioId)) cluster.scenarioIds.push(run.scenarioId);
  }
  const arr = [...clusters.values()].filter((c) => c.runCount >= minSize).sort((a, b) => b.runCount - a.runCount);
  return { clusters: arr, totalFailures, totalRuns: runs.length };
}
function firstErrorMessage(spans) {
  const errored = spans.find((s) => s.status === "error");
  return errored?.error;
}

// src/pipelines/first-divergence.ts
async function firstDivergenceView(store, runA, runB, options = {}) {
  const [a, b] = await Promise.all([buildTrajectory(store, runA), buildTrajectory(store, runB)]);
  const eq = options.stepEquals ?? defaultStepEquals;
  const minLen = Math.min(a.steps.length, b.steps.length);
  for (let i = 0; i < minLen; i++) {
    const aStep = a.steps[i];
    const bStep = b.steps[i];
    if (!eq(aStep, bStep)) {
      return {
        runA,
        runB,
        firstDivergenceIndex: i,
        aStep,
        bStep,
        reason: describeDifference(aStep, bStep),
        commonPrefixLen: i
      };
    }
  }
  if (a.steps.length === b.steps.length) {
    return { runA, runB, firstDivergenceIndex: null, commonPrefixLen: minLen };
  }
  const longer = a.steps.length > b.steps.length ? a : b;
  const extra = longer.steps.length - minLen;
  const reason = minLen === 0 ? `one trajectory is empty; the other has ${extra} step(s) starting at index 0` : `one trajectory has ${extra} more step(s) after index ${minLen - 1}`;
  return {
    runA,
    runB,
    firstDivergenceIndex: minLen,
    aStep: a.steps[minLen],
    bStep: b.steps[minLen],
    reason,
    commonPrefixLen: minLen
  };
}
function defaultStepEquals(a, b) {
  if (a.span.kind !== b.span.kind) return false;
  if (a.span.kind === "tool" && b.span.kind === "tool") return a.span.toolName === b.span.toolName;
  if (a.span.kind === "llm" && b.span.kind === "llm") return a.span.model === b.span.model;
  if (a.span.kind === "judge" && b.span.kind === "judge")
    return a.span.dimension === b.span.dimension;
  return a.span.name === b.span.name;
}
function describeDifference(a, b) {
  if (a.span.kind !== b.span.kind) return `kind ${a.span.kind} vs ${b.span.kind}`;
  if (a.span.kind === "tool" && b.span.kind === "tool" && a.span.toolName !== b.span.toolName) {
    return `tool ${a.span.toolName} vs ${b.span.toolName}`;
  }
  if (a.span.kind === "llm" && b.span.kind === "llm" && a.span.model !== b.span.model) {
    return `model ${a.span.model} vs ${b.span.model}`;
  }
  return `name "${a.span.name}" vs "${b.span.name}"`;
}

// src/pipelines/judge-agreement.ts
async function judgeAgreementView(store) {
  const all = (await store.spans({ kind: "judge" })).filter(
    (s) => s.kind === "judge"
  );
  if (all.length === 0) return { pairs: [], dimensions: [], judgeIds: [] };
  const byDimension = /* @__PURE__ */ new Map();
  for (const s of all) {
    const arr = byDimension.get(s.dimension) ?? [];
    arr.push(s);
    byDimension.set(s.dimension, arr);
  }
  const judgeIds = [...new Set(all.map((s) => s.judgeId))].sort();
  const pairs = [];
  for (const [dim, spans] of byDimension) {
    const byJudge = /* @__PURE__ */ new Map();
    for (const s of spans) {
      const m = byJudge.get(s.judgeId) ?? /* @__PURE__ */ new Map();
      m.set(s.targetSpanId, s.score);
      byJudge.set(s.judgeId, m);
    }
    const judgesHere = [...byJudge.keys()];
    for (let i = 0; i < judgesHere.length; i++) {
      for (let j = i + 1; j < judgesHere.length; j++) {
        const judgeI = judgesHere[i];
        const judgeJ = judgesHere[j];
        const a = byJudge.get(judgeI);
        const b = byJudge.get(judgeJ);
        const common = [];
        for (const [target, scoreA] of a) {
          const scoreB = b.get(target);
          if (scoreB !== void 0) common.push([scoreA, scoreB]);
        }
        if (common.length < 2) continue;
        const judgeScores = common.map(
          ([scoreA, scoreB]) => [
            { judgeName: judgeI, dimension: dim, score: scoreA, reasoning: "" },
            { judgeName: judgeJ, dimension: dim, score: scoreB, reasoning: "" }
          ]
        );
        const k = interRaterReliability(
          judgeScores[0].map((_, k2) => judgeScores.map((pair) => pair[k2]))
        );
        pairs.push({
          judgeA: judgeI,
          judgeB: judgeJ,
          dimension: dim,
          commonItems: common.length,
          pearson: pearsonR(
            common.map((c) => c[0]),
            common.map((c) => c[1])
          ),
          krippendorff: k
        });
      }
    }
  }
  return {
    pairs: pairs.sort((a, b) => b.commonItems - a.commonItems),
    dimensions: [...byDimension.keys()].sort(),
    judgeIds
  };
}

// src/pipelines/regression.ts
async function regressionView(store, metrics, options) {
  const baselineRuns = await store.listRuns(options.baseline);
  const candidateRuns = await store.listRuns(options.candidate);
  const samples = await Promise.all(
    metrics.map(async (m) => {
      const extract = m.extract ?? defaultExtract(m.metric);
      const baseline = await extractAll(baselineRuns, extract, store);
      const candidate = await extractAll(candidateRuns, extract, store);
      return { metric: m.metric, higherIsBetter: m.higherIsBetter, baseline, candidate };
    })
  );
  return compareToBaseline(samples, options);
}
async function extractAll(runs, extract, store) {
  const out = [];
  for (const r of runs) {
    const v = await extract(r, store);
    if (v !== null && Number.isFinite(v)) out.push(v);
  }
  return out;
}
function defaultExtract(metric) {
  return async (run, store) => {
    switch (metric) {
      case "score":
      case "overallScore":
        return run.outcome?.score ?? null;
      case "pass":
        return run.outcome?.pass === true ? 1 : 0;
      case "durationMs":
        return run.endedAt && run.startedAt ? run.endedAt - run.startedAt : null;
      case "costUsd": {
        const llm = await llmSpans(store, run.runId);
        return aggregateLlm(llm).costUsd;
      }
      case "inputTokens": {
        const llm = await llmSpans(store, run.runId);
        return aggregateLlm(llm).inputTokens;
      }
      case "outputTokens": {
        const llm = await llmSpans(store, run.runId);
        return aggregateLlm(llm).outputTokens;
      }
      case "failureClass": {
        return runFailureClass(run) === "success" ? 1 : 0;
      }
      default:
        return null;
    }
  };
}

// src/pipelines/stuck-loop.ts
var DEFAULT_MAX_WINDOW_MS = 6e4;
var DEFAULT_MAX_INTERVENING_TOOL_CALLS = 0;
async function stuckLoopView(store, options = {}) {
  const minOccurrences = options.minOccurrences ?? 3;
  const maxWindowMs = options.maxWindowMs ?? DEFAULT_MAX_WINDOW_MS;
  const maxInterveningToolCalls = options.maxInterveningToolCalls ?? DEFAULT_MAX_INTERVENING_TOOL_CALLS;
  if (!Number.isInteger(minOccurrences) || minOccurrences < 1) {
    throw new RangeError("minOccurrences must be a positive integer");
  }
  if (!Number.isFinite(maxWindowMs) || maxWindowMs < 0) {
    throw new RangeError("maxWindowMs must be a finite non-negative number");
  }
  if (!Number.isInteger(maxInterveningToolCalls) || maxInterveningToolCalls < 0) {
    throw new RangeError("maxInterveningToolCalls must be a non-negative integer");
  }
  const runs = options.runId ? [{ runId: options.runId }] : (await store.listRuns()).map((r) => ({ runId: r.runId }));
  const findings = [];
  for (const { runId } of runs) {
    const spans = await store.spans({ runId });
    const spansById = new Map(spans.map((span) => [span.spanId, span]));
    const scopedTools = spans.filter(isToolSpan).map((span, sourceIndex) => ({ span, sourceIndex })).sort((a, b) => a.span.startedAt - b.span.startedAt || a.sourceIndex - b.sourceIndex).map(({ span }) => ({ span, ...executionScope(span, spansById) }));
    const trackByLane = executionTrackByLane(
      scopedTools.map((call) => {
        const direct = call.laneSpanId === null || call.laneSpanId === call.scopeSpanId;
        const timed = direct ? call.span : call.laneSpanId ? spansById.get(call.laneSpanId) : void 0;
        return {
          key: executionKey(call),
          scopeKey: JSON.stringify(call.scopeSpanId),
          start: timed?.startedAt ?? null,
          end: timed?.endedAt ?? null
        };
      })
    );
    const nextToolIndexByTrack = /* @__PURE__ */ new Map();
    const orderedTools = scopedTools.map((call) => {
      const trackId = trackByLane.get(executionKey(call));
      const toolCallIndex = nextToolIndexByTrack.get(trackId) ?? 0;
      nextToolIndexByTrack.set(trackId, toolCallIndex + 1);
      return { ...call, toolCallIndex, trackId };
    });
    const byKey = /* @__PURE__ */ new Map();
    for (const call of orderedTools) {
      if (!hasCapturedToolArgs(call.span)) continue;
      const h = argHash(call.span.args);
      const key = JSON.stringify([call.trackId, call.span.toolName, h]);
      const bucket = byKey.get(key) ?? {
        calls: [],
        argHash: h,
        toolName: call.span.toolName,
        scopeSpanId: call.scopeSpanId
      };
      bucket.calls.push(call);
      byKey.set(key, bucket);
    }
    for (const { calls, argHash: h, toolName, scopeSpanId } of byKey.values()) {
      if (calls.length < minOccurrences) continue;
      let episodeStart = 0;
      for (let episodeEnd = 1; episodeEnd <= calls.length; episodeEnd += 1) {
        const previous = calls[episodeEnd - 1];
        const next = calls[episodeEnd];
        const episodeEnded = next === void 0 || next.span.startedAt - previous.span.startedAt > maxWindowMs || next.toolCallIndex - previous.toolCallIndex - 1 > maxInterveningToolCalls || !callsAreSerial(previous, next);
        if (!episodeEnded) continue;
        const episode = calls.slice(episodeStart, episodeEnd);
        let left = 0;
        let bestStart = 0;
        let bestEnd = -1;
        for (let right = 0; right < episode.length; right += 1) {
          while (episode[right].span.startedAt - episode[left].span.startedAt > maxWindowMs) {
            left += 1;
          }
          if (right - left > bestEnd - bestStart) {
            bestStart = left;
            bestEnd = right;
          }
        }
        if (bestEnd - bestStart + 1 >= minOccurrences) {
          const loop = episode.slice(bestStart, bestEnd + 1);
          const first = loop[0].span.startedAt;
          const last = loop[loop.length - 1].span.startedAt;
          findings.push({
            runId,
            toolName,
            argHash: h,
            occurrences: loop.length,
            spanIds: loop.map((call) => call.span.spanId),
            ...scopeSpanId ? { scopeSpanId } : {},
            windowMs: last - first
          });
        }
        episodeStart = episodeEnd;
      }
    }
  }
  const affectedRuns = new Set(findings.map((f) => f.runId));
  return {
    findings,
    affectedRunRatio: runs.length > 0 ? affectedRuns.size / runs.length : 0,
    totalRuns: runs.length
  };
}
function laneKey(call) {
  return JSON.stringify([call.scopeSpanId, call.laneSpanId]);
}
function executionKey(call) {
  return call.laneSpanId === null || call.laneSpanId === call.scopeSpanId ? JSON.stringify([call.scopeSpanId, call.span.spanId]) : laneKey(call);
}
function executionScope(span, spansById) {
  const directParent = span.parentSpanId;
  if (!directParent) return { scopeSpanId: null, laneSpanId: null };
  let currentId = directParent;
  let laneSpanId = null;
  const seen = /* @__PURE__ */ new Set();
  while (currentId && !seen.has(currentId)) {
    seen.add(currentId);
    const current = spansById.get(currentId);
    if (!current) return { scopeSpanId: directParent, laneSpanId: directParent };
    if (current.kind === "agent") {
      return { scopeSpanId: current.spanId, laneSpanId: laneSpanId ?? current.spanId };
    }
    laneSpanId = current.spanId;
    currentId = current.parentSpanId;
  }
  return { scopeSpanId: directParent, laneSpanId: directParent };
}
function callsAreSerial(previous, next) {
  return previous.span.endedAt !== void 0 && previous.span.endedAt <= next.span.startedAt;
}

// src/pipelines/tool-waste.ts
async function toolWasteView(store, options = {}) {
  const runs = options.runId ? [options.runId] : (await store.listRuns()).map((r) => r.runId);
  const byRun = [];
  let totalCalls = 0;
  let totalWasted = 0;
  for (const runId of runs) {
    const tools = await toolSpans(store, runId);
    if (tools.length === 0) {
      byRun.push({ runId, wastedCalls: 0, totalCalls: 0, wasteRate: 0 });
      continue;
    }
    const llms = await llmSpans(store, runId);
    const sortedLlm = [...llms].sort((a, b) => a.startedAt - b.startedAt);
    const startTimes = sortedLlm.map((l) => l.startedAt);
    const suffixText = buildSuffixText(sortedLlm);
    let wasted = 0;
    for (const t of tools) {
      if (t.status === "error") {
        wasted++;
        continue;
      }
      const cutoff = upperBound(startTimes, t.startedAt);
      if (options.usageOracle) {
        if (!options.usageOracle(t, { llm: sortedLlm.slice(cutoff) })) wasted++;
      } else {
        const resultStr = stringify(t.result);
        if (resultStr === "") continue;
        const haystack = suffixText[cutoff] ?? "";
        const used = haystack.includes(resultStr.slice(0, 120));
        if (!used) wasted++;
      }
    }
    const wasteRate = wasted / tools.length;
    byRun.push({ runId, wastedCalls: wasted, totalCalls: tools.length, wasteRate });
    totalCalls += tools.length;
    totalWasted += wasted;
  }
  return { byRun, overallWasteRate: totalCalls > 0 ? totalWasted / totalCalls : 0 };
}
function buildSuffixText(spans) {
  const result = new Array(spans.length + 1);
  result[spans.length] = "";
  for (let i = spans.length - 1; i >= 0; i--) {
    const own = spans[i].messages.map(
      (m) => typeof m.content === "string" ? m.content : ""
    ).join("\n");
    result[i] = `${own}
${result[i + 1]}`;
  }
  return result;
}
function upperBound(sorted, target) {
  let lo = 0;
  let hi = sorted.length;
  while (lo < hi) {
    const mid = lo + hi >>> 1;
    if (sorted[mid] <= target) lo = mid + 1;
    else hi = mid;
  }
  return lo;
}
function stringify(v) {
  if (v === null || v === void 0) return "";
  if (typeof v === "string") return v;
  try {
    return JSON.stringify(v);
  } catch {
    return String(v);
  }
}
export {
  budgetBreachView,
  computeToolUseMetrics,
  failureClusterView,
  firstDivergenceView,
  judgeAgreementView,
  regressionView,
  stuckLoopView,
  toolWasteView
};
//# sourceMappingURL=index.js.map