import {
  NotFoundError
} from "./chunk-ONWEPEDO.js";
import {
  INPUT_VALUE,
  LLM_MODEL_ATTR_KEYS,
  OUTPUT_VALUE,
  SPAN_KIND_ATTR_KEYS,
  TOOL_ARGS_CAPTURED,
  TOOL_LATENCY_MS,
  TOOL_NAME,
  TOOL_NAME_ATTR_KEYS
} from "./chunk-K4DBDHLK.js";

// src/trace/otlp-attributes.ts
var TOOL_SPAN_ATTRIBUTE_KEYS = [
  TOOL_NAME,
  TOOL_ARGS_CAPTURED,
  TOOL_LATENCY_MS,
  INPUT_VALUE,
  OUTPUT_VALUE
];
function toolSpanOtlpAttributes(span) {
  const argsCaptured = span.argsCaptured !== false;
  const attributes = {
    [TOOL_NAME]: span.toolName,
    [TOOL_ARGS_CAPTURED]: argsCaptured
  };
  if (span.latencyMs !== void 0) attributes[TOOL_LATENCY_MS] = span.latencyMs;
  if (argsCaptured) attributes[INPUT_VALUE] = stringifyTraceValue(span.args);
  if (span.result !== void 0) attributes[OUTPUT_VALUE] = stringifyTraceValue(span.result);
  return attributes;
}
function applyToolSpanOtlpAttributes(attributes, span) {
  for (const key of TOOL_SPAN_ATTRIBUTE_KEYS) delete attributes[key];
  Object.assign(attributes, toolSpanOtlpAttributes(span));
}
function traceSpanKindToOpenInferenceKind(kind) {
  switch (kind) {
    case "llm":
      return "LLM";
    case "tool":
      return "TOOL";
    case "retrieval":
      return "CHAIN";
    case "judge":
      return "GUARDRAIL";
    case "sandbox":
      return "CHAIN";
    case "agent":
      return "AGENT";
    default:
      return "SPAN";
  }
}
function stringifyTraceValue(value) {
  if (value === void 0) return "null";
  if (typeof value === "string") return value;
  try {
    return JSON.stringify(value) ?? String(value);
  } catch {
    return String(value);
  }
}

// src/trace-analyst/otlp-span.ts
function projectOtlpFlatLine(raw) {
  const trace_id = stringField(raw, "trace_id") ?? stringField(raw, "traceId");
  const span_id = stringField(raw, "span_id") ?? stringField(raw, "spanId");
  if (!trace_id || !span_id) return null;
  const rawParentId = stringField(raw, "parent_span_id") ?? stringField(raw, "parentSpanId") ?? null;
  const parent_id = normalizeParentSpanId(trace_id, span_id, rawParentId);
  const name = stringField(raw, "name") ?? "unknown";
  const start_time = stringField(raw, "start_time") ?? stringField(raw, "startTime") ?? "";
  const end_time = stringField(raw, "end_time") ?? stringField(raw, "endTime") ?? start_time;
  const status = readOtlpStatus(raw);
  const attributes = extractOtlpAttributes(raw);
  const service_name = asString(attributes["service.name"]) ?? asString(attributes["resource.attributes.service.name"]) ?? null;
  const agent_name = asString(attributes["agent.name"]) ?? asString(attributes["inference.agent.name"]) ?? asString(attributes["inference.agent_name"]) ?? null;
  const model_name = firstStringAttr(attributes, LLM_MODEL_ATTR_KEYS);
  const tool_name = firstStringAttr(attributes, TOOL_NAME_ATTR_KEYS);
  const kind = inferOtlpKind(attributes);
  let duration_ms = 0;
  if (start_time && end_time) {
    const a = spanEpochMillis(start_time);
    const b = spanEpochMillis(end_time);
    if (a !== null && b !== null) duration_ms = Math.max(0, b - a);
  }
  return {
    trace_id,
    span_id,
    parent_span_id: parent_id && parent_id.length > 0 ? parent_id : null,
    name,
    kind,
    start_time,
    end_time,
    duration_ms,
    status: status.code,
    status_message: status.message,
    service_name,
    agent_name,
    model_name,
    tool_name,
    attributes
  };
}
function normalizeParentSpanId(traceId, spanId, parentId) {
  if (!parentId) return null;
  const prefix = `${traceId}:`;
  return spanId.startsWith(prefix) && !parentId.startsWith(prefix) ? `${prefix}${parentId}` : parentId;
}
function readOtlpStatus(raw) {
  const status = raw.status;
  if (status && typeof status === "object" && !Array.isArray(status)) {
    const codeRaw = status.code;
    const code = codeRaw === "STATUS_CODE_OK" || codeRaw === "OK" ? "OK" : codeRaw === "STATUS_CODE_ERROR" || codeRaw === "ERROR" ? "ERROR" : "UNSET";
    const messageRaw = status.message;
    const message = typeof messageRaw === "string" && messageRaw.length > 0 ? messageRaw : void 0;
    return { code, message };
  }
  return { code: "UNSET", message: void 0 };
}
function inferOtlpKind(attrs) {
  const opik = firstStringAttr(attrs, SPAN_KIND_ATTR_KEYS);
  if (opik) {
    const upper = opik.toUpperCase();
    if (upper === "AGENT" || upper === "LLM" || upper === "TOOL" || upper === "CHAIN" || upper === "GUARDRAIL" || upper === "SPAN") {
      return upper;
    }
  }
  return "UNKNOWN";
}
function extractOtlpAttributes(raw) {
  const out = {};
  const resource = raw.resource;
  if (resource && typeof resource === "object" && !Array.isArray(resource)) {
    const ra = resource.attributes;
    if (ra && typeof ra === "object" && !Array.isArray(ra)) {
      for (const [k, v] of Object.entries(ra)) {
        out[k] = v;
      }
    }
  }
  const spanAttrs = raw.attributes;
  if (spanAttrs && typeof spanAttrs === "object" && !Array.isArray(spanAttrs)) {
    for (const [k, v] of Object.entries(spanAttrs)) {
      out[k] = v;
    }
  }
  return out;
}
function stringField(raw, key) {
  const v = raw[key];
  return typeof v === "string" ? v : void 0;
}
function asString(v) {
  return typeof v === "string" && v.length > 0 ? v : null;
}
function firstStringAttr(attrs, keys) {
  for (const k of keys) {
    const s = asString(attrs[k]);
    if (s !== null) return s;
  }
  return null;
}
function spanEpochMillis(ts) {
  if (!ts) return null;
  if (/^\d+$/.test(ts)) return Number(ts);
  const n = Date.parse(ts);
  return Number.isNaN(n) ? null : n;
}
function compareSpanTime(a, b) {
  return (spanEpochMillis(a) ?? 0) - (spanEpochMillis(b) ?? 0);
}

