Anthropic AI
Full logic layer for integrating Claude into a Next.js App Router project using the official @anthropic-ai/sdk — no other provider mixed in, no UI included. Covers plain text generation, streaming, structured (schema-validated) output, tool calling with an agentic loop, rate limiting, and reusable prompt builders for summarization, translation, and extraction.
Everything non-streaming is exposed as a Server Action, callable directly from a Client Component with no fetch and no API route to write. Streaming is the one exception — it ships with its own ready-to-copy Route Handler.
Dependencies
@anthropic-ai/sdk: Official Anthropic client — every raw call inservice.tsgoes through it.zod: Schema validation for structured output and tool parameters.zod-to-json-schema: Converts Zod schemas into the JSON Schema shape Anthropic's toolinput_schemaexpects.
Folder structure
ai/anthropic/
├── actions.ts // "use server" — the public API, import this from components
├── service.ts // Raw Anthropic SDK calls — internal, never import from a component
├── client.ts // fetch-based helper for the one streaming endpoint
├── hooks.ts // useChat / useChatStream for Client Components
├── models.ts // Supported Claude models + validation
├── types.ts // Shared types
├── schemas.ts // Zod → Anthropic tool schema conversion (structured output)
├── constants.ts // Defaults and config values (models, tokens, retries, rate limits)
├── errors.ts // Normalized error handling (AIError, retryable flags)
├── rate-limit.ts // Rate limiting — in-memory by default, pluggable store
├── prompts/
│ ├── system.ts // System prompt builder + reusable rule sets
│ ├── summarize.ts // Summarization prompt + structured schema
│ ├── translate.ts // Translation prompt
│ ├── extract.ts // Extraction prompt + generic schemas
│ └── builder.ts // Message assembly, few-shot, history trimming
├── tools/
│ ├── calculator.ts // Example tool — safe arithmetic (no eval)
│ ├── search.ts // Example tool — pluggable search provider
│ └── registry.ts // Default tool registry
├── routes/
│ └── chat-stream.route.ts // Copy verbatim to app/api/ai/chat/stream/route.ts
└── env.example
How the files fit together
actions.ts is the only file most consumers need to import. It's marked "use server", so every function it exports can be called directly from a Server Component, a form action, or a Client Component — Next.js handles the client→server call automatically, with no fetch and no route to write. It wraps service.ts with prompt construction (from prompts/), rate limiting (rate-limit.ts), and the default tool registry (tools/registry.ts).
service.ts holds the raw, low-level Anthropic calls — client initialization, request validation, retries, and the actual SDK invocations for text generation, streaming, structured output, and tool calling. It's internal: nothing outside actions.ts and routes/chat-stream.route.ts should import it directly, and it's marked server-only so it can't accidentally end up in a client bundle.
client.ts exists for exactly one reason: streaming. A Server Action can't return a ReadableStream, so the streaming call is the only one that still needs a real HTTP endpoint. client.ts sends the fetch request to that endpoint and parses the newline-delimited JSON (NDJSON) response back into typed stream events.
hooks.ts gives Client Components two ready-made hooks: useChat (calls the chat Server Action directly — no network code involved) and useChatStream (uses client.ts under the hood for token-by-token streaming, with stop()/cancellation built in).
What actions.ts covers
chat/continueChat— general-purpose text generation, with optional coarse history trimming.summarizeText/summarizeStructured— plain-text or typed ({ summary, keyPoints }) summarization.translateText— translation with tone, glossary, and formatting-preservation options.extractData— structured data extraction against any Zod schema you pass in.askWithTools— runs the agentic tool-calling loop against the default tool registry (calculator,search) or a custom set of tools.isWithinContextBudget— checks a message set's real token count against a context budget.generateImage/generateEmbeddings— typed stubs that throw a clear "unsupported by this provider" error, since the official Anthropic SDK doesn't offer either.
Streaming (useChatStream / routes/chat-stream.route.ts) is deliberately not in actions.ts — Server Actions can't stream a response.
Usage examples
Chat from a Server Component or form
import { chat } from "@/ai/anthropic/actions";
const result = await chat({
messages: [{ role: "user", content: "What's the capital of Argentina?" }],
});
console.log(result.text);
Chat from a Client Component — no route needed
"use client";
import { useChat } from "@/ai/anthropic/hooks";
export function ChatBox() {
const { messages, sendUserMessage, isLoading } = useChat({
systemPrompt: "You are a helpful assistant.",
});
// wire sendUserMessage(input) to your form, render `messages`
}
Streaming from a Client Component
"use client";
import { useChatStream } from "@/ai/anthropic/hooks";
export function StreamingChatBox() {
const { messages, streamingText, isStreaming, sendUserMessage, stop } =
useChatStream({ systemPrompt: "You are a helpful assistant." });
// render `streamingText` while isStreaming, then it merges into `messages`
}
Structured extraction with a custom schema
import { z } from "zod";
import { extractData } from "@/ai/anthropic/actions";
const invoiceSchema = z.object({
invoiceNumber: z.string(),
totalAmount: z.number(),
dueDate: z.string().nullable(),
});
const { object } = await extractData(
emailBody,
invoiceSchema,
{ instructions: "invoice number, total amount, and due date", strict: true }
);
// object: { invoiceNumber: string; totalAmount: number; dueDate: string | null }
Summarize and translate
import { summarizeStructured, translateText } from "@/ai/anthropic/actions";
const { object } = await summarizeStructured(article);
// { summary: string; keyPoints: string[] }
const { text } = await translateText(text, {
targetLanguage: "Spanish",
preserveFormatting: true,
glossary: ["Acme Corp", "SKU"],
});
Tool calling with a custom search provider
import { createSearchTool } from "@/ai/anthropic/tools/search";
import { askWithTools } from "@/ai/anthropic/actions";
const mySearchTool = createSearchTool({
search: async ({ query }) => {
// call your search API / vector store / database here
return [{ title: "...", url: "...", snippet: "..." }];
},
});
const result = await askWithTools({
messages: [{ role: "user", content: "Find recent articles about X" }],
tools: [mySearchTool],
});
Rate limiting
import { chat } from "@/ai/anthropic/actions";
import { userIdentifier } from "@/ai/anthropic/rate-limit";
await chat(
{ messages },
{ rateLimit: { identifier: userIdentifier(session.userId) } }
);
// Throws AIError("rate_limit_exceeded") if the limit is hit.
What you can build with this## Final notes
This module is a general-purpose AI backbone, not a single-feature integration — the same pieces combine into different products depending on what you build on top of actions.ts:
- A chat product —
useChat/useChatStreamare already a complete conversational UI backend; add a system prompt and you have a support bot, an onboarding assistant, or an in-app copilot. - A content pipeline —
summarizeText/summarizeStructuredandtranslateTextcover automatic summarization and localization of user-generated or CMS content, callable straight from a Server Action on save. - A document/data extraction tool —
extractDatawith a custom Zod schema turns unstructured text (emails, PDFs already converted to text, support tickets) into typed, validated records ready to insert into a database. - An agent with real capabilities —
askWithToolsplus custom tools (beyond thecalculator/searchexamples) lets Claude call your own internal APIs, databases, or third-party services as part of answering a request.
Rate limiting, retries, and normalized errors are shared across all of the above, so adding a second AI feature to a project that already uses this module costs a new actions.ts export, not a new integration.