// src/tracing/signal/segment.ts
import { randomBytes } from "node:crypto";
function freshId(byteLength) {
  return randomBytes(byteLength).toString("hex");
}
function freshTraceId() {
  return freshId(16);
}
function freshSegmentId() {
  return freshId(8);
}
function openSegment(input) {
  const attributes = Object.freeze({ ...input.attributes ?? {} });
  const segment = {
    id: input.id ?? freshSegmentId(),
    traceId: input.traceId ?? freshTraceId(),
    parentId: input.parentId ?? null,
    kind: input.kind,
    name: input.name,
    startedAt: input.startedAt ?? Date.now(),
    endedAt: null,
    status: "open",
    attributes
  };
  return Object.freeze(segment);
}
function withAttributes(segment, patch) {
  const attributes = Object.freeze({ ...segment.attributes, ...patch });
  return Object.freeze({ ...segment, attributes });
}
function closeSegment(segment, status, options) {
  const error = options?.error ?? segment.error;
  const record = {
    ...segment,
    endedAt: options?.endedAt ?? Date.now(),
    status,
    ...status === "error" && error ? { error } : {}
  };
  return Object.freeze(record);
}

// src/tracing/signal/handle.ts
function toSegmentError(error) {
  if (error instanceof Error) return { message: error.message };
  if (typeof error === "string") return { message: error };
  try {
    return { message: String(error) };
  } catch {
    return { message: "unknown error" };
  }
}
function openHandle(input, sink) {
  let segment = openSegment({
    kind: input.kind,
    name: input.name,
    traceId: input.traceId,
    parentId: input.parentId ?? null,
    attributes: input.attributes
  });
  let pendingError;
  let done = false;
  sink({ type: "open", segment });
  const handle = {
    traceId: segment.traceId,
    id: segment.id,
    active: true,
    note(attributes) {
      if (done) return;
      segment = withAttributes(segment, attributes);
      sink({ type: "update", id: segment.id, attributes });
    },
    child(kind, name) {
      if (done) return NOOP_HANDLE;
      return openHandle(
        { kind, name, traceId: segment.traceId, parentId: segment.id },
        sink
      );
    },
    fail(error) {
      if (done) return;
      pendingError = toSegmentError(error);
    },
    close(status) {
      if (done) return;
      done = true;
      const finalStatus = status ?? (pendingError ? "error" : "ok");
      const record = closeSegment(segment, finalStatus, {
        error: pendingError
      });
      sink({ type: "close", record });
    }
  };
  return handle;
}
var NOOP_HANDLE = Object.freeze({
  traceId: "",
  id: "",
  active: false,
  note() {
  },
  child() {
    return NOOP_HANDLE;
  },
  fail() {
  },
  close() {
  }
});

// src/tracing/channel/signal.ts
var SignalChannel = class {
  /** Buffered signals awaiting a consumer, oldest first. */
  buffer = [];
  /** A parked consumer's resolver, set while a pull awaits the next signal. */
  waiting = null;
  /** Once true, no further signals are accepted and pulls drain then end. */
  closed = false;
  /** Upper bound on the unread buffer; `Infinity` means unbounded. */
  bound;
  constructor(options) {
    this.bound = options?.bound !== void 0 && options.bound > 0 ? options.bound : Number.POSITIVE_INFINITY;
  }
  /**
   * Publish a signal onto the stream.
   *
   * If a consumer is parked, the signal is handed to it directly; otherwise it
   * is buffered (dropping the oldest entry past `bound`). A no-op once closed.
   */
  emit(signal) {
    if (this.closed) return;
    if (this.waiting) {
      const resolve = this.waiting;
      this.waiting = null;
      resolve({ value: signal, done: false });
      return;
    }
    this.buffer.push(signal);
    if (this.buffer.length > this.bound) {
      this.buffer.shift();
    }
  }
  /**
   * End the stream.
   *
   * A parked consumer is completed immediately; a consumer that later pulls
   * receives any still-buffered signals before the terminal `done`. Idempotent.
   */
  close() {
    if (this.closed) return;
    this.closed = true;
    if (this.waiting) {
      const resolve = this.waiting;
      this.waiting = null;
      resolve({ value: void 0, done: true });
    }
  }
  /** Whether {@link close} has been called. */
  get isClosed() {
    return this.closed;
  }
  /** Count of signals buffered but not yet consumed. */
  get pending() {
    return this.buffer.length;
  }
  /**
   * The async-iteration entrypoint.
   *
   * Yields buffered signals immediately, then parks awaiting the next `emit`,
   * and returns `{ done: true }` once the channel is closed and drained.
   */
  [Symbol.asyncIterator]() {
    const next = () => {
      if (this.buffer.length > 0) {
        const value = this.buffer.shift();
        return Promise.resolve({ value, done: false });
      }
      if (this.closed) {
        return Promise.resolve({ value: void 0, done: true });
      }
      return new Promise((resolve) => {
        this.waiting = resolve;
      });
    };
    const ret = () => {
      this.close();
      return Promise.resolve({ value: void 0, done: true });
    };
    return {
      next,
      return: ret
    };
  }
};

