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

@nrouter_ai/sdk

v3.1.2

Published

nRouter SDK — one API key for models across six provider clouds. Native OpenAI and Anthropic wire formats, plus embeddings, audio, images and video.

Readme

@nrouter_ai/sdk (JS/TS)

npm Socket License: MIT

SDK for the nRouter LLM gateway: one API key for models across six provider clouds. It wraps the official openai package with the same API surface, pre-configured for nRouter.

The examples below pass a claude-* id on purpose: client.nr.chat() sends Claude ids to /v1/messages itself (the only wire Anthropic serves) and translates the response back to an OpenAI-shaped completion. The plain client.chat.completions.create() surface does not do that — give it a model whose provider serves chat-completions, such as gpt-5.4-mini. Pass any model returned by client.nrouterModels.list() for your key.

Install

npm install @nrouter_ai/sdk

Authentication & Setup

The SDK automatically reads your API key from the NROUTER_API_KEY environment variable:

export NROUTER_API_KEY="sk-nrouter-your-api-key-here"

Usage

import { nRouter } from "@nrouter_ai/sdk";

const client = new nRouter(); // reads NROUTER_API_KEY from env

const response = await client.nr.chat({
  model: "claude-sonnet-4-5-20250929",
  prompt: "Hello!",
  maxTokens: 32,
});
console.log(client.nr.text(response));
const { nRouter } = require("@nrouter_ai/sdk");

const client = new nRouter({ apiKey: process.env.NROUTER_API_KEY });

nRouter extends the OpenAI class directly, so the resources nRouter serves (chat.completions, completions, responses, embeddings, images, audio, videos, models, streaming) are called exactly as you would call them against OpenAI — same method names, same request and response shapes.

What "compatible" does and does not mean

The openai package exposes a larger API than nRouter serves, and a model is callable only on the routes ITS provider serves. Three limits. Every one of them fails loudly — a 404 from the gateway, never a silent wrong answer — but they fail at call time, not at compile time: the wrapper inherits the full openai type surface, so all three type-check.

  • Resources nRouter does not mount. The served resources are exactly chat.completions, completions, responses, embeddings, images, audio, videos and models. Everything else the openai client carries 404s — files, fineTuning, batches, beta (assistants/threads), vectorStores, uploads, containers, conversations, webhooks, moderations, evals, graders, admin, skills and contentProvenanceChecks. realtime is the one that does not 404, because it is a WebSocket surface with no gateway to connect to; it fails to open. Treat the served list, not this one, as authoritative — it is spec/nrouter-sdk-spec.json, derived from the gateway's own route table.
  • Methods a served resource does not mount. Being on the served list is per ROUTE, not per resource. images.generate() is served and images.edit() / images.createVariation() are not; videos.create(), videos.retrieve() and videos.downloadContent() are served and videos.list() / videos.delete() / videos.remix() are not; models.list() and models.retrieve() are served and models.delete() is not.
  • Routes a model's provider does not serve. An Anthropic model answers /v1/messages and neither /v1/chat/completions nor /v1/responses. Only OpenAI and Azure models answer /v1/responses. Sending a Claude id to client.chat.completions.create() is a 404 from the gateway, not a translation. AWS Bedrock is narrower than its name suggests: only the Anthropic family on Bedrock is served, and only on /v1/messages. A Nova, Llama, Titan, Qwen, Mistral or DeepSeek id on Bedrock has no text route here at all — the refusal says so rather than pointing you at a second wire.

client.nr.chat() covers the common half of the third one: it recognises a Claude id by name and sends it to /v1/messages. That is a NAME heuristic, so it does not help with an alias whose name hides its provider — a Bedrock or Vertex id for a non-Claude model. For those, and whenever you call client.chat.completions directly, ask the gateway instead of guessing:

const models = await client.nrouterModels.list();
const entry = models.data.find((m) => m.id === alias);
// e.g. ["/v1/messages", "/v1/messages/count_tokens"]
console.log(entry.nrouter_endpoints);

Any alias whose nrouter_endpoints contains /v1/chat/completions works with the stock openai resource unmodified. An empty array means no route on this gateway serves that alias — pick another model rather than trying a second wire.

