/docs/sdks/node

Node SDK

TypeScript SDK for tracing AI agents, model calls, tool calls, metadata, media, metrics, and trace reads.

Installation

npm install @theta-lab/obsrv

Configuration

API keys are project-scoped on the server. THETA_PROJECTis optional and only needed when you want explicit project filtering.

export THETA_API_KEY="tk_live_…"
export THETA_BASE_URL="https://api.obsrv.tech"  # optional
export THETA_PROJECT="proj_…"                   # optional

Tracing

await client.trace({ name: "checkout", runType: "prod" }, async (t) => {
  t.setMetadata({ user_id: "u_a72c", release: "v3.2.1" });

  await t.step({ name: "plan", type: "llm", model: "claude-sonnet-4-6" }, (s) => {
    s.logMessage({ role: "user", text: "Buy milk" });
    s.logMessage({ role: "assistant", text: "On it." });
    s.setTokenUsage({ input: 900, output: 120 });
    s.setCost(0.014);
  });

  await t.step({ name: "inventory.lookup", type: "tool" }, (s) => {
    s.logToolCall({
      name: "inventory.lookup",
      arguments: { sku: "milk" },
      result: { in_stock: true },
      latencyMs: 83,
    });
  });

  await t.attachImage("screenshot.png");
});

await client.flush();

Agent wrapping

const agent = client.wrapAgent("support-agent", async (ctx, query: string) => {
  const result = await callLlm(query);
  ctx.onComplete(result);
  return result;
});

const { result, runId } = await agent("Help me cancel my order");
await client.recordMetric("task_adherence", runId, { passed: true });

Multimodal attachments

await t.attachImage("./screenshot.png");
await t.attachAudio(buffer, { filename: "audio.wav", mime: "audio/wav" });
await t.attachVideo("https://cdn.example.com/recording.mp4");
await t.attachFile("./report.pdf");
await t.attachSensor(frameBuffer, { modality: "depth-camera" });

Provider integrations

import OpenAI from "openai";
import { wrapOpenAI } from "@theta-lab/obsrv/openai";

const oai = wrapOpenAI(new OpenAI(), { client });
await oai.chat.completions.create({ /* ... */ });

Reading data back

const traces = await client.listTraces({
  status: ["error"],
  metadata: [{ key: "release", value: "v3.2.1" }],
  limit: 50,
});

for (const trace of traces.data) {
  console.log(trace.trace_id, trace.status, trace.latency_ms);
}

const detail = await client.getTrace("tr_…");
console.log(detail?.name, detail?.steps.length);

Reference

  • new TraceClient({ apiKey, project?, baseUrl?, flushInterval?, maxBatch? })
  • client.trace({ name, runType, userId, metadata }, async (t) => {...})
  • client.wrapAgent(name, fn)
  • t.step({ name, type, model, metadata }, async (s) => {...})
  • s.logMessage({ role, text, images?, audio?, video?, attachments?, toolCalls? })
  • s.logToolCall({ name, arguments, result?, error?, latencyMs? })
  • s.setTokenUsage({ input, output, total? })
  • t.setMetadata(object) and t.setMetadataPath("dot.path", value)
  • client.recordMetric(name, traceId, { passed | score | label })
  • client.listTraces(filters) and client.getTrace(traceId)
  • client.flush() and client.shutdown()