// src/tracing/redaction/secret-scrubber.ts
var REDACTION_TOKEN = "\u2039redacted\u203A";
var DEFAULT_SECRET_PATTERNS = Object.freeze([
  // Key-name rules ----------------------------------------------------------
  {
    label: "credential-key-name",
    // …secret / …password / passwd / passphrase / …token / api[_-]?key / apikey
    key: /(?:secret|password|passwd|passphrase|token|api[_-]?key|apikey)/i
  },
  {
    label: "authorization-header-name",
    // authorization, proxy-authorization, x-…-key, x-…-token, cookie, set-cookie
    key: /^(?:proxy-)?authorization$|^x-[\w-]*?(?:key|token|secret)$|^set-cookie$|^cookie$/i
  },
  {
    label: "private-key-or-access-name",
    // private_key, signing_key, client_secret, access_key, refresh_token, session_id
    key: /(?:private[_-]?key|signing[_-]?key|client[_-]?secret|access[_-]?key|refresh[_-]?token|session[_-]?id)/i
  },
  // Value-shape rules -------------------------------------------------------
  {
    label: "bearer-token-value",
    // an HTTP-style scheme prefix followed by an opaque blob
    value: /\b(?:bearer|basic|token)\s+[A-Za-z0-9._~+/-]{8,}=*\b/i
  },
  {
    label: "vendor-key-prefix-value",
    // provider-style prefixed keys: sk-…, pk-…, rk-…, xoxb-…, ghp_…, AKIA…
    value: /\b(?:sk|pk|rk|api|key)[-_][A-Za-z0-9]{12,}\b|\bxox[baprs]-[A-Za-z0-9-]{10,}\b|\bgh[pousr]_[A-Za-z0-9]{20,}\b|\bAKIA[0-9A-Z]{12,}\b/
  },
  {
    label: "json-web-token-value",
    // three base64url segments separated by dots (a JWT)
    value: /\beyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\b/
  },
  {
    label: "private-key-block-value",
    // a PEM-armoured private key body
    value: /-----BEGIN(?:[A-Z ]+)?PRIVATE KEY-----/
  }
]);
function isContainer(value) {
  return typeof value === "object" && value !== null;
}
var SecretScrubber = class {
  patterns;
  token;
  /** Pre-split views so the hot loop never re-checks which rules have a key/value side. */
  keyRules;
  valueRules;
  constructor(options = {}) {
    this.patterns = options.patterns ?? DEFAULT_SECRET_PATTERNS;
    this.token = options.token ?? REDACTION_TOKEN;
    this.keyRules = this.patterns.filter((p) => p.key !== void 0);
    this.valueRules = this.patterns.filter((p) => p.value !== void 0);
  }
  /** True when a field name matches any key-shaped rule. */
  keyIsSecret(key) {
    for (const rule of this.keyRules) {
      if (rule.key.test(key)) return true;
    }
    return false;
  }
  /** True when a string leaf matches any value-shaped rule. */
  valueIsSecret(value) {
    for (const rule of this.valueRules) {
      if (rule.value.test(value)) return true;
    }
    return false;
  }
  /**
   * Decide a leaf's sanitized form given the key that points at it.
   *
   * Redacts when the key looks secret, or when a string value looks secret.
   * Non-string leaves under an innocuous key pass through untouched.
   */
  sanitizeLeaf(key, value) {
    if (this.keyIsSecret(key)) return this.token;
    if (typeof value === "string" && this.valueIsSecret(value)) return this.token;
    return value;
  }
  /**
   * Return a deep copy of `attributes` with secret-looking keys and values
   * replaced by the redaction token.
   *
   * Walks the structure with a single explicit stack (no recursion). Containers
   * are cloned shallowly up front and their children enqueued; leaves are
   * resolved through {@link sanitizeLeaf}. The input — including nested objects
   * and arrays — is never mutated. A key whose name is itself secret short-
   * circuits the descent: the whole subtree under it collapses to the token,
   * so credentials nested inside (e.g. `auth: { token: … }`) cannot leak.
   */
  scrub(attributes) {
    const root = {};
    const stack = [{ node: attributes, clone: root }];
    while (stack.length > 0) {
      const { node, clone } = stack.pop();
      if (Array.isArray(node)) {
        const out2 = clone;
        for (let i = 0; i < node.length; i++) {
          const key = String(i);
          const value = node[i];
          if (isContainer(value) && !this.keyIsSecret(key)) {
            const childClone = Array.isArray(value) ? [] : {};
            out2[i] = childClone;
            stack.push({ node: value, clone: childClone });
          } else {
            out2[i] = this.sanitizeLeaf(key, value);
          }
        }
        continue;
      }
      const out = clone;
      for (const key of Object.keys(node)) {
        const value = node[key];
        if (isContainer(value) && !this.keyIsSecret(key)) {
          const childClone = Array.isArray(value) ? [] : {};
          out[key] = childClone;
          stack.push({ node: value, clone: childClone });
        } else {
          out[key] = this.sanitizeLeaf(key, value);
        }
      }
    }
    return root;
  }
};

