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

@keld/keld

v0.5.1

Published

OpenAI-compatible Node client for Keld, OpenAI, Anthropic and Bedrock, with opt-in usage telemetry to a Keld Atlas backend over OTLP logs.

Readme

@keld/keld

An OpenAI-compatible client for Node that reports what your inference costs. Keld subclasses the official openai client — same methods, same types, same responses, reached through openai's own prototypes — and an atlasToken adds usage telemetry to Keld Atlas.

import Keld from "@keld/keld";                    // was: import OpenAI from "openai"

const client = new Keld({                         // was: new OpenAI({ ... })
  baseURL: "https://api-gateway.keld.co",         // or OpenAI's, Anthropic's, Bedrock's
  apiKey: process.env.KELD_TOKEN,
  atlasToken: process.env.ATLAS_INGEST_TOKEN,     // omit -> no telemetry, plain inference client
});

const response = await client.chat.completions.create({ model, messages });

That is the whole integration; no call site changes.

Install

npm install @keld/keld

Node 18 or newer (the SDK uses the global fetch). openai is a dependency, not a peer, and its own root exports (ClientOptions, APIError, APIPromise, toFile, ...) are re-exported from @keld/keld, so the import swap is invisible to TypeScript. Both CJS (dist/index.cjs) and ESM (dist/index.mjs) ship from one root entry with type declarations for each: require and import both work, nothing to configure, no loader flag.

Options

KeldClientOptions extends ClientOptions, so everything the official client takes, this one takes.

| Option | Env fallback | Notes | |---|---|---| | baseURL, apiKey | OPENAI_BASE_URL, OPENAI_API_KEY | openai's. Which endpoint, and your inference provider's credential — this SDK only passes it through | | timeout, maxRetries, defaultHeaders, fetch, ... | openai's own | openai's, unchanged — no request or response handling is overridden | | atlasToken | none, deliberately | Keld Atlas agent key (kagt_...), minted on the Atlas Integrations page. Passing it starts telemetry; omitting it means none at all. Not read from the environment: the option is the opt-in, so a token sitting in a shell cannot silently start reporting | | atlasEndpoint | ATLAS_ENDPOINT | Where telemetry goes. Defaults to https://atlas.keld.co; the SDK POSTs gzipped to {endpoint}/v1/logs | | atlasMetadata | none, deliberately | Record<string, string> of reporting dimensions, on every call this client makes. Each key becomes one attribute named metadata.<key> |

atlasMetadata: { environment: "production", repo: "acme/checkout-service", team: "payments" }

To tag one call, use openai's own withOptions — the tags go on the calls made through that copy, on top of the client's, and a key both set takes the call's value:

await client
  .withOptions({ atlasMetadata: { customer: "acme", feature: "summary" } })
  .chat.completions.create({ model, messages });

Each client reports its own tags, so two clients in one process can be tagged differently. Tags go to Atlas only; the request to the provider is the same with or without them. They reach Atlas as metadata.<key> (metadata.environment, metadata.customer), so no tag can be mistaken for a field the SDK or Atlas owns, and any key is allowed, model included.

A new key needs no SDK release. Atlas does not read the prefixed names yet, so until it does an environment tag is stored but no longer drives Atlas's environment (CapEx/OpEx). An entry that cannot become a well-formed attribute is dropped with a warning rather than thrown — a blank key or a non-string value; an empty value means unset and is omitted silently. See .env.example.

What the SDK does on its own

  • Appends /v1 to a baseURL that carries no path. openai-node appends chat/completions verbatim, so a bare gateway host would 404. A URL that already names a path is left alone.

  • Injects stream_options: { include_usage: true } into streamed chat completions — the one outgoing request it modifies, because OpenAI omits token usage from a stream without it. It merges with any stream_options you set (an explicit include_usage: false is respected), and every chunk, including the usage-only one with empty choices, reaches your code unchanged.

  • Resolves provider from the endpoint's host, so nothing at the call site changes per provider:

    | baseURL | Reported provider | |---|---| | any .keld.co host, e.g. https://api-gateway.keld.co | keld | | omitted (openai's default) | openai | | https://api.anthropic.com/v1/ | anthropic | | https://bedrock-runtime.{region}.amazonaws.com/openai/v1 | bedrock | | OpenRouter, Together, Groq, DeepSeek, xAI, Mistral, Fireworks, Ollama (port 11434) | their own names | | anything else | hosted_vllm, with a one-time warning |

    An unknown host is not guessed and there is no override: a confidently-wrong label prices at $0 while looking like real data, so the warning is the point.

  • Never breaks inference. Recording cannot throw, the queue is bounded at 2048 records and drops the oldest under backpressure, and export is fire-and-forget — Atlas being down, slow or rate-limiting is invisible to your app. The call goes straight to your baseURL.

  • Covers every openai client in the process, because instrumentation is applied to openai's prototypes rather than instances: a plain new OpenAI() from your own import is reported too, and a second new Keld({ atlasToken }) shares one core, so each call is reported once.

  • Flushes every 2s and on beforeExit, so a short-lived script reports instead of exiting with its batch in memory. A process killed before the event loop drains needs flush():

    import { flush } from "@keld/keld";
    process.on("SIGTERM", async () => { await flush(); process.exit(0); });

    The SDK installs no signal handlers of its own — attaching one to SIGTERM/SIGINT suppresses Node's default terminate-on-signal behaviour and would change how your app shuts down.