// src/trace-analyst/types.ts
var DEFAULT_TRACE_ANALYST_BUDGETS = {
  perCallByteCeiling: 15e4,
  perAttributeViewBudget: 4096,
  perAttributeSpanBudget: 16384,
  perMatchTextBudget: 1024
};
var TRACE_ANALYST_TRUNCATION_MARKER_PREFIX = "[trace-analyst truncated:";

// src/trace-analyst/store-otlp.ts
import { readFile, stat } from "fs/promises";

// src/trace-analyst/store.ts
function compileSearchRegex(pattern) {
  let source = pattern;
  let flags = "m";
  if (source.startsWith("(?i)")) {
    source = source.slice(4);
    flags += "i";
  }
  return new RegExp(source, flags);
}
function truncateForBudget(value, byteCap) {
  const original = Buffer.byteLength(value, "utf8");
  if (original <= byteCap) return value;
  const ratio = byteCap / original;
  let cut = Math.max(0, Math.floor(value.length * ratio));
  while (cut > 0 && Buffer.byteLength(value.slice(0, cut), "utf8") > byteCap) {
    cut -= 1;
  }
  return `${value.slice(0, cut)}
[trace-analyst truncated: original ${original} bytes]`;
}

// src/trace-analyst/store-otlp.ts
var INDEX_YIELD_LINES = 5e3;
function yieldToEventLoop() {
  return new Promise((resolve) => setImmediate(resolve));
}
var DEFAULT_MAX_TRACE_FILE_BYTES = 256 * 1024 * 1024;
var OtlpFileTraceStore = class {
  path;
  perAttributeViewBudget;
  perAttributeSpanBudget;
  perCallByteCeiling;
  perMatchTextBudget;
  maxFileBytes;
  indexPromise;
  /** Cached UTF-8 buffer of the file. We pin it once because every
   *  read needs slice access and re-reading on each call balloons the
   *  syscall count. */
  bufferPromise;
  constructor(opts) {
    this.path = opts.path;
    this.perAttributeViewBudget = opts.perAttributeViewBudget ?? DEFAULT_TRACE_ANALYST_BUDGETS.perAttributeViewBudget;
    this.perAttributeSpanBudget = opts.perAttributeSpanBudget ?? DEFAULT_TRACE_ANALYST_BUDGETS.perAttributeSpanBudget;
    this.perCallByteCeiling = opts.perCallByteCeiling ?? DEFAULT_TRACE_ANALYST_BUDGETS.perCallByteCeiling;
    this.perMatchTextBudget = opts.perMatchTextBudget ?? DEFAULT_TRACE_ANALYST_BUDGETS.perMatchTextBudget;
    this.maxFileBytes = opts.maxFileBytes ?? DEFAULT_MAX_TRACE_FILE_BYTES;
  }
  // ─── Public API ────────────────────────────────────────────────────
  async getOverview(filters) {
    const idx = await this.index();
    const matched = await this.matchedTraces(idx, filters);
    const services = /* @__PURE__ */ new Set();
    const agents = /* @__PURE__ */ new Set();
    const models = /* @__PURE__ */ new Set();
    const tools = /* @__PURE__ */ new Set();
    let rawBytes = 0;
    let earliest = null;
    let latest = null;
    let errorTraceCount = 0;
    let errorSpanCount = 0;
    const clusters = /* @__PURE__ */ new Map();
    for (const t of matched) {
      if (t.service_name) services.add(t.service_name);
      if (t.agent_name) agents.add(t.agent_name);
      for (const m of t.models) models.add(m);
      for (const tn of t.tools) tools.add(tn);
      rawBytes += t.raw_jsonl_bytes;
      if (!earliest || compareSpanTime(t.start_time, earliest) < 0) earliest = t.start_time;
      if (!latest || compareSpanTime(t.end_time, latest) > 0) latest = t.end_time;
      if (t.has_errors) {
        errorTraceCount += 1;
        for (const s of t.spans) {
          if (s.status !== "ERROR") continue;
          errorSpanCount += 1;
          accumulateErrorCluster(clusters, t.trace_id, s);
        }
      }
    }
    const sample_trace_ids = matched.slice(0, 20).map((t) => t.trace_id);
    return {
      total_traces: matched.length,
      raw_jsonl_bytes: rawBytes,
      services: [...services].sort(),
      agents: [...agents].sort(),
      models: [...models].sort(),
      tool_names: [...tools].sort(),
      sample_trace_ids,
      errors: { trace_count: errorTraceCount, span_count: errorSpanCount },
      error_clusters: finalizeErrorClusters(clusters, errorTraceCount),
      time_range: earliest && latest ? { earliest, latest } : null
    };
  }
  async queryTraces(opts) {
    if (!Number.isInteger(opts.limit) || opts.limit < 1 || opts.limit > 200) {
      throw new RangeError(`queryTraces.limit must be 1..200, got ${opts.limit}`);
    }
    const offset = opts.offset ?? 0;
    if (!Number.isInteger(offset) || offset < 0) {
      throw new RangeError(`queryTraces.offset must be >=0, got ${offset}`);
    }
    const idx = await this.index();
    const matched = await this.matchedTraces(idx, opts.filters);
    const slice = matched.slice(offset, offset + opts.limit);
    return {
      traces: slice.map((t) => this.toSummary(t)),
      total: matched.length,
      has_more: offset + slice.length < matched.length
    };
  }
  async countTraces(filters) {
    const idx = await this.index();
    const matched = await this.matchedTraces(idx, filters);
    return matched.length;
  }
  async viewTrace(opts) {
    const idx = await this.index();
    const trace = idx.byTrace.get(opts.trace_id);
    if (!trace) {
      throw new TraceNotFoundError(opts.trace_id);
    }
    const cap = opts.per_attribute_byte_cap ?? this.perAttributeViewBudget;
    const buf = await this.buffer();
    const spans = [];
    let runningBytes = 0;
    let span_response_bytes_max = 0;
    const counter = { value: 0 };
    for (const s of trace.spans) {
      const projected = this.projectSpan(buf, trace.trace_id, s, cap, counter);
      const bytes = Buffer.byteLength(JSON.stringify(projected), "utf8");
      span_response_bytes_max = Math.max(span_response_bytes_max, bytes);
      runningBytes += bytes;
      if (runningBytes > this.perCallByteCeiling) {
        return {
          trace_id: trace.trace_id,
          oversized: this.buildOversizedSummary(trace, span_response_bytes_max)
        };
      }
      spans.push(projected);
    }
    return { trace_id: trace.trace_id, spans };
  }
  async viewSpans(opts) {
    const idx = await this.index();
    const trace = idx.byTrace.get(opts.trace_id);
    if (!trace) throw new TraceNotFoundError(opts.trace_id);
    if (opts.span_ids.length === 0) {
      return {
        trace_id: trace.trace_id,
        spans: [],
        missing_span_ids: [],
        truncated_attribute_count: 0
      };
    }
    if (opts.span_ids.length > 100) {
      throw new RangeError(`viewSpans.span_ids cap is 100, got ${opts.span_ids.length}`);
    }
    const cap = opts.per_attribute_byte_cap ?? this.perAttributeSpanBudget;
    const wantSet = new Set(opts.span_ids);
    const found = trace.spans.filter((s) => wantSet.has(s.span_id));
    const missing = opts.span_ids.filter((id) => !found.some((f2) => f2.span_id === id));
    const buf = await this.buffer();
    const spans = [];
    const counter = { value: 0 };
    let runningBytes = 0;
    for (const s of found) {
      const projected = this.projectSpan(buf, trace.trace_id, s, cap, counter);
      const bytes = Buffer.byteLength(JSON.stringify(projected), "utf8");
      runningBytes += bytes;
      if (runningBytes > this.perCallByteCeiling) {
        break;
      }
      spans.push(projected);
    }
    return {
      trace_id: trace.trace_id,
      spans,
      missing_span_ids: missing,
      truncated_attribute_count: counter.value
    };
  }
  async searchTrace(opts) {
    const max_matches = opts.max_matches ?? 50;
    if (!Number.isInteger(max_matches) || max_matches < 1 || max_matches > 500) {
      throw new RangeError(`searchTrace.max_matches must be 1..500, got ${max_matches}`);
    }
    const idx = await this.index();
    const trace = idx.byTrace.get(opts.trace_id);
    if (!trace) throw new TraceNotFoundError(opts.trace_id);
    const re = compileSearchRegex(opts.regex_pattern);
    const buf = await this.buffer();
    const hits = [];
    let total = 0;
    let capped = false;
    for (const s of trace.spans) {
      const remaining = max_matches - hits.length;
      const localHits = await this.scanSpanForMatches(
        buf,
        trace.trace_id,
        s,
        re,
        this.perMatchTextBudget,
        remaining
      );
      total += localHits.total;
      for (const h of localHits.records) {
        if (hits.length >= max_matches) break;
        hits.push(h);
      }
      if (hits.length >= max_matches) {
        capped = true;
        break;
      }
    }
    return {
      trace_id: trace.trace_id,
      hits,
      // Uncapped: every span scanned to completion, so `total` is exact.
      total_matches: capped ? hits.length : total,
      has_more: capped || total > hits.length
    };
  }
  async searchSpan(opts) {
    const max_matches = opts.max_matches ?? 50;
    if (!Number.isInteger(max_matches) || max_matches < 1 || max_matches > 500) {
      throw new RangeError(`searchSpan.max_matches must be 1..500, got ${max_matches}`);
    }
    const idx = await this.index();
    const trace = idx.byTrace.get(opts.trace_id);
    if (!trace) throw new TraceNotFoundError(opts.trace_id);
    const span = trace.spans.find((s) => s.span_id === opts.span_id);
    if (!span) {
      throw new SpanNotFoundError(opts.trace_id, opts.span_id);
    }
    const re = compileSearchRegex(opts.regex_pattern);
    const buf = await this.buffer();
    const localHits = await this.scanSpanForMatches(
      buf,
      trace.trace_id,
      span,
      re,
      this.perMatchTextBudget,
      max_matches
    );
    return {
      trace_id: trace.trace_id,
      span_id: span.span_id,
      hits: localHits.records,
      total_matches: localHits.total,
      has_more: localHits.total > localHits.records.length
    };
  }
  // ─── Index building ────────────────────────────────────────────────
  /** Force the index to materialise. Useful to amortise startup cost
   *  before the first agent call. */
  async ensureIndexed() {
    await this.index();
  }
  async buffer() {
    if (!this.bufferPromise) {
      this.bufferPromise = this.readGuarded();
    }
    return this.bufferPromise;
  }
  /** Stat-then-read so an oversized file fails loud BEFORE we allocate a
   *  multi-hundred-MB Buffer and OOM the process. A missing file surfaces
   *  as TraceFileMissingError; any other stat/read error propagates. */
  async readGuarded() {
    let stats;
    try {
      stats = await stat(this.path);
    } catch (err) {
      if (err?.code === "ENOENT") {
        throw new TraceFileMissingError(this.path);
      }
      throw err;
    }
    if (stats.size > this.maxFileBytes) {
      throw new TraceFileTooLargeError(this.path, stats.size, this.maxFileBytes);
    }
    return readFile(this.path);
  }
  async index() {
    if (!this.indexPromise) {
      this.indexPromise = this.buildIndex();
    }
    return this.indexPromise;
  }
  async buildIndex() {
    const buf = await this.buffer();
    const byTrace = /* @__PURE__ */ new Map();
    let cursor = 0;
    let sinceYield = 0;
    while (cursor < buf.length) {
      if (++sinceYield >= INDEX_YIELD_LINES) {
        sinceYield = 0;
        await yieldToEventLoop();
      }
      const newlineIndex = buf.indexOf(10, cursor);
      const lineEnd = newlineIndex === -1 ? buf.length : newlineIndex;
      const lineLength = lineEnd - cursor;
      if (lineLength === 0) {
        cursor = lineEnd + 1;
        continue;
      }
      const lineSlice = buf.subarray(cursor, lineEnd).toString("utf8");
      const lineOffset = cursor;
      cursor = lineEnd + 1;
      let parsed;
      try {
        parsed = JSON.parse(lineSlice);
      } catch {
        continue;
      }
      if (!parsed || typeof parsed !== "object") continue;
      const span = projectOtlpFlatLine(parsed);
      if (!span) continue;
      let entry = byTrace.get(span.trace_id);
      if (!entry) {
        entry = {
          trace_id: span.trace_id,
          service_name: span.service_name,
          agent_name: span.agent_name,
          span_count: 0,
          has_errors: false,
          start_time: span.start_time,
          end_time: span.end_time,
          duration_ms: 0,
          raw_jsonl_bytes: 0,
          models: /* @__PURE__ */ new Set(),
          tools: /* @__PURE__ */ new Set(),
          spans: []
        };
        byTrace.set(span.trace_id, entry);
      } else {
        if (!entry.service_name && span.service_name) entry.service_name = span.service_name;
        if (!entry.agent_name && span.agent_name) entry.agent_name = span.agent_name;
      }
      const indexEntry = {
        span_id: span.span_id,
        parent_span_id: span.parent_span_id,
        name: span.name,
        kind: span.kind,
        start_time: span.start_time,
        end_time: span.end_time,
        duration_ms: span.duration_ms,
        status: span.status,
        status_message: span.status_message,
        service_name: span.service_name,
        agent_name: span.agent_name,
        model_name: span.model_name,
        tool_name: span.tool_name,
        line_byte_offset: lineOffset,
        line_byte_length: lineLength
      };
      entry.spans.push(indexEntry);
      entry.span_count += 1;
      entry.raw_jsonl_bytes += lineLength + 1;
      if (span.status === "ERROR") entry.has_errors = true;
      if (compareSpanTime(span.start_time, entry.start_time) < 0) entry.start_time = span.start_time;
      if (compareSpanTime(span.end_time, entry.end_time) > 0) entry.end_time = span.end_time;
      if (span.model_name) entry.models.add(span.model_name);
      if (span.tool_name) entry.tools.add(span.tool_name);
    }
    let totalRawBytes = 0;
    for (const t of byTrace.values()) {
      totalRawBytes += t.raw_jsonl_bytes;
      t.spans.sort(
        (a, b) => compareSpanTime(a.start_time, b.start_time) || a.line_byte_offset - b.line_byte_offset
      );
      const startMs = spanEpochMillis(t.start_time);
      const endMs = spanEpochMillis(t.end_time);
      t.duration_ms = startMs === null || endMs === null ? 0 : Math.max(0, endMs - startMs);
    }
    const sortedTraceIds = [...byTrace.keys()].sort();
    return { byTrace, totalRawBytes, sortedTraceIds };
  }
  // ─── Filter pipeline ───────────────────────────────────────────────
  async matchedTraces(idx, filters) {
    const traces = idx.sortedTraceIds.map((id) => idx.byTrace.get(id)).filter(isPresent);
    if (!filters) return traces;
    const indexedFiltered = traces.filter((t) => {
      if (filters.has_errors !== void 0 && t.has_errors !== filters.has_errors) return false;
      if (filters.service_names && filters.service_names.length > 0) {
        if (!t.service_name || !filters.service_names.includes(t.service_name)) return false;
      }
      if (filters.agent_names && filters.agent_names.length > 0) {
        if (!t.agent_name || !filters.agent_names.includes(t.agent_name)) return false;
      }
      if (filters.model_names && filters.model_names.length > 0) {
        if (![...t.models].some((m) => filters.model_names.includes(m))) return false;
      }
      if (filters.tool_names && filters.tool_names.length > 0) {
        if (![...t.tools].some((tn) => filters.tool_names.includes(tn))) return false;
      }
      if (filters.start_time_after && t.start_time < filters.start_time_after) return false;
      if (filters.start_time_before && t.start_time > filters.start_time_before) return false;
      return true;
    });
    if (!filters.regex_pattern) return indexedFiltered;
    const re = compileSearchRegex(filters.regex_pattern);
    const buf = await this.buffer();
    const out = [];
    for (const t of indexedFiltered) {
      let matched = false;
      for (const s of t.spans) {
        const slice = buf.subarray(s.line_byte_offset, s.line_byte_offset + s.line_byte_length);
        if (re.test(slice.toString("utf8"))) {
          matched = true;
          break;
        }
      }
      if (matched) out.push(t);
    }
    return out;
  }
  toSummary(t) {
    return {
      trace_id: t.trace_id,
      service_name: t.service_name,
      agent_name: t.agent_name,
      span_count: t.span_count,
      has_errors: t.has_errors,
      start_time: t.start_time,
      end_time: t.end_time,
      duration_ms: t.duration_ms,
      raw_jsonl_bytes: t.raw_jsonl_bytes,
      models: [...t.models].sort(),
      tools: [...t.tools].sort()
    };
  }
  // ─── Span projection (lazy attribute reads) ────────────────────────
  projectSpan(buf, trace_id, s, perAttrCap, counter) {
    const slice = buf.subarray(s.line_byte_offset, s.line_byte_offset + s.line_byte_length).toString("utf8");
    let raw = {};
    try {
      const parsed = JSON.parse(slice);
      if (parsed && typeof parsed === "object") raw = parsed;
    } catch {
    }
    const attrs = extractOtlpAttributes(raw);
    const projected = {};
    for (const [k, v] of Object.entries(attrs)) {
      if (typeof v === "string") {
        const trunc = truncateForBudget(v, perAttrCap);
        if (trunc !== v) counter.value += 1;
        projected[k] = trunc;
      } else if (Array.isArray(v) || v && typeof v === "object") {
        const json = JSON.stringify(v);
        const trunc = truncateForBudget(json, perAttrCap);
        if (trunc !== json) {
          counter.value += 1;
          projected[k] = trunc;
        } else {
          projected[k] = v;
        }
      } else {
        projected[k] = v;
      }
    }
    return {
      trace_id,
      span_id: s.span_id,
      parent_span_id: s.parent_span_id,
      name: s.name,
      kind: s.kind,
      start_time: s.start_time,
      end_time: s.end_time,
      duration_ms: s.duration_ms,
      status: s.status,
      status_message: s.status_message,
      service_name: s.service_name,
      agent_name: s.agent_name,
      model_name: s.model_name,
      tool_name: s.tool_name,
      attributes: projected
    };
  }
  buildOversizedSummary(t, span_response_bytes_max) {
    const counts = /* @__PURE__ */ new Map();
    let errorCount = 0;
    for (const s of t.spans) {
      counts.set(s.name, (counts.get(s.name) ?? 0) + 1);
      if (s.status === "ERROR") errorCount += 1;
    }
    const top = [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 20);
    return {
      span_count: t.span_count,
      top_span_names: top,
      span_response_bytes_max,
      error_span_count: errorCount
    };
  }
  async scanSpanForMatches(buf, trace_id, s, re, textBudget, recordCap) {
    const slice = buf.subarray(s.line_byte_offset, s.line_byte_offset + s.line_byte_length).toString("utf8");
    const records = [];
    const globalRe = new RegExp(re.source, re.flags.includes("g") ? re.flags : `${re.flags}g`);
    let total = 0;
    let hasMore = false;
    let m = globalRe.exec(slice);
    while (m !== null) {
      total += 1;
      if (m.index === globalRe.lastIndex) globalRe.lastIndex += 1;
      if (records.length >= recordCap) {
        hasMore = true;
        break;
      }
      const before = slice.slice(Math.max(0, m.index - textBudget / 2), m.index);
      const after = slice.slice(
        m.index + m[0].length,
        m.index + m[0].length + Math.floor(textBudget / 2)
      );
      records.push({
        trace_id,
        span_id: s.span_id,
        span_name: s.name,
        span_kind: s.kind,
        attribute_path: bestAttributePathForOffset(slice, m.index) ?? "span.raw",
        matched_text: truncateForBudget(m[0], textBudget),
        context_before: truncateForBudget(before, textBudget),
        context_after: truncateForBudget(after, textBudget),
        match_offset: m.index
      });
      m = globalRe.exec(slice);
    }
    return { records, total, hasMore };
  }
};
var TraceFileMissingError = class extends NotFoundError {
  constructor(path) {
    super(`trace file not found: ${path}`);
  }
};
var TraceFileTooLargeError = class extends Error {
  path;
  size_bytes;
  max_bytes;
  constructor(path, size_bytes, max_bytes) {
    super(
      `trace file ${path} is ${size_bytes} bytes, over the ${max_bytes}-byte limit; raise OtlpFileTraceStoreOptions.maxFileBytes or pre-split the file`
    );
    this.path = path;
    this.size_bytes = size_bytes;
    this.max_bytes = max_bytes;
  }
};
var TraceNotFoundError = class extends NotFoundError {
  trace_id;
  constructor(trace_id) {
    super(`trace not found: ${trace_id}`);
    this.trace_id = trace_id;
  }
};
var SpanNotFoundError = class extends NotFoundError {
  trace_id;
  span_id;
  constructor(trace_id, span_id) {
    super(`span ${span_id} not found in trace ${trace_id}`);
    this.trace_id = trace_id;
    this.span_id = span_id;
  }
};
function isPresent(v) {
  return v !== void 0;
}
function isUnescapedQuote(slice, idx) {
  if (slice[idx] !== '"') return false;
  let backslashes = 0;
  let b = idx - 1;
  while (b >= 0 && slice[b] === "\\") {
    backslashes += 1;
    b -= 1;
  }
  return backslashes % 2 === 0;
}
function prevUnescapedQuote(slice, from) {
  for (let i = from; i >= 0; i -= 1) {
    if (slice[i] === '"' && isUnescapedQuote(slice, i)) return i;
  }
  return -1;
}
function bestAttributePathForOffset(slice, offset) {
  const valueQuote = prevUnescapedQuote(slice, Math.min(offset, slice.length - 1));
  if (valueQuote < 1) return null;
  let j = valueQuote - 1;
  while (j >= 0 && slice[j] !== ":") j -= 1;
  if (j < 1) return null;
  const keyClose = prevUnescapedQuote(slice, j - 1);
  if (keyClose < 1) return null;
  const keyOpen = prevUnescapedQuote(slice, keyClose - 1);
  if (keyOpen < 0) return null;
  return slice.slice(keyOpen + 1, keyClose);
}
var ERROR_CLUSTER_MAX = 50;
var ERROR_CLUSTER_EXEMPLARS = 5;
var SIGNATURE_MAX_CHARS = 160;
function normalizeErrorSignature(message, spanName) {
  const raw = (message ?? "").trim();
  const base = raw.length > 0 ? raw : `(${spanName || "error"} \u2014 no message)`;
  const norm = base.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "<id>").replace(/\b[0-9a-f]{12,}\b/gi, "<id>").replace(/(?:\/[\w.\-@]+){2,}/g, "<path>").replace(/\b\d+(?:\.\d+)?(ms|s|m|h|kb|mb|gb)?\b/gi, (_m, u) => u ? `#${u}` : "#").replace(/\s+/g, " ").trim();
  return norm.length > SIGNATURE_MAX_CHARS ? `${norm.slice(0, SIGNATURE_MAX_CHARS)}\u2026` : norm;
}
function bump(map, key) {
  if (!key) return;
  map.set(key, (map.get(key) ?? 0) + 1);
}
function topKey(map) {
  let best = null;
  let bestN = 0;
  for (const [k, n] of map)
    if (n > bestN) {
      best = k;
      bestN = n;
    }
  return best;
}
function accumulateErrorCluster(clusters, traceId, span) {
  const signature = normalizeErrorSignature(span.status_message, span.name);
  let acc = clusters.get(signature);
  if (!acc) {
    acc = {
      signature,
      sample: (span.status_message ?? span.name ?? "").slice(0, 500),
      traceIds: /* @__PURE__ */ new Set(),
      spanIds: [],
      spanCount: 0,
      spanNames: /* @__PURE__ */ new Map(),
      toolNames: /* @__PURE__ */ new Map()
    };
    clusters.set(signature, acc);
  }
  acc.traceIds.add(traceId);
  acc.spanCount += 1;
  if (acc.spanIds.length < ERROR_CLUSTER_EXEMPLARS && !acc.spanIds.includes(span.span_id)) {
    acc.spanIds.push(span.span_id);
  }
  bump(acc.spanNames, span.name);
  bump(acc.toolNames, span.tool_name);
}
function finalizeErrorClusters(clusters, errorTraceCount) {
  const out = [...clusters.values()].map(
    (acc) => ({
      signature: acc.signature,
      status_message_sample: acc.sample,
      span_name: topKey(acc.spanNames),
      tool_name: topKey(acc.toolNames),
      trace_count: acc.traceIds.size,
      span_count: acc.spanCount,
      prevalence: errorTraceCount > 0 ? acc.traceIds.size / errorTraceCount : 0,
      exemplar_trace_ids: [...acc.traceIds].slice(0, ERROR_CLUSTER_EXEMPLARS),
      exemplar_span_ids: acc.spanIds.slice(0, ERROR_CLUSTER_EXEMPLARS)
    })
  );
  out.sort((a, b) => b.trace_count - a.trace_count || b.span_count - a.span_count);
  return out.slice(0, ERROR_CLUSTER_MAX);
}

