page wrnexusai { seo { title = "@wrnexus/ai" description = "Server-side Anthropic client with generation and streaming." } view {
W WRNexusJS
AI · Package reference

@wrnexus/ai

Server-side Anthropic client with generation and streaming.

v0.5.13Private registryAI

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/ai@0.5.13

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

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

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
  • 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

Generated from the exact installed package declarations.

interface AIUsage {
    inputTokens?: number;
    outputTokens?: number;
    totalTokens?: number;
}
interface AIResult<T = string> {
    value: T;
    provider: string;
    model?: string;
    usage?: AIUsage;
    finishReason?: string;
    raw?: unknown;
}
interface AIProvider {
    name: string;
    generate(prompt: string | Message[], options?: GenerateOptions): Promise<AIResult<string>>;
    stream?(prompt: string | Message[], options?: GenerateOptions): AsyncGenerator<string, void, unknown>;
}
interface AIClientOptions {
    providers: AIProvider[];
    fallback?: boolean;
    onAttempt?: (provider: string, error?: unknown) => void | Promise<void>;
}
interface AIClient {
    generate(prompt: string | Message[], options?: GenerateOptions & {
        provider?: string;
    }): Promise<AIResult<string>>;
    generateObject<T>(prompt: string | Message[], options?: GenerateOptions & {
        provider?: string;
        validate?: (value: unknown) => value is T;
    }): Promise<AIResult<T>>;
    stream(prompt: string | Message[], options?: GenerateOptions & {
        provider?: string;
    }): AsyncGenerator<string, void, unknown>;
}
declare function anthropicProvider(config?: AIConfig): AIProvider;
declare function aiProvider(name: string, client: AI): AIProvider;
declare function createAIClient(options: AIClientOptions): AIClient;

/**
 * @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 AIClient, type AIClientOptions, type AIConfig, AIError, type AIProvider, type AIResult, type AIUsage, type Effort, type GenerateOptions, type Message, type Role, aiProvider, anthropicProvider, createAI, createAIClient };

Examples

Copy-ready examples from the installed package documentation.

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