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

@rxmxdhxni/agentrouter

v0.1.4

Published

Unofficial Node/Bun/Deno SDK for AgentRouter — wraps the OpenAI-compatible reseller proxy at agentrouter.org.

Readme

@rxmxdhxni/agentrouter

npm version license node

Unofficial Node/Bun/Deno SDK for AgentRouter — handles the quirks so you can skip straight to ar.chat("hi").

60-second SDK overview

[!WARNING] AgentRouter (agentrouter.org) is a third-party reseller offering free credits for Claude, DeepSeek, and GLM via a proxy. This package is a community wrapper — not affiliated with or endorsed by AgentRouter or Anthropic. Your prompts pass through AgentRouter's infrastructure; do not send proprietary code, credentials, or sensitive data. For production, get a key directly from the model provider.

Install

npm install @rxmxdhxni/agentrouter
# or
bun add @rxmxdhxni/agentrouter

[!IMPORTANT] Requires Node 20+, Bun latest, or Deno latest. Zero runtime dependencies.

Quick start

import { AgentRouter } from "@rxmxdhxni/agentrouter";

const ar = new AgentRouter({ apiKey: "sk-..." });
const reply = await ar.chat("What is 2 + 2?");
console.log(reply); // "2 + 2 equals 4."

API

new AgentRouter(options)

| Option | Type | Default | Description | | ----------- | ----------------------- | ------------------------------- | ----------------------------------------------------------------------------------- | | apiKey | string | — | Required. Your AgentRouter API key | | model | string | "claude-opus-4-7" | Model for all requests from this instance | | maxTokens | number | 1024 | Max tokens per completion | | baseURL | string | "https://agentrouter.org/v1" | Override the API endpoint | | userAgent | string | "QwenCode/0.2.0 (linux; x64)" | Override the User-Agent header | | timeout | number | 120000 | Request timeout in ms | | fetch | typeof fetch | globalThis.fetch | Custom fetch. Receives raw Authorization header — do not wrap with untrusted code | | debug | (msg: string) => void | — | Debug callback. Authorization is redacted before the callback is called |

[!IMPORTANT] baseURL must use https://. The SDK throws TypeError at construction if given http://, file://, or any non-HTTPS scheme — prevents accidentally leaking your key over plaintext.

chat(prompt, opts?)

Sends a single user message, returns the reply as a string.

const reply = await ar.chat("Summarize this in one sentence: ...");

[!NOTE] For reasoning models (glm-4.5, glm-5.1, deepseek-r1-0528), chat() returns an empty string if the model only produced reasoning output. Use complete() to access result.reasoning.

complete(request)

Full control over messages, model, and parameters.

const result = await ar.complete({
  messages: [
    { role: "system", content: "You are a concise assistant." },
    { role: "user", content: "Explain TCP handshake." },
  ],
  model: "deepseek-v3.2",
  temperature: 0.7,
  maxTokens: 512,
});

console.log(result.content);    // answer text
console.log(result.reasoning);  // defined for reasoning models
console.log(result.usage);      // { promptTokens, completionTokens, totalTokens }
console.log(result.raw);        // unwrapped upstream response

CompletionRequest fields:

| Field | Type | Description | | ------------- | -------------------- | --------------------------------------------- | | messages | ChatMessage[] | Required. Array of { role, content, name? } | | model | string | Overrides instance default | | maxTokens | number | Overrides instance default | | temperature | number | 0–2 | | topP | number | Nucleus sampling | | stop | string \| string[] | Stop sequences | | signal | AbortSignal | Cancellation |

CompletionResult fields:

| Field | Type | Description | | -------------- | --------------------- | ---------------------------------------------------------- | | content | string | Always a string; "" if the model only produced reasoning | | reasoning | string \| undefined | Reasoning output from reasoning models | | usage | Usage | Token counts | | model | string | Model echoed from upstream | | finishReason | string | "stop", "length", etc. | | raw | unknown | Unwrapped upstream JSON response |

stream(input, opts?)

Returns an async iterator of StreamChunk objects.

for await (const chunk of ar.stream("Write a haiku about DNS.")) {
  if (chunk.type === "content") process.stdout.write(chunk.delta);
  if (chunk.done) break;
}

input accepts a plain string or a full CompletionRequest.

StreamChunk fields:

| Field | Type | Description | | ------- | -------------------------- | --------------------------------- | | type | "content" \| "reasoning" | Which field this delta belongs to | | delta | string | Incremental text | | done | boolean | true on the final chunk |

AgentRouter.models

