npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@elsium-ai/observe

v0.7.0

Published

Observability, tracing, and cost tracking for ElsiumAI

Downloads

1,465

Readme

@elsium-ai/observe

Observability, tracing, cost intelligence, metrics, audit trails, provenance tracking, and OpenTelemetry compatibility for ElsiumAI.

npm License: MIT

Install

npm install @elsium-ai/observe @elsium-ai/core

What's Inside

| Category | Exports | Description | |---|---|---| | Spans | createSpan, Span, SpanData, SpanEvent, SpanKind, SpanStatus, SpanHandler | Low-level span creation with nested context propagation | | Cost Engine | createCostEngine, registerModelTier, CostEngine, CostEngineConfig, BudgetConfig, LoopDetectionConfig, CostAlert, CostDimension, CostIntelligenceReport, ModelSuggestion, ModelTierEntry | Budget enforcement, cost projections, loop detection, and model optimization suggestions | | Tracer | observe, Tracer, TracerConfig, TracerOutput, TracerExporter, CostReport | High-level tracing with sampling, console output, and custom exporters | | Metrics | createMetrics, MetricsCollector, MetricEntry | Counters, gauges, and histograms for application-level metrics | | Audit Trail | createAuditTrail, auditMiddleware, AuditEventType, AuditEvent, AuditStorageAdapter, AuditQueryFilter, AuditIntegrityResult, AuditTrailConfig, AuditTrail | SHA-256 hash-chained audit events with tamper detection and middleware | | Provenance | createProvenanceTracker, ProvenanceRecord, ProvenanceTracker | Full lineage tracking per output: prompt, model, config, input, output | | OpenTelemetry | toOTelSpan, toOTelExportRequest, toTraceparent, parseTraceparent, injectTraceContext, extractTraceContext, createOTLPExporter, OTelSpan, OTelSpanKind, OTelStatusCode, OTelAttribute, OTelAttributeValue, OTelEvent, OTelResource, OTelExportRequest, TraceContext, OTLPExporterConfig | W3C Trace Context propagation, OTel span conversion, and OTLP JSON export |


Spans

Low-level building blocks for distributed tracing. Each span records a named operation with timing, metadata, events, and parent-child relationships.

SpanKind

type SpanKind = 'llm' | 'tool' | 'agent' | 'workflow' | 'custom'

SpanStatus

type SpanStatus = 'running' | 'ok' | 'error'

SpanEvent

interface SpanEvent {
  name: string
  timestamp: number
  data?: Record<string, unknown>
}

SpanData

The serializable representation of a span, returned by span.toJSON().

interface SpanData {
  id: string
  traceId: string
  parentId?: string
  name: string
  kind: SpanKind
  status: SpanStatus
  startTime: number
  endTime?: number
  durationMs?: number
  metadata: Record<string, unknown>
  events: SpanEvent[]
}

Span

The live span interface used during operation execution.

interface Span {
  readonly id: string
  readonly traceId: string
  readonly name: string
  readonly kind: SpanKind

  addEvent(name: string, data?: Record<string, unknown>): void
  setMetadata(key: string, value: unknown): void
  end(result?: { status?: SpanStatus; metadata?: Record<string, unknown> }): void
  child(name: string, kind?: SpanKind): Span
  toJSON(): SpanData
}

SpanHandler

Callback invoked when a span ends.

type SpanHandler = (span: SpanData) => void

createSpan()

Creates a new span for tracing an operation.

function createSpan(
  name: string,
  options?: {
    traceId?: string
    parentId?: string
    kind?: SpanKind
    onEnd?: SpanHandler
  },
): Span

| Parameter | Type | Default | Description | |---|---|---|---| | name | string | -- | Human-readable name for the operation | | options.traceId | string | auto-generated | Trace ID to group related spans | | options.parentId | string | undefined | Parent span ID for nesting | | options.kind | SpanKind | 'custom' | Category of work being performed | | options.onEnd | SpanHandler | undefined | Callback fired when span.end() is called |

Returns: Span

import { createSpan } from '@elsium-ai/observe'

const span = createSpan('generate-summary', { kind: 'llm' })
span.setMetadata('model', 'gpt-4o')
span.addEvent('prompt-sent', { tokens: 120 })

