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

@oberhahn/sdk

v0.1.0

Published

Thin server-side SDK that wraps native provider SDKs, extracts token usage + attribution, and posts canonical events to OBERHAHN.

Readme

@oberhahn/sdk

A thin, server-side TypeScript SDK that wraps native provider SDKs, extracts token usage + attribution from each call, and posts canonical events to OBERHAHN's POST /v0/events.

Wrap your existing provider client once and keep calling it exactly as before — the SDK captures token usage and attribution from each call and reports it to OBERHAHN in the background. It stays out of the request path: no config to thread through your call sites, no buffering, and failures never break your calls.

Supports OpenAI, Anthropic, AWS Bedrock, Google Gemini, and more — call knownProviders() for the live list (it's the source of truth; new providers are one-file adapters). Provider SDKs are optional peer dependencies — install only what you use.

Install

npm install @oberhahn/sdk

Provider SDKs are optional peer dependencies — install only the ones you use (openai, @anthropic-ai/sdk, @aws-sdk/client-bedrock-runtime, @google/genai).

Usage

import { init, wrap } from "@oberhahn/sdk";
import OpenAI from "openai";

init({ apiKey: process.env.OBERHAHN_API_KEY }); // oak_… 

const openai = wrap(new OpenAI(), {
  metadata: { environment: "production", feature: "support_agent" },
});

await openai.chat.completions.create({
  model: "gpt-4o",
  messages,
  obMetadata: { customer_id: "cust_123", session_id, turn_id },
});

API

  • init(config) — set global config (also read from env). Returns the client.
  • wrap(client, options?) — patch the provider's usage-bearing methods in place and return the same client (typed as itself). The provider is auto-detected from the client when omitted; pass it explicitly via options.provider when the client is ambiguous — e.g. an OpenAI-compatible client pointed at another host: wrap(client, { provider: "openrouter" }).
  • obMetadata (or ob_metadata) on a request → per-call attribution; stripped from the args before the provider call is forwarded.
  • flush() — await every event submitted so far. Optional: submission is immediate and fire-and-forget (nothing sits buffered), so a long-lived server never needs it. Call it in short-lived / serverless contexts (e.g. at the end of a Lambda handler) to drain before the runtime freezes the process.

Submission is fire-and-forget and best-effort — the wrapped request never waits on it, and a delivery failure past maxRetries is dropped (surfaced via onError), never thrown into your call path.

export async function handler(event) {
  const res = await openai.chat.completions.create({ model, messages });
  await flush();   // drain before the platform freezes this invocation
  return res;
}

Config

| Field | Env | Default | Purpose | | -------------- | --------------------- | ------- | -------------------------------------------------------------- | | apiKey | OBERHAHN_API_KEY | — | oak_ key; sent as Authorization: Bearer. | | metadata | — | {} | Global attribution merged into every event. | | throwOnError | — | false | Attribution failures must not break the request path. | | enabled | OBERHAHN_SDK_DISABLED | true | Kill switch (no-op passthrough when off). | | maxRetries | — | 2 | Transient-failure retries (exponential backoff). | | fetch | — | global | Injectable transport (tests / custom). |

Metadata routing

Global (init) + per-client (wrap) + per-call (obMetadata) metadata are merged (later wins). Known keys map to canonical event fields (session_idprovider_session_id, plus task_id, consumer_id, consumer_type); everything else is coerced to strings and placed in custom.

Global — on init, applied to every event:

init({ apiKey: process.env.OBERHAHN_API_KEY, metadata: { environment: "production" } });

Per-client — on wrap, applied to every call through that client:

const openai = wrap(new OpenAI(), { metadata: { feature: "support_agent" } });

Per-call — on the request itself, highest precedence:

await openai.chat.completions.create({
  model: "gpt-4o",
  messages,
  obMetadata: { session_id: "conv_9", customer_id: "cust_123", turn_id: 7 },
});

The three merge into one event. Given the above, the event carries provider_session_id: "conv_9" (session_id is a known key) and custom: { environment: "production", feature: "support_agent", customer_id: "cust_123", turn_id: "7" }.

Integrations

wrap() patches every token-usage-bearing method the provider SDK exposes; every other method on the client is passed through untouched (still fully usable — it just isn't metered, because it emits no usage). So you keep the entire native SDK surface and only the calls that return token usage are reported. Streaming is supported on every provider that streams — usage is read from the final/aggregated chunk(s), so you must fully consume the stream.

Per-call attribution is the same obMetadata key on the request (merged over wrap(client, { metadata }) and init({ metadata })) — except Bedrock, whose send(command) takes a Command instance, so obMetadata goes on the command object (see below).

Compatibility matrix

| Provider | wrap() target | Instrumented methods (streaming ✅) | Cache tokens | | ----------------- | ---------------------------------------- | -------------------------------------------------------------------------------- | -------------- | | OpenAI | new OpenAI() | chat.completions.create ✅ · responses.create ✅ · embeddings.create | read | | Anthropic | new Anthropic() | messages.create ✅ | read + write | | AWS Bedrock | new BedrockRuntimeClient() | send(ConverseCommand) · send(ConverseStreamCommand) ✅ | read + write | | Google Gemini | new GoogleGenAI() | models.generateContent · models.generateContentStream ✅ | read |

Provider is auto-detected from the client, so wrap(new OpenAI()) is enough; pass the slug explicitly (wrap(client, { provider: "openai" })) only to be unambiguous. knownProviders() returns the live list.

OpenAI

npm install openai
import { init, wrap } from "@oberhahn/sdk";
import OpenAI from "openai";

init({ apiKey: process.env.OBERHAHN_API_KEY });
const openai = wrap(new OpenAI());

// Chat Completions
await openai.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: "Hello" }],
  obMetadata: { session_id: "sess_1", customer_id: "cust_123" },
});

