5.5 KiB
@wrnexus/ai
A tiny, zero-dependency Claude (Anthropic) client for WrNexus apps — generate and stream text with Claude from any server-side code.
Part of the WrNexus 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
wrnexusnpm 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
Return generated JSON from an API route
// 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 });
};
Stream a chat response to the browser
// app/api/chat.ts
import { createAI } from "@wrnexus/ai";
const ai = createAI({ model: "claude-sonnet-5" });
export const POST = async (ctx) => {
const { messages } = await ctx.req.json();
return ai.streamResponse(messages, {
system: "Answer using concise Markdown.",
maxTokens: 1_500,
});
};
Requirements / Notes
- Bun-only. Uses
fetch,ReadableStream,TextDecoder/TextEncoder, and readsANTHROPIC_API_KEYfromBun.env(falls back toprocess.env). - 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."claude-sonnet-5"for speed/cost,"claude-haiku-4-5"for the fastest).