Static read-only array of known-working models at the time of publish.

console.log(AgentRouter.models);
// ["claude-opus-4-6", "claude-opus-4-7", "deepseek-r1-0528", ...]

Models

Known-good models (verified against live API; subject to upstream availability):

  • claude-opus-4-6
  • claude-opus-4-7 — default
  • deepseek-r1-0528 — reasoning model
  • deepseek-v3.1
  • deepseek-v3.2
  • glm-4.5 — reasoning model
  • glm-4.6
  • glm-5.1 — reasoning model

[!NOTE] Channel availability fluctuates upstream. A model that worked yesterday may return NoChannelError today. Check agentrouter.org for the live list of available models, or catch NoChannelError and fall back to another model from AgentRouter.models.

Errors

All errors extend AgentRouterError, which carries .status (HTTP code) and .body (raw response).

| Class | Status | Cause | Action | | ------------------------- | ------- | ------------------------------------------- | --------------------------------------------------------------------------- | | UnauthorizedClientError | 401 | Edge rejected the request fingerprint | SDK bug — do not override userAgent or fetch without restoring defaults | | AuthError | 401/403 | Invalid API key | Check your key | | NoChannelError | 503 | No upstream channel for the requested model | Try a different model; .model property names the offender | | ContentBlockedError | 400 | Upstream content policy blocked the prompt | Rephrase the prompt — switching models does NOT help (filter is edge-level) | | RateLimitError | 429 | Too many requests | Back off; .retryAfter (seconds) may be set | | TimeoutError | 0 | Request exceeded timeout ms | Increase timeout or retry | | AgentRouterError | any | Unclassified HTTP error | Inspect .status and .body |

[!NOTE] User-initiated aborts (via AbortSignal) throw the native DOMException / AbortError — matches standard fetch behavior. TimeoutError only fires for the SDK's internal timeout option.

import {
  AgentRouter,
  ContentBlockedError,
  NoChannelError,
  RateLimitError,
  UnauthorizedClientError,
} from "@rxmxdhxni/agentrouter";

try {
  const result = await ar.complete({
    messages: [{ role: "user", content: "..." }],
    model: "deepseek-v3.2",
  });
} catch (err) {
  if (err instanceof ContentBlockedError) {
    console.error("Prompt blocked by upstream filter — rephrase and retry");
  } else if (err instanceof NoChannelError) {
    console.error(`No channel for ${err.model} — switch model`);
  } else if (err instanceof RateLimitError) {
    const wait = err.retryAfter ?? 10;
    console.error(`Rate limited. Retry in ${wait}s`);
  } else if (err instanceof UnauthorizedClientError) {
    console.error("SDK misconfigured — do not override userAgent");
  } else {
    throw err;
  }
}

Streaming

Reasoning models emit both "content" and "reasoning" chunks. Separate them:

let answer = "";
let thinking = "";

for await (const chunk of ar.stream({
  messages: [{ role: "user", content: "Why is the sky blue?" }],
  model: "deepseek-r1-0528",
})) {
  if (chunk.type === "content") answer += chunk.delta;
  if (chunk.type === "reasoning") thinking += chunk.delta;
}

FAQ

Why do I get unauthorized client detected?

AgentRouter blocks requests that don't look like the official OpenAI Node SDK. The SDK ships the exact required headers (x-stainless-lang: js, etc.). This error appears when you override userAgent or pass a custom fetch that strips headers. Revert to defaults.

Why do I get NoChannelError for a model that worked before?

Channel availability fluctuates upstream. AgentRouter rents pooled access to Claude/DeepSeek/GLM, and a model can lose its channel at any time — the 503 body contains 无可用渠道. Catch NoChannelError and fall back to another model from AgentRouter.models.

Why do I get ContentBlockedError?

AgentRouter runs an edge-level content filter that blocks prompts matching certain patterns (e.g. Who is <person>?). The block is BEFORE model dispatch, so switching models will not help — rephrase the prompt instead.

Why is content empty but reasoning has text?

glm-4.5, glm-5.1, and deepseek-r1-0528 are reasoning models that put their output in reasoning_content, not content. Use complete() and read result.reasoning. chat() will return "" for these models.

Is this package official?

No. It is not affiliated with AgentRouter, Anthropic, DeepSeek, or Zhipu AI.

Can I use the openai npm package directly?

Yes — point baseURL at https://agentrouter.org/v1. The openai package already sends the right Stainless headers. This SDK exists to handle the double-encoded JSON responses from Claude models and to provide typed errors for AgentRouter-specific failures.

License

MIT