// Create a child span for a sub-operation
const child = span.child('embed-context', 'tool')
child.end()

span.end({ status: 'ok', metadata: { outputTokens: 350 } })

console.log(span.toJSON())

Cost Engine

Budget enforcement and cost intelligence for LLM usage. Tracks spend across models, agents, users, and features. Detects runaway loops and suggests cheaper model alternatives.

BudgetConfig

interface BudgetConfig {
  totalBudget?: number
  dailyBudget?: number
  perUser?: number
  perFeature?: number
  perAgent?: number
}

LoopDetectionConfig

interface LoopDetectionConfig {
  maxCallsPerMinute?: number
  maxCostPerMinute?: number
}

CostAlert

interface CostAlert {
  type: 'threshold' | 'loop_detected' | 'budget_exceeded' | 'projection_warning'
  dimension: string
  currentValue: number
  limit: number
  message: string
  timestamp: number
}

CostDimension

Aggregated cost data for a single dimension (model, agent, user, or feature).

interface CostDimension {
  totalCost: number
  totalTokens: number
  callCount: number
  firstCallAt: number
  lastCallAt: number
}

CostIntelligenceReport

Full cost intelligence report with projections and recommendations.

interface CostIntelligenceReport {
  totalSpend: number
  totalTokens: number
  totalCalls: number
  projectedDailySpend: number
  projectedMonthlySpend: number
  byModel: Record<string, CostDimension>
  byAgent: Record<string, CostDimension>
  byUser: Record<string, CostDimension>
  byFeature: Record<string, CostDimension>
  recommendations: string[]
  alerts: CostAlert[]
}

ModelSuggestion

A recommendation to switch to a cheaper model.

interface ModelSuggestion {
  currentModel: string
  suggestedModel: string
  estimatedSavings: number
  reason: string
}

ModelTierEntry

Defines the pricing tier for a model.

interface ModelTierEntry {
  tier: 'low' | 'mid' | 'high'
  costPerMToken: number
}

CostEngineConfig

interface CostEngineConfig {
  totalBudget?: number
  dailyBudget?: number
  perUser?: number
  perFeature?: number
  perAgent?: number
  loopDetection?: LoopDetectionConfig
  onAlert?: (alert: CostAlert) => void
  alertThresholds?: number[]
}

CostEngine

interface CostEngine {
  middleware(): Middleware
  getReport(): CostIntelligenceReport
  suggestModel(currentModel: string, inputTokens: number): ModelSuggestion | null
  trackCall(
    response: LLMResponse,
    dimensions?: { agent?: string; user?: string; feature?: string },
  ): void
  reset(): void
}

createCostEngine()

Creates a cost engine with budget enforcement, alerting, and cost intelligence.

function createCostEngine(config?: CostEngineConfig): CostEngine

| Parameter | Type | Default | Description | |---|---|---|---| | config.totalBudget | number | undefined | Maximum total spend before calls are rejected | | config.dailyBudget | number | undefined | Maximum daily spend rate before alerts fire | | config.perUser | number | undefined | Per-user budget limit | | config.perFeature | number | undefined | Per-feature budget limit | | config.perAgent | number | undefined | Per-agent budget limit | | config.loopDetection | LoopDetectionConfig | undefined | Thresholds for detecting runaway loops | | config.onAlert | (alert: CostAlert) => void | undefined | Callback for every alert | | config.alertThresholds | number[] | undefined | Budget percentage thresholds that trigger alerts (e.g. [0.5, 0.8, 0.95]) |

Returns: CostEngine

import { createCostEngine } from '@elsium-ai/observe'

const engine = createCostEngine({
  totalBudget: 50.0,
  dailyBudget: 5.0,
  perAgent: 10.0,
  loopDetection: { maxCallsPerMinute: 60, maxCostPerMinute: 1.0 },
  alertThresholds: [0.5, 0.8, 0.95],
  onAlert: (alert) => console.warn(alert.message),
})

// Use as middleware in an ElsiumAI gateway
// gateway.use(engine.middleware())

// Or track calls manually
// engine.trackCall(response, { agent: 'summarizer', user: 'user-123' })

