import {
  executionTrackByLane
} from "./chunk-HHWE3POT.js";
import {
  AnalystRegistry,
  DEFAULT_TRACE_ANALYST_KINDS,
  createTraceAnalystKind,
  makeFinding
} from "./chunk-WXQTVEKM.js";
import {
  LlmClient
} from "./chunk-J7S4YM27.js";
import {
  spanEpochMillis
} from "./chunk-IR3KBHOY.js";
import {
  LLM_CONTEXT_TOKENS,
  LLM_INPUT_TOKEN_ATTR_KEYS,
  LLM_OUTPUT_TOKEN_ATTR_KEYS,
  TOOL_NAME_ATTR_KEYS
} from "./chunk-K4DBDHLK.js";

// src/analyst/chat-client.ts
function createChatClient(opts) {
  switch (opts.transport) {
    case "router":
      return wrapLlmClient(
        opts.transport,
        opts.defaultModel,
        new LlmClient({
          baseUrl: opts.baseUrl ?? "https://router.tangle.tools/v1",
          apiKey: opts.apiKey,
          maxRetries: opts.maximumAttempts
        })
      );
    case "cli-bridge":
      return wrapLlmClient(
        opts.transport,
        opts.defaultModel,
        new LlmClient({
          baseUrl: opts.baseUrl ?? "http://127.0.0.1:3344/v1",
          apiKey: opts.bearer ?? "",
          maxRetries: opts.maximumAttempts
        })
      );
    case "direct-provider":
      return wrapLlmClient(
        opts.transport,
        opts.defaultModel,
        new LlmClient({
          baseUrl: opts.baseUrl,
          apiKey: opts.apiKey,
          maxRetries: opts.maximumAttempts
        })
      );
    case "sandbox-sdk":
      return {
        transport: "sandbox-sdk",
        defaultModel: opts.defaultModel,
        maximumAttempts: opts.maximumAttempts,
        chat: async (req, callOpts) => opts.chat(resolveModel(req, opts.defaultModel), callOpts)
      };
    case "mock":
      return {
        transport: "mock",
        defaultModel: opts.defaultModel,
        maximumAttempts: 1,
        chat: async (req, callOpts) => opts.handler(resolveModel(req, opts.defaultModel), callOpts)
      };
  }
}
function wrapLlmClient(transport, defaultModel, inner) {
  return {
    transport,
    defaultModel,
    maximumAttempts: inner.maximumAttempts,
    chat: (req, callOpts) => {
      const resolved = resolveModel(req, defaultModel);
      const request = {
        model: resolved.model,
        messages: req.messages,
        jsonMode: req.jsonMode,
        jsonSchema: req.jsonSchema,
        temperature: req.temperature,
        maxTokens: req.maxTokens,
        timeoutMs: req.timeoutMs
      };
      return inner.call(request, {
        signal: callOpts?.signal,
        idempotencyKey: callOpts?.idempotencyKey
      });
    }
  };
}
function resolveModel(req, defaultModel) {
  if (req.model) return req;
  if (!defaultModel) {
    throw new Error(
      "ChatClient.chat: no model on request and no defaultModel on the client. Either pass req.model or bind defaultModel at createChatClient()."
    );
  }
  return { ...req, model: defaultModel };
}

