// src/trace/emitter.ts
var TraceEmitter = class {
  store;
  stack = [];
  _runId;
  now;
  id;
  hooks;
  hookErrors;
  constructor(store, options = {}) {
    this.store = store;
    this.now = options.now ?? (() => Date.now());
    this.id = options.id ?? (() => cryptoRandomId());
    this._runId = options.runId ?? this.id();
    this.hooks = options.onRunComplete ?? [];
    this.hookErrors = options.hookErrors ?? "swallow";
  }
  get runId() {
    return this._runId;
  }
  get traceStore() {
    return this.store;
  }
  /** Append a hook after construction (e.g. attach the trace analyst). */
  addRunCompleteHook(hook) {
    this.hooks.push(hook);
  }
  // ── Run lifecycle ──────────────────────────────────────────────────
  /**
   * Begin a Run.
   *
   * `scenarioId` is required on the persisted Run shape — every Run downstream
   * gets a non-empty scenarioId so filters and aggregations stay simple — but
   * the INPUT here accepts it as optional. When omitted, startRun substitutes
   * a sensible default (`run.layer ?? run.tags?.['kind'] ?? 'runtime'`) so
   * runtime / operator / meta-eval runs that have no curated-scenario corpus
   * to anchor to don't have to invent placeholder strings at the call site.
   */
  async startRun(run) {
    const scenarioId = run.scenarioId ?? run.layer ?? run.tags?.kind ?? "runtime";
    const full = {
      ...run,
      scenarioId,
      runId: this._runId,
      startedAt: this.now(),
      status: "running"
    };
    await this.store.appendRun(full);
    return full;
  }
  async endRun(outcome) {
    const status = outcome?.pass === false ? "failed" : "completed";
    await this.store.updateRun(this._runId, { endedAt: this.now(), status, outcome });
    await this.runHooks({ runId: this._runId, emitter: this, store: this.store, outcome, status });
  }
  async abortRun(reason) {
    const outcome = { pass: false, notes: reason };
    await this.store.updateRun(this._runId, {
      endedAt: this.now(),
      status: "aborted",
      outcome
    });
    await this.runHooks({
      runId: this._runId,
      emitter: this,
      store: this.store,
      outcome,
      status: "aborted"
    });
  }
  async runHooks(ctx) {
    for (const hook of this.hooks) {
      try {
        await hook(ctx);
      } catch (err) {
        if (this.hookErrors === "throw") throw err;
        try {
          await this.store.appendEvent({
            eventId: this.id(),
            runId: this._runId,
            kind: "log",
            timestamp: this.now(),
            payload: {
              source: "run_complete_hook",
              error: err instanceof Error ? err.message : String(err)
            }
          });
        } catch {
        }
      }
    }
  }
  // ── Generic span ───────────────────────────────────────────────────
  async span(init) {
    const spanId = this.id();
    const parent = init.parentSpanId ?? this.stack[this.stack.length - 1];
    const span = {
      spanId,
      parentSpanId: parent,
      runId: this._runId,
      startedAt: this.now(),
      ...init
    };
    await this.store.appendSpan(span);
    this.stack.push(spanId);
    return this.handle(span);
  }
  handle(span) {
    return {
      span,
      end: async (patch) => {
        const endedAt = this.now();
        await this.store.updateSpan(span.spanId, {
          endedAt,
          status: "ok",
          ...patch
        });
        this.pop(span.spanId);
      },
      fail: async (error, patch) => {
        const endedAt = this.now();
        const errStr = error instanceof Error ? error.message : error;
        await this.store.updateSpan(span.spanId, {
          endedAt,
          status: "error",
          error: errStr,
          ...patch
        });
        this.pop(span.spanId);
      }
    };
  }
  pop(spanId) {
    const idx = this.stack.lastIndexOf(spanId);
    if (idx >= 0) this.stack.splice(idx, 1);
  }
  // ── Typed span conveniences ────────────────────────────────────────
  llm(init) {
    return this.span({ kind: "llm", ...init });
  }
  tool(init) {
    return this.span({ kind: "tool", ...init });
  }
  retrieval(init) {
    return this.span({ kind: "retrieval", ...init });
  }
  async recordJudge(verdict) {
    const spanId = this.id();
    const now = this.now();
    const full = {
      spanId,
      runId: this._runId,
      kind: "judge",
      startedAt: now,
      endedAt: now,
      status: "ok",
      ...verdict
    };
    await this.store.appendSpan(full);
    return full;
  }
  sandbox(init) {
    return this.span({ kind: "sandbox", ...init });
  }
  // ── Events ─────────────────────────────────────────────────────────
  async emit(event) {
    const full = {
      eventId: this.id(),
      runId: this._runId,
      spanId: event.spanId ?? this.stack[this.stack.length - 1],
      kind: event.kind,
      timestamp: this.now(),
      payload: event.payload ?? {}
    };
    await this.store.appendEvent(full);
    return full;
  }
  // ── Budget ledger ──────────────────────────────────────────────────
  async recordBudget(entry) {
    const full = {
      runId: this._runId,
      timestamp: entry.timestamp ?? this.now(),
      dimension: entry.dimension,
      limit: entry.limit,
      consumed: entry.consumed,
      remaining: entry.remaining,
      breached: entry.breached,
      spanId: entry.spanId ?? this.stack[this.stack.length - 1]
    };
    await this.store.appendBudgetEntry(full);
    if (full.breached) {
      await this.emit({
        kind: "budget_breach",
        spanId: full.spanId,
        payload: { dimension: full.dimension, limit: full.limit, consumed: full.consumed }
      });
    }
    return full;
  }
  // ── Artifacts ──────────────────────────────────────────────────────
  async recordArtifact(artifact) {
    const full = { artifactId: this.id(), runId: this._runId, ...artifact };
    await this.store.appendArtifact(full);
    return full;
  }
  // ── Nested composition ─────────────────────────────────────────────
  /**
   * Runs `fn` inside a span; auto-ends on success, auto-fails on throw.
   * Returns the fn's return value. Use this for the 95% case.
   */
  async within(init, fn) {
    const handle = await this.span(init);
    try {
      const result = await fn(handle);
      await handle.end();
      return result;
    } catch (err) {
      await handle.fail(err instanceof Error ? err : String(err));
      throw err;
    }
  }
};
function cryptoRandomId() {
  if (typeof globalThis.crypto?.randomUUID === "function") return globalThis.crypto.randomUUID();
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
}
function llmSpanFromProvider(args) {
  return {
    name: args.name ?? args.model,
    model: args.model,
    messages: args.messages,
    output: args.output,
    inputTokens: args.usage?.inputTokens,
    outputTokens: args.usage?.outputTokens,
    cachedTokens: args.usage?.cachedTokens,
    cacheWriteTokens: args.usage?.cacheWriteTokens,
    reasoningTokens: args.usage?.reasoningTokens,
    costUsd: args.costUsd,
    finishReason: args.finishReason
  };
}

export {
  TraceEmitter,
  llmSpanFromProvider
};
//# sourceMappingURL=chunk-VQMK5FMP.js.map