const stream = await openai.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: "Hello" }],
  stream: true,
  stream_options: { include_usage: true },
});
for await (const chunk of stream) {
  // …
}

// Responses API and Embeddings are instrumented too.
await openai.responses.create({ model: "gpt-4o", input: "Hello" });
await openai.embeddings.create({ model: "text-embedding-3-small", input: "Hello" });

OpenAI-compatible providers (OpenRouter, Together, Groq, …)

Providers that speak the OpenAI wire protocol are just the OpenAI client pointed at a different baseURL. Wrap that client and name the provider explicitly — auto-detection can't tell them apart from OpenAI:

import { init, wrap } from "@oberhahn/sdk";
import OpenAI from "openai";

init({ apiKey: process.env.OBERHAHN_API_KEY });
const client = wrap(
  new OpenAI({ baseURL: "https://openrouter.ai/api/v1", apiKey: process.env.OPENROUTER_API_KEY }),
  { provider: "openrouter" },
);
await client.chat.completions.create({ model: "openai/gpt-4o", messages: [{ role: "user", content: "Hello" }] });

The explicit slug is recorded on every event as custom.provider (the routing layer), while model_provider stays what the OpenAI adapter reports — so your dashboards can group by routing layer without losing the model vendor. A user obMetadata: { provider: ... } still wins on collision. Any provider-reported cost in the usage block (e.g. OpenRouter's) is forwarded to spend_details.cost; otherwise the server derives cost from tokens.

The recognized slugs are exported as OPENAI_COMPATIBLE_PROVIDERS: openrouter, together, fireworks, groq, deepinfra, perplexity, xai, deepseek, mistral, azure.

Anthropic

npm install @anthropic-ai/sdk
import { init, wrap } from "@oberhahn/sdk";
import Anthropic from "@anthropic-ai/sdk";

init({ apiKey: process.env.OBERHAHN_API_KEY });
const anthropic = wrap(new Anthropic());

await anthropic.messages.create({
  model: "claude-sonnet-4-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Hello" }],
  obMetadata: { session_id: "sess_1" },
});

const stream = await anthropic.messages.create({
  model: "claude-sonnet-4-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Hello" }],
  stream: true,
});
for await (const event of stream) {
  // …
}

Anthropic reports both cache-read and cache-write tokens.

AWS Bedrock

npm install @aws-sdk/client-bedrock-runtime

send(command) takes a Command instance, not a plain body, so attach per-call obMetadata to the command object itself. The SDK reads and removes it before send(), preserving the command instance.

import { init, wrap } from "@oberhahn/sdk";
import {
  BedrockRuntimeClient,
  ConverseCommand,
  ConverseStreamCommand,
} from "@aws-sdk/client-bedrock-runtime";

init({ apiKey: process.env.OBERHAHN_API_KEY });
const client = wrap(new BedrockRuntimeClient({ region: "us-east-1" }));

// Converse
const cmd = new ConverseCommand({
  modelId: "anthropic.claude-3-5-haiku-20241022-v1:0",
  messages: [{ role: "user", content: [{ text: "Hello" }] }],
});
Object.assign(cmd, { obMetadata: { session_id: "sess_1" } });
await client.send(cmd);

const streamCmd = new ConverseStreamCommand({
  modelId: "anthropic.claude-3-5-haiku-20241022-v1:0",
  messages: [{ role: "user", content: [{ text: "Hello" }] }],
});
const res = await client.send(streamCmd);
for await (const event of res.stream) {
  // …
}

Bedrock is a hosting layer, so model_provider is set to the real model vendor parsed from modelId (e.g. anthropic), and custom.provider records that Bedrock routed the call. The optional cross-region inference-profile prefix (us. / eu. / apac. / us-gov.) is stripped for pricing.

Google Gemini

npm install @google/genai
import { init, wrap } from "@oberhahn/sdk";
import { GoogleGenAI } from "@google/genai";

init({ apiKey: process.env.OBERHAHN_API_KEY });
const genai = wrap(new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }));

await genai.models.generateContent({
  model: "gemini-2.0-flash",
  contents: "Hello",
  obMetadata: { session_id: "sess_1" },
});

const stream = await genai.models.generateContentStream({
  model: "gemini-2.0-flash",
  contents: "Hello",
});
for await (const chunk of stream) {
  // …
}

Gemini models are priced under the google vendor, so model_provider is google (the gemini slug is only the SDK/registry identity).

License

MIT — see LICENSE.