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

@radonsdk/ai

v0.1.0

Published

One unified, provider-agnostic API for 10 AI model providers — OpenAI, Anthropic, Google Gemini, Groq, Mistral, DeepSeek, OpenRouter, Ollama, xAI (Grok), Together AI. Write chat() once and swap providers via config. Normalized streaming, one tool schema t

Readme

@radonsdk/ai

One unified, provider-agnostic API for 10 AI model providers — OpenAI, Anthropic, Google Gemini, Groq, Mistral, DeepSeek, OpenRouter, Ollama, xAI (Grok), and Together AI. Write ai.chat() once and swap providers with a config change, never a code change.

Radon AI gives you a single, typed interface (chat, stream, embed) that every provider implements. Provider-specific quirks — auth schemes, wildly different tool-calling formats, SSE framing, JSON-mode dialects — are absorbed inside each adapter and never leak into your code. And because AI APIs move faster than any wrapper can keep up, every adapter exposes an escape hatch to the provider's raw or official-SDK client, so you never get stuck.

  • 10 providers, one API — one message shape, one result shape.
  • One tool schema that works everywhere — define a tool once; Radon translates it into OpenAI's function format, Anthropic's tool_use blocks, and Gemini's functionDeclarations, and normalizes the calls that come back. This is the hard part, and it's the point.
  • Normalized streamingfor await text deltas and tool-call deltas identically across providers, no matter how each one frames its SSE.
  • Escape hatch, first-classai.native() returns the official provider SDK (preconfigured); ai.rawClient() returns a preconfigured fetch client. Drop to raw features Radon hasn't wrapped without leaving your config.
  • Lazy-loaded adapters — an OpenAI + Anthropic app never bundles the other eight. The core is ~43 KB.
  • Strict TypeScript, ESM + CJS, Node ≥ 18, zero required dependencies (official SDKs are optional peer deps, used only by native()).

Install

npm install @radonsdk/ai
# or: pnpm add @radonsdk/ai

Radon never stores your secrets. Each adapter reads credentials from RADON_<PROVIDER>_API_KEY environment variables (or from the providers config block, which takes precedence). See .env.example for every provider's variable names.


Quickstart (< 5 minutes)

import { RadonAI } from "@radonsdk/ai";

const ai = new RadonAI({
  providers: { openai: {} },   // credentials come from RADON_OPENAI_API_KEY
  defaultProvider: "openai",
});

const res = await ai.chat({
  messages: [{ role: "user", content: "Say hello in one word." }],
});

console.log(res.content);        // "Hello"
console.log(res.usage);          // { promptTokens, completionTokens, totalTokens }
console.log(res.finishReason);   // "stop" | "length" | "tool_calls" | ...

Switch providers without touching your chat code — it's a config/option change:

const ai = new RadonAI({ providers: { openai: {}, anthropic: {} } });

await ai.chat({ messages, provider: "openai" });
await ai.chat({ messages, provider: "anthropic" }); // same messages, same shape

Set model per call (e.g. { model: "gpt-4o-mini" }) or a defaultModel per provider in config. Model catalogs move fast, so model is always yours to set — the built-in defaults are a convenience, not a lock-in.


Free vs. Pro

Three providers are free. The other seven — and a handful of features — require a Radon Pro license key, verified once in await ai.init() and cached for the process lifetime.

| Tier | What you get | | --- | --- | | Free | openai, anthropic, groq — full chat + streaming + normalized tool-calling. Tool-calling is core, never an upsell. | | Pro (license) | The other 7 providers (google, mistral, deepseek, openrouter, xai, together, ollama) plus these features on any provider: embeddings, vision / image input, structured output / JSON mode, and fallback chains. |

const ai = new RadonAI({
  providers: { google: {} },                     // a Pro provider
  licenseKey: process.env.RADON_LICENSE_KEY,      // or config.license: { key, verifyUrl, ... }
});

await ai.init();                                  // verifies the license; unlocks Pro
await ai.chat({ messages, provider: "google" });
  • Free providers work with no license and no init().
  • Using a Pro provider — or a Pro feature — without a valid license throws LicenseRequiredError; a bad/unreachable key throws LicenseInvalidError (fail-closed).
  • Introspect tiers with the exported FREE_PROVIDERS set and isProProvider(slug).

Every provider — Free or Pro — is built to the same completeness bar. Free/Pro is a licensing concern, not a quality one.