// src/trace-analyst/behavioral-metrics.ts
var INPUT_GROWTH_FACTOR = 3;
var MIN_TOOL_CALLS = 3;
var VERIFY_RE = /verif|eval|inspect|check|assert|validat|review|confirm|read|grep|glob|search|view|\blist\b|\bls\b|\bcat\b|\bfind\b|diff|status|\btest|lint|typecheck/i;
function num(v) {
  return typeof v === "number" && Number.isFinite(v) ? v : null;
}
function numAttr(attrs, keys) {
  for (const key of keys) {
    const value = num(attrs[key]);
    if (value !== null) return value;
  }
  return null;
}
function inputTokensOf(s) {
  const exactContext = num(s.attributes[LLM_CONTEXT_TOKENS]);
  if (exactContext !== null) return exactContext;
  return numAttr(s.attributes, LLM_INPUT_TOKEN_ATTR_KEYS) ?? num(s.attributes["llm.usage.input_tokens"]);
}
function outputTokensOf(s) {
  return numAttr(s.attributes, LLM_OUTPUT_TOKEN_ATTR_KEYS) ?? num(s.attributes["llm.usage.output_tokens"]);
}
function stepOf(s) {
  return num(s.attributes.step);
}
function toolNameOf(s) {
  if (s.tool_name) return s.tool_name;
  for (const key of TOOL_NAME_ATTR_KEYS) {
    const t = s.attributes[key];
    if (typeof t === "string" && t.length > 0) return t;
  }
  return null;
}
function computeTraceMetrics(spans) {
  const traceIds = new Set(spans.map((span) => span.trace_id));
  if (traceIds.size > 1) {
    throw new Error(
      `computeTraceMetrics: expected spans from one trace, received ${traceIds.size} traces`
    );
  }
  const traceId = traceIds.values().next().value ?? null;
  const samples = spans.map((span) => ({
    span,
    input: inputTokensOf(span),
    output: outputTokensOf(span),
    step: stepOf(span)
  }));
  const llmSamples = samples.filter((sample) => sample.span.kind === "LLM");
  const tokenSamples = llmSamples.length > 0 ? llmSamples : samples.filter((sample) => sample.input !== null || sample.output !== null);
  const tokenSequences = buildTokenSequences(tokenSamples, spans);
  const primarySequence = tokenSequences[0];
  const inputTokenTrajectory = primarySequence?.inputTokenTrajectory.filter((value) => value !== null) ?? [];
  const outputTokenTrajectory = primarySequence?.outputTokenTrajectory.filter((value) => value !== null) ?? [];
  const toolHistogram = {};
  let hasSelfVerification = false;
  for (const s of spans) {
    const tool = toolNameOf(s);
    if (tool) {
      toolHistogram[tool] = (toolHistogram[tool] ?? 0) + 1;
      if (VERIFY_RE.test(tool)) hasSelfVerification = true;
    }
  }
  const totalToolCalls = Object.values(toolHistogram).reduce((a, b) => a + b, 0);
  const distinctTools = Object.keys(toolHistogram).length;
  const toolDiversityRatio = totalToolCalls === 0 ? 1 : distinctTools / totalToolCalls;
  const signals = [];
  const seenTokenSignals = /* @__PURE__ */ new Set();
  for (const sequence of tokenSequences) {
    for (const signal of tokenSignals(sequence)) {
      if (seenTokenSignals.has(signal.code)) continue;
      seenTokenSignals.add(signal.code);
      signals.push(signal);
    }
  }
  if (totalToolCalls >= MIN_TOOL_CALLS && distinctTools === 1) {
    const only = Object.keys(toolHistogram)[0];
    signals.push({
      code: "single-tool-dependency",
      severity: "medium",
      detail: `All ${totalToolCalls} observed tool calls are \`${only}\`; no alternate tool call was observed.`,
      evidence: { tool: only, calls: totalToolCalls, distinct_tools: 1 }
    });
  }
  if (totalToolCalls >= MIN_TOOL_CALLS && !hasSelfVerification) {
    signals.push({
      code: "no-self-verification",
      severity: "medium",
      detail: `${totalToolCalls} tool calls were observed without a verification-named tool call.`,
      evidence: { tool_calls: totalToolCalls, verification_calls: 0 }
    });
  }
  return {
    traceId,
    llmCallCount: tokenSamples.length,
    tokenSequences,
    inputTokenTrajectory,
    outputTokenTrajectory,
    toolHistogram,
    totalToolCalls,
    distinctTools,
    toolDiversityRatio,
    hasSelfVerification,
    signals
  };
}
function buildTokenSequences(samples, spans) {
  const spansById = new Map(spans.map((span) => [span.span_id, span]));
  const executionScopeFor = createTokenExecutionScopeResolver(spansById);
  const scopedSamples = samples.map((sample) => ({
    sample,
    ...executionScopeFor(sample.span)
  }));
  const trackByLane = executionTrackByLane(scopedSamples);
  const byTrack = /* @__PURE__ */ new Map();
  for (const scoped of scopedSamples) {
    const trackId = trackByLane.get(scoped.key);
    const track = byTrack.get(trackId) ?? { scopeId: scoped.scopeId, samples: [] };
    track.samples.push(scoped.sample);
    byTrack.set(trackId, track);
  }
  const sequences = [];
  for (const { scopeId, samples: tracked } of byTrack.values()) {
    const runs = serialTokenRuns([...tracked].sort(compareTokenSamples));
    runs.forEach((run, index) => {
      sequences.push({
        scopeId: runs.length === 1 ? scopeId : `${scopeId}#${index + 1}`,
        spanIds: run.map((sample) => sample.span.span_id),
        inputTokenTrajectory: run.map((sample) => sample.input),
        outputTokenTrajectory: run.map((sample) => sample.output)
      });
    });
  }
  return sequences.sort(
    (a, b) => b.spanIds.length - a.spanIds.length || a.scopeId.localeCompare(b.scopeId) || a.spanIds[0].localeCompare(b.spanIds[0])
  );
}
function createTokenExecutionScopeResolver(spansById) {
  const cache = /* @__PURE__ */ new Map();
  return (span) => {
    const ancestry = resolveAncestorScope(span.parent_span_id, spansById, cache);
    const rootId = ancestry.rootId;
    const scopeId = ancestry.agentId ? `span:${ancestry.agentId}` : ancestry.missingParentId ? `parent:${ancestry.missingParentId}` : rootId ? `root:${rootId}` : span.agent_name ? `agent:${span.agent_name}` : `trace:${span.trace_id}`;
    const scopeSpanId = ancestry.agentId ?? ancestry.missingParentId ?? rootId;
    const laneSpan = ancestry.laneSpanId ? spansById.get(ancestry.laneSpanId) : void 0;
    const direct = scopeSpanId === null || ancestry.laneSpanId === scopeSpanId;
    const timedSpan = direct ? span : laneSpan;
    return {
      key: JSON.stringify([scopeId, direct ? span.span_id : ancestry.laneSpanId]),
      scopeKey: scopeId,
      scopeId,
      start: timedSpan ? spanEpochMillis(timedSpan.start_time) : null,
      end: timedSpan ? spanEpochMillis(timedSpan.end_time) : null
    };
  };
}
function resolveAncestorScope(startId, spansById, cache) {
  const empty = {
    agentId: null,
    rootId: null,
    missingParentId: null,
    laneSpanId: null
  };
  if (!startId) return empty;
  const path = [];
  const pathIndex = /* @__PURE__ */ new Map();
  let currentId = startId;
  let resolved = empty;
  while (currentId) {
    const cached = cache.get(currentId);
    if (cached) {
      resolved = cached;
      break;
    }
    const cycleStart = pathIndex.get(currentId);
    if (cycleStart !== void 0) {
      resolved = {
        agentId: null,
        rootId: [...path.slice(cycleStart)].sort()[0],
        missingParentId: null,
        laneSpanId: [...path.slice(cycleStart)].sort()[0]
      };
      break;
    }
    const current = spansById.get(currentId);
    if (!current) {
      resolved = {
        agentId: null,
        rootId: null,
        missingParentId: currentId,
        laneSpanId: currentId
      };
      break;
    }
    if (current.kind === "AGENT") {
      resolved = {
        agentId: current.span_id,
        rootId: null,
        missingParentId: null,
        laneSpanId: current.span_id
      };
      break;
    }
    pathIndex.set(currentId, path.length);
    path.push(currentId);
    currentId = current.parent_span_id;
  }
  for (let index = path.length - 1; index >= 0; index -= 1) {
    if (resolved.agentId === null && resolved.rootId === null) {
      resolved = { ...resolved, rootId: path[index], laneSpanId: path[index] };
    } else if (resolved.laneSpanId === (resolved.agentId ?? resolved.missingParentId ?? resolved.rootId)) {
      resolved = { ...resolved, laneSpanId: path[index] };
    }
    cache.set(path[index], resolved);
  }
  return resolved;
}
function compareTokenSamples(a, b) {
  const aStart = spanEpochMillis(a.span.start_time);
  const bStart = spanEpochMillis(b.span.start_time);
  if (aStart === null && bStart !== null) return 1;
  if (aStart !== null && bStart === null) return -1;
  if (aStart !== null && bStart !== null && aStart !== bStart) return aStart - bStart;
  if (a.step !== null && b.step !== null && a.step !== b.step) return a.step - b.step;
  return a.span.span_id.localeCompare(b.span.span_id);
}
function serialTokenRuns(ordered) {
  const runs = [];
  let serial = [];
  let overlap = [];
  let overlapEnd = Number.NEGATIVE_INFINITY;
  const flushSerial = () => {
    if (serial.length > 0) runs.push(serial);
    serial = [];
  };
  const flushOverlap = () => {
    if (overlap.length === 1) {
      serial.push(overlap[0]);
    } else if (overlap.length > 1) {
      flushSerial();
      for (const sample of overlap) runs.push([sample]);
    }
    overlap = [];
    overlapEnd = Number.NEGATIVE_INFINITY;
  };
  for (const sample of ordered) {
    const start = spanEpochMillis(sample.span.start_time);
    const end = spanEpochMillis(sample.span.end_time);
    if (start === null || end === null || sample.span.duration_ms <= 0 || end < start) {
      flushOverlap();
      flushSerial();
      runs.push([sample]);
      continue;
    }
    if (overlap.length > 0 && start >= overlapEnd) flushOverlap();
    overlap.push(sample);
    overlapEnd = Math.max(overlapEnd, end);
  }
  flushOverlap();
  flushSerial();
  return runs;
}
function tokenSignals(sequence) {
  const signals = [];
  const inputs = sequence.inputTokenTrajectory;
  const outputs = sequence.outputTokenTrajectory;
  if (inputs.length >= 3 && inputs.every((value) => value !== null)) {
    const first = inputs[0];
    const last = inputs[inputs.length - 1];
    const isMonotonic = everyAdjacent(inputs, (previous, current) => current >= previous);
    const growthFromZero = first === 0 && last > 0;
    const growth = growthFromZero ? Infinity : first > 0 ? last / first : 0;
    if (isMonotonic && last > first && growth >= INPUT_GROWTH_FACTOR) {
      const growthLabel = growthFromZero ? "0\u2192nonzero (unbounded)" : `${growth.toFixed(1)}x`;
      signals.push({
        code: "monotonic-input-growth",
        severity: "high",
        detail: `LLM input tokens grew ${growthLabel} (${first}\u2192${last}) across ${inputs.length} serial calls without an intervening decrease.`,
        evidence: {
          first,
          last,
          growth_x: growthFromZero ? "unbounded" : Number(growth.toFixed(2)),
          calls: inputs.length,
          scope: sequence.scopeId,
          first_span_id: sequence.spanIds[0],
          last_span_id: sequence.spanIds[sequence.spanIds.length - 1]
        }
      });
    }
  }
  if (inputs.length >= 3 && inputs.length === outputs.length && inputs.every((value) => value !== null) && outputs.every((value) => value !== null)) {
    const first = outputs[0];
    const last = outputs[outputs.length - 1];
    const inputIsMonotonic = everyAdjacent(inputs, (previous, current) => current >= previous);
    const outputIsMonotonic = everyAdjacent(outputs, (previous, current) => current <= previous);
    const inputGrew = inputs[inputs.length - 1] > inputs[0];
    if (inputIsMonotonic && inputGrew && outputIsMonotonic && last < first) {
      signals.push({
        code: "output-length-decay",
        severity: "medium",
        detail: `LLM output tokens shrank ${first}\u2192${last} over ${outputs.length} serial calls while input tokens increased monotonically.`,
        evidence: {
          first,
          last,
          calls: outputs.length,
          scope: sequence.scopeId,
          first_span_id: sequence.spanIds[0],
          last_span_id: sequence.spanIds[sequence.spanIds.length - 1]
        }
      });
    }
  }
  return signals;
}
function everyAdjacent(values, predicate) {
  return values.slice(1).every((current, index) => predicate(values[index], current));
}

