// src/otel-export.ts
var SCOPE = { name: "@tangle-network/agent-runtime", version: "0.83.0" };
var GEN_AI = {
  operation: "gen_ai.operation.name",
  agentName: "gen_ai.agent.name",
  conversationId: "gen_ai.conversation.id",
  inputTokens: "gen_ai.usage.input_tokens",
  outputTokens: "gen_ai.usage.output_tokens"
};
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-runtime";
  const resourceAttrs = config?.resourceAttributes ?? {};
  const pending = [];
  let timer;
  let stopped = false;
  const exporter = {
    exportSpan(span) {
      if (stopped) return;
      pending.push(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: toAttributes({
              "service.name": serviceName,
              ...resourceAttrs
            })
          },
          scopeSpans: [{ scope: 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 loopEventToOtelSpan(event, traceId, parentSpanId) {
  const spanId = generateSpanId();
  const attrs = {
    "loop.event_kind": event.kind,
    "loop.run_id": event.runId
  };
  for (const [k, v] of Object.entries(event.payload)) {
    if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
      attrs[`loop.${k}`] = v;
    }
  }
  const ts = msToNs(event.timestamp);
  return {
    traceId: padTraceId(traceId),
    spanId,
    parentSpanId: parentSpanId ? padSpanId(parentSpanId) : void 0,
    name: event.kind,
    kind: 1,
    startTimeUnixNano: ts,
    endTimeUnixNano: ts,
    attributes: toAttributes(attrs),
    status: { code: 1 }
  };
}
function flatOtelSpan(name, attributes, traceId, timestampMs, parentSpanId) {
  const ts = msToNs(timestampMs);
  return {
    traceId: padTraceId(traceId),
    spanId: generateSpanId(),
    parentSpanId: parentSpanId ? padSpanId(parentSpanId) : void 0,
    name,
    kind: 1,
    startTimeUnixNano: ts,
    endTimeUnixNano: ts,
    attributes: toAttributes(attributes),
    status: { code: 1 }
  };
}
function buildLoopOtelSpans(events, traceId, rootParentSpanId) {
  const tid = padTraceId(traceId);
  return buildLoopSpanNodes(events).map((node) => ({
    traceId: tid,
    spanId: node.spanId,
    parentSpanId: node.parentSpanId ? padSpanId(node.parentSpanId) : rootParentSpanId ? padSpanId(rootParentSpanId) : void 0,
    name: node.name,
    kind: 1,
    startTimeUnixNano: msToNs(node.startMs),
    endTimeUnixNano: msToNs(node.endMs),
    attributes: toAttributes(node.attrs),
    status: { code: node.error ? 2 : 1 }
  }));
}
function buildLoopSpanNodes(events) {
  if (events.length === 0) return [];
  const out = [];
  const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
  const str = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
  const rec = (v) => v && typeof v === "object" ? v : {};
  const started = events.find((e) => e.kind === "loop.started");
  const ended = events.find((e) => e.kind === "loop.ended");
  const runId = events[0]?.runId ?? "";
  const rootStart = started?.timestamp ?? events[0].timestamp;
  const rootEnd = ended?.timestamp ?? events[events.length - 1].timestamp;
  const rootId = generateSpanId();
  const make = (spanId, parentSpanId, name, kind, startMs, endMs, attrs, error = false) => ({
    spanId,
    parentSpanId,
    name,
    kind,
    startMs,
    endMs,
    attrs,
    error
  });
  const sp = rec(started?.payload);
  const rootAttrs = {
    [GEN_AI.operation]: "invoke_workflow",
    [GEN_AI.conversationId]: runId,
    // Explicit run identity under the tangle namespace so a consuming system of
    // record (Tangle Intelligence's run spine) maps this span tree to one run
    // deterministically, instead of inferring it from the conversation id.
    "tangle.run.id": runId,
    "tangle.loop.driver": str(sp.driver) ?? "driver"
  };
  if (Array.isArray(sp.agentRunNames) && sp.agentRunNames.length > 0) {
    rootAttrs["tangle.loop.agents"] = sp.agentRunNames.map(String).join(",");
    rootAttrs["tangle.subject.key"] = String(sp.agentRunNames[0]);
  }
  if (ended) {
    const ep = rec(ended.payload);
    const win = num(ep.winnerIterationIndex);
    if (win !== void 0) rootAttrs["tangle.loop.winner.iteration_index"] = win;
    const cost = num(ep.totalCostUsd);
    if (cost !== void 0) rootAttrs["tangle.cost.usd"] = cost;
    const dur = num(ep.durationMs);
    if (dur !== void 0) rootAttrs["tangle.loop.duration_ms"] = dur;
    const iters = num(ep.iterations);
    if (iters !== void 0) rootAttrs["tangle.loop.iterations"] = iters;
  }
  out.push(make(rootId, void 0, "loop", "loop", rootStart, rootEnd, rootAttrs));
  const iterStartTs = /* @__PURE__ */ new Map();
  const placementByIdx = /* @__PURE__ */ new Map();
  let currentRoundId;
  let pendingRound;
  const flushRound = (endMs) => {
    if (!pendingRound) return;
    out.push(
      make(
        pendingRound.id,
        rootId,
        "loop.round",
        "round",
        pendingRound.start,
        endMs,
        pendingRound.attrs
      )
    );
    pendingRound = void 0;
  };
  for (const e of events) {
    const p = rec(e.payload);
    switch (e.kind) {
      case "loop.plan": {
        flushRound(e.timestamp);
        const id = generateSpanId();
        const roundIdx = num(p.roundIndex) ?? 0;
        const attrs = {
          [GEN_AI.operation]: "invoke_workflow",
          "tangle.loop.round.index": roundIdx,
          "tangle.loop.move.kind": str(p.moveKind) ?? "unknown",
          "tangle.loop.move.round": roundIdx,
          "tangle.loop.move.width": num(p.plannedCount) ?? 0
        };
        const r = str(p.rationale);
        if (r) attrs["tangle.loop.move.rationale"] = r;
        const parent = num(p.parentIndex);
        if (parent !== void 0) attrs["tangle.loop.move.parent_index"] = parent;
        if (Array.isArray(p.childIndices) && p.childIndices.length > 0) {
          attrs["tangle.loop.move.child_indices"] = p.childIndices.map(String).join(",");
        }
        pendingRound = { id, start: e.timestamp, attrs };
        currentRoundId = id;
        break;
      }
      case "loop.iteration.started": {
        const idx = num(p.iterationIndex);
        if (idx !== void 0) iterStartTs.set(idx, e.timestamp);
        break;
      }
      case "loop.iteration.dispatch": {
        const idx = num(p.iterationIndex);
        if (idx === void 0) break;
        const place = {};
        const kind = str(p.placement);
        if (kind) place["tangle.loop.placement.kind"] = kind;
        const sid = str(p.sandboxId);
        if (sid) place["tangle.sandbox.id"] = sid;
        const fid = str(p.fleetId);
        if (fid) place["tangle.fleet.id"] = fid;
        const mid = str(p.machineId);
        if (mid) place["tangle.machine.id"] = mid;
        placementByIdx.set(idx, place);
        break;
      }
      case "loop.iteration.ended": {
        const idx = num(p.iterationIndex) ?? 0;
        const start = iterStartTs.get(idx) ?? e.timestamp;
        const err = str(p.error);
        const attrs = {
          [GEN_AI.operation]: "invoke_agent",
          "tangle.loop.iteration.index": idx
        };
        const agent = str(p.agentRunName);
        if (agent) attrs[GEN_AI.agentName] = agent;
        const tu = rec(p.tokenUsage);
        const inTok = num(tu.input);
        if (inTok !== void 0) attrs[GEN_AI.inputTokens] = inTok;
        const outTok = num(tu.output);
        if (outTok !== void 0) attrs[GEN_AI.outputTokens] = outTok;
        const cost = num(p.costUsd);
        if (cost !== void 0) attrs["tangle.cost.usd"] = cost;
        const verdict = rec(p.verdict);
        if (typeof verdict.valid === "boolean") attrs["tangle.loop.verdict.valid"] = verdict.valid;
        const score = num(verdict.score);
        if (score !== void 0) attrs["tangle.loop.verdict.score"] = score;
        if (err) attrs["tangle.loop.error"] = err;
        const gid = num(p.groupId);
        if (gid !== void 0) attrs["tangle.loop.iteration.group_id"] = gid;
        const par = num(p.parentIndex);
        if (par !== void 0) attrs["tangle.loop.iteration.parent_index"] = par;
        const dur = num(p.durationMs);
        if (dur !== void 0) attrs["tangle.loop.iteration.duration_ms"] = dur;
        const preview = str(p.outputPreview);
        if (preview) attrs["tangle.loop.iteration.output_preview"] = preview;
        Object.assign(attrs, placementByIdx.get(idx) ?? {});
        out.push(
          make(
            generateSpanId(),
            currentRoundId ?? rootId,
            "loop.iteration",
            "branch",
            start,
            e.timestamp,
            attrs,
            err !== void 0
          )
        );
        break;
      }
      case "loop.decision": {
        if (pendingRound) {
          const dec = str(p.decision);
          if (dec) pendingRound.attrs["tangle.loop.decision"] = dec;
          flushRound(e.timestamp);
        }
        currentRoundId = void 0;
        break;
      }
    }
  }
  flushRound(rootEnd);
  return out;
}
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 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 padTraceId(id) {
  const cleaned = id.replace(/-/g, "");
  return cleaned.slice(0, 32).padEnd(32, "0");
}
function generateSpanId() {
  const bytes = new Uint8Array(8);
  if (typeof globalThis.crypto?.getRandomValues === "function") {
    globalThis.crypto.getRandomValues(bytes);
  } else {
    for (let i = 0; i < 8; i++) bytes[i] = Math.floor(Math.random() * 256);
  }
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
}
var INTELLIGENCE_WIRE_VERSION = "2026-05-26.v1";
var DEFAULT_INTELLIGENCE_BASE = "https://intelligence.tangle.tools";
async function exportEvalRuns(events, config) {
  if (events.length === 0) return { ok: true, status: 0, accepted: 0, rejected: [] };
  const apiKey = config?.apiKey ?? (typeof process !== "undefined" ? process.env.TANGLE_API_KEY : void 0);
  if (!apiKey)
    throw new Error("exportEvalRuns: apiKey required (pass config.apiKey or set TANGLE_API_KEY)");
  const base = config?.base ?? (typeof process !== "undefined" ? process.env.INTELLIGENCE_BASE : void 0) ?? DEFAULT_INTELLIGENCE_BASE;
  const url = `${base.replace(/\/+$/, "")}/v1/ingest/eval-runs`;
  const res = await fetch(url, {
    method: "POST",
    headers: {
      "content-type": "application/json",
      authorization: `Bearer ${apiKey}`,
      "X-Tangle-Wire-Version": INTELLIGENCE_WIRE_VERSION,
      ...config?.idempotencyKey ? { "Idempotency-Key": config.idempotencyKey } : {}
    },
    body: JSON.stringify({ wireVersion: INTELLIGENCE_WIRE_VERSION, events })
  });
  let parsed = {};
  try {
    parsed = await res.json();
  } catch {
  }
  return {
    ok: res.ok,
    status: res.status,
    accepted: parsed.accepted ?? (res.ok ? events.length : 0),
    rejected: parsed.rejected ?? []
  };
}

export {
  createOtelExporter,
  loopEventToOtelSpan,
  flatOtelSpan,
  buildLoopOtelSpans,
  buildLoopSpanNodes,
  INTELLIGENCE_WIRE_VERSION,
  exportEvalRuns
};
//# sourceMappingURL=chunk-UD4BHQMI.js.map