// src/tracing/sinks/console.ts
var STATUS_GLYPH = {
  ok: "\u2713",
  error: "\u2717"
};
function padEnd(value, width) {
  return value.length >= width ? value : value + " ".repeat(width - value.length);
}
function padStart(value, width) {
  return value.length >= width ? value : " ".repeat(width - value.length) + value;
}
var ConsoleSink = class {
  log;
  constructor(options) {
    this.log = options?.log ?? ((line) => console.log(line));
  }
  /** Render the one-line summary for a closed segment. */
  format(record) {
    const glyph = STATUS_GLYPH[record.status] ?? "?";
    const duration = `${record.endedAt - record.startedAt}ms`;
    const tag = `[${record.traceId.slice(0, 8)}/${record.id}]`;
    const head = `${glyph} ${padEnd(record.kind, 10)} ${padEnd(record.name, 24)} ${padStart(duration, 8)}  ${tag}`;
    return record.status === "error" && record.error ? `${head} \u2014 ${record.error.message}` : head;
  }
  /** Drain `channel`, printing a line for every `close` signal. */
  async drain(channel) {
    for await (const signal of channel) {
      if (signal.type === "close") {
        this.log(this.format(signal.record));
      }
    }
  }
};

// src/tracing/sinks/file.ts
import { appendFile } from "node:fs/promises";
var FileSink = class {
  path;
  flushEvery;
  /** Serialized records awaiting their append, oldest first. */
  pending = [];
  constructor(path, options) {
    this.path = path;
    this.flushEvery = options?.flushEvery !== void 0 && options.flushEvery > 0 ? options.flushEvery : 1;
  }
  /** Drain `channel`, buffering each closed record and flushing on threshold/end. */
  async drain(channel) {
    for await (const signal of channel) {
      if (signal.type !== "close") continue;
      this.pending.push(JSON.stringify(signal.record));
      if (this.pending.length >= this.flushEvery) {
        await this.flush();
      }
    }
    await this.flush();
  }
  /** Append all pending records to the file as NDJSON lines, then clear them. */
  async flush() {
    if (this.pending.length === 0) return;
    const batch = this.pending;
    this.pending = [];
    await appendFile(this.path, batch.join("\n") + "\n", "utf8");
  }
  /** Flush any remaining records. Idempotent; the sink holds no OS handle. */
  async close() {
    await this.flush();
  }
};

// src/tracing/sinks/stream.ts
var StreamSink = class {
  stream;
  endOnClose;
  constructor(stream, options) {
    this.stream = stream;
    this.endOnClose = options?.endOnClose ?? false;
  }
  /** Write `line`, resolving once it is accepted or once backpressure clears. */
  writeLine(line) {
    return new Promise((resolve, reject) => {
      const onError = (error) => reject(error);
      this.stream.once("error", onError);
      const flushed = this.stream.write(line, "utf8", () => {
        this.stream.removeListener("error", onError);
      });
      if (flushed) {
        resolve();
      } else {
        this.stream.once("drain", () => resolve());
      }
    });
  }
  /** Drain `channel`, writing a JSON line per closed record (with backpressure). */
  async drain(channel) {
    for await (const signal of channel) {
      if (signal.type !== "close") continue;
      const line = JSON.stringify(signal.record) + "\n";
      await this.writeLine(line);
    }
  }
  /** No-op: bytes are not buffered by this sink. Present for interface symmetry. */
  async flush() {
  }
  /** End the underlying stream when this sink owns it (`endOnClose`); else no-op. */
  async close() {
    if (!this.endOnClose) return;
    await new Promise((resolve, reject) => {
      this.stream.end(
        (error) => error ? reject(error) : resolve()
      );
    });
  }
};