// Get a full intelligence report
const report = engine.getReport()
console.log('Projected monthly spend:', report.projectedMonthlySpend)
console.log('Recommendations:', report.recommendations)

// Get model optimization suggestions
const suggestion = engine.suggestModel('claude-opus-4-6', 200)
if (suggestion) {
  console.log(`Switch to ${suggestion.suggestedModel} for ${suggestion.estimatedSavings.toFixed(0)}% savings`)
}

registerModelTier()

Registers a custom model with its pricing tier so the cost engine can track it.

function registerModelTier(model: string, entry: ModelTierEntry): void

| Parameter | Type | Description | |---|---|---| | model | string | The model identifier | | entry | ModelTierEntry | The tier classification and cost per million tokens |

import { registerModelTier } from '@elsium-ai/observe'

registerModelTier('my-custom-model', { tier: 'mid', costPerMToken: 1.5 })

The cost engine ships with built-in tiers for common models including GPT-4o, GPT-4.1, GPT-5, Claude Sonnet 4.6, Claude Opus 4.6, Claude Haiku 4.5, Gemini 2.0 Flash, Gemini 2.5 Pro, o1, o3, o4-mini, and more.


Tracer

High-level tracing API that wraps spans with sampling, console output, cost tracking, and pluggable exporters.

TracerOutput

type TracerOutput = 'console' | 'json-file' | TracerExporter

TracerExporter

Interface for custom span exporters.

interface TracerExporter {
  name: string
  export(spans: SpanData[]): void | Promise<void>
}

TracerConfig

interface TracerConfig {
  output?: TracerOutput[]
  costTracking?: boolean
  samplingRate?: number
  maxSpans?: number
}

CostReport

interface CostReport {
  totalCost: number
  totalTokens: number
  totalInputTokens: number
  totalOutputTokens: number
  callCount: number
  byModel: Record<
    string,
    {
      cost: number
      tokens: number
      calls: number
    }
  >
}

Tracer

interface Tracer {
  startSpan(name: string, kind?: SpanKind): Span
  getSpans(): SpanData[]
  getCostReport(): CostReport
  trackLLMCall(data: {
    model: string
    inputTokens: number
    outputTokens: number
    cost: number
    latencyMs: number
  }): void
  reset(): void
  flush(): Promise<void>
}

observe()

Creates a tracer instance for recording spans and LLM call costs.

function observe(config?: TracerConfig): Tracer

| Parameter | Type | Default | Description | |---|---|---|---| | config.output | TracerOutput[] | ['console'] | Where to send completed spans | | config.costTracking | boolean | true | Whether trackLLMCall records data | | config.samplingRate | number | 1.0 | Fraction of spans to sample (0.0 to 1.0) | | config.maxSpans | number | 10000 | Maximum spans held in memory before oldest are evicted |

Returns: Tracer

import { observe } from '@elsium-ai/observe'

const tracer = observe({
  output: ['console'],
  samplingRate: 1.0,
  costTracking: true,
})

// Start a span
const span = tracer.startSpan('chat-completion', 'llm')
span.setMetadata('model', 'gpt-4o')
span.end()

// Track an LLM call for cost reporting
tracer.trackLLMCall({
  model: 'gpt-4o',
  inputTokens: 500,
  outputTokens: 200,
  cost: 0.0035,
  latencyMs: 1200,
})

// Get cost report
const report = tracer.getCostReport()
console.log('Total cost:', report.totalCost)

// Flush spans to all exporters
await tracer.flush()

You can provide a custom exporter:

import { observe } from '@elsium-ai/observe'
import type { TracerExporter } from '@elsium-ai/observe'

const myExporter: TracerExporter = {
  name: 'my-backend',
  async export(spans) {
    await fetch('https://my-telemetry.example.com/spans', {
      method: 'POST',
      body: JSON.stringify(spans),
    })
  },
}

const tracer = observe({ output: [myExporter] })

Metrics

General-purpose metrics collection with counters, gauges, and histograms.

MetricEntry

interface MetricEntry {
  name: string
  type: 'counter' | 'gauge' | 'histogram'
  value: number
  tags: Record<string, string>
  timestamp: number
}

MetricsCollector