Tool calling — one schema, every provider

This is the SDK's reason to exist. Define a tool once; it works on all ten providers, which structure function-calling completely differently under the hood.

const tools = [
  {
    name: "get_weather",
    description: "Get the current weather for a city.",
    parameters: {
      type: "object",
      properties: { city: { type: "string", description: "City name" } },
      required: ["city"],
    },
  },
];

const res = await ai.chat({
  messages: [{ role: "user", content: "What's the weather in Lagos?" }],
  tools,
  toolChoice: "auto", // "auto" | "none" | "required" | { name: "get_weather" }
});

// Normalized across every provider: `arguments` is always a parsed object,
// never a JSON string you have to decode yourself.
for (const call of res.toolCalls) {
  console.log(call.name, call.arguments); // "get_weather" { city: "Lagos" }
  const result = await runYourTool(call);
  // Feed the result back — same message shape everywhere:
  messages.push(
    { role: "assistant", content: "", toolCalls: [call] },
    { role: "tool", toolCallId: call.id, name: call.name, content: JSON.stringify(result) },
  );
}

Under the hood Radon translates your one schema into OpenAI's { type: "function", function: {...} }, Anthropic's { name, input_schema } + tool_use/tool_result blocks, and Gemini's functionDeclarations + functionCall/functionResponse parts — and normalizes the calls that come back. You write the loop once.


Streaming — normalized deltas

const stream = ai.stream({ messages: [{ role: "user", content: "Write a haiku." }] });

// Simple case — just the text:
for await (const text of stream.textStream()) process.stdout.write(text);

// Or the full normalized event stream (text + tool-call deltas + a terminal result):
for await (const chunk of stream) {
  if (chunk.type === "text") process.stdout.write(chunk.delta);
  else if (chunk.type === "tool_call") { /* incremental tool-call fragment */ }
  else if (chunk.type === "finish") console.log(chunk.message); // assembled ChatResult
}

// Or stream AND get the final assembled object (tool-call args reassembled + parsed):
const final = await stream.final();

OpenAI streams tool-call arguments as string fragments; Anthropic streams them as input_json_delta; Gemini sends whole functionCall parts. Radon reassembles all of them into complete, parsed toolCalls on the terminal chunk — one implementation, identical behavior.


The escape hatch (this matters more here than anywhere else)

AI providers ship new features weekly. The unified API handles the 80% case; for the other 20%, drop to raw access without leaving your Radon config.

// 1) The official provider SDK, preconfigured with your Radon credentials.
//    Requires the optional peer dep (e.g. `npm i openai` / `@anthropic-ai/sdk`).
const openai = await ai.native<import("openai").OpenAI>("openai");
const assistants = await openai.beta.assistants.list(); // anything the SDK can do

// 2) A preconfigured fetch client (base URL + auth ready) — zero extra install.
const client = await ai.rawClient("anthropic");
const raw = await client.request("/messages", { method: "POST", json: { /* raw body */ } });

// 3) Per-call escape hatch: merge arbitrary provider fields into the request.
await ai.chat({
  messages,
  providerOptions: { logit_bias: { "50256": -100 } }, // provider-specific, merged in
});

// 4) The untouched provider response is always on `result.raw`.
const res = await ai.chat({ messages });
res.raw; // the exact JSON the provider returned

native() throws a clear NativeClientUnavailableError (with an npm install hint) if the official SDK isn't installed — rawClient() never needs one.


Embeddings (Pro)

const { embeddings } = await ai.embed({
  provider: "openai",
  input: ["hello world", "goodbye world"],
});
embeddings[0]; // number[]

Not every provider has an embeddings API. Providers without one (anthropic, groq, deepseek, xai, openrouter) throw a typed UnsupportedOperationError — check provider.capabilities.embeddings. See DEFERRED.md.


Vision / multimodal input (Pro)

await ai.chat({
  provider: "openai",
  messages: [
    {
      role: "user",
      content: [
        { type: "text", text: "What's in this image?" },
        { type: "image", source: { url: "https://example.com/cat.png" } },
        // or inline base64: { type: "image", source: { data: "<base64>", mimeType: "image/png" } }
      ],
    },
  ],
});

Vision is model-dependent as well as provider-dependent — pick a multimodal model. Gemini can't fetch arbitrary web URLs (use inline base64 or its File API). See DEFERRED.md.


