Files
WRNexusJS/packages/ai/README.md
T
Clintchiz 586a6db8ff
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s
release: WRNexusJS 0.8.0
2026-08-02 23:18:51 +05:30

7.0 KiB

@wrnexus/ai

Provider-neutral AI orchestration for OpenAI, Anthropic, Google and local OpenAI-compatible models, with streaming, structured output, tools, embeddings, vector search/RAG, conversation persistence, templates, guardrails, usage events, fallback, rate limits and evaluation reports.

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 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_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 */
  }
}

Multi-provider client

createAIClient adds named-provider selection and fallback, capability discovery, validated JSON output, validated tool execution, abort-aware exponential retries, and per-provider circuit breakers. Attempt events intentionally contain metadata only: prompts, credentials, and raw model responses are never passed to telemetry.

import { anthropicProvider, createAIClient } from "@wrnexus/ai";

const ai = createAIClient({
  providers: [anthropicProvider()],
  retry: { attempts: 3, baseDelayMs: 100, maxDelayMs: 2_000 },
  circuitBreaker: { failureThreshold: 5, resetAfterMs: 30_000 },
});

const result = await ai.generateObject<{ title: string }>("Return a JSON title", {
  validate: (value): value is { title: string } =>
    typeof value === "object" && value !== null && "title" in value,
});

Providers can return normalized usage (inputTokens, outputTokens, totalTokens, and costUsd) and toolCalls. Use executeTools with a named, validated tool registry; unknown tools and invalid arguments are rejected before application code runs. deterministicAIProvider supplies ordered or computed offline responses for tests and examples without API keys or network calls.

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