What gets reported

One OTLP log record per inference call: provider, model, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, duration_ms and request_id, plus session.id, event.sequence and prompt.id identifying the process and the call's place in it. Every value is read from the provider's own response, never inferred. On a chat completion, cache_read_tokens is OpenAI's usage.prompt_tokens_details.cached_tokens, and input_tokens is the uncached rest (prompt_tokens minus it), because Atlas prices the two at different rates. A failed call is still recorded, with zero tokens and an error attribute, before the exception propagates unchanged. Batch-level attributes carry the SDK's name, language and version, and the calling client's atlasMetadata; a request's own tags from withOptions are on its record.

trace.id and span.id are the call's own OpenTelemetry span. If your app runs its own OTel tracing with a context manager registered (the Node SDK does this), the call's span sits inside your current trace, so trace.id is the one your tracing tool shows for that request.

Never reported: prompt or response content, or your API keys. The SDK never prices a call either. cost_usd is sent only when the provider itself reports what the call cost — a Keld gateway returns it as usage.cost, streamed or not. Otherwise there is no cost_usd attribute and Atlas derives cost server-side from a price table, so stale client-side pricing cannot silently under- or over-report spend.

Capture is through @opentelemetry/instrumentation-openai (pinned 0.20.0), covering chat.completions.create and responses.create, streamed and not — and with them chat.completions.stream, responses.stream and .withResponse() / .asResponse(), which route through those two, returning the exact object the SDK produced. Nothing else is captured: images.*, audio.*, videos.*, batches, fineTuning, beta.threads.* and beta.realtime.* bill per image, per character or per second and return no token block, and reporting them at zero would look like real data rather than an absence; embeddings spans are deliberately not mapped; legacy completions is not instrumented. Nor is anything that skips the client — a raw fetch, another vendor's SDK, inference behind an internal service.

LangChain and LangGraph

For an app built on LangChain.js or LangGraph.js, one call reports every chat-model call the app makes, whichever provider package the model comes from (@langchain/anthropic, @langchain/openai, @langchain/aws, @langchain/google-genai, ...), inside a graph or not.

npm install @keld/keld @langchain/core    # most apps have @langchain/core already

Supported LangChain versions

| | Supported | Tested with (oldest / newest at release) | |---|---|---| | @langchain/core | >=1.3.0 <2 | 1.3.0 / 1.3.1 | | @langchain/openai | any that supports the core above | 1.7.1 / 1.8.0 | | @langchain/anthropic | same | 1.6.0 / 1.6.0 | | @langchain/langgraph | same | 1.5.3 / 1.5.3 | | Node | 22 or newer | 22, 24 |

CI re-checks the newest 1.x of each every week. If instrument() finds an unsupported @langchain/core, or a part of LangChain it relies on is missing, it says so once on console.error: spend from LangChain calls may not reach Atlas until you move to a supported version.

Use the app's own key, from Atlas → Teams → Agents & Apps. instrument() does not read the environment itself: pass the key in.

A · Telemetry only. The models stay on your own providers; each call is reported under its provider.

import { instrument } from "@keld/keld/langchain";
import { ChatAnthropic } from "@langchain/anthropic";
import { createReactAgent } from "@langchain/langgraph/prebuilt";

instrument({
  atlasToken: process.env.KELD_APP_KEY,   // kagt_...
  atlasMetadata: { team: "payments" },    // optional: workstreams
});

const claude = new ChatAnthropic({ model: "claude-sonnet-5-5" });
await claude.invoke("Summarise this ticket: ...");   // reported
const agent = createReactAgent({ llm: claude, tools: [getWeather] });
await agent.invoke({ messages: [{ role: "user", content: "Weather in Paris?" }] });   // every call reported

B · Inference on Keld, with telemetry. Point LangChain's OpenAI wrapper at Keld's gateway and keep instrument() on. Those calls are reported as keld, at the cost the gateway returns. The same app key works for both.

import { instrument } from "@keld/keld/langchain";
import { ChatOpenAI } from "@langchain/openai";

const key = process.env.KELD_APP_KEY;
instrument({ atlasToken: key });

const model = new ChatOpenAI({
  model: "deepseek/deepseek-v4-flash-0731",   // any model Keld serves
  apiKey: key,
  configuration: { baseURL: "https://api.keld.co/v1" },
});
await model.invoke("Summarise this ticket: ...");   // runs on Keld, reported as Keld

The two mix: some models on Keld, the rest on your own providers, each call reported once, also beside a Keld client.

