@wrnexus/ai
Server-side Anthropic client with generation and streaming.
Private registry access required
This package is not available from the public npm registry. After WorkRoot approves access and supplies private registry instructions, install the release-aligned package:
bun add @wrnexus/ai@0.2.19Request preview access. Never put registry tokens in source control.
A 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.
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 comes from the exact installed 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
Examples are taken from this package's installed documentation and must be evaluated with its requirements and stability notes.
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." },
);