// src/tracing/recorder/sampling.ts
function isRatioStrategy(strategy) {
  return typeof strategy === "object" && strategy !== null && "ratio" in strategy;
}
var FNV_OFFSET_BASIS = 2166136261;
var FNV_PRIME = 16777619;
var TWO_POW_32 = 4294967296;
function unitHash(traceId) {
  let hash = FNV_OFFSET_BASIS;
  for (let i = 0; i < traceId.length; i++) {
    hash ^= traceId.charCodeAt(i);
    hash = Math.imul(hash, FNV_PRIME);
  }
  return (hash >>> 0) / TWO_POW_32;
}
function clampRatio(ratio) {
  if (Number.isNaN(ratio)) return 0;
  if (ratio <= 0) return 0;
  if (ratio >= 1) return 1;
  return ratio;
}
var SampleGate = class {
  strategy;
  /** Pre-clamped admission threshold for the ratio strategy; unused otherwise. */
  threshold;
  constructor(strategy = "always") {
    this.strategy = strategy;
    this.threshold = isRatioStrategy(strategy) ? clampRatio(strategy.ratio) : 0;
  }
  /**
   * Decide whether the trace identified by `traceId` is admitted.
   *
   * - `"always"` → always `true`.
   * - `"never"`  → always `false`.
   * - `{ ratio }` → `unitHash(traceId) < ratio` (clamped): deterministic, so the
   *   same id always yields the same answer. Ratio `0` admits nothing and ratio
   *   `1` admits everything regardless of the hash.
   */
  decide(traceId) {
    if (this.strategy === "always") return true;
    if (this.strategy === "never") return false;
    if (this.threshold <= 0) return false;
    if (this.threshold >= 1) return true;
    return unitHash(traceId) < this.threshold;
  }
};

// src/tracing/recorder/recorder.ts
var SERVICE_NAME_KEY = "service.name";
function toGate(sampling) {
  if (sampling instanceof SampleGate) return sampling;
  return new SampleGate(sampling ?? "always");
}
var Recorder = class {
  /** Logical service/agent name stamped onto admitted root traces. */
  serviceName;
  gate;
  signalChannel;
  constructor(options) {
    this.serviceName = options.serviceName;
    this.gate = toGate(options.sampling);
    this.signalChannel = options.channel ?? new SignalChannel();
  }
  /**
   * Open a segment, subject to sampling.
   *
   * Resolves the trace id (a supplied `opts.traceId` joins an existing trace; a
   * root mints a fresh one), then consults the {@link SampleGate}. Sampled out →
   * the frozen {@link NOOP_HANDLE} (nothing is emitted). Sampled in → a live
   * {@link SegmentHandle} from {@link openHandle}, whose `open` signal — and all
   * subsequent `update`/`close` signals — land on this recorder's channel. A
   * root open also folds `serviceName` into the segment's attributes.
   */
  open(kind, name, opts) {
    const isRoot = opts?.traceId === void 0;
    const traceId = opts?.traceId ?? freshTraceId();
    if (!this.gate.decide(traceId)) return NOOP_HANDLE;
    const attributes = isRoot ? { [SERVICE_NAME_KEY]: this.serviceName, ...opts?.attributes ?? {} } : opts?.attributes;
    return openHandle(
      {
        kind,
        name,
        traceId,
        parentId: opts?.parentId ?? null,
        attributes
      },
      (signal) => this.signalChannel.emit(signal)
    );
  }
  /**
   * The recorder's signal stream.
   *
   * Sinks drain this with `for await … of recorder.channel()`. It is the same
   * underlying {@link SignalChannel} every {@link open}ed handle emits onto.
   */
  channel() {
    return this.signalChannel;
  }
};

