TypeScript SDK

Zero-dependency TypeScript SDK for Kalibr LLM observability. Works with Next.js, Node.js, and Edge runtimes.

Package: @kalibr/sdk
Version: 1.0.0
Node.js: >=18.0.0

Note: Unlike the Python SDK, the TypeScript SDK does not have auto-instrumentation. You must explicitly create spans using SpanBuilder.


Installation

npm install @kalibr/sdk
# or
pnpm add @kalibr/sdk

Quick Start

import { Kalibr, SpanBuilder } from '@kalibr/sdk';
import OpenAI from 'openai';

// 1. Initialize Kalibr once at app startup
Kalibr.init({
  apiKey: process.env.KALIBR_API_KEY!,
  tenantId: process.env.KALIBR_TENANT_ID!,
});

// 2. Create OpenAI client
const openai = new OpenAI();

// 3. Create span before LLM call
const span = new SpanBuilder()
  .setProvider('openai')
  .setModel('gpt-4o')
  .setOperation('chat_completion')
  .start();

// 4. Make LLM call
const response = await openai.chat.completions.create({
  model: 'gpt-4o',
  messages: [{ role: 'user', content: 'Hello!' }],
});

// 5. Finish span with token counts
await span.finish({
  inputTokens: response.usage?.prompt_tokens ?? 0,
  outputTokens: response.usage?.completion_tokens ?? 0,
});

Kalibr.init()

Initialize the singleton client. Call once at application startup:

Kalibr.init({
  apiKey: string,          // Required
  tenantId: string,        // Required
  endpoint?: string,       // Default: https://api.kalibr.systems/api/ingest
  environment?: 'prod' | 'staging' | 'dev',
  service?: string,        // Service name for grouping
  debug?: boolean,         // Enable debug logging
});

Static Methods

Method Description
Kalibr.init(config) Initialize singleton
Kalibr.isInitialized() Check if initialized
Kalibr.getInstance() Get singleton instance
Kalibr.sendSpan(span) Send a span
Kalibr.sendSpans(spans) Send multiple spans

SpanBuilder

Fluent builder for creating spans with automatic timing:

const span = new SpanBuilder()
  // Required
  .setProvider('openai')     // 'openai' | 'anthropic' | 'google' | 'cohere' | 'custom'
  .setModel('gpt-4o')        // Model identifier
  .setOperation('chat')      // Operation name
  
  // Optional - IDs
  .setTraceId('custom-id')   // Default: auto-generated
  .setSpanId('span-id')      // Default: auto-generated
  .setParentSpanId('parent') // For nested spans
  
  // Optional - Context
  .setTenantId('tenant')     // Default: from Kalibr.init()
  .setWorkflowId('workflow-123')
  .setSandboxId('sandbox-456')
  .setRuntimeEnv('vercel_vm')
  .setEndpoint('/v1/chat/completions')
  
  // Optional - User
  .setUserId('user-789')
  .setRequestId('req-abc')
  
  // Optional - Environment
  .setEnvironment('prod')    // Default: from Kalibr.init()
  .setService('my-service')  // Default: from Kalibr.init()
  
  // Optional - Metadata
  .setMetadata({ key: 'value' })
  .setDataClass('economic')  // 'economic' | 'performance' | 'diagnostic'
  
  // Start timing
  .start();

StartedSpan

Returned by SpanBuilder.start():

span.finish(options)

await span.finish({
  inputTokens: number,       // Required
  outputTokens: number,      // Required
  status?: 'success' | 'error' | 'timeout',  // Default: 'success'
  errorType?: string,        // If status is 'error'
  errorMessage?: string,
  stackTrace?: string,
  costUsd?: number,          // Override calculated cost
  metadata?: object,         // Merge with existing metadata
  autoSend?: boolean,        // Default: true
});

span.error(error, options)

try {
  const response = await openai.chat.completions.create({...});
  await span.finish({ inputTokens: 100, outputTokens: 50 });
} catch (error) {
  await span.error(error as Error, {
    inputTokens: 0,
    outputTokens: 0,
  });
  throw error;
}

span.timeout(options)

await span.timeout({ inputTokens: 100, outputTokens: 0 });

Getters

const traceId = span.getTraceId();
const spanId = span.getSpanId();

Helper Functions

createSpan()

Create a complete span manually (when timing is handled externally):

import { createSpan, Kalibr } from '@kalibr/sdk';

const span = createSpan({
  tenantId: 'my-tenant',
  provider: 'openai',
  modelId: 'gpt-4o',
  operation: 'chat_completion',
  durationMs: 250,
  inputTokens: 100,
  outputTokens: 50,
  status: 'success',
  // ... other optional fields
});

await Kalibr.sendSpan(span);

withSpan()

Wrap an async function with automatic span creation:

import { withSpan } from '@kalibr/sdk';

const response = await withSpan(
  {
    provider: 'openai',
    modelId: 'gpt-4o',
    operation: 'chat_completion',
  },
  async () => {
    const res = await openai.chat.completions.create({...});
    return {
      result: res,
      inputTokens: res.usage?.prompt_tokens ?? 0,
      outputTokens: res.usage?.completion_tokens ?? 0,
    };
  }
);

Utility Functions

import { generateId, timestamp, calculateCost } from '@kalibr/sdk';

// Generate unique ID (32 hex chars)
const id = generateId();

// ISO 8601 timestamp
const ts = timestamp();

// Calculate cost in USD
const cost = calculateCost('openai', 'gpt-4o', 1000, 500);

Types

import type {
  KalibrConfig,
  KalibrSpan,
  PartialSpan,
  FinishOptions,
  Provider,      // 'openai' | 'anthropic' | 'google' | 'cohere' | 'custom'
  Status,        // 'success' | 'error' | 'timeout'
  Environment,   // 'prod' | 'staging' | 'dev'
  DataClass,     // 'economic' | 'performance' | 'diagnostic'
} from '@kalibr/sdk';

Next.js Example

// app/api/chat/route.ts
import { Kalibr, SpanBuilder } from '@kalibr/sdk';
import OpenAI from 'openai';

Kalibr.init({
  apiKey: process.env.KALIBR_API_KEY!,
  tenantId: 'my-app',
});

const openai = new OpenAI();

export async function POST(request: Request) {
  const { message } = await request.json();
  
  const span = new SpanBuilder()
    .setProvider('openai')
    .setModel('gpt-4o-mini')
    .setOperation('chat')
    .start();
  
  try {
    const response = await openai.chat.completions.create({
      model: 'gpt-4o-mini',
      messages: [{ role: 'user', content: message }],
    });
    
    await span.finish({
      inputTokens: response.usage?.prompt_tokens ?? 0,
      outputTokens: response.usage?.completion_tokens ?? 0,
    });
    
    return Response.json({ message: response.choices[0].message.content });
  } catch (error) {
    await span.error(error as Error, { inputTokens: 0, outputTokens: 0 });
    throw error;
  }
}

All Exports

Export Description
KalibrMain client class
SpanBuilderFluent span builder
StartedSpanStarted span (from .start())
createSpan()Create complete span manually
withSpan()Wrap async function with span
generateId()Generate unique ID
timestamp()ISO 8601 timestamp
calculateCost()Calculate cost from tokens

Next Steps