"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var traces_exports = {};
__export(traces_exports, {
  flushOtelLogs: () => flushOtelLogs,
  setTracerProvider: () => setTracerProvider,
  setupCloudTracer: () => setupCloudTracer,
  tracer: () => tracer,
  uploadSessionReport: () => uploadSessionReport
});
module.exports = __toCommonJS(traces_exports);
var import_protocol = require("@livekit/protocol");
var import_throws = require("@livekit/throws-transformer/throws");
var import_api = require("@opentelemetry/api");
var import_api_logs = require("@opentelemetry/api-logs");
var import_exporter_trace_otlp_proto = require("@opentelemetry/exporter-trace-otlp-proto");
var import_otlp_exporter_base = require("@opentelemetry/otlp-exporter-base");
var import_resources = require("@opentelemetry/resources");
var import_sdk_trace_node = require("@opentelemetry/sdk-trace-node");
var import_semantic_conventions = require("@opentelemetry/semantic-conventions");
var import_form_data = __toESM(require("form-data"), 1);
var import_livekit_server_sdk = require("livekit-server-sdk");
var import_promises = __toESM(require("node:fs/promises"), 1);
var import_chat_context = require("../llm/chat_context.cjs");
var import_log = require("../log.cjs");
var import_model_usage = require("../metrics/model_usage.cjs");
var import_otel_http_exporter = require("./otel_http_exporter.cjs");
var import_pino_otel_transport = require("./pino_otel_transport.cjs");
class DynamicTracer {
  tracerProvider;
  tracer;
  instrumentingModuleName;
  constructor(instrumentingModuleName) {
    this.instrumentingModuleName = instrumentingModuleName;
    this.tracerProvider = import_api.trace.getTracerProvider();
    this.tracer = import_api.trace.getTracer(instrumentingModuleName);
  }
  /**
   * Set a new tracer provider. This updates the underlying tracer instance.
   * @param provider - The new tracer provider to use
   */
  setProvider(provider) {
    this.tracerProvider = provider;
    this.tracer = this.tracerProvider.getTracer(this.instrumentingModuleName);
  }
  /**
   * Get the underlying OpenTelemetry tracer.
   * Use this to access the full Tracer API when needed.
   */
  getTracer() {
    return this.tracer;
  }
  /**
   * Start a span manually (without making it active).
   * You must call span.end() when done.
   *
   * @param options - Span configuration including name
   * @returns The created span
   */
  startSpan(options) {
    const ctx = options.context || import_api.context.active();
    const span = this.tracer.startSpan(
      options.name,
      {
        attributes: options.attributes,
        startTime: options.startTime
      },
      ctx
    );
    return span;
  }
  /**
   * Start a new span and make it active in the current context.
   * The span will automatically be ended when the provided function completes (unless endOnExit=false).
   *
   * @param fn - The function to execute within the span context
   * @param options - Span configuration including name
   * @returns The result of the provided function
   */
  async startActiveSpan(fn, options) {
    const ctx = options.context || import_api.context.active();
    const endOnExit = options.endOnExit === void 0 ? true : options.endOnExit;
    const opts = { attributes: options.attributes, startTime: options.startTime };
    return await this.tracer.startActiveSpan(options.name, opts, ctx, async (span) => {
      try {
        return await fn(span);
      } finally {
        if (endOnExit) {
          span.end();
        }
      }
    });
  }
  /**
   * Synchronous version of startActiveSpan for non-async operations.
   *
   * @param fn - The function to execute within the span context
   * @param options - Span configuration including name
   * @returns The result of the provided function
   */
  startActiveSpanSync(fn, options) {
    const ctx = options.context || import_api.context.active();
    const endOnExit = options.endOnExit === void 0 ? true : options.endOnExit;
    const opts = { attributes: options.attributes, startTime: options.startTime };
    return this.tracer.startActiveSpan(options.name, opts, ctx, (span) => {
      try {
        return fn(span);
      } finally {
        if (endOnExit) {
          span.end();
        }
      }
    });
  }
}
const tracer = new DynamicTracer("livekit-agents");
class MetadataSpanProcessor {
  metadata;
  constructor(metadata) {
    this.metadata = metadata;
  }
  onStart(span, _parentContext) {
    span.setAttributes(this.metadata);
  }
  onEnd(_span) {
  }
  shutdown() {
    return import_throws.ThrowsPromise.resolve();
  }
  forceFlush() {
    return import_throws.ThrowsPromise.resolve();
  }
}
function setTracerProvider(provider, options) {
  if (options == null ? void 0 : options.metadata) {
    provider.addSpanProcessor(new MetadataSpanProcessor(options.metadata));
  }
  tracer.setProvider(provider);
}
async function setupCloudTracer(options) {
  const { roomId, jobId, cloudHostname, enableTraces = true, enableLogs = true } = options;
  const apiKey = process.env.LIVEKIT_API_KEY;
  const apiSecret = process.env.LIVEKIT_API_SECRET;
  if (!apiKey || !apiSecret) {
    throw new Error("LIVEKIT_API_KEY and LIVEKIT_API_SECRET must be set for cloud tracing");
  }
  const token = new import_livekit_server_sdk.AccessToken(apiKey, apiSecret, {
    identity: "livekit-agents-telemetry",
    ttl: "6h"
  });
  token.addObservabilityGrant({ write: true });
  try {
    const jwt = await token.toJwt();
    const headers = {
      Authorization: `Bearer ${jwt}`
    };
    const metadata = {
      room_id: roomId,
      job_id: jobId
    };
    const resource = new import_resources.Resource({
      [import_semantic_conventions.ATTR_SERVICE_NAME]: "livekit-agents",
      room_id: roomId,
      job_id: jobId
    });
    if (enableTraces) {
      const spanExporter = new import_exporter_trace_otlp_proto.OTLPTraceExporter({
        url: `https://${cloudHostname}/observability/traces/otlp/v0`,
        headers,
        compression: import_otlp_exporter_base.CompressionAlgorithm.GZIP
      });
      const tracerProvider = new import_sdk_trace_node.NodeTracerProvider({
        resource,
        spanProcessors: [new MetadataSpanProcessor(metadata), new import_sdk_trace_node.BatchSpanProcessor(spanExporter)]
      });
      tracerProvider.register();
      setTracerProvider(tracerProvider);
    }
    if (enableLogs) {
      (0, import_pino_otel_transport.initPinoCloudExporter)({
        cloudHostname,
        roomId,
        jobId
      });
      (0, import_log.enableOtelLogging)();
    }
  } catch (error) {
    console.error("Failed to setup cloud tracer:", error);
    throw error;
  }
}
async function flushOtelLogs() {
  await (0, import_pino_otel_transport.flushPinoLogs)();
}
const ROLE_MAP = {
  developer: "DEVELOPER",
  system: "SYSTEM",
  user: "USER",
  assistant: "ASSISTANT"
};
function chatItemToProto(item) {
  var _a, _b;
  const itemDict = {};
  if (item.type === "message") {
    const msg = {
      id: item.id,
      role: ROLE_MAP[item.role] ?? item.role.toUpperCase(),
      content: item.content.map((c) => ({
        text: (0, import_chat_context.isInstructions)(c) ? c.value : c
      })),
      createdAt: toRFC3339(item.createdAt)
    };
    if (item.interrupted) {
      msg.interrupted = item.interrupted;
    }
    if (item.extra && Object.keys(item.extra).length > 0) {
      msg.extra = item.extra;
    }
    if (item.transcriptConfidence !== void 0) {
      msg.transcriptConfidence = item.transcriptConfidence;
    }
    const metrics = item.metrics;
    if (metrics && Object.keys(metrics).length > 0) {
      const protoMetrics = {};
      if (metrics.startedSpeakingAt !== void 0) {
        protoMetrics.startedSpeakingAt = toRFC3339(metrics.startedSpeakingAt * 1e3);
      }
      if (metrics.stoppedSpeakingAt !== void 0) {
        protoMetrics.stoppedSpeakingAt = toRFC3339(metrics.stoppedSpeakingAt * 1e3);
      }
      if (metrics.transcriptionDelay !== void 0) {
        protoMetrics.transcriptionDelay = metrics.transcriptionDelay;
      }
      if (metrics.endOfTurnDelay !== void 0) {
        protoMetrics.endOfTurnDelay = metrics.endOfTurnDelay;
      }
      if (metrics.onUserTurnCompletedDelay !== void 0) {
        protoMetrics.onUserTurnCompletedDelay = metrics.onUserTurnCompletedDelay;
      }
      if (metrics.llmNodeTtft !== void 0) {
        protoMetrics.llmNodeTtft = metrics.llmNodeTtft;
      }
      if (metrics.ttsNodeTtfb !== void 0) {
        protoMetrics.ttsNodeTtfb = metrics.ttsNodeTtfb;
      }
      if (metrics.playbackLatency !== void 0) {
        protoMetrics.playbackLatency = metrics.playbackLatency;
      }
      if (metrics.e2eLatency !== void 0) {
        protoMetrics.e2eLatency = metrics.e2eLatency;
      }
      msg.metrics = protoMetrics;
    }
    itemDict.message = msg;
  } else if (item.type === "function_call") {
    itemDict.functionCall = {
      id: item.id,
      callId: item.callId,
      arguments: item.args,
      name: item.name,
      createdAt: toRFC3339(item.createdAt)
    };
  } else if (item.type === "function_call_output") {
    itemDict.functionCallOutput = {
      id: item.id,
      name: item.name,
      callId: item.callId,
      output: item.output,
      isError: item.isError,
      createdAt: toRFC3339(item.createdAt)
    };
  } else if (item.type === "agent_handoff") {
    const handoff = {
      id: item.id,
      newAgentId: item.newAgentId,
      createdAt: toRFC3339(item.createdAt)
    };
    if (item.oldAgentId !== void 0 && item.oldAgentId !== null && item.oldAgentId !== "") {
      handoff.oldAgentId = item.oldAgentId;
    }
    itemDict.agentHandoff = handoff;
  } else if (item.type === "agent_config_update") {
    const configUpdate = {
      id: item.id,
      createdAt: toRFC3339(item.createdAt)
    };
    if (item.instructions !== void 0) {
      configUpdate.instructions = (0, import_chat_context.renderInstructions)(item.instructions);
    }
    if (item.toolsAdded !== void 0) {
      configUpdate.toolsAdded = item.toolsAdded;
    }
    if (item.toolsRemoved !== void 0) {
      configUpdate.toolsRemoved = item.toolsRemoved;
    }
    itemDict.agentConfigUpdate = configUpdate;
  }
  try {
    if (item.type === "function_call" && typeof ((_a = itemDict.functionCall) == null ? void 0 : _a.arguments) === "string") {
      itemDict.functionCall.arguments = JSON.parse(itemDict.functionCall.arguments);
    } else if (item.type === "function_call_output" && typeof ((_b = itemDict.functionCallOutput) == null ? void 0 : _b.output) === "string") {
      itemDict.functionCallOutput.output = JSON.parse(itemDict.functionCallOutput.output);
    }
  } catch {
  }
  return itemDict;
}
function toRFC3339(valueMs) {
  const dt = valueMs instanceof Date ? valueMs : new Date(valueMs);
  const truncated = new Date(Math.floor(dt.getTime()));
  return truncated.toISOString();
}
async function uploadSessionReport(options) {
  var _a;
  const { agentName, cloudHostname, report } = options;
  const logExporter = new import_otel_http_exporter.SimpleOTLPHttpLogExporter({
    cloudHostname,
    resourceAttributes: {
      room_id: report.roomId,
      job_id: report.jobId
    },
    scopeName: "chat_history",
    scopeAttributes: {
      room_id: report.roomId,
      job_id: report.jobId,
      room: report.room
    }
  });
  const logRecords = [];
  const commonAttrs = {
    room_id: report.roomId,
    job_id: report.jobId,
    "logger.name": "chat_history"
  };
  const usage = ((_a = report.modelUsage) == null ? void 0 : _a.map(import_model_usage.filterZeroValues)) || null;
  logRecords.push({
    body: "session report",
    timestampMs: report.startedAt || report.timestamp || 0,
    attributes: {
      ...commonAttrs,
      "session.options": report.options || {},
      "session.report_timestamp": report.timestamp,
      agent_name: agentName,
      usage
    }
  });
  let lastTimestamp = 0;
  const chatItems = report.recordingOptions.transcript ? report.chatHistory.items : [];
  for (const item of chatItems) {
    if (!item) continue;
    const hasValidTimestamp = Number.isFinite(item.createdAt);
    let itemTimestamp = hasValidTimestamp ? item.createdAt : Date.now();
    if (itemTimestamp <= lastTimestamp) {
      itemTimestamp = lastTimestamp + 1e-3;
    }
    lastTimestamp = itemTimestamp;
    const itemProto = chatItemToProto(item);
    let severityNumber = import_api_logs.SeverityNumber.UNSPECIFIED;
    let severityText = "unspecified";
    if (item.type === "function_call_output" && item.isError) {
      severityNumber = import_api_logs.SeverityNumber.ERROR;
      severityText = "error";
    }
    logRecords.push({
      body: "chat item",
      timestampMs: itemTimestamp,
      // Adjusted for monotonic ordering
      attributes: { "chat.item": itemProto, ...commonAttrs },
      severityNumber,
      severityText
    });
  }
  await logExporter.export(logRecords);
  const hasAudio = Boolean(
    report.recordingOptions.audio && report.audioRecordingPath && report.audioRecordingStartedAt
  );
  if (!report.recordingOptions.transcript && !hasAudio) {
    return;
  }
  const apiKey = process.env.LIVEKIT_API_KEY;
  const apiSecret = process.env.LIVEKIT_API_SECRET;
  if (!apiKey || !apiSecret) {
    throw new Error("LIVEKIT_API_KEY and LIVEKIT_API_SECRET must be set for session upload");
  }
  const token = new import_livekit_server_sdk.AccessToken(apiKey, apiSecret, { ttl: "6h" });
  token.addObservabilityGrant({ write: true });
  const jwt = await token.toJwt();
  const formData = new import_form_data.default();
  const audioStartTime = report.audioRecordingStartedAt ?? 0;
  const headerMsg = new import_protocol.MetricsRecordingHeader({
    roomId: report.roomId,
    duration: BigInt(0),
    // TODO: Calculate actual duration from report
    startTime: {
      seconds: BigInt(Math.floor(audioStartTime / 1e3)),
      nanos: Math.floor(audioStartTime % 1e3 * 1e6)
    }
  });
  const headerBytes = Buffer.from(headerMsg.toBinary());
  formData.append("header", headerBytes, {
    filename: "header.binpb",
    contentType: "application/protobuf",
    knownLength: headerBytes.length,
    header: {
      "Content-Type": "application/protobuf",
      "Content-Length": headerBytes.length.toString()
    }
  });
  if (report.recordingOptions.transcript) {
    const chatHistoryJson = JSON.stringify(report.chatHistory.toJSON({ excludeTimestamp: false }));
    const chatHistoryBuffer = Buffer.from(chatHistoryJson, "utf-8");
    formData.append("chat_history", chatHistoryBuffer, {
      filename: "chat_history.json",
      contentType: "application/json",
      knownLength: chatHistoryBuffer.length,
      header: {
        "Content-Type": "application/json",
        "Content-Length": chatHistoryBuffer.length.toString()
      }
    });
  }
  if (report.recordingOptions.audio && report.audioRecordingPath && report.audioRecordingStartedAt) {
    let audioBytes;
    try {
      audioBytes = await import_promises.default.readFile(report.audioRecordingPath);
    } catch {
      audioBytes = Buffer.alloc(0);
    }
    if (audioBytes.length > 0) {
      formData.append("audio", audioBytes, {
        filename: "recording.ogg",
        contentType: "audio/ogg",
        knownLength: audioBytes.length,
        header: {
          "Content-Type": "audio/ogg",
          "Content-Length": audioBytes.length.toString()
        }
      });
    }
  }
  return new import_throws.ThrowsPromise((resolve, reject) => {
    formData.submit(
      {
        protocol: "https:",
        host: cloudHostname,
        path: "/observability/recordings/v0",
        method: "POST",
        headers: {
          Authorization: `Bearer ${jwt}`
        }
      },
      (err, res) => {
        if (err) {
          reject(new Error(`Failed to upload session report: ${err.message}`));
          return;
        }
        if (res.statusCode && res.statusCode >= 400) {
          let body = "";
          res.on("data", (chunk) => {
            body += chunk.toString();
          });
          res.on("error", (readErr) => {
            reject(
              new Error(
                `Failed to upload session report: ${res.statusCode} ${res.statusMessage} (body read error: ${readErr.message})`
              )
            );
          });
          res.on("end", () => {
            reject(
              new Error(
                `Failed to upload session report: ${res.statusCode} ${res.statusMessage} - ${body}`
              )
            );
          });
          return;
        }
        res.resume();
        res.on("error", (readErr) => reject(new Error(`Response read error: ${readErr.message}`)));
        res.on("end", () => resolve());
      }
    );
  });
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  flushOtelLogs,
  setTracerProvider,
  setupCloudTracer,
  tracer,
  uploadSessionReport
});
//# sourceMappingURL=traces.cjs.map