@wrnexus/ai
Server-side Anthropic client with generation and streaming.
bun add @wrnexus/ai@0.2.15A tiny, zero-dependency Claude (Anthropic) client for WRNexusJS apps — generate and stream text with Claude from any server-side code.
Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.
Overview
@wrnexus/ai is a thin, dependency-free wrapper over the Anthropic Messages API, built on fetch (Bun-native, no SDK). Use it in API routes, jobs, or middleware to call Claude. It defaults to the most capable model, claude-opus-4-8, reads your key from ANTHROPIC_API_KEY, and supports both one-shot generation and streaming.
Installation
bun add @wrnexus/ai
Private package — the machine must be authenticated to the wrnexus npm org
(a read token in ~/.npmrc). Requires Bun (Node is not supported).
Set your key in the environment (e.g. .env):
ANTHROPIC_API_KEY=sk-ant-...
API
createAI(config?)
Creates a client. The key is read at call time, so it's safe to create at import.
import { createAI } from "@wrnexus/ai";
const ai = createAI(); // or createAI({ apiKey, model, maxTokens, baseURL, version })
AIConfig fields (all optional):
| Field | Default | Description |
|---|---|---|
apiKey | ANTHROPIC_API_KEY | Anthropic API key |
model | "claude-opus-4-8" | Model id |
maxTokens | 4096 | Default max output tokens |
baseURL | https://api.anthropic.com | API base URL |
version | "2023-06-01" | anthropic-version header |
ai.generate(prompt, opts?): Promise<string>
One-shot text generation. prompt is a string or a Message[] history.
const text = await ai.generate("Write a haiku about Bun.");
const reply = await ai.generate(
[
{ role: "user", content: "My name is Ada." },
{ role: "assistant", content: "Hi Ada!" },
{ role: "user", content: "What's my name?" },
],
{ system: "You are concise." },
);
ai.stream(prompt, opts?): AsyncGenerator<string>
Yields text deltas as they arrive.
for await (const chunk of ai.stream("Tell me a story.")) {
process.stdout.write(chunk);
}
ai.streamResponse(prompt, opts?): Response
Returns a streaming text/plain Response — drop it straight into an API route.
// app/api/chat.ts
import { createAI } from "@wrnexus/ai";
const ai = createAI();
export const POST = async (ctx) => {
const { prompt } = await ctx.req.json();
return ai.streamResponse(prompt);
};
GenerateOptions
| Option | Type | Description | ||||
|---|---|---|---|---|---|---|
system | string | System prompt | ||||
model | string | Override the model for this call | ||||
maxTokens | number | Override max output tokens | ||||
thinking | boolean | Enable adaptive extended thinking (deeper reasoning) | ||||
effort | `"low" \ | "medium" \ | "high" \ | "xhigh" \ | "max"` | Reasoning effort / token spend |
messages | Message[] | Full history — supersedes prompt | ||||
signal | AbortSignal | Cancel the request |
temperature/top_pare intentionally not exposed — the current Claude
models reject them (400). Steer output with prompting instead.
AIError
Thrown on non-2xx responses or a model refusal. Carries .status and .type (e.g. "authentication_error", "rate_limit_error", "refusal").
import { AIError } from "@wrnexus/ai";
try {
await ai.generate("...");
} catch (e) {
if (e instanceof AIError && e.type === "rate_limit_error") {
/* back off */
}
}
Usage
// app/api/summarize.ts — summarize posted text
import { createAI } from "@wrnexus/ai";
const ai = createAI();
export const POST = async (ctx) => {
const { text } = await ctx.req.json().catch(() => ({}));
if (!text) return Response.json({ error: "Provide 'text'." }, { status: 400 });
const summary = await ai.generate(`Summarize in one sentence:\n\n${text}`, {
system: "You are a precise summarizer.",
});
return Response.json({ summary });
};
Requirements / Notes
- Bun-only. Uses
fetch,ReadableStream,TextDecoder/TextEncoder, and - Zero dependencies — no
@anthropic-ai/sdk; talks to the Messages API directly. - Defaults to
claude-opus-4-8. Pass{ model }for a different model (e.g.
reads ANTHROPIC_API_KEY from Bun.env (falls back to process.env).
"claude-sonnet-5" for speed/cost, "claude-haiku-4-5" for the fastest).
Complete TypeScript API
This declaration is generated from the exact published package and lists its exported functions, classes, interfaces, and types.
/**
* @wrnexus/ai — a tiny, zero-dependency Claude (Anthropic) client for WRNexusJS apps.
*
* Use it in API routes, jobs, or anywhere server-side to generate text with Claude.
* It talks to the Anthropic Messages API over `fetch` (no SDK dependency, Bun-native),
* and defaults to the most capable model, `claude-opus-4-8`.
*
* import { createAI } from "@wrnexus/ai";
* const ai = createAI(); // reads ANTHROPIC_API_KEY
* const text = await ai.generate("Write a haiku about Bun.");
*
* Streaming (great for API routes):
* export const POST = async (ctx) => ai.streamResponse(await ctx.req.text());
*/
type Role = "user" | "assistant";
interface Message {
role: Role;
content: string;
}
/** Reasoning effort — higher means deeper thinking + more tokens. */
type Effort = "low" | "medium" | "high" | "xhigh" | "max";
interface AIConfig {
/** Anthropic API key. Default: `ANTHROPIC_API_KEY` from the environment. */
apiKey?: string;
/** Model id. Default: `claude-opus-4-8` (the most capable Claude model). */
model?: string;
/** Default max output tokens. Default: 4096. */
maxTokens?: number;
/** API base URL. Default: `https://api.anthropic.com`. */
baseURL?: string;
/** `anthropic-version` header. Default: `2023-06-01`. */
version?: string;
}
interface GenerateOptions {
/** System prompt — sets the assistant's role/behavior. */
system?: string;
/** Override the model for this call. */
model?: string;
/** Override max output tokens for this call. */
maxTokens?: number;
/** Enable adaptive extended thinking (slower, deeper reasoning). */
thinking?: boolean;
/** Reasoning effort / token spend (`output_config.effort`). */
effort?: Effort;
/** Full message history — supersedes the `prompt` argument when provided. */
messages?: Message[];
/** Abort the request. */
signal?: AbortSignal;
}
/** Thrown when the API returns a non-2xx response or refuses the request. */
declare class AIError extends Error {
readonly status: number;
readonly type: string;
constructor(message: string, status?: number, type?: string);
}
interface AI {
/** Generate a full text response (non-streaming). */
generate(prompt: string | Message[], opts?: GenerateOptions): Promise<string>;
/** Stream the response as text deltas, as they arrive. */
stream(prompt: string | Message[], opts?: GenerateOptions): AsyncGenerator<string, void, unknown>;
/** Stream straight to a `Response` (text/plain) — drop-in for an API route return. */
streamResponse(prompt: string | Message[], opts?: GenerateOptions): Response;
}
/** Create a Claude client. Reads `ANTHROPIC_API_KEY` from the environment by default. */
declare function createAI(config?: AIConfig): AI;
export { type AI, type AIConfig, AIError, type Effort, type GenerateOptions, type Message, type Role, createAI };
Examples
Copy-ready examples taken from this package's published documentation.
Example 1
bun add @wrnexus/aiExample 2
ANTHROPIC_API_KEY=sk-ant-...Example 3
import { createAI } from "@wrnexus/ai";
const ai = createAI(); // or createAI({ apiKey, model, maxTokens, baseURL, version })Example 4
const text = await ai.generate("Write a haiku about Bun.");
const reply = await ai.generate(
[
{ role: "user", content: "My name is Ada." },
{ role: "assistant", content: "Hi Ada!" },
{ role: "user", content: "What's my name?" },
],
{ system: "You are concise." },
);