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

@tansohq/observe

v0.1.0

Published

Zero-latency AI cost observability SDK

Readme

@tansohq/observe

AI cost observability SDK. Track every LLM call with 3 lines of code.

Install

npm install @tansohq/observe

Quickstart (recommended)

import { Observe } from '@tansohq/observe'
import OpenAI from 'openai'

// 1. Configure once at startup
Observe.configure({ apiKey: 'obs_your_api_key' })

// 2. Identify customer once on login
Observe.identify({ customerId: 'cus_123' })

// 3. Wrap your client -- all calls auto-tracked
const openai = Observe.wrap(new OpenAI())

const response = await openai.chat.completions.create({
  model: 'gpt-4o',
  messages: [{ role: 'user', content: 'Hello' }],
})
// Cost, model, tokens, customer, and feature tracked automatically.

How it works

Observe.wrap() sets your client's baseURL to the Observe proxy and injects tracking headers. Your OpenAI/Anthropic API key still authenticates with the provider -- Observe just logs the call on the way through.

API

| Method | Description | |---|---| | Observe.configure({ apiKey, baseUrl? }) | Set API key. Call once at startup. baseUrl defaults to https://app.tanso.io | | Observe.identify({ customerId, name?, email? }) | Set customer context globally. Call once on login | | Observe.feature(featureKey) | Set default feature attribution | | Observe.wrap(client, overrides?) | Wrap an OpenAI or Anthropic client. Returns the same instance | | Observe.agent(agentId) | Set the current agent context for multi-agent tracking. Attaches agentId to all subsequent events |

Per-call overrides use the client's native options:

await openai.chat.completions.create(
  { model: 'gpt-4o', messages },
  { headers: { 'x-tanso-feature': 'export_report' } }
)

Self-hosted

Observe.configure({
  apiKey: 'obs_your_api_key',
  baseUrl: 'https://your-instance.example.com',
})

Multi-agent tracking (A2A)

Use Observe.agent() to attribute costs to individual agents in a multi-agent system:

import { Observe } from '@tansohq/observe'
import OpenAI from 'openai'

Observe.configure({ apiKey: 'obs_your_api_key' })
Observe.identify({ customerId: 'cus_123' })

// Research agent
Observe.agent('research-agent')
const researchClient = Observe.wrap(new OpenAI())
await researchClient.chat.completions.create({
  model: 'gpt-4o',
  messages: [{ role: 'user', content: 'Find recent papers on X' }],
})

// Summarization agent
Observe.agent('summarization-agent')
const summaryClient = Observe.wrap(new OpenAI())
await summaryClient.chat.completions.create({
  model: 'gpt-4o-mini',
  messages: [{ role: 'user', content: 'Summarize these findings' }],
})

// Each call is tagged with its agentId, so you can see cost per agent
// in the AI model breakdown dashboard.

Alternative: Direct proxy (no SDK)

Set your OpenAI/Anthropic base URL to Observe and add one header:

from openai import OpenAI

client = OpenAI(
    api_key="sk-...",
    base_url="https://app.tanso.io/v1",
    default_headers={
        "x-tanso-key": "obs_your_api_key",
        "x-tanso-customer": user.stripe_id,
        "x-tanso-feature": "ai_chat",
    },
)

Alternative: Manual event tracking

For non-OpenAI/Anthropic providers or custom cost attribution:

import { TansoObserve } from '@tansohq/observe';

const observe = new TansoObserve({ apiKey: 'your-api-key' });

observe.track({
  eventName: 'llm.chat',
  customerReferenceId: 'user-123',
  featureKey: 'ai-assistant',
  model: 'gpt-4o',
  usageUnits: 1500,
  costAmount: 0.03,
});

// Before process exit or serverless teardown:
await observe.shutdown();

Architecture

The TansoObserve client uses an internal BatchQueue. When you call track(), the event is pushed into an in-memory queue. The queue is flushed automatically on a timer (default: every 5 seconds) or when the batch reaches its max size (default: 100 events). Flushes send events via POST /events/ingest with Bearer token auth.

Failed sends are retried up to 3 times with exponential backoff (1s, 2s, 4s). After all retries are exhausted, the error is passed to the onError callback if one was provided. If no callback is set, the error is silently dropped.

Client Options

const observe = new TansoObserve({
  apiKey: 'your-api-key',           // required -- SDK API key from Data Sources page
  baseUrl: 'https://app.tanso.io',  // default; override for self-hosted
  flushIntervalMs: 5000,            // default: 5 seconds
  maxBatchSize: 100,                // default: 100 events per batch
  onError: (err) => {               // called after retries exhausted
    console.error('Observe send failed:', err);
  },
});

| Option | Type | Default | Description | |--------|------|---------|-------------| | apiKey | string | required | Your SDK API key from the Data Sources page | | baseUrl | string | https://app.tanso.io | API endpoint | | flushIntervalMs | number | 5000 | Auto-flush interval in ms | | maxBatchSize | number | 100 | Max events per batch | | onError | (err: Error) => void | - | Error callback; errors are silent by default |

Event Schema Reference

Every event requires eventName, customerReferenceId, and featureKey. All other fields are optional.