interface MetricsCollector {
  increment(name: string, value?: number, tags?: Record<string, string>): void
  gauge(name: string, value: number, tags?: Record<string, string>): void
  histogram(name: string, value: number, tags?: Record<string, string>): void
  getMetrics(): MetricEntry[]
  reset(): void
}

createMetrics()

Creates a metrics collector for counters, gauges, and histograms.

function createMetrics(options?: { maxEntries?: number }): MetricsCollector

| Parameter | Type | Default | Description | |---|---|---|---| | options.maxEntries | number | 50000 | Maximum metric entries held in memory |

Returns: MetricsCollector

import { createMetrics } from '@elsium-ai/observe'

const metrics = createMetrics()

// Increment a counter (default increment is 1)
metrics.increment('llm.calls', 1, { model: 'gpt-4o' })

// Set a gauge to a current value
metrics.gauge('queue.depth', 42, { queue: 'embeddings' })

// Record a histogram observation
metrics.histogram('llm.latency_ms', 1200, { model: 'gpt-4o' })

// Retrieve all recorded entries
const entries = metrics.getMetrics()
console.log(`Recorded ${entries.length} metric entries`)

// Reset all state
metrics.reset()

Audit Trail

Tamper-evident audit logging with SHA-256 hash-chaining. Every event is linked to the previous one via its hash, enabling integrity verification of the full event history.

AuditEventType

type AuditEventType =
  | 'llm_call'
  | 'tool_execution'
  | 'security_violation'
  | 'budget_alert'
  | 'policy_violation'
  | 'auth_event'
  | 'approval_request'
  | 'approval_decision'
  | 'config_change'
  | 'provider_failover'
  | 'circuit_breaker_state_change'

AuditEvent

interface AuditEvent {
  id: string
  sequenceId: number
  type: AuditEventType
  timestamp: number
  actor?: string
  traceId?: string
  data: Record<string, unknown>
  hash: string
  previousHash: string
}

AuditStorageAdapter

Interface for custom audit storage backends (database, file system, etc.).

interface AuditStorageAdapter {
  append(event: AuditEvent): void | Promise<void>
  query(filter: AuditQueryFilter): AuditEvent[] | Promise<AuditEvent[]>
  count(): number | Promise<number>
  verifyIntegrity(): AuditIntegrityResult | Promise<AuditIntegrityResult>
  getLastHash?(): string | Promise<string>
}

AuditQueryFilter

interface AuditQueryFilter {
  type?: AuditEventType | AuditEventType[]
  actor?: string
  traceId?: string
  fromTimestamp?: number
  toTimestamp?: number
  limit?: number
  offset?: number
}

AuditIntegrityResult

interface AuditIntegrityResult {
  valid: boolean
  totalEvents: number
  brokenAt?: number
  chainComplete?: boolean
}

AuditBatchConfig

interface AuditBatchConfig {
  size?: number
  intervalMs?: number
}

| Field | Type | Default | Description | |---|---|---|---| | size | number | 100 | Flush the buffer when it reaches this many events. | | intervalMs | number | 50 | Flush the buffer on this interval (ms), regardless of size. |

AuditTrailConfig

interface AuditTrailConfig {
  storage?: AuditStorageAdapter | 'memory'
  hashChain?: boolean
  maxEvents?: number
  batch?: AuditBatchConfig
  onError?: (error: unknown) => void
}

AuditTrail

interface AuditTrail {
  log(
    type: AuditEventType,
    data: Record<string, unknown>,
    options?: { actor?: string; traceId?: string },
  ): void
  ready(): Promise<void>
  query(filter: AuditQueryFilter): Promise<AuditEvent[]>
  verifyIntegrity(): Promise<AuditIntegrityResult>
  flush(): Promise<void>
  dispose(): void
  readonly count: number
  readonly pending: number
}

| Member | Description | |---|---| | log() | Append an event. In batched mode, this buffers the event without hashing — zero CPU on the hot path. | | ready() | Resolves once async initialization (e.g. getLastHash) has completed. | | query() | Query events by type, actor, traceId, or timestamp range. Auto-flushes pending events in batched mode. | | verifyIntegrity() | Verify the hash chain has not been tampered with. Auto-flushes pending events in batched mode. | | flush() | Drain all pending events — computes hashes and writes to storage. No-op when not in batched mode. | | dispose() | Stop the flush timer and drain remaining events. Call this on shutdown. | | count | Total events (stored + pending). | | pending | Number of buffered events not yet flushed (0 when not in batched mode). |

