W WRNexusJS
AI

@wrnexus/ai

Server-side Anthropic client with generation and streaming.

bun add @wrnexus/ai@0.2.15
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.

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):

FieldDefaultDescription
apiKeyANTHROPIC_API_KEYAnthropic API key
model"claude-opus-4-8"Model id
maxTokens4096Default max output tokens
baseURLhttps://api.anthropic.comAPI 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

OptionTypeDescription
systemstringSystem prompt
modelstringOverride the model for this call
maxTokensnumberOverride max output tokens
thinkingbooleanEnable adaptive extended thinking (deeper reasoning)
effort`"low" \"medium" \"high" \"xhigh" \"max"`Reasoning effort / token spend
messagesMessage[]Full history — supersedes prompt
signalAbortSignalCancel the request
temperature / top_p are 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
  • reads ANTHROPIC_API_KEY from Bun.env (falls back to process.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).

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/ai

Example 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." },
);