OpenTelemetry & trace viewer
Every run already produces a RunTrace — these two features turn that data into something you can look at or pipe into existing infra.
exportRunTraceToOtel()
Converts a RunTrace into real OpenTelemetry spans on whatever tracer your app has already configured. Model calls and tool calls become duration spans (paired from the trace's start/end events, so they carry real timing); handoffs, retries, fallbacks, timeouts, guardrail trips, and approvals become short child spans — all correctly parented under one root span per run.
import { runAgent, exportRunTraceToOtel } from "samai-sdk";
const result = await runAgent(client, agent, "hi");
await exportRunTraceToOtel(result.trace); // needs the optional @opentelemetry/api peer dependency
// Now visible wherever your traces already go — Honeycomb, Datadog, Grafana Tempo, or
// anything else that speaks OTLP, using whatever exporter/provider you've already set up.renderTraceHTML() + samai-sdk trace
Renders a RunTrace as a self-contained, offline-viewable HTML timeline — no server, no build step, color-coded events proportionally positioned by real elapsed time, filterable by type, raw JSON available inline.
import { writeFileSync } from "node:fs";
import { runAgent, renderTraceHTML } from "samai-sdk";
const result = await runAgent(client, agent, "hi");
writeFileSync("trace.html", renderTraceHTML(result.trace)); // open directly in a browsernpx samai-sdk trace ./trace.json --port 4949
# ✅ Trace viewer running at http://localhost:4949Verified end to end
@opentelemetry/sdk-trace-base in-memory exporter — actual span names, attributes, parent/child nesting, and status codes. The trace viewer starts the real CLI server and fetches from it over HTTP. Voice events (voice-turn / interruption / clarification / goal-update) are included as short child spans / timeline entries — no extra setup.Voice tracing
Every VoiceAgentEvent also records into RunTrace — the same trace the agent loop already uses. That means exportRunTraceToOtel() and renderTraceHTML() work for voice sessions unchanged; interruptions, clarifications, and goal updates show up as spans / timeline entries alongside model and tool calls.
import { exportRunTraceToOtel, renderTraceHTML } from "samai-sdk";
import { writeFileSync } from "node:fs";
const session = await pipelineVoice({ stt, llm, tts }).connect({ agent, session: memSession });
// ... drive the session via stt.simulateTranscript(...)
await exportRunTraceToOtel((session as any)._trace);
writeFileSync("voice-trace.html", renderTraceHTML((session as any)._trace));