nRouter Helpers

Use client.nr.chat() when you want nRouter features and response metadata in one call:

const result = await client.nr.chat({
  model: "claude-sonnet-4-5-20250929",
  prompt: "Summarize this ticket.",
  systemPrompt: "Be concise.",
  promptTemplateId: "<prompt-template-id>",
  promptVariables: { customer: "Acme" },
  cache: false, // force provider egress; omit or true uses the gateway default
});

console.log(client.nr.text(result));
console.log(result.meta.requestId, result.meta.cost, result.meta.model);

Guardrails are not selected per request. They are assigned per key, team or organization in the nRouter dashboard and apply automatically to every call. The guardrailIds option is deprecated and throws a configuration error: the gateway runs no per-request override, so it never scoped anything.

Other helpers:

  • client.nr.compare(options, models) runs one prompt against several models and returns results in the same order as models.
  • client.nr.stream(options, signal) opens an SSE stream with typed errors.
  • client.nr.responses(body, options) posts to /v1/responses and applies the same nRouter guardrail, prompt-template and cache fields.
  • client.nr.messages(body, options) posts to /v1/messages for Anthropic-style message bodies while keeping nRouter metadata and errors.
  • client.nr.countTokens(body) posts to /v1/messages/count_tokens; the body is sent unchanged so callers can use the gateway token-count contract directly.
  • client.nr.meta(headers) parses x-nr-* headers from a response you obtained another way.
  • client.nr.media.speech(), .transcribe(), .translate(), .image(), .video(), .videoStatus(), .videoContent() and .embeddings() cover the non-chat endpoints with the same metadata and error handling.

Audio and voice

speech() returns audio BYTES with the metadata attached, and transcribe() / translate() return a result discriminated on the media type you asked for, so an srt request comes back as a cue track rather than flattened into { text }:

const spoken = await client.nr.media.speech({
  model: "tts-1",
  input: "The build is green.",
  voice: "alloy",
  response_format: "mp3",
});
await fs.writeFile("out.mp3", spoken.bytes);

const heard = await client.nr.media.transcribe({
  file: spoken.bytes,
  fileName: "out.mp3",              // the extension is required
  model: "gpt-4o-mini-transcribe",
  response_format: "verbose_json",  // see below — this is a billing decision
});

response_format is not only a formatting choice. A whisper-1 transcription is priced from the response's duration, and duration arrives only with verbose_json — ask for anything else on a per-second model and the call is served but settles unpriced, with no cost figure to report. Token-priced models (gpt-4o-mini-transcribe, gpt-4o-transcribe) are unaffected. Speech is priced per character of input.

There is no streaming TTS and no realtime session: a voice turn is a cascade of three separately billed calls, transcribe()nr.chat()speech(). The runnable version is demo/voice-agent/, and the full semantics — the upload rules, what is and is not guardrail-scanned, and why a missing cost must never be summed as zero — are in docs/audio.md.

Images

image() always returns JSON — url gives you links and b64_json gives you base64 inside the body, and there is no image-bytes route to return instead:

const res = await client.nr.media.image({
  model: "gpt-image-1-mini",
  prompt: "A flat vector lighthouse at dusk, three colours, no text.",
  n: 1,                     // 1 through 10 — each image is a separate charge
  size: "1024x1024",
  quality: "low",
});

n, size, quality and response_format are checked before the request leaves the process, against the gateway's own bounds, so a typo costs no round trip; validateImageParams() is exported so a form can use the same check. Pass an unlisted value through extra if a model takes one.

Which quantity you are billed on depends on the model — gpt-image-* prices from the usage block in the body, everything else prices per image from n × size × quality — and no response header carries the count, size or quality. Reconcile against the spend row by request id rather than recomputing. The runnable version is demo/image-agent/ and the semantics are in docs/images.md.

Video

Video is asynchronous: the create returns a job, and you collect the result over two more calls. Only the create bills.