createAuditTrail()

Creates a hash-chained audit trail for tamper-evident event logging.

function createAuditTrail(config?: AuditTrailConfig): AuditTrail

| Parameter | Type | Default | Description | |---|---|---|---| | config.storage | AuditStorageAdapter \| 'memory' | 'memory' | Storage backend for audit events | | config.hashChain | boolean | true | Enable SHA-256 hash chaining for tamper detection | | config.maxEvents | number | 10000 | Maximum events retained (ring buffer — O(1) eviction) | | config.batch | AuditBatchConfig | undefined | Enable batched mode for high-volume scenarios | | config.onError | (error: unknown) => void | undefined | Error handler for async storage failures |

Returns: AuditTrail

import { createAuditTrail } from '@elsium-ai/observe'

const audit = createAuditTrail({ hashChain: true })

// Log events
audit.log('llm_call', {
  model: 'gpt-4o',
  inputTokens: 500,
  outputTokens: 200,
  cost: 0.0035,
}, { actor: 'user-123', traceId: 'trc_abc' })

audit.log('tool_execution', {
  tool: 'web-search',
  query: 'latest news',
  resultCount: 10,
})

// Query events
const llmEvents = await audit.query({ type: 'llm_call', limit: 50 })
console.log(`Found ${llmEvents.length} LLM call events`)

// Verify the hash chain has not been tampered with
const integrity = await audit.verifyIntegrity()
console.log('Audit chain valid:', integrity.valid)
console.log('Total events:', integrity.totalEvents)

Batched mode (high-volume)

In high-volume scenarios, log() computes a SHA-256 hash on every call — blocking the hot path. Batched mode moves hashing off the critical path: log() buffers raw event data, and hashing + storage writes happen asynchronously on flush.

import { createAuditTrail } from '@elsium-ai/observe'

const audit = createAuditTrail({
  batch: {
    size: 500,         // Flush after 500 events
    intervalMs: 100,   // Or every 100ms, whichever comes first
  },
  maxEvents: 100_000,
})

// log() is now near-zero cost — just pushes to an internal buffer
audit.log('llm_call', { model: 'gpt-4o', tokens: 100 })

// Force-flush before reading (query/verifyIntegrity auto-flush)
await audit.flush()

// Clean up on shutdown
process.on('SIGTERM', () => audit.dispose())

Hash chain integrity is fully preserved — events are hashed sequentially during flush, not during log().

auditMiddleware()

Creates an ElsiumAI middleware that automatically logs every LLM call (success or failure) to the given audit trail.

function auditMiddleware(auditTrail: AuditTrail): Middleware

| Parameter | Type | Description | |---|---|---| | auditTrail | AuditTrail | The audit trail instance to log events to |

Returns: Middleware (from @elsium-ai/core)

import { createAuditTrail, auditMiddleware } from '@elsium-ai/observe'

const audit = createAuditTrail({ hashChain: true })
const middleware = auditMiddleware(audit)

// Use with an ElsiumAI gateway
// gateway.use(middleware)

The middleware automatically records llm_call events containing provider, model, inputTokens, outputTokens, totalTokens, cost, latencyMs, and stopReason. On errors, it records the error message and success: false.


Provenance

Tracks the full lineage of every AI-generated output by hashing the prompt, model, config, input, and output. Enables reproducibility audits and output-to-source tracing.

ProvenanceRecord

interface ProvenanceRecord {
  id: string
  outputHash: string
  promptVersion: string
  modelVersion: string
  configHash: string
  inputHash: string
  timestamp: number
  traceId?: string
  metadata?: Record<string, unknown>
}

All hash fields (outputHash, promptVersion, modelVersion, configHash, inputHash) are SHA-256 hex digests of their respective inputs.

ProvenanceTracker

interface ProvenanceTracker {
  record(data: {
    prompt: string
    model: string
    config: Record<string, unknown>
    input: string
    output: string
    traceId?: string
    metadata?: Record<string, unknown>
  }): ProvenanceRecord
  query(filter: {
    outputHash?: string
    promptVersion?: string
    modelVersion?: string
    traceId?: string
  }): ProvenanceRecord[]
  getLineage(outputHash: string): ProvenanceRecord[]
  readonly count: number
  clear(): void
}