// src/analyst/behavioral-analyst.ts
var RECOMMENDED_ACTION = {
  "monotonic-input-growth": "Inspect context assembly; if prior history is repeatedly included, summarize completed work before the next model call.",
  "output-length-decay": "Check late-step completeness; if shorter responses omit required work, add explicit completion criteria to the agent instructions.",
  "single-tool-dependency": "Test whether an inspect or verification tool improves outcomes after the repeated call fails or returns no progress.",
  "no-self-verification": "After state-changing actions, require an observable check before the agent proceeds."
};
var ANALYST_ID = "efficiency-behavioral";
var AGGREGATE_CLAIM = {
  "monotonic-input-growth": (observed, analyzed) => `${observed}/${analyzed} analyzed traces showed input tokens grow from zero to nonzero or to at least 3x their initial value across at least 3 serial model calls without a decrease.`,
  "output-length-decay": (observed, analyzed) => `${observed}/${analyzed} analyzed traces showed output tokens decrease while input tokens increased monotonically across at least 3 serial model calls.`,
  "single-tool-dependency": (observed, analyzed) => `${observed}/${analyzed} analyzed traces used only one named tool across at least 3 tool calls.`,
  "no-self-verification": (observed, analyzed) => `${observed}/${analyzed} analyzed traces had at least 3 tool calls without a verification-named tool call.`
};
function deriveEfficiencyFindings(metrics, opts = {}) {
  const analystId = opts.analystId ?? ANALYST_ID;
  const traceId = metrics.traceId;
  return metrics.signals.map(
    (sig) => makeFinding({
      analyst_id: analystId,
      area: "efficiency",
      subject: sig.code,
      claim: sig.detail,
      severity: sig.severity,
      // Deterministic arithmetic over spans, not a model judgment → certain.
      confidence: 1,
      evidence_refs: [
        {
          kind: "metric",
          uri: traceId ? `metric://trace/${encodeURIComponent(traceId)}/efficiency/${sig.code}` : `metric://efficiency/${sig.code}`,
          excerpt: JSON.stringify(sig.evidence)
        }
      ],
      recommended_action: RECOMMENDED_ACTION[sig.code],
      metadata: {
        deterministic: true,
        evidence: sig.evidence,
        ...traceId ? { trace_id: traceId } : {}
      },
      id_basis: sig.code,
      ...opts.producedAt ? { produced_at: opts.producedAt } : {}
    })
  );
}
function behavioralAnalyst() {
  return {
    id: ANALYST_ID,
    description: "Deterministic behavioral/efficiency findings over OTLP spans \u2014 token-growth, output-decay, tool-monoculture, missing self-verification. Zero LLM; model-agnostic by construction.",
    inputKind: "trace-store",
    cost: { kind: "deterministic" },
    version: "2.0.0",
    async analyze(store) {
      const overview = await store.getOverview();
      const analyzedTraceIds = [...new Set(overview.sample_trace_ids)].sort();
      const findingsById = /* @__PURE__ */ new Map();
      for (const traceId of analyzedTraceIds) {
        const viewed = await store.viewTrace({ trace_id: traceId });
        if (viewed.trace_id !== traceId) {
          throw new Error(
            `behavioralAnalyst: requested trace '${traceId}', received '${viewed.trace_id}'`
          );
        }
        if (!viewed.spans) {
          throw new Error(
            `behavioralAnalyst: trace '${traceId}' is oversized; complete spans are required`
          );
        }
        const metrics = computeTraceMetrics(viewed.spans);
        if (metrics.traceId !== null && metrics.traceId !== traceId) {
          throw new Error(
            `behavioralAnalyst: requested trace '${traceId}', received '${metrics.traceId}'`
          );
        }
        for (const finding of deriveEfficiencyFindings(metrics)) {
          const current = findingsById.get(finding.finding_id);
          if (!current) {
            findingsById.set(finding.finding_id, {
              finding,
              traceIds: [traceId],
              evidence: [...finding.evidence_refs]
            });
            continue;
          }
          current.traceIds.push(traceId);
          current.evidence.push(...finding.evidence_refs);
        }
      }
      return [...findingsById.values()].map(({ finding, traceIds, evidence }) => ({
        ...finding,
        claim: AGGREGATE_CLAIM[finding.subject](
          traceIds.length,
          analyzedTraceIds.length
        ),
        rationale: `${traceIds.length}/${analyzedTraceIds.length} analyzed traces exhibited this pattern.`,
        evidence_refs: evidence,
        metadata: {
          deterministic: true,
          trace_ids: traceIds,
          observed_trace_count: traceIds.length,
          analyzed_trace_count: analyzedTraceIds.length
        }
      }));
    }
  };
}

// src/analyst/default-registry.ts
function buildDefaultAnalystRegistry(opts = {}) {
  const registry = new AnalystRegistry(opts.registry);
  if (opts.includeBehavioral !== false) {
    registry.register(behavioralAnalyst());
  }
  if (opts.ai) {
    const kinds = opts.kinds ?? DEFAULT_TRACE_ANALYST_KINDS;
    for (const spec of kinds) {
      registry.register(createTraceAnalystKind(spec, { ai: opts.ai, model: opts.model }));
    }
  }
  return registry;
}

export {
  createChatClient,
  computeTraceMetrics,
  deriveEfficiencyFindings,
  behavioralAnalyst,
  buildDefaultAnalystRegistry
};
//# sourceMappingURL=chunk-5YMKIFYP.js.map