// src/trace-analyst/tools.ts
import { f, fn } from "@ax-llm/ax";
var NAMESPACE = "traces";
var filtersField = f.json("Filter set. ALL fields are AND-composed. Leave empty to scan everything.").optional();
function buildTraceAnalystTools(opts) {
  const { store } = opts;
  const getDatasetOverview = fn("getDatasetOverview").description(
    "Dataset rollup: total traces, raw_jsonl_bytes, services, agents, models, tools, and sample_trace_ids (real ids passable to view/search). Always call this FIRST without a regex_pattern."
  ).namespace(NAMESPACE).arg("filters", filtersField).returns(f.json("DatasetOverview")).handler(async ({ filters }) => store.getOverview(parseFilters(filters))).build();
  const queryTraces = fn("queryTraces").description(
    "Paginated trace summaries. Each summary carries raw_jsonl_bytes \u2014 use it to size traces BEFORE calling viewTrace. Narrow with indexed filters before adding regex_pattern."
  ).namespace(NAMESPACE).arg("filters", filtersField).arg("limit", f.number("Page size, 1..200")).arg("offset", f.number("Page offset; default 0").optional()).returns(f.json("QueryTracesPage")).handler(
    async ({ filters, limit, offset }) => store.queryTraces({
      filters: parseFilters(filters),
      limit: assertPageLimit(limit),
      offset: assertOffset(offset)
    })
  ).build();
  const countTraces = fn("countTraces").description(
    "Count traces matching `filters`. Use as a cheap pre-flight before opting into a regex_pattern scan."
  ).namespace(NAMESPACE).arg("filters", filtersField).returns(f.number("count")).handler(async ({ filters }) => store.countTraces(parseFilters(filters))).build();
  const viewTrace = fn("viewTrace").description(
    "Return ALL spans for a single trace, with each attribute capped at ~4KB. If the response would exceed the per-call ceiling the result carries `oversized` instead of `spans` \u2014 DO NOT retry with the same trace_id; switch to searchTrace / viewSpans."
  ).namespace(NAMESPACE).arg("trace_id", f.string("Real trace id from a prior overview/query")).returns(f.json("ViewTraceResult")).handler(
    async ({ trace_id }) => store.viewTrace({ trace_id: assertString(trace_id, "trace_id") })
  ).build();
  const viewSpans = fn("viewSpans").description(
    "Surgical read of specific spans within a trace, with each attribute capped at ~16KB (4\xD7 the discovery cap). Use after searchTrace narrows to specific span_ids."
  ).namespace(NAMESPACE).arg("trace_id", f.string("Real trace id")).arg("span_ids", f.string("Span ids to fetch").array()).returns(f.json("ViewSpansResult")).handler(
    async ({ trace_id, span_ids }) => store.viewSpans({
      trace_id: assertString(trace_id, "trace_id"),
      span_ids: assertStringArray(span_ids, "span_ids")
    })
  ).build();
  const searchTrace = fn("searchTrace").description(
    "Regex search across all spans of one trace. Returns `{trace_id, hits: SpanMatchRecord[], total_matches, has_more}`. **Iterate `result.hits`, NOT `result.matches`** \u2014 the field is `hits`. Each hit has `{span_id, span_name, span_kind, attribute_path, matched_text, context_before, context_after, match_offset}`. Bounded regardless of trace size by max_matches (1..500, default 50). If has_more=true, REFINE the regex rather than blindly raising max_matches."
  ).namespace(NAMESPACE).arg("trace_id", f.string("Real trace id")).arg("regex_pattern", f.string("JS-compatible regex, multiline")).arg("max_matches", f.number("Max records returned, 1..500; default 50").optional()).returns(f.json("SearchTraceResult")).handler(
    async ({ trace_id, regex_pattern, max_matches }) => store.searchTrace({
      trace_id: assertString(trace_id, "trace_id"),
      regex_pattern: assertRegex(regex_pattern),
      max_matches: assertMaxMatches(max_matches)
    })
  ).build();
  const searchSpan = fn("searchSpan").description(
    "Regex search inside a single span. Use when viewSpans returned a 16KB-truncated payload and you need to narrow further. Returns `{trace_id, span_id, hits: SpanMatchRecord[], total_matches, has_more}` \u2014 iterate `result.hits`, NOT `result.matches`."
  ).namespace(NAMESPACE).arg("trace_id", f.string("Real trace id")).arg("span_id", f.string("Real span id within trace")).arg("regex_pattern", f.string("JS-compatible regex, multiline")).arg("max_matches", f.number("Max records, 1..500; default 50").optional()).returns(f.json("SearchSpanResult")).handler(
    async ({ trace_id, span_id, regex_pattern, max_matches }) => store.searchSpan({
      trace_id: assertString(trace_id, "trace_id"),
      span_id: assertString(span_id, "span_id"),
      regex_pattern: assertRegex(regex_pattern),
      max_matches: assertMaxMatches(max_matches)
    })
  ).build();
  return [
    getDatasetOverview,
    queryTraces,
    countTraces,
    viewTrace,
    viewSpans,
    searchTrace,
    searchSpan
  ];
}
function traceAnalystFunctionGroup(opts) {
  return {
    namespace: NAMESPACE,
    title: "Trace Analysis",
    selectionCriteria: "Use for any inspection of OTLP-shaped trace data.",
    description: "Discovery \u2192 narrow \u2192 deep-read tools over a JSONL trace dataset. Always call getDatasetOverview first.",
    functions: buildTraceAnalystTools(opts)
  };
}
function parseFilters(input) {
  if (input == null) return void 0;
  if (typeof input !== "object" || Array.isArray(input)) {
    throw new TypeError(`filters must be an object, got ${typeof input}`);
  }
  const f2 = input;
  const out = {};
  if (typeof f2.has_errors === "boolean") out.has_errors = f2.has_errors;
  out.service_names = stringArrayOrUndefined(f2.service_names, "service_names");
  out.agent_names = stringArrayOrUndefined(f2.agent_names, "agent_names");
  out.model_names = stringArrayOrUndefined(f2.model_names, "model_names");
  out.tool_names = stringArrayOrUndefined(f2.tool_names, "tool_names");
  if (typeof f2.start_time_after === "string") out.start_time_after = f2.start_time_after;
  if (typeof f2.start_time_before === "string") out.start_time_before = f2.start_time_before;
  if (typeof f2.regex_pattern === "string") {
    if (f2.regex_pattern.length === 0) {
      throw new TypeError("filters.regex_pattern cannot be empty");
    }
    out.regex_pattern = f2.regex_pattern;
  }
  return out;
}
function stringArrayOrUndefined(v, label) {
  if (v === void 0 || v === null) return void 0;
  if (!Array.isArray(v)) throw new TypeError(`${label} must be an array of strings`);
  if (v.some((x) => typeof x !== "string")) {
    throw new TypeError(`${label} entries must be strings`);
  }
  return v;
}
function assertPageLimit(limit) {
  if (typeof limit !== "number" || !Number.isInteger(limit) || limit < 1 || limit > 200) {
    throw new RangeError(`limit must be an integer 1..200`);
  }
  return limit;
}
function assertOffset(offset) {
  if (offset === void 0) return void 0;
  if (typeof offset !== "number" || !Number.isInteger(offset) || offset < 0) {
    throw new RangeError(`offset must be a non-negative integer`);
  }
  return offset;
}
function assertRegex(pattern) {
  if (typeof pattern !== "string" || pattern.length === 0) {
    throw new TypeError(`regex_pattern must be a non-empty string`);
  }
  new RegExp(pattern, "m");
  return pattern;
}
function assertMaxMatches(n) {
  if (n === void 0) return void 0;
  if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > 500) {
    throw new RangeError(`max_matches must be an integer 1..500`);
  }
  return n;
}
function assertString(v, label) {
  if (typeof v !== "string" || v.length === 0) {
    throw new TypeError(`${label} must be a non-empty string`);
  }
  return v;
}
function assertStringArray(v, label) {
  if (!Array.isArray(v)) throw new TypeError(`${label} must be an array of strings`);
  if (v.some((x) => typeof x !== "string")) {
    throw new TypeError(`${label} entries must be strings`);
  }
  return v;
}