createProvenanceTracker()

Creates a provenance tracker for recording and querying output lineage.

function createProvenanceTracker(options?: {
  maxRecords?: number
}): ProvenanceTracker

| Parameter | Type | Default | Description | |---|---|---|---| | options.maxRecords | number | 10000 | Maximum records held in memory |

Returns: ProvenanceTracker

import { createProvenanceTracker } from '@elsium-ai/observe'

const provenance = createProvenanceTracker()

// Record a generation
const record = provenance.record({
  prompt: 'Summarize the following article...',
  model: 'gpt-4o',
  config: { temperature: 0.7, maxTokens: 500 },
  input: 'The article text here...',
  output: 'A concise summary of the article.',
  traceId: 'trc_abc',
})

console.log('Output hash:', record.outputHash)

// Query records by output hash
const matches = provenance.query({ outputHash: record.outputHash })

// Get the full lineage for a given output (all records sharing the same traceId)
const lineage = provenance.getLineage(record.outputHash)
console.log(`Lineage has ${lineage.length} steps`)

// Check count and clear
console.log('Total records:', provenance.count)
provenance.clear()

OpenTelemetry

Compatibility layer for converting ElsiumAI spans to the OpenTelemetry format, propagating W3C Trace Context headers, and exporting via OTLP JSON to any OTel-compatible backend (Jaeger, Grafana Tempo, Datadog, Honeycomb, etc.).

OTelSpanKind

type OTelSpanKind = 0 | 1 | 2 | 3 | 4 | 5
// 0 = UNSPECIFIED, 1 = INTERNAL, 2 = SERVER, 3 = CLIENT, 4 = PRODUCER, 5 = CONSUMER

OTelStatusCode

type OTelStatusCode = 0 | 1 | 2
// 0 = UNSET, 1 = OK, 2 = ERROR

OTelAttributeValue

interface OTelAttributeValue {
  stringValue?: string
  intValue?: number
  doubleValue?: number
  boolValue?: boolean
  arrayValue?: { values: OTelAttributeValue[] }
}

OTelAttribute

interface OTelAttribute {
  key: string
  value: OTelAttributeValue
}

OTelEvent

interface OTelEvent {
  name: string
  timeUnixNano: string
  attributes: OTelAttribute[]
}

OTelSpan

The OpenTelemetry-compatible span representation.

interface OTelSpan {
  traceId: string
  spanId: string
  parentSpanId?: string
  name: string
  kind: OTelSpanKind
  startTimeUnixNano: string
  endTimeUnixNano: string
  attributes: OTelAttribute[]
  events: OTelEvent[]
  status: {
    code: OTelStatusCode
    message?: string
  }
}

OTelResource

interface OTelResource {
  attributes: OTelAttribute[]
}

OTelExportRequest

The full OTLP JSON export payload structure.

interface OTelExportRequest {
  resourceSpans: Array<{
    resource: OTelResource
    scopeSpans: Array<{
      scope: {
        name: string
        version: string
      }
      spans: OTelSpan[]
    }>
  }>
}

TraceContext

Parsed W3C Trace Context.

interface TraceContext {
  traceId: string
  spanId: string
  traceFlags: number
  traceState?: string
}

OTLPExporterConfig

interface OTLPExporterConfig {
  /** OTLP endpoint URL (e.g. http://localhost:4318/v1/traces) */
  endpoint: string
  /** Optional headers (e.g. for auth) */
  headers?: Record<string, string>
  /** Service name for resource attributes */
  serviceName?: string
  /** Service version */
  serviceVersion?: string
  /** Batch size before sending */
  batchSize?: number
  /** Flush interval in ms */
  flushIntervalMs?: number
}

toOTelSpan()

Converts an ElsiumAI SpanData to an OpenTelemetry-compatible span.

function toOTelSpan(span: SpanData): OTelSpan

| Parameter | Type | Description | |---|---|---| | span | SpanData | The ElsiumAI span to convert |

Returns: OTelSpan

