Radondocs

Core concepts

The mental model behind Radon AI — providers, the one message and result shape, models, streaming, tool calling, and the escape hatch to raw provider access.

Radon AI has a small number of ideas. Once they click, every method and every provider behaves predictably.

Providers

A provider is a model vendor — OpenAI, Anthropic, Google Gemini, Groq. You configure the ones you use; Radon lazy-loads each adapter only when it's first touched, so an OpenAI-plus-Anthropic app never bundles the other eight adapters' code. The core is roughly 43 KB.

const ai = new RadonAI({
  providers: {
    openai: {},                       // credentials from RADON_OPENAI_API_KEY
    anthropic: { apiKey: "sk-ant-..." }, // or inline
  },
  defaultProvider: "openai",
});

Every call uses defaultProvider unless you override it per call:

await ai.chat({ messages, provider: "anthropic" });

Why swapping is a config change

Every provider implements the same interfacechat, streamChat, embed, and the escape hatches. The message shape you pass in and the result shape you get back are identical across providers, so changing model vendor is a config edit, not a rewrite.

One message shape

A conversation is an array of ChatMessages. Each has a role and content. content is usually a plain string; for vision it can be an array of content parts. An assistant turn that called tools carries toolCalls; a role: "tool" message carries one tool's result, keyed back by toolCallId.

const messages = [
  { role: "system", content: "You are terse." },
  { role: "user", content: "Capital of France?" },
];
role"system" | "user" | "assistant" | "tool"required

Who authored the message.

contentstring | ContentPart[]required

Plain text, or an array of text / image parts for multimodal input.

toolCallsToolCall[]

Present on an assistant turn that invoked tools.

toolCallIdstring

On a role: "tool" message: which call this is the result of.

namestring

The tool/function name for a role: "tool" message.

One result shape

Every chat() returns the same ChatResult, whatever the provider. Radon maps each vendor's response into one shape and normalizes the finish reason and token usage.

contentstring

The concatenated assistant text. Empty string when the turn was only tool calls.

toolCallsToolCall[]

Tool calls the model made this turn ([] when none). arguments is always a parsed object, never a JSON string.

finishReason"stop" | "length" | "tool_calls" | "content_filter" | "error" | "other"

Why generation stopped, normalized across providers.

usage{ promptTokens, completionTokens, totalTokens }

Normalized token accounting. Fields are 0 when a provider doesn't report them.

providerstring

The provider that produced this result.

modelstring

The model that produced it (the provider's echoed id, or the one you asked for).

rawunknown

The untouched provider response — reach here for fields Radon doesn't normalize (reasoning traces, log-probs, provider-specific metadata).

Models

Set the model per call, or a defaultModel per provider in config. Every provider ships a sensible built-in default (see Providers), but model catalogs move fast — model is always yours to set.

await ai.chat({ messages, model: "gpt-4o-mini" });               // per call
new RadonAI({ providers: { openai: { defaultModel: "gpt-4o-mini" } } }); // per provider

Resolution order is: options.model → provider config defaultModel → the adapter's built-in default.

Defaults are a convenience, not a lock-in

The built-in defaults (e.g. gpt-4o, claude-sonnet-4-6) are documented as "update as needed", not a guaranteed-current list. Always set model explicitly for production.

Streaming

For a typing-indicator experience, ai.stream() returns a ChatStream you can for await over. Providers frame their server-sent events completely differently — OpenAI streams tool arguments as string fragments, Anthropic as input_json_delta, Gemini as whole functionCall parts — and Radon reassembles all of them into one normalized event stream.

const stream = ai.stream({ messages: [{ role: "user", content: "Write a haiku." }] });
for await (const text of stream.textStream()) process.stdout.write(text);

Streams are single-consumption and stream() does not honor fallback. Full details in Streaming.

Tool calling

This is the SDK's reason to exist. Define a tool once as a JSON-Schema-shaped object; Radon translates it into each provider's own function-calling format and normalizes the calls that come back — arguments is always a parsed object.

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

const res = await ai.chat({ messages, tools });
for (const call of res.toolCalls) console.log(call.name, call.arguments);

Tool calling is core and free on every provider. See Tools for the full request-run-respond loop.

Free vs. Pro

Gating happens at two levels:

  • Provider-level. Three providers work with no license: openai, anthropic, groq. The other seven (google, mistral, deepseek, openrouter, xai, together, ollama) are Pro.
  • Feature-level. Four capabilities are Pro even on a free provider: embeddings, vision, structured output, and fallback chains.

Unlock Pro by setting a license key and calling await ai.init(), which verifies it once and caches it for the process lifetime.

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

await ai.init();                               // verifies the license; unlocks Pro
await ai.chat({ messages, provider: "google" });

Using a Pro provider or feature without a valid license throws LicenseRequiredError; a bad or unreachable key throws LicenseInvalidError (fail-closed). Introspect tiers with the exported FREE_PROVIDERS set and isProProvider(slug).

Free providers work with no license and no init() — providers load lazily on first use. Call init() when you want misconfiguration to fail fast at boot, or whenever any Pro provider or feature is in play.

Escape hatch

AI providers ship new features weekly. The unified API handles the common case; for everything else, 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`).
const openai = await ai.native<import("openai").OpenAI>("openai");
await openai.beta.assistants.list();

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

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

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

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

On this page