// src/trace-analyst/loop.ts
import {
  AxJSRuntime,
  agent
} from "@ax-llm/ax";
var TRACE_ANALYSIS_FINAL_TASK = "Submit the completed trace analysis.";
var TRACE_ANALYSIS_COMPLETION_INSTRUCTION = `This host consumes your executor result directly; there is no downstream responder for this run. Ignore generic executor guidance that says a responder will format the answer. You must produce the final report and findings yourself.

Return exactly one executable JavaScript program per turn. Never emit multiple JavaScript or code fences; put every tool call and the final call in that one program.

When the analysis is complete, call \`await final(${JSON.stringify(TRACE_ANALYSIS_FINAL_TASK)}, { report, findings })\` exactly once. Do not return a one-argument string from \`final(...)\`.`;
var TraceAnalysisTurnLimitError = class extends Error {
  analystId;
  maxTurns;
  constructor(analystId, maxTurns, cause) {
    super(
      `Trace analyst '${analystId}' reached maxTurns=${maxTurns} without a structured final result`,
      { cause }
    );
    this.name = "TraceAnalysisTurnLimitError";
    this.analystId = analystId;
    this.maxTurns = maxTurns;
  }
};
async function runTraceAnalysisLoop(options) {
  validateLoopLimits(options);
  const config = {
    agentIdentity: { name: options.id, description: options.description },
    contextFields: [],
    runtime: new AxJSRuntime({
      permissions: [],
      blockDynamicImport: true,
      allowedModules: [],
      freezeIntrinsics: true,
      blockShadowRealm: true,
      preventGlobalThisExtensions: false
    }),
    maxSubAgentCalls: options.maxSubqueries,
    maxTurns: options.maxTurns,
    maxRuntimeChars: options.maxRuntimeChars,
    maxBatchedLlmQueryConcurrency: options.maxParallelSubqueries,
    promptLevel: "detailed",
    contextPolicy: { preset: "full", budget: "balanced" },
    functions: options.tools,
    executorOptions: {
      description: `${options.prompt.trim()}

${TRACE_ANALYSIS_COMPLETION_INSTRUCTION}`,
      ...options.model ? { model: options.model } : {},
      showThoughts: false,
      thinkingTokenBudget: "none"
    },
    ...options.onTurn ? { actorTurnCallback: options.onTurn } : {},
    bubbleErrors: [TraceFileMissingError]
  };
  const analyst = options.findingType === "string" ? agent("question:string -> report:string, findings:string[]", config) : agent("question:string -> report:string, findings:json[]", config);
  const state = await analyst.executor.run(
    options.ai,
    { question: options.question },
    options.signal ? { abortSignal: options.signal } : void 0
  );
  let completed;
  try {
    completed = options.findingType === "string" ? readTraceAnalysisCompletion(state.executorResult, "string") : readTraceAnalysisCompletion(state.executorResult, "object");
  } catch (error) {
    if (state.turnCount >= options.maxTurns) {
      throw new TraceAnalysisTurnLimitError(options.id, options.maxTurns, error);
    }
    throw new Error(`Trace analyst '${options.id}' stopped without a structured final result`, {
      cause: error
    });
  }
  return {
    ...completed,
    usage: analyst.executor.getUsage(),
    chatLog: analyst.executor.getChatLog(),
    turnCount: state.turnCount
  };
}
function readTraceAnalysisCompletion(value, findingType) {
  if (!value || typeof value !== "object") {
    throw new Error("Trace analyst did not return a structured final result");
  }
  const completion = value;
  if (completion.type !== "final" || !Array.isArray(completion.args) || completion.args.length !== 2 || completion.args[0] !== TRACE_ANALYSIS_FINAL_TASK) {
    throw new Error("Trace analyst did not return a structured final result");
  }
  const payload = completion.args[1];
  if (!payload || typeof payload !== "object") {
    throw new Error("Trace analyst final result must contain report and findings");
  }
  const { report, findings } = payload;
  if (typeof report !== "string" || !Array.isArray(findings)) {
    throw new Error("Trace analyst final result must contain report and findings");
  }
  if (findingType === "string") {
    if (findings.some((finding) => typeof finding !== "string")) {
      throw new Error("Trace analyst final result must contain string findings");
    }
    return { report, findings };
  }
  if (findings.some((finding) => !finding || typeof finding !== "object" || Array.isArray(finding))) {
    throw new Error("Trace analyst final result must contain object findings");
  }
  return { report, findings };
}
function validateLoopLimits(options) {
  if (!Number.isSafeInteger(options.maxSubqueries) || options.maxSubqueries < 0) {
    throw new TypeError("maxSubqueries must be a non-negative integer");
  }
  if (!Number.isSafeInteger(options.maxParallelSubqueries) || options.maxParallelSubqueries < 1) {
    throw new TypeError("maxParallelSubqueries must be a positive integer");
  }
}

export {
  applyToolSpanOtlpAttributes,
  traceSpanKindToOpenInferenceKind,
  projectOtlpFlatLine,
  readOtlpStatus,
  inferOtlpKind,
  extractOtlpAttributes,
  stringField,
  asString,
  firstStringAttr,
  spanEpochMillis,
  compareSpanTime,
  DEFAULT_TRACE_ANALYST_BUDGETS,
  TRACE_ANALYST_TRUNCATION_MARKER_PREFIX,
  OtlpFileTraceStore,
  TraceFileMissingError,
  TraceNotFoundError,
  SpanNotFoundError,
  runTraceAnalysisLoop,
  buildTraceAnalystTools,
  traceAnalystFunctionGroup
};
//# sourceMappingURL=chunk-IR3KBHOY.js.map