import { createSpan, toOTelSpan } from '@elsium-ai/observe'

const span = createSpan('my-operation', { kind: 'llm' })
span.end()

const otelSpan = toOTelSpan(span.toJSON())
console.log(otelSpan.traceId, otelSpan.spanId)

ElsiumAI span kinds are mapped as follows: llm to CLIENT (3), tool/agent/workflow to INTERNAL (1), custom to UNSPECIFIED (0).

toOTelExportRequest()

Builds a full OTLP JSON export request from a batch of spans.

function toOTelExportRequest(
  spans: SpanData[],
  options?: {
    serviceName?: string
    serviceVersion?: string
  },
): OTelExportRequest

| Parameter | Type | Default | Description | |---|---|---|---| | spans | SpanData[] | -- | Batch of spans to export | | options.serviceName | string | 'elsium-ai' | Service name in resource attributes | | options.serviceVersion | string | '0.1.0' | Service version in resource attributes |

Returns: OTelExportRequest

import { observe, toOTelExportRequest } from '@elsium-ai/observe'

const tracer = observe({ output: [] })
const span = tracer.startSpan('process-request', 'agent')
span.end()

const payload = toOTelExportRequest(tracer.getSpans(), {
  serviceName: 'my-ai-service',
  serviceVersion: '1.0.0',
})

// Send to any OTLP-compatible endpoint
await fetch('http://localhost:4318/v1/traces', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
})

toTraceparent()

Creates a W3C traceparent header value from a span.

function toTraceparent(span: SpanData): string

| Parameter | Type | Description | |---|---|---| | span | SpanData | The span to derive the traceparent from |

Returns: string -- Format: 00-{traceId}-{spanId}-01

import { createSpan, toTraceparent } from '@elsium-ai/observe'

const span = createSpan('outgoing-call', { kind: 'llm' })
span.end()

const header = toTraceparent(span.toJSON())
// e.g. "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"

parseTraceparent()

Parses a W3C traceparent header string into a TraceContext.

function parseTraceparent(header: string): TraceContext | null

| Parameter | Type | Description | |---|---|---| | header | string | The traceparent header value |

Returns: TraceContext | null -- Returns null if the header is malformed or uses an unsupported version.

import { parseTraceparent } from '@elsium-ai/observe'

const ctx = parseTraceparent('00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01')
if (ctx) {
  console.log('Trace ID:', ctx.traceId)
  console.log('Span ID:', ctx.spanId)
  console.log('Sampled:', ctx.traceFlags === 1)
}

injectTraceContext()

Injects the traceparent header into an outgoing HTTP headers object for distributed trace propagation.

function injectTraceContext(
  span: SpanData,
  headers?: Record<string, string>,
): Record<string, string>

| Parameter | Type | Default | Description | |---|---|---|---| | span | SpanData | -- | The span whose trace context to inject | | headers | Record<string, string> | {} | Existing headers to merge with |

Returns: Record<string, string> -- A new headers object with traceparent set.

import { createSpan, injectTraceContext } from '@elsium-ai/observe'

const span = createSpan('api-call', { kind: 'llm' })
const headers = injectTraceContext(span.toJSON(), {
  'Content-Type': 'application/json',
  Authorization: 'Bearer token',
})
// headers now includes { traceparent: '00-...-...-01', ... }

await fetch('https://api.example.com/generate', { headers })
span.end()

extractTraceContext()

Extracts a TraceContext from incoming HTTP headers. Checks both traceparent and Traceparent keys.

function extractTraceContext(
  headers: Record<string, string | undefined>,
): TraceContext | null

| Parameter | Type | Description | |---|---|---| | headers | Record<string, string \| undefined> | Incoming HTTP headers |

Returns: TraceContext | null

import { extractTraceContext, createSpan } from '@elsium-ai/observe'

// In an HTTP handler
function handleRequest(req: { headers: Record<string, string> }) {
  const parentCtx = extractTraceContext(req.headers)

  const span = createSpan('handle-request', {
    traceId: parentCtx?.traceId,
    parentId: parentCtx?.spanId,
    kind: 'agent',
  })

  // ... process request ...
  span.end()
}

createOTLPExporter()

Creates an OTLP JSON exporter that sends spans to any OTel-compatible backend. Supports batching and automatic periodic flushing.