// src/tracing/registry/hub.ts
var DEFAULT_RECORDER_NAME = "default";
var TelemetryHub = class {
  recorders = /* @__PURE__ */ new Map();
  /** Name of the recorder returned by {@link recorder} with no argument. */
  defaultName = null;
  /**
   * Register a recorder under `name`, returning the constructed instance.
   *
   * Re-registering a name replaces the prior recorder. If no default has been
   * chosen yet, the first registration becomes the default; registering under
   * the reserved `"default"` name always (re)designates the default.
   */
  register(name, options) {
    const recorder = new Recorder(options);
    this.recorders.set(name, recorder);
    if (this.defaultName === null || name === DEFAULT_RECORDER_NAME) {
      this.defaultName = name;
    }
    return recorder;
  }
  /**
   * Look up a recorder by `name`, or the default when `name` is omitted.
   *
   * Throws if the requested (or default) recorder is not registered, so a
   * missing recorder fails loudly rather than silently dropping traces.
   */
  recorder(name) {
    const key = name ?? this.defaultName;
    const found = key !== null && key !== void 0 ? this.recorders.get(key) : void 0;
    if (!found) {
      const which = name ?? "(default)";
      throw new Error(`TelemetryHub: no recorder registered as "${which}"`);
    }
    return found;
  }
  /** The default recorder, or `undefined` if none has been registered. */
  getDefault() {
    if (this.defaultName === null) return void 0;
    return this.recorders.get(this.defaultName);
  }
  /** Whether a recorder is registered under `name`. */
  has(name) {
    return this.recorders.has(name);
  }
};
var processHub = null;
function getDefaultHub() {
  if (processHub === null) processHub = new TelemetryHub();
  return processHub;
}
function setDefaultHub(hub) {
  processHub = hub;
}

// src/tracing/adapter/runtime-trace.ts
function traceAgentRun(recorder, subscribe) {
  let runHandle = null;
  let inferenceHandle = null;
  const actions = /* @__PURE__ */ new Map();
  let textDeltas = 0;
  let thinkingDeltas = 0;
  let lastPhase;
  function closeInference(status = "ok") {
    if (inferenceHandle) {
      if (textDeltas > 0 || thinkingDeltas > 0) {
        inferenceHandle.note({
          "stream.text_deltas": textDeltas,
          "stream.thinking_deltas": thinkingDeltas
        });
      }
      inferenceHandle.close(status);
      inferenceHandle = null;
    }
    textDeltas = 0;
    thinkingDeltas = 0;
  }
  function closeAllActions(status = "ok") {
    for (const handle of actions.values()) handle.close(status);
    actions.clear();
  }
  function ensureRun(runId) {
    if (!runHandle) {
      runHandle = recorder.open("run", runId ? `run ${runId}` : "run", {
        attributes: runId ? { "run.id": runId } : void 0
      });
    }
    return runHandle;
  }
  function closeRun(status) {
    closeAllActions(status);
    closeInference(status);
    if (runHandle) {
      runHandle.close(status);
      runHandle = null;
    }
    lastPhase = void 0;
  }
  function onPhase(phase, run) {
    if (phase === lastPhase) return;
    if (phase === "invoking") {
      closeInference("ok");
      inferenceHandle = run.child("inference", "model turn");
    } else if (phase === "dispatching" || phase === "compacting" || phase === "idle") {
      closeInference("ok");
    }
    lastPhase = phase;
  }
  const unsubscribe = subscribe((event) => {
    switch (event.kind) {
      case "snapshot": {
        const run = ensureRun(event.snapshot.runId);
        onPhase(event.snapshot.phase, run);
        break;
      }
      case "text_delta": {
        if (inferenceHandle) textDeltas += 1;
        break;
      }
      case "thinking_delta": {
        if (inferenceHandle) thinkingDeltas += 1;
        break;
      }
      case "tool_started": {
        const run = ensureRun();
        const handle = run.child("action", event.name);
        handle.note({ "tool.id": event.id, "tool.name": event.name });
        actions.set(event.id, handle);
        break;
      }
      case "tool_finished": {
        const handle = actions.get(event.id);
        if (handle) {
          handle.note({ "tool.is_error": event.outcome.isError });
          handle.close(event.outcome.isError ? "error" : "ok");
          actions.delete(event.id);
        }
        break;
      }
      case "settled": {
        ensureRun(event.snapshot.runId);
        closeRun("ok");
        break;
      }
      case "faulted": {
        const run = ensureRun(event.snapshot.runId);
        if (event.error) run.fail(event.error);
        closeRun("error");
        break;
      }
      default: {
        break;
      }
    }
  });
  return () => {
    unsubscribe();
    closeAllActions("ok");
    closeInference("ok");
    if (runHandle) {
      runHandle.close("ok");
      runHandle = null;
    }
  };
}
export {
  ConsoleSink,
  DEFAULT_SECRET_PATTERNS,
  FileSink,
  NOOP_HANDLE,
  REDACTION_TOKEN,
  Recorder,
  SampleGate,
  SecretScrubber,
  SignalChannel,
  StreamSink,
  TelemetryHub,
  closeSegment,
  freshId,
  freshSegmentId,
  freshTraceId,
  getDefaultHub,
  openHandle,
  openSegment,
  setDefaultHub,
  traceAgentRun,
  withAttributes
};