const created = await client.nr.media.video({
  model: "sora-2",
  prompt: "A lighthouse beam sweeping over water at dusk.",
  seconds: 4,
  size: "1280x720",
});

const jobId = String(created.body.id);
await client.nr.media.waitForVideo(jobId, { pollIntervalMs: 5000, timeoutMs: 600_000 });
const file = await client.nr.media.videoContent(jobId);
await fs.writeFile("out.mp4", file.bytes);

Polling and downloading are free of credit but not free of quota — every poll spends a rate-limit slot, which is why pollIntervalMs has a floor. A free call reports costStatus: null, which is not the same as the unpriced a billed call reports when it could not be priced; conflating them turns a long render into a pricing bug that does not exist. A retry of the create is a second render, not merely a second bill. The runnable version is demo/video-agent/ and the semantics — the sealed job handle, why an accepted-then-failed job stays billed, and the download bound — are in docs/video.md.

Model Discovery

Use the nRouter helper for model listing:

const models = await client.nrouterModels.list();
console.log(models.data[0].id);

The raw nRouter /models response is valid JSON, but the current OpenAI JS SDK page parser exposes it with an empty data array. nrouterModels.list() bypasses that parser and returns the gateway response directly. It still travels the client's own request pipeline, so a configured fetch, timeout, maxRetries, fetchOptions and default headers apply to it exactly as they do to every other call.

Development

npm ci
npm test

The test suite runs TypeScript test files directly through Node's built-in test runner, so use Node 22.18.0 or newer. Older Node 22 builds fail before the tests execute because they cannot strip TypeScript syntax from .ts test files.

Examples And Live Diagnostics

The repo includes JavaScript examples and interactive tools for manual SDK checks:

npm run build
node demo/agent.js --live
node demo/interactive-agent.mjs --live --voice
node demo/ui/server.js

demo/interactive-agent.mjs provides an interactive terminal REPL with multi-turn chat, streaming tokens, model switching, per-turn latency/cost tracking, and optional speech synthesis playback (--voice).

demo/ui/server.js starts a conversational browser UI at http://127.0.0.1:4317 with real-time SSE streaming, speech recognition via browser microphone, audio replay for voice synthesis, and one-click test suite execution. The browser does not receive the API key; the local Node server reads NROUTER_API_KEY and calls the built SDK package.

See demo/README.md for commands and docs/live-sdk-agent-report.md for the latest manual test findings.

Requirements

Node 22.18 or newer, declared in engines. That is the floor for this package because the test runner executes TypeScript tests directly through Node's native type stripping. Before 2.0.0 nothing declared a floor at all, so an unsupported runtime failed somewhere further in with a worse message.

The dependency tree is deliberately one package. openai 7 has no dependencies of its own, where openai 4 pulled in 36 transitive packages — which is where every supply-chain advisory against this package used to come from.

How guardrails, budgets and routing work

They are configured in the dashboard and enforced at the gateway, not in this package. The useful guarantee is not that they are always on — it is that whatever you have enabled cannot be bypassed by a client, this one included, and behaves identically from every nRouter SDK and from raw curl.

  • Guardrails — PII redaction, injection protection, secret and keyword scanning, pre-call and post-call. Which ones run is resolved per request: the organization's guardrail switch first, then the narrowest applicable assignment wins across key > team > org > default, and a winner disabled at that scope does not run.
  • Budget controls — spend limits per key, team and organization.
  • Observability — cost and usage on billable calls. Free routes are genuinely free and carry no x-nr-request-cost: /v1/messages/count_tokens, and video polling and content retrieval.

Smart Router aliases and fallback chains carry two conditions worth knowing before you rely on failover you have not enabled:

  • Opt-in by what you put in model. An alias gets the strategy and its chain; a concrete model is never re-routed and inherits no hidden fallback.
  • Text wires only — chat completions, responses, messages and legacy completions. Audio, image and video calls take a single-provider route and are not cross-provider Smart Router wires.
  • Node.js / TypeScript quickstart and the API reference.

Demos & Examples

Runnable demonstrations live in demo/:

Validation Playbook

This SDK maintains a repeatable 18-step verification process:

Open-Source Standards & License