166 lines
5.5 KiB
Markdown
166 lines
5.5 KiB
Markdown
# @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
|
|
|
|
```bash
|
|
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.
|
|
|
|
```ts
|
|
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.
|
|
|
|
```ts
|
|
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.
|
|
|
|
```ts
|
|
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.
|
|
|
|
```ts
|
|
// 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"`).
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
// 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
|
|
|
|
```ts
|
|
// 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).
|