function createOTLPExporter(config: OTLPExporterConfig): TracerExporter

| Parameter | Type | Default | Description | |---|---|---|---| | config.endpoint | string | -- | OTLP endpoint URL (e.g. http://localhost:4318/v1/traces) | | config.headers | Record<string, string> | {} | Optional HTTP headers (e.g. for authentication) | | config.serviceName | string | undefined | Service name for resource attributes | | config.serviceVersion | string | undefined | Service version for resource attributes | | config.batchSize | number | 100 | Number of spans to buffer before sending a batch | | config.flushIntervalMs | number | 5000 | Automatic flush interval in milliseconds |

Returns: TracerExporter

import { observe, createOTLPExporter } from '@elsium-ai/observe'

const exporter = createOTLPExporter({
  endpoint: 'http://localhost:4318/v1/traces',
  serviceName: 'my-ai-service',
  serviceVersion: '1.0.0',
  headers: { Authorization: 'Bearer my-token' },
  batchSize: 50,
  flushIntervalMs: 10000,
})

const tracer = observe({ output: [exporter] })

const span = tracer.startSpan('generate', 'llm')
span.end()

// Spans are batched and sent automatically, or flush manually:
await tracer.flush()

Experiments Persistence

createFileExperimentStore

Creates a file-based storage adapter for saving and loading experiment results to disk. Experiment data is serialized as JSON files in the specified directory.

function createFileExperimentStore(dir: string): ExperimentStore

| Parameter | Type | Description | |---|---|---| | dir | string | Directory path where experiment result files will be stored |

Returns: ExperimentStore

interface ExperimentStore {
  save(experiment: ExperimentResults): Promise<void>
  load(experimentId: string): Promise<ExperimentResults | null>
  list(): Promise<string[]>
}
import { createExperiment, createFileExperimentStore } from 'elsium-ai/observe'

const store = createFileExperimentStore('./experiments')

const experiment = createExperiment({
  name: 'prompt-comparison',
  variants: [
    { name: 'concise', config: { system: 'Be brief.' } },
    { name: 'detailed', config: { system: 'Be thorough.' } },
  ],
})

const results = await experiment.run(evaluator)

// Persist results to disk
await store.save(results)

// Load results later
const loaded = await store.load(results.id)

Auto-Instrumentation

instrumentComplete

Wraps an LLM completion function with automatic span creation. Every call produces a span with model, token, cost, and latency metadata.

function instrumentComplete(
  complete: (request: CompletionRequest) => Promise<LLMResponse>,
  tracer: Tracer,
): (request: CompletionRequest) => Promise<LLMResponse>

| Parameter | Type | Description | |---|---|---| | complete | (request: CompletionRequest) => Promise<LLMResponse> | The LLM completion function to instrument | | tracer | Tracer | The tracer instance to record spans to |

Returns: A wrapped completion function with the same signature.

import { observe, instrumentComplete } from 'elsium-ai/observe'

const tracer = observe()

const tracedComplete = instrumentComplete(
  (req) => llm.complete(req),
  tracer,
)

// Every call now creates an 'llm' span automatically
const response = await tracedComplete({ model: 'gpt-4o', messages })

instrumentAgent

Wraps an agent's run method with automatic span creation. Produces an agent span that captures the agent name, input, output, token usage, and tool calls.

function instrumentAgent(
  agent: Agent,
  tracer: Tracer,
): Agent

| Parameter | Type | Description | |---|---|---| | agent | Agent | The agent to instrument | | tracer | Tracer | The tracer instance to record spans to |

Returns: A new Agent with the same interface, where run and chat are automatically traced.

import { observe, instrumentAgent } from 'elsium-ai/observe'
import { defineAgent } from 'elsium-ai/agents'

const tracer = observe()

const agent = defineAgent(
  { name: 'assistant', system: 'You are helpful.' },
  { complete: (req) => llm.complete(req) },
)

const tracedAgent = instrumentAgent(agent, tracer)

// Every run/chat call now creates an 'agent' span automatically
const result = await tracedAgent.run('Hello')

Part of ElsiumAI

This package is the observability layer of the ElsiumAI framework. See the full documentation for guides and examples.

License

MIT