interface ObserveEvent {
  eventName: string;           // required
  customerReferenceId: string; // required
  featureKey: string;          // required
  timestamp?: string;
  costAmount?: number;
  costUnit?: string;
  revenueAmount?: number;
  usageUnits?: number;
  model?: string;
  modelProvider?: string;      // auto-inferred from model name if not provided
  properties?: Record<string, unknown>;
  idempotencyKey?: string;
}
  • model -- the LLM model name (e.g. gpt-4o, claude-sonnet-4-20250514). If provided, modelProvider is auto-detected.
  • modelProvider -- explicitly set the provider. Overrides auto-detection.
  • costAmount / costUnit -- the cost of this event in your chosen unit.
  • revenueAmount -- revenue attributed to this event.
  • usageUnits -- token count or other usage metric.
  • idempotencyKey -- deduplicate events on the server side.
  • properties -- arbitrary key-value metadata attached to the event.

OpenAI Wrapper

wrapOpenAI uses a Proxy to intercept client.chat.completions.create calls and automatically track them.

import OpenAI from 'openai';
import { TansoObserve } from '@tansohq/observe';
import { wrapOpenAI } from '@tansohq/observe/openai';

const observe = new TansoObserve({ apiKey: 'your-api-key' });

const client = wrapOpenAI(new OpenAI(), observe, {
  customerReferenceId: 'user-123',
  featureKey: 'chat',
});

// All calls are now automatically tracked
const response = await client.chat.completions.create({
  model: 'gpt-4o',
  messages: [{ role: 'user', content: 'Hello' }],
});

How it works:

  • Non-streaming responses are tracked immediately after completion.
  • Streaming responses wrap the async iterator to capture usage from the final chunk.
  • Cost is automatically calculated from a built-in pricing table (OPENAI_PRICING).
  • Events are emitted with eventName: "llm.chat.completion" and modelProvider: "openai".
  • Tracking failures never break the caller -- a try/catch around tracking ensures your app is unaffected.

Anthropic Wrapper

wrapAnthropic uses a Proxy to intercept client.messages.create calls and automatically track them.

import Anthropic from '@anthropic-ai/sdk';
import { TansoObserve } from '@tansohq/observe';
import { wrapAnthropic } from '@tansohq/observe/anthropic';

const observe = new TansoObserve({ apiKey: 'your-api-key' });

const client = wrapAnthropic(new Anthropic(), observe, {
  customerReferenceId: 'user-123',
  featureKey: 'chat',
});

const response = await client.messages.create({
  model: 'claude-sonnet-4-20250514',
  max_tokens: 1024,
  messages: [{ role: 'user', content: 'Hello' }],
});

How it works:

  • Non-streaming responses are tracked after completion.
  • Events are emitted with eventName: "llm.messages.create" and modelProvider: "anthropic".
  • Tracking failures never break the caller.

Model Provider Auto-Detection

When you set a model name on an event, the SDK automatically infers modelProvider using prefix matching. You can override this by setting modelProvider explicitly.

| Model prefix | Detected provider | |---|---| | claude-* | anthropic | | gpt-*, dall-e-*, text-embedding-* | openai | | gemini-* | google | | mistral-*, mixtral-* | mistral | | llama-* | meta | | command-* | cohere |

Lifecycle Management

  • track(event) -- Fire-and-forget. Queues an event in memory. Never throws.
  • flush() -- Triggers an immediate send of all queued events. Non-blocking.
  • shutdown() -- Clears the flush interval timer and flushes remaining events. Returns a Promise. Call this before process exit, in serverless teardown, or in SIGTERM handlers.
// Express shutdown example
process.on('SIGTERM', async () => {
  await observe.shutdown();
  process.exit(0);
});

Error Handling and Retries

The SDK is designed to never affect your application's reliability:

  • track() never throws. Events are queued in memory regardless of network state.
  • When a flush fails, it is retried up to 3 times with exponential backoff (1s, 2s, 4s).
  • After all retries are exhausted, the error is passed to onError if provided. Otherwise, it is silently dropped.
const observe = new TansoObserve({
  apiKey: 'your-api-key',
  onError: (err) => {
    // Log to your error tracking service
    Sentry.captureException(err);
  },
});

Self-Hosted

Point the SDK at your own instance:

const observe = new TansoObserve({
  apiKey: 'your-api-key',
  baseUrl: 'https://your-instance.example.com',
});

Debugging Tips

  • Events are queued in memory. If you're not seeing data in the dashboard, check that flush() or shutdown() is being called before the process exits.
  • Set an onError callback to surface send failures. Without it, errors after retries are silently dropped.
  • In serverless environments (Lambda, Vercel Functions), always await observe.shutdown() at the end of the handler. Otherwise the process may exit before the flush completes.
  • Check that your apiKey is a valid SDK key from the Data Sources page.

Known Limitations

  • Anthropic streaming is not tracked. When stream: true is passed to wrapAnthropic, the response is returned as-is without tracking. Non-streaming Anthropic calls are tracked normally.
  • No persistent queue. Events are held in memory only. If the process crashes before a flush, queued events are lost.