Core patterns in TypeScript
The exam guide expects proficiency in Python and/or TypeScript. The worked examples in this book use Python; here are the same core patterns with the official TypeScript SDK (@anthropicai/sdk). Concepts are identical; mainly syntax differs. One practical difference to remember: SDK timeouts are in milliseconds in TypeScript and seconds in Python.
F.1 A basic call
- import Anthropic from "@anthropic-ai/sdk";
- const client = new Anthropic(); // reads ANTHROPIC_API_KEY
- const response = await client.messages.create({
- model: "claude-opus-5-5",
- max_tokens: 1024,
- system: "You are a concise support assistant.",
messages: [{ role: "user", content: "How do I reset my password?" }], });
if (response.stop_reason === "max_tokens") console.warn("truncated"); for (const block of response.content) {
- if (block.type === "text") console.log(block.text);
- }
- console.log(response.usage);
F.2 Streaming
- const stream = client.messages.stream({
- model: "claude-sonnet-5-5",
- max_tokens: 2048,
messages: [{ role: "user", content: "Explain prompt caching in two paragraphs." }], });
stream.on("text", (delta) => process.stdout.write(delta));
const final = await stream.finalMessage(); // full message, stop_reason, usage console.log("\noutput tokens:", final.usage.output_tokens);
Use finalMessage() rather than wrapping events in your own Promise; it handles completion, errors, and aborts.
F.3 Error handling
- try {
- await client.messages.create({ /* ... */ });
- } catch (error) {
- if (error instanceof Anthropic.BadRequestError) {
- // 400: fix the request, do not retry
- } else if (error instanceof Anthropic.AuthenticationError) {
- // 401: check the key
- } else if (error instanceof Anthropic.RateLimitError) {
- // 429: the SDK already retried; back off and requeue
- } else if (error instanceof Anthropic.APIError) {
- console.error(`API error ${error.status}`, error.message);
- } else {
- throw error;
- }
- }
All API error classes extend Anthropic.APIError with a typed status. Check the most specific class first.
F.4 Tools with the Tool Runner and Zod
import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod"; import { z } from "zod";
- const getOrder = betaZodTool({
- name: "orders_get_order",
- description:
"Look up an order's status, carrier, and estimated delivery by order ID. " + "Use when the customer asks where an order is. Does not modify orders.", inputSchema: z.object({
order_id: z.string().describe("Order ID such as ORD-12345"),
}),
run: async ({ order_id }) => JSON.stringify(await ordersDb.get(order_id)), });
- const final = await client.beta.messages.toolRunner({
- model: "claude-opus-5-5",
- max_tokens: 4096,
- tools: [getOrder],
messages: [{ role: "user", content: "Where is order ORD-881?" }], });
The runner validates each tool input against the Zod schema before calling run, then loops until Claude finishes.
F.5 A manual tool loop
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: "Where is order ORD-881 and who placed it?" }, ];
- for (let turn = 0; turn < 8; turn++) {
- const res = await client.messages.create({
- model: "claude-opus-5-5", max_tokens: 4096, tools, messages,
- });
- messages.push({ role: "assistant", content: res.content });
- if (res.stop_reason !== "tool_use") break;
- const results: Anthropic.ToolResultBlockParam[] = [];
- for (const block of res.content) {
- if (block.type !== "tool_use") continue;
- try {
- const output = await runTool(block.name, block.input);
results.push({ type: "tool_result", tool_use_id: block.id, content: JSON.stringify(output) });
} catch (e) {
results.push({ type: "tool_result", tool_use_id: block.id, content: String(e), is_error: true });
}
}
messages.push({ role: "user", content: results }); // all results in one message }
Use the SDK's types (Anthropic.MessageParam, Anthropic.ToolResultBlockParam) rather than defining your own interfaces.
F.6 Structured outputs with Zod
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod"; const Ticket = z.object({
category: z.enum(["billing", "login", "bug", "feature_request", "other"]), urgency: z.enum(["low", "normal", "high"]),
- summary: z.string(),
- });
- const res = await client.messages.parse({
- model: "claude-haiku-5-5",
- max_tokens: 512,
messages: [{ role: "user", content: `<ticket>${ticketText}</ticket>` }], output_config: { format: zodOutputFormat(Ticket) },
});
const ticket = res.parsed_output; // null if parsing failed: guard before use
F.7 Prompt caching
- const res = await client.messages.create({
- model: "claude-sonnet-5-5",
- max_tokens: 1024,
- system: [
- { type: "text", text: "You are Acme's policy assistant." },
{ type: "text", text: POLICY_MANUAL, cache_control: { type: "ephemeral" } },
Appendix f: core patterns in typescript
- ],
- messages: [{ role: "user", content: question }],
- });
console.log(res.usage.cache_read_input_tokens, res.usage.cache_creation_input_tokens);
F.8 Message Batches
for await (const item of await client.messages.batches.results(batch.id)) { if (item.result.type === "succeeded") {
save(item.custom_id, item.result.message.content);
} else {
retryQueue.push(item.custom_id); // errored, canceled, or expired }
}
F.9 Uploading a file
- import { toFile } from "@anthropic-ai/sdk";
- import fs from "fs";
- const uploaded = await client.files.upload({
file: await toFile(fs.createReadStream("handbook.pdf"), undefined, { type: "application/ pdf" }),
});
// reference later as { type: "document", source: { type: "file", file_id: uploaded.id } } Older SDK versions expose the Files API under client.beta.files with a beta header; check your installed version.
F.10 An MCP server in TypeScript
The official MCP TypeScript SDK follows the same model as the Python example in Chapter 9: create a server, register tools (with a name, description, and input schema), optionally register resources and prompts, and connect a transport (stdio for local use, Streamable HTTP for remote). Consult the MCP SDK's README for the current registration API, as it has evolved across versions.