Structured output / JSON mode (Pro)

// JSON object mode:
await ai.chat({ messages, responseFormat: "json" });

// JSON Schema (upgraded to strict schema enforcement where the provider supports it):
await ai.chat({
  messages,
  responseFormat: { type: "json_schema", schema: { type: "object", properties: { /* … */ } } },
});

This is the least-uniform capability across providers — OpenAI and Gemini enforce a schema natively; Anthropic is best-effort via a system instruction; others offer JSON-object mode only. Radon normalizes the request and documents exactly what each guarantees in DEFERRED.md.


Fallback chains (Pro)

// Try OpenAI; if it fails, fall back to Anthropic, then Groq — same call.
await ai.chat({ messages, provider: "openai", fallback: ["anthropic", "groq"] });

Bring your own provider

Implement the ModelProvider interface (or extend BaseProvider) and register it. For an OpenAI-compatible endpoint, extend OpenAICompatibleProvider and you get chat/streaming/tools/embeddings for free.

import { OpenAICompatibleProvider, registerProvider } from "@radonsdk/ai";

class MyLlmProvider extends OpenAICompatibleProvider {
  readonly name = "my-llm";
  readonly defaultModel = "my-model";
  readonly capabilities = { chat: true, streaming: true, tools: true, embeddings: false, vision: false, jsonMode: true };
  protected defaultBaseUrl() { return "https://llm.mycompany.internal/v1"; }
}

registerProvider("my-llm", async () => MyLlmProvider);

Provider catalog

Import any adapter directly for an explicit dependency: import { OpenAIProvider } from "@radonsdk/ai/providers/openai".

| Slug | Provider | Tier | Wire format | Embeddings | Vision | | --- | --- | --- | --- | --- | --- | | openai | OpenAI | Free | OpenAI | ✅ | ✅ | | anthropic | Anthropic (Claude) | Free | Anthropic Messages | — | ✅ | | groq | Groq | Free | OpenAI-compatible | — | ✅ | | google | Google Gemini | Pro | Gemini | ✅ | ✅ | | mistral | Mistral AI | Pro | OpenAI-compatible | ✅ | ✅ | | deepseek | DeepSeek | Pro | OpenAI-compatible | — | — | | openrouter | OpenRouter | Pro | OpenAI-compatible | — | ✅ | | xai | xAI (Grok) | Pro | OpenAI-compatible | — | ✅ | | together | Together AI | Pro | OpenAI-compatible | ✅ | ✅ | | ollama | Ollama (local) | Pro | OpenAI-compatible | ✅ | ✅ |

Vision/embeddings columns are provider-can-ever — the specific model still matters. Full honest caveats in DEFERRED.md.


Errors

Every failure is a typed subclass of AIError with a stable .code, so you branch on kind, not message text:

InvalidConfigError, ProviderNotFoundError, ProviderNotConfiguredError, LicenseRequiredError, LicenseInvalidError, UnsupportedOperationError, AuthenticationError, ProviderApiError (carries status, providerCode, raw), RateLimitError (carries retryAfterSec), ContextLengthExceededError, NetworkError, InvalidToolArgumentsError (carries the raw string the model produced), StreamError, and NativeClientUnavailableError.


Install size & lazy loading

The core (import { RadonAI } from "@radonsdk/ai") contains zero provider code — only the interface, registry, and the streaming/tool-normalization engines (~43 KB). Adapters are loaded on demand via dynamic import() the first time a provider is used, and each is emitted as its own subpath entry. A dev who configures only OpenAI + Anthropic never executes — or, under a code-splitting bundler, bundles — the other eight adapters. Official provider SDKs are optional peer dependencies, pulled in only when you call native().


API surface

RadonAI, createAI, the unified types (ChatMessage, Tool, ToolCall, ChatOptions, ChatResult, StreamChunk, EmbedOptions, …), ChatStream, BaseProvider / OpenAICompatibleProvider, registerProvider, FREE_PROVIDERS / isProProvider / PRO_FEATURES, LicenseClient, the HttpClient + parseSSE + tool-normalization helpers (for BYO adapters), and every typed error. All types are exported.

License

MIT © Radon SDK. The SDK is MIT-licensed; the Pro tier requires a commercial license key at runtime for Pro providers and features. "# radonsdk-ai"