Logo FormaUI

FormaUI

All providers

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 in service.ts goes 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 tool input_schema expects.

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 productuseChat/useChatStream are 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 pipelinesummarizeText/summarizeStructured and translateText cover automatic summarization and localization of user-generated or CMS content, callable straight from a Server Action on save.
  • A document/data extraction toolextractData with 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 capabilitiesaskWithTools plus custom tools (beyond the calculator/search examples) 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.