instrument() takes atlasToken, atlasEndpoint and atlasMetadata, which mean what they do on the client. Call it once, wherever your app sets up telemetry: the top of the entry file, or a framework's startup hook such as Next.js's instrumentation.ts. Later calls change nothing. Each call is reported with the same fields as a client's call; prompts and completions are never read. Before a forced shutdown, await flush() from @keld/keld.

| LangChain provider | Reported provider | |---|---| | anthropic | anthropic | | openai (ChatOpenAI and the wrappers built on it) | from the model's address, by the client's host table: keld for Keld's gateway, openai with no address, hosted_vllm with a warning for an unknown host | | azure, azure_openai | azure_openai | | amazon_bedrock, bedrock, bedrock_converse | bedrock | | google_genai, google_vertexai | gemini | | mistral, mistralai | mistral | | fireworks, together | fireworks_ai, together_ai | | groq, ollama, xai, deepseek | their own names | | anything else | LangChain's own name, with a one-time warning; Atlas shows it as unpriced |

Leave streamUsage on for a streamed ChatOpenAI (it is the default): without it the call reports 0 tokens, with a one-time warning.

Known limits

Measured, not assumed.

  • Native @anthropic-ai/sdk and @aws-sdk/client-bedrock-runtime clients are not instrumented. Both providers are covered through their OpenAI-compatible endpoints, reached with a baseURL above; a call made with a vendor's own SDK is reported nowhere.
  • Bedrock's OpenAI-compatible endpoint does not serve Claude. Live Claude model ids return 404 - doesn't exist or doesn't support this API; only the OpenAI and open-weight families are reachable there.
  • A private or self-hosted host reports hosted_vllm and prices at $0, with no override. An arbitrary hostname cannot be covered by a table, so the fix is a host entry here — open an issue rather than working around it.
  • An endpoint that rejects stream_options will reject every streamed call. Every endpoint this SDK is tested against accepts it. Breaking loudly is the deliberate trade against silent zero-token billing.
  • chat.completions.parse() and responses.parse() throw under telemetry. Upstream bug in @opentelemetry/instrumentation-openai 0.20.0: its promise tap reads the response body, and parse()'s own read then fails with "Body is unusable". Pinned by tests/caller-fidelity.test.ts; fixed by opentelemetry-js-contrib #3667.
  • openai v7+ produces no telemetry. The instrumentation supports >=4.19.0 <7 and emits nothing outside it. This package depends on openai@^6 and warns loudly at a v7 install.

Development

npm install
npm test        # vitest — unit and contract tests
npm run build   # tsup — dist/*.cjs, dist/*.mjs, dist/*.d.ts
npm run typecheck

tests/fixtures/atlas-sdk-otlp-export.golden.json is this package's copy of the wire contract pinned by keld-atlas and must stay structurally identical to it, with sdk.language: "typescript" as the one intended difference; tests/contract.test.ts asserts it.

Releasing. Bump version in package.json and SDK_VERSION in src/core.ts (a test fails if they disagree), move the Unreleased notes into a new CHANGELOG.md section for that version, and merge to main. Nothing else: no tag to push. .github/workflows/ci.yml runs on every pull request: it tests on Node 18/20/22/24, checks CHANGELOG.md has a section for the current version, runs publint and attw on the packed tarball, and imports it from both CJS and ESM. On each push to main, .github/workflows/release.yml asks npm whether that version exists. If it does, the run stops there. If not, it runs the same CI on the merged commit, publishes via npm trusted publishing, then tags vX.Y.Z and creates a GitHub release from the changelog section. Its comments carry the one-time npmjs.com setup.

LangChain demo. scripts/langchain-demo.mjs runs one LangGraph graph against real Anthropic (a ReAct agent with a cached system prompt), OpenAI (streamed) and Keld's gateway through the built @keld/keld/langchain, and prints the records it exported beside each provider's own usage figures. It needs ANTHROPIC_API_KEY, OPENAI_API_KEY, KELD_PROD_TOKEN and KELD_MODEL (KELD_PROD_GATEWAY_URL defaults to https://api.keld.co/v1); with ATLAS_INGEST_TOKEN (and optionally ATLAS_ENDPOINT) the records also go to Atlas, without it nothing leaves the machine.

npm run build && node scripts/langchain-demo.mjs

Smoke test. scripts/smoke.mjs drives the built client against a local OpenAI-compatible stub, so the only real network call is the OTLP export. It makes a plain, a streaming and a failing call, asserts no prompt text or cost_usd is in the payload, and flushes. The stub is on 127.0.0.1, an unknown host, so the hosted_vllm warning it prints is part of the test.

npm run build
ATLAS_INGEST_TOKEN=kagt_... ATLAS_ENDPOINT=http://localhost:8000 node scripts/smoke.mjs

In Atlas, expect three source=atlas_sdk rows for the printed session.id, with dedup_key suffixes :1 :2 :3 and cost_usd computed server-side.