添加 12 个技能:agent-browser、claude-api、docx、frontend-design、pdf、pptx、skill-creator、slack-gif-creator、ui-ux-pro-max、web-artifacts-builder、webapp-testing、xlsx
This commit is contained in:
@@ -0,0 +1,372 @@
|
||||
# Claude API — TypeScript
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @anthropic-ai/sdk
|
||||
```
|
||||
|
||||
## Client Initialization
|
||||
|
||||
```typescript
|
||||
import Anthropic from "@anthropic-ai/sdk";
|
||||
|
||||
// Default — resolves credentials from the environment:
|
||||
// ANTHROPIC_API_KEY, or ANTHROPIC_AUTH_TOKEN, or an `ant auth login` profile.
|
||||
// Prefer this for local dev; don't hardcode a key.
|
||||
const client = new Anthropic();
|
||||
|
||||
// Explicit API key (only when you must inject a specific key)
|
||||
const client = new Anthropic({ apiKey: "your-api-key" });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Basic Message Request
|
||||
|
||||
```typescript
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
messages: [{ role: "user", content: "What is the capital of France?" }],
|
||||
});
|
||||
// response.content is ContentBlock[] — a discriminated union. Narrow by .type
|
||||
// before accessing .text (TypeScript will error on content[0].text without this).
|
||||
for (const block of response.content) {
|
||||
if (block.type === "text") {
|
||||
console.log(block.text);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## System Prompts
|
||||
|
||||
```typescript
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
system:
|
||||
"You are a helpful coding assistant. Always provide examples in Python.",
|
||||
messages: [{ role: "user", content: "How do I read a JSON file?" }],
|
||||
});
|
||||
```
|
||||
|
||||
### Mid-conversation system messages (beta, model-gated)
|
||||
|
||||
For operator instructions that arrive mid-conversation (mode switches, injected state), append `{role: "system", ...}` to `messages` instead of editing top-level `system` — this preserves the cached prefix and carries operator authority. Must follow a user message; cannot be `messages[0]`. Unsupported models return a 400 (`role 'system' is not supported on this model`). See `shared/prompt-caching.md` for when to use this vs. top-level `system`.
|
||||
|
||||
```typescript
|
||||
// SDK types for role:"system" in messages are pending — pass the beta header
|
||||
// directly until the SDK updates, then switch to client.beta.messages.create
|
||||
// with betas: ["mid-conversation-system-2026-04-07"].
|
||||
const response = await client.messages.create(
|
||||
{
|
||||
model: MODEL_ID, // must support mid-conversation system messages
|
||||
max_tokens: 16000,
|
||||
system: [
|
||||
{ type: "text", text: STABLE_SYSTEM, cache_control: { type: "ephemeral" } },
|
||||
],
|
||||
messages: [
|
||||
...history,
|
||||
{ role: "user", content: userMessage },
|
||||
// @ts-expect-error — role:"system" pending SDK types
|
||||
{ role: "system", content: "Terse mode enabled — keep responses under 40 words." },
|
||||
],
|
||||
},
|
||||
{ headers: { "anthropic-beta": "mid-conversation-system-2026-04-07" } },
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Vision (Images)
|
||||
|
||||
### URL
|
||||
|
||||
```typescript
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "image",
|
||||
source: { type: "url", url: "https://example.com/image.png" },
|
||||
},
|
||||
{ type: "text", text: "Describe this image" },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### Base64
|
||||
|
||||
```typescript
|
||||
import fs from "fs";
|
||||
|
||||
const imageData = fs.readFileSync("image.png").toString("base64");
|
||||
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "image",
|
||||
source: { type: "base64", media_type: "image/png", data: imageData },
|
||||
},
|
||||
{ type: "text", text: "What's in this image?" },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Prompt Caching
|
||||
|
||||
**Caching is a prefix match** — any byte change anywhere in the prefix invalidates everything after it. For placement patterns, architectural guidance (frozen system prompt, deterministic tool order, where to put volatile content), and the silent-invalidator audit checklist, read `shared/prompt-caching.md`.
|
||||
|
||||
### Automatic Caching (Recommended)
|
||||
|
||||
Use top-level `cache_control` to automatically cache the last cacheable block in the request:
|
||||
|
||||
```typescript
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
cache_control: { type: "ephemeral" }, // auto-caches the last cacheable block
|
||||
system: "You are an expert on this large document...",
|
||||
messages: [{ role: "user", content: "Summarize the key points" }],
|
||||
});
|
||||
```
|
||||
|
||||
### Manual Cache Control
|
||||
|
||||
For fine-grained control, add `cache_control` to specific content blocks:
|
||||
|
||||
```typescript
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
system: [
|
||||
{
|
||||
type: "text",
|
||||
text: "You are an expert on this large document...",
|
||||
cache_control: { type: "ephemeral" }, // default TTL is 5 minutes
|
||||
},
|
||||
],
|
||||
messages: [{ role: "user", content: "Summarize the key points" }],
|
||||
});
|
||||
|
||||
// With explicit TTL (time-to-live)
|
||||
const response2 = await client.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
system: [
|
||||
{
|
||||
type: "text",
|
||||
text: "You are an expert on this large document...",
|
||||
cache_control: { type: "ephemeral", ttl: "1h" }, // 1 hour TTL
|
||||
},
|
||||
],
|
||||
messages: [{ role: "user", content: "Summarize the key points" }],
|
||||
});
|
||||
```
|
||||
|
||||
### Verifying Cache Hits
|
||||
|
||||
```typescript
|
||||
console.log(response.usage.cache_creation_input_tokens); // tokens written to cache (~1.25x cost)
|
||||
console.log(response.usage.cache_read_input_tokens); // tokens served from cache (~0.1x cost)
|
||||
console.log(response.usage.input_tokens); // uncached tokens (full cost)
|
||||
```
|
||||
|
||||
If `cache_read_input_tokens` is zero across repeated identical-prefix requests, a silent invalidator is at work — `Date.now()` or a UUID in the system prompt, non-deterministic key ordering, or a varying tool set. See `shared/prompt-caching.md` for the full audit table.
|
||||
|
||||
---
|
||||
|
||||
## Extended Thinking
|
||||
|
||||
> **Fable 5, Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6:** Use adaptive thinking. `budget_tokens` is removed on Fable 5, Opus 4.8, and 4.7 (400 if sent); deprecated on Opus 4.6 and Sonnet 4.6.
|
||||
> **Older models:** Use `thinking: {type: "enabled", budget_tokens: N}` (must be < `max_tokens`, min 1024).
|
||||
|
||||
```typescript
|
||||
// Fable 5 / Opus 4.8 / 4.7 / 4.6: adaptive thinking (recommended)
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
thinking: { type: "adaptive" },
|
||||
output_config: { effort: "high" }, // low | medium | high | max
|
||||
messages: [
|
||||
{ role: "user", content: "Solve this math problem step by step..." },
|
||||
],
|
||||
});
|
||||
|
||||
for (const block of response.content) {
|
||||
if (block.type === "thinking") {
|
||||
console.log("Thinking:", block.thinking);
|
||||
} else if (block.type === "text") {
|
||||
console.log("Response:", block.text);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
Use the SDK's typed exception classes — never check error messages with string matching:
|
||||
|
||||
```typescript
|
||||
import Anthropic from "@anthropic-ai/sdk";
|
||||
|
||||
try {
|
||||
const response = await client.messages.create({...});
|
||||
} catch (error) {
|
||||
if (error instanceof Anthropic.BadRequestError) {
|
||||
console.error("Bad request:", error.message);
|
||||
} else if (error instanceof Anthropic.AuthenticationError) {
|
||||
console.error("Invalid API key");
|
||||
} else if (error instanceof Anthropic.RateLimitError) {
|
||||
console.error("Rate limited - retry later");
|
||||
} else if (error instanceof Anthropic.APIError) {
|
||||
console.error(`API error ${error.status}:`, error.message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
All classes extend `Anthropic.APIError` with a typed `status` field. Check from most specific to least specific. See [shared/error-codes.md](../../shared/error-codes.md) for the full error code reference.
|
||||
|
||||
---
|
||||
|
||||
## Multi-Turn Conversations
|
||||
|
||||
The API is stateless — send the full conversation history each time. Use `Anthropic.MessageParam[]` to type the messages array:
|
||||
|
||||
```typescript
|
||||
const messages: Anthropic.MessageParam[] = [
|
||||
{ role: "user", content: "My name is Alice." },
|
||||
{ role: "assistant", content: "Hello Alice! Nice to meet you." },
|
||||
{ role: "user", content: "What's my name?" },
|
||||
];
|
||||
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
messages: messages,
|
||||
});
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
|
||||
- Consecutive same-role messages are allowed — the API combines them into a single turn
|
||||
- First message must be `user`
|
||||
- Use SDK types (`Anthropic.MessageParam`, `Anthropic.Message`, `Anthropic.Tool`, etc.) for all API data structures — don't redefine equivalent interfaces
|
||||
|
||||
---
|
||||
|
||||
### Compaction (long conversations)
|
||||
|
||||
> **Beta, Fable 5, Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6.** When conversations approach the 200K context window, compaction automatically summarizes earlier context server-side. The API returns a `compaction` block; you must pass it back on subsequent requests — append `response.content`, not just the text.
|
||||
|
||||
```typescript
|
||||
import Anthropic from "@anthropic-ai/sdk";
|
||||
|
||||
const client = new Anthropic();
|
||||
const messages: Anthropic.Beta.BetaMessageParam[] = [];
|
||||
|
||||
async function chat(userMessage: string): Promise<string> {
|
||||
messages.push({ role: "user", content: userMessage });
|
||||
|
||||
const response = await client.beta.messages.create({
|
||||
betas: ["compact-2026-01-12"],
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
messages,
|
||||
context_management: {
|
||||
edits: [{ type: "compact_20260112" }],
|
||||
},
|
||||
});
|
||||
|
||||
// Append full content — compaction blocks must be preserved
|
||||
messages.push({ role: "assistant", content: response.content });
|
||||
|
||||
const textBlock = response.content.find(
|
||||
(b): b is Anthropic.Beta.BetaTextBlock => b.type === "text",
|
||||
);
|
||||
return textBlock?.text ?? "";
|
||||
}
|
||||
|
||||
// Compaction triggers automatically when context grows large
|
||||
console.log(await chat("Help me build a Python web scraper"));
|
||||
console.log(await chat("Add support for JavaScript-rendered pages"));
|
||||
console.log(await chat("Now add rate limiting and error handling"));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stop Reasons
|
||||
|
||||
The `stop_reason` field in the response indicates why the model stopped generating:
|
||||
|
||||
| Value | Meaning |
|
||||
| --------------- | --------------------------------------------------------------- |
|
||||
| `end_turn` | Claude finished its response naturally |
|
||||
| `max_tokens` | Hit the `max_tokens` limit — increase it or use streaming |
|
||||
| `stop_sequence` | Hit a custom stop sequence |
|
||||
| `tool_use` | Claude wants to call a tool — execute it and continue |
|
||||
| `pause_turn` | Model paused and can be resumed (agentic flows) |
|
||||
| `refusal` | Claude refused for safety reasons — check `stop_details` |
|
||||
|
||||
### Structured Stop Details
|
||||
|
||||
When `stop_reason` is `"refusal"`, the response includes a `stop_details` object with structured information about the refusal:
|
||||
|
||||
```typescript
|
||||
if (response.stop_reason === "refusal" && response.stop_details) {
|
||||
console.log(`Category: ${response.stop_details.category}`); // "cyber" | "bio" | null
|
||||
console.log(`Explanation: ${response.stop_details.explanation}`);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cost Optimization Strategies
|
||||
|
||||
### 1. Use Prompt Caching for Repeated Context
|
||||
|
||||
```typescript
|
||||
// Automatic caching (simplest — caches the last cacheable block)
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
cache_control: { type: "ephemeral" },
|
||||
system: largeDocumentText, // e.g., 50KB of context
|
||||
messages: [{ role: "user", content: "Summarize the key points" }],
|
||||
});
|
||||
|
||||
// First request: full cost
|
||||
// Subsequent requests: ~90% cheaper for cached portion
|
||||
```
|
||||
|
||||
### 2. Use Token Counting Before Requests
|
||||
|
||||
```typescript
|
||||
const countResponse = await client.messages.countTokens({
|
||||
model: "claude-opus-4-8",
|
||||
messages: messages,
|
||||
system: system,
|
||||
});
|
||||
|
||||
const estimatedInputCost = countResponse.input_tokens * 0.000005; // $5/1M tokens
|
||||
console.log(`Estimated input cost: $${estimatedInputCost.toFixed(4)}`);
|
||||
```
|
||||
@@ -0,0 +1,106 @@
|
||||
# Message Batches API — TypeScript
|
||||
|
||||
The Batches API (`POST /v1/messages/batches`) processes Messages API requests asynchronously at 50% of standard prices.
|
||||
|
||||
## Key Facts
|
||||
|
||||
- Up to 100,000 requests or 256 MB per batch
|
||||
- Most batches complete within 1 hour; maximum 24 hours
|
||||
- Results available for 29 days after creation
|
||||
- 50% cost reduction on all token usage
|
||||
- All Messages API features supported (vision, tools, caching, etc.)
|
||||
|
||||
---
|
||||
|
||||
## Create a Batch
|
||||
|
||||
```typescript
|
||||
import Anthropic from "@anthropic-ai/sdk";
|
||||
|
||||
const client = new Anthropic();
|
||||
|
||||
const messageBatch = await client.messages.batches.create({
|
||||
requests: [
|
||||
{
|
||||
custom_id: "request-1",
|
||||
params: {
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{ role: "user", content: "Summarize climate change impacts" },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
custom_id: "request-2",
|
||||
params: {
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{ role: "user", content: "Explain quantum computing basics" },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
console.log(`Batch ID: ${messageBatch.id}`);
|
||||
console.log(`Status: ${messageBatch.processing_status}`);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Poll for Completion
|
||||
|
||||
```typescript
|
||||
let batch;
|
||||
while (true) {
|
||||
batch = await client.messages.batches.retrieve(messageBatch.id);
|
||||
if (batch.processing_status === "ended") break;
|
||||
console.log(
|
||||
`Status: ${batch.processing_status}, processing: ${batch.request_counts.processing}`,
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, 60_000));
|
||||
}
|
||||
|
||||
console.log("Batch complete!");
|
||||
console.log(`Succeeded: ${batch.request_counts.succeeded}`);
|
||||
console.log(`Errored: ${batch.request_counts.errored}`);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Results
|
||||
|
||||
```typescript
|
||||
for await (const result of await client.messages.batches.results(
|
||||
messageBatch.id,
|
||||
)) {
|
||||
switch (result.result.type) {
|
||||
case "succeeded":
|
||||
console.log(
|
||||
`[${result.custom_id}] ${result.result.message.content[0].text.slice(0, 100)}`,
|
||||
);
|
||||
break;
|
||||
case "errored":
|
||||
if (result.result.error.type === "invalid_request") {
|
||||
console.log(`[${result.custom_id}] Validation error - fix and retry`);
|
||||
} else {
|
||||
console.log(`[${result.custom_id}] Server error - safe to retry`);
|
||||
}
|
||||
break;
|
||||
case "expired":
|
||||
console.log(`[${result.custom_id}] Expired - resubmit`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cancel a Batch
|
||||
|
||||
```typescript
|
||||
const cancelled = await client.messages.batches.cancel(messageBatch.id);
|
||||
console.log(`Status: ${cancelled.processing_status}`); // "canceling"
|
||||
```
|
||||
@@ -0,0 +1,98 @@
|
||||
# Files API — TypeScript
|
||||
|
||||
The Files API uploads files for use in Messages API requests. Reference files via `file_id` in content blocks, avoiding re-uploads across multiple API calls.
|
||||
|
||||
**Beta:** Pass `betas: ["files-api-2025-04-14"]` in your API calls (the SDK sets the required header automatically).
|
||||
|
||||
## Key Facts
|
||||
|
||||
- Maximum file size: 500 MB
|
||||
- Total storage: 100 GB per organization
|
||||
- Files persist until deleted
|
||||
- File operations (upload, list, delete) are free; content used in messages is billed as input tokens
|
||||
- Not available on Amazon Bedrock or Google Vertex AI
|
||||
|
||||
---
|
||||
|
||||
## Upload a File
|
||||
|
||||
```typescript
|
||||
import Anthropic, { toFile } from "@anthropic-ai/sdk";
|
||||
import fs from "fs";
|
||||
|
||||
const client = new Anthropic();
|
||||
|
||||
const uploaded = await client.beta.files.upload({
|
||||
file: await toFile(fs.createReadStream("report.pdf"), undefined, {
|
||||
type: "application/pdf",
|
||||
}),
|
||||
betas: ["files-api-2025-04-14"],
|
||||
});
|
||||
|
||||
console.log(`File ID: ${uploaded.id}`);
|
||||
console.log(`Size: ${uploaded.size_bytes} bytes`);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use a File in Messages
|
||||
|
||||
### PDF / Text Document
|
||||
|
||||
```typescript
|
||||
const response = await client.beta.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Summarize the key findings in this report." },
|
||||
{
|
||||
type: "document",
|
||||
source: { type: "file", file_id: uploaded.id },
|
||||
title: "Q4 Report",
|
||||
citations: { enabled: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
betas: ["files-api-2025-04-14"],
|
||||
});
|
||||
|
||||
console.log(response.content[0].text);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Manage Files
|
||||
|
||||
### List Files
|
||||
|
||||
```typescript
|
||||
const files = await client.beta.files.list({
|
||||
betas: ["files-api-2025-04-14"],
|
||||
});
|
||||
for (const f of files.data) {
|
||||
console.log(`${f.id}: ${f.filename} (${f.size_bytes} bytes)`);
|
||||
}
|
||||
```
|
||||
|
||||
### Delete a File
|
||||
|
||||
```typescript
|
||||
await client.beta.files.delete("file_011CNha8iCJcU1wXNR6q4V8w", {
|
||||
betas: ["files-api-2025-04-14"],
|
||||
});
|
||||
```
|
||||
|
||||
### Download a File
|
||||
|
||||
```typescript
|
||||
const response = await client.beta.files.download(
|
||||
"file_011CNha8iCJcU1wXNR6q4V8w",
|
||||
{ betas: ["files-api-2025-04-14"] },
|
||||
);
|
||||
const content = Buffer.from(await response.arrayBuffer());
|
||||
await fs.promises.writeFile("output.txt", content);
|
||||
```
|
||||
@@ -0,0 +1,178 @@
|
||||
# Streaming — TypeScript
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
const stream = client.messages.stream({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 64000,
|
||||
messages: [{ role: "user", content: "Write a story" }],
|
||||
});
|
||||
|
||||
for await (const event of stream) {
|
||||
if (
|
||||
event.type === "content_block_delta" &&
|
||||
event.delta.type === "text_delta"
|
||||
) {
|
||||
process.stdout.write(event.delta.text);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Handling Different Content Types
|
||||
|
||||
> **Fable 5 / Opus 4.8 / Opus 4.7 / Opus 4.6:** Use `thinking: {type: "adaptive"}`. On older models, use `thinking: {type: "enabled", budget_tokens: N}` instead.
|
||||
|
||||
```typescript
|
||||
const stream = client.messages.stream({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 64000,
|
||||
thinking: { type: "adaptive" },
|
||||
messages: [{ role: "user", content: "Analyze this problem" }],
|
||||
});
|
||||
|
||||
for await (const event of stream) {
|
||||
switch (event.type) {
|
||||
case "content_block_start":
|
||||
switch (event.content_block.type) {
|
||||
case "thinking":
|
||||
console.log("\n[Thinking...]");
|
||||
break;
|
||||
case "text":
|
||||
console.log("\n[Response:]");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "content_block_delta":
|
||||
switch (event.delta.type) {
|
||||
case "thinking_delta":
|
||||
process.stdout.write(event.delta.thinking);
|
||||
break;
|
||||
case "text_delta":
|
||||
process.stdout.write(event.delta.text);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Streaming with Tool Use (Tool Runner)
|
||||
|
||||
Use the tool runner with `stream: true`. The outer loop iterates over tool runner iterations (messages), the inner loop processes stream events:
|
||||
|
||||
```typescript
|
||||
import Anthropic from "@anthropic-ai/sdk";
|
||||
import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
const client = new Anthropic();
|
||||
|
||||
const getWeather = betaZodTool({
|
||||
name: "get_weather",
|
||||
description: "Get current weather for a location",
|
||||
inputSchema: z.object({
|
||||
location: z.string().describe("City and state, e.g., San Francisco, CA"),
|
||||
}),
|
||||
run: async ({ location }) => `72°F and sunny in ${location}`,
|
||||
});
|
||||
|
||||
const runner = client.beta.messages.toolRunner({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 64000,
|
||||
tools: [getWeather],
|
||||
messages: [
|
||||
{ role: "user", content: "What's the weather in Paris and London?" },
|
||||
],
|
||||
stream: true,
|
||||
});
|
||||
|
||||
// Outer loop: each tool runner iteration
|
||||
for await (const messageStream of runner) {
|
||||
// Inner loop: stream events for this iteration
|
||||
for await (const event of messageStream) {
|
||||
switch (event.type) {
|
||||
case "content_block_delta":
|
||||
switch (event.delta.type) {
|
||||
case "text_delta":
|
||||
process.stdout.write(event.delta.text);
|
||||
break;
|
||||
case "input_json_delta":
|
||||
// Tool input being streamed
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Getting the Final Message
|
||||
|
||||
```typescript
|
||||
const stream = client.messages.stream({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 64000,
|
||||
messages: [{ role: "user", content: "Hello" }],
|
||||
});
|
||||
|
||||
for await (const event of stream) {
|
||||
// Process events...
|
||||
}
|
||||
|
||||
const finalMessage = await stream.finalMessage();
|
||||
console.log(`Tokens used: ${finalMessage.usage.output_tokens}`);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stream Event Types
|
||||
|
||||
| Event Type | Description | When it fires |
|
||||
| --------------------- | --------------------------- | --------------------------------- |
|
||||
| `message_start` | Contains message metadata | Once at the beginning |
|
||||
| `content_block_start` | New content block beginning | When a text/tool_use block starts |
|
||||
| `content_block_delta` | Incremental content update | For each token/chunk |
|
||||
| `content_block_stop` | Content block complete | When a block finishes |
|
||||
| `message_delta` | Message-level updates | Contains `stop_reason`, usage |
|
||||
| `message_stop` | Message complete | Once at the end |
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always flush output** — Use `process.stdout.write()` for immediate display
|
||||
2. **Handle partial responses** — If the stream is interrupted, you may have incomplete content
|
||||
3. **Track token usage** — The `message_delta` event contains usage information
|
||||
4. **Use `finalMessage()`** — Get the complete `Anthropic.Message` object even when streaming. Don't wrap `.on()` events in `new Promise()` — `finalMessage()` handles all completion/error/abort states internally
|
||||
5. **Buffer for web UIs** — Consider buffering a few tokens before rendering to avoid excessive DOM updates
|
||||
6. **Use `stream.on("text", ...)` for deltas** — The `text` event provides just the delta string, simpler than manually filtering `content_block_delta` events
|
||||
7. **For agentic loops with streaming** — See the [Streaming Manual Loop](./tool-use.md#streaming-manual-loop) section in tool-use.md for combining `stream()` + `finalMessage()` with a tool-use loop
|
||||
|
||||
## Raw SSE Format
|
||||
|
||||
If using raw HTTP (not SDKs), the stream returns Server-Sent Events:
|
||||
|
||||
```
|
||||
event: message_start
|
||||
data: {"type":"message_start","message":{"id":"msg_...","type":"message",...}}
|
||||
|
||||
event: content_block_start
|
||||
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
|
||||
|
||||
event: content_block_delta
|
||||
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}
|
||||
|
||||
event: content_block_stop
|
||||
data: {"type":"content_block_stop","index":0}
|
||||
|
||||
event: message_delta
|
||||
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":12}}
|
||||
|
||||
event: message_stop
|
||||
data: {"type":"message_stop"}
|
||||
```
|
||||
@@ -0,0 +1,527 @@
|
||||
# Tool Use — TypeScript
|
||||
|
||||
For conceptual overview (tool definitions, tool choice, tips), see [shared/tool-use-concepts.md](../../shared/tool-use-concepts.md).
|
||||
|
||||
## Tool Runner (Recommended)
|
||||
|
||||
**Beta:** The tool runner is in beta in the TypeScript SDK.
|
||||
|
||||
Use `betaZodTool` with Zod schemas to define tools with a `run` function, then pass them to `client.beta.messages.toolRunner()`:
|
||||
|
||||
```typescript
|
||||
import Anthropic from "@anthropic-ai/sdk";
|
||||
import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
const client = new Anthropic();
|
||||
|
||||
const getWeather = betaZodTool({
|
||||
name: "get_weather",
|
||||
description: "Get current weather for a location",
|
||||
inputSchema: z.object({
|
||||
location: z.string().describe("City and state, e.g., San Francisco, CA"),
|
||||
unit: z.enum(["celsius", "fahrenheit"]).optional(),
|
||||
}),
|
||||
run: async (input) => {
|
||||
// Your implementation here
|
||||
return `72°F and sunny in ${input.location}`;
|
||||
},
|
||||
});
|
||||
|
||||
// The tool runner handles the agentic loop and returns the final message
|
||||
const finalMessage = await client.beta.messages.toolRunner({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
tools: [getWeather],
|
||||
messages: [{ role: "user", content: "What's the weather in Paris?" }],
|
||||
});
|
||||
|
||||
console.log(finalMessage.content);
|
||||
```
|
||||
|
||||
**Key benefits of the tool runner:**
|
||||
|
||||
- No manual loop — the SDK handles calling tools and feeding results back
|
||||
- Type-safe tool inputs via Zod schemas
|
||||
- Tool schemas are generated automatically from Zod definitions
|
||||
- Iteration stops automatically when Claude has no more tool calls
|
||||
|
||||
---
|
||||
|
||||
## Manual Agentic Loop
|
||||
|
||||
Use this when you need fine-grained control (custom logging, conditional tool execution, streaming individual iterations, human-in-the-loop approval):
|
||||
|
||||
```typescript
|
||||
import Anthropic from "@anthropic-ai/sdk";
|
||||
|
||||
const client = new Anthropic();
|
||||
const tools: Anthropic.Tool[] = [...]; // Your tool definitions
|
||||
let messages: Anthropic.MessageParam[] = [{ role: "user", content: userInput }];
|
||||
|
||||
while (true) {
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
tools: tools,
|
||||
messages: messages,
|
||||
});
|
||||
|
||||
if (response.stop_reason === "end_turn") break;
|
||||
|
||||
// Server-side tool hit iteration limit; append assistant turn and re-send to continue
|
||||
if (response.stop_reason === "pause_turn") {
|
||||
messages.push({ role: "assistant", content: response.content });
|
||||
continue;
|
||||
}
|
||||
|
||||
const toolUseBlocks = response.content.filter(
|
||||
(b): b is Anthropic.ToolUseBlock => b.type === "tool_use",
|
||||
);
|
||||
|
||||
messages.push({ role: "assistant", content: response.content });
|
||||
|
||||
const toolResults: Anthropic.ToolResultBlockParam[] = [];
|
||||
for (const tool of toolUseBlocks) {
|
||||
const result = await executeTool(tool.name, tool.input);
|
||||
toolResults.push({
|
||||
type: "tool_result",
|
||||
tool_use_id: tool.id,
|
||||
content: result,
|
||||
});
|
||||
}
|
||||
|
||||
messages.push({ role: "user", content: toolResults });
|
||||
}
|
||||
```
|
||||
|
||||
### Streaming Manual Loop
|
||||
|
||||
Use `client.messages.stream()` + `finalMessage()` instead of `.create()` when you need streaming within a manual loop. Text deltas are streamed on each iteration; `finalMessage()` collects the complete `Message` so you can inspect `stop_reason` and extract tool-use blocks:
|
||||
|
||||
```typescript
|
||||
import Anthropic from "@anthropic-ai/sdk";
|
||||
|
||||
const client = new Anthropic();
|
||||
const tools: Anthropic.Tool[] = [...];
|
||||
let messages: Anthropic.MessageParam[] = [{ role: "user", content: userInput }];
|
||||
|
||||
while (true) {
|
||||
const stream = client.messages.stream({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 64000,
|
||||
tools,
|
||||
messages,
|
||||
});
|
||||
|
||||
// Stream text deltas on each iteration
|
||||
stream.on("text", (delta) => {
|
||||
process.stdout.write(delta);
|
||||
});
|
||||
|
||||
// finalMessage() resolves with the complete Message — no need to
|
||||
// manually wire up .on("message") / .on("error") / .on("abort")
|
||||
const message = await stream.finalMessage();
|
||||
|
||||
if (message.stop_reason === "end_turn") break;
|
||||
|
||||
// Server-side tool hit iteration limit; append assistant turn and re-send to continue
|
||||
if (message.stop_reason === "pause_turn") {
|
||||
messages.push({ role: "assistant", content: message.content });
|
||||
continue;
|
||||
}
|
||||
|
||||
const toolUseBlocks = message.content.filter(
|
||||
(b): b is Anthropic.ToolUseBlock => b.type === "tool_use",
|
||||
);
|
||||
|
||||
messages.push({ role: "assistant", content: message.content });
|
||||
|
||||
const toolResults: Anthropic.ToolResultBlockParam[] = [];
|
||||
for (const tool of toolUseBlocks) {
|
||||
const result = await executeTool(tool.name, tool.input);
|
||||
toolResults.push({
|
||||
type: "tool_result",
|
||||
tool_use_id: tool.id,
|
||||
content: result,
|
||||
});
|
||||
}
|
||||
|
||||
messages.push({ role: "user", content: toolResults });
|
||||
}
|
||||
```
|
||||
|
||||
> **Important:** Don't wrap `.on()` events in `new Promise()` to collect the final message — use `stream.finalMessage()` instead. The SDK handles all error/abort/completion states internally.
|
||||
|
||||
> **Error handling in the loop:** Use the SDK's typed exceptions (e.g., `Anthropic.RateLimitError`, `Anthropic.APIError`) — see [Error Handling](./README.md#error-handling) for examples. Don't check error messages with string matching.
|
||||
|
||||
> **SDK types:** Use `Anthropic.MessageParam`, `Anthropic.Tool`, `Anthropic.ToolUseBlock`, `Anthropic.ToolResultBlockParam`, `Anthropic.Message`, etc. for all API-related data structures. Don't redefine equivalent interfaces.
|
||||
|
||||
---
|
||||
|
||||
## Handling Tool Results
|
||||
|
||||
```typescript
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
tools: tools,
|
||||
messages: [{ role: "user", content: "What's the weather in Paris?" }],
|
||||
});
|
||||
|
||||
for (const block of response.content) {
|
||||
if (block.type === "tool_use") {
|
||||
const result = await executeTool(block.name, block.input);
|
||||
|
||||
const followup = await client.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
tools: tools,
|
||||
messages: [
|
||||
{ role: "user", content: "What's the weather in Paris?" },
|
||||
{ role: "assistant", content: response.content },
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "tool_result", tool_use_id: block.id, content: result },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tool Choice
|
||||
|
||||
```typescript
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
tools: tools,
|
||||
tool_choice: { type: "tool", name: "get_weather" },
|
||||
messages: [{ role: "user", content: "What's the weather in Paris?" }],
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Server-Side Tools
|
||||
|
||||
Version-suffixed `type` literals; `name` is fixed per interface. Pass plain object literals — the `ToolUnion` type is satisfied structurally. **The `name`/`type` pair must match the interface**: mixing `str_replace_based_edit_tool` (20250728 name) with `text_editor_20250124` (which expects `str_replace_editor`) is a TS2322.
|
||||
|
||||
**Don't type-annotate as `Tool[]`** — `Tool` is just the custom-tool variant. Let structural typing infer from the `tools` param, or annotate as `Anthropic.Messages.ToolUnion[]` if you must:
|
||||
|
||||
```typescript
|
||||
// ✓ let inference work — no annotation
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
tools: [
|
||||
{ type: "text_editor_20250728", name: "str_replace_based_edit_tool" },
|
||||
{ type: "bash_20250124", name: "bash" },
|
||||
{ type: "web_search_20260209", name: "web_search" },
|
||||
{ type: "code_execution_20260120", name: "code_execution" },
|
||||
],
|
||||
messages: [{ role: "user", content: "..." }],
|
||||
});
|
||||
|
||||
// ✗ this is a TS2352 — Tool is the CUSTOM tool variant only
|
||||
// const tools: Anthropic.Tool[] = [{ type: "text_editor_20250728", ... }]
|
||||
```
|
||||
|
||||
| Interface | `name` | `type` |
|
||||
|---|---|---|
|
||||
| `ToolTextEditor20250124` | `str_replace_editor` | `text_editor_20250124` |
|
||||
| `ToolTextEditor20250429` | `str_replace_based_edit_tool` | `text_editor_20250429` |
|
||||
| `ToolTextEditor20250728` | `str_replace_based_edit_tool` | `text_editor_20250728` |
|
||||
| `ToolBash20250124` | `bash` | `bash_20250124` |
|
||||
| `WebSearchTool20260209` | `web_search` | `web_search_20260209` |
|
||||
| `WebFetchTool20260209` | `web_fetch` | `web_fetch_20260209` |
|
||||
| `CodeExecutionTool20260120` | `code_execution` | `code_execution_20260120` |
|
||||
|
||||
**Don't mix beta and non-beta types**: if you call `client.beta.messages.create()`, the response `content` is `BetaContentBlock[]` — you cannot pass that to a non-beta `ContentBlockParam[]` without narrowing each element.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Code Execution
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```typescript
|
||||
import Anthropic from "@anthropic-ai/sdk";
|
||||
|
||||
const client = new Anthropic();
|
||||
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content:
|
||||
"Calculate the mean and standard deviation of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]",
|
||||
},
|
||||
],
|
||||
tools: [{ type: "code_execution_20260120", name: "code_execution" }],
|
||||
});
|
||||
```
|
||||
|
||||
### Reading Local Files (ESM note)
|
||||
|
||||
`__dirname` doesn't exist in ES modules. For script-relative paths use `import.meta.url`:
|
||||
|
||||
```typescript
|
||||
import { readFileSync } from "fs";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname, join } from "path";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const pdfBytes = readFileSync(join(__dirname, "sample.pdf"));
|
||||
```
|
||||
|
||||
Or use a CWD-relative path if the script runs from a known directory: `readFileSync("./sample.pdf")`.
|
||||
|
||||
### Upload Files for Analysis
|
||||
|
||||
```typescript
|
||||
import Anthropic, { toFile } from "@anthropic-ai/sdk";
|
||||
import { createReadStream } from "fs";
|
||||
|
||||
const client = new Anthropic();
|
||||
|
||||
// 1. Upload a file
|
||||
const uploaded = await client.beta.files.upload({
|
||||
file: await toFile(createReadStream("sales_data.csv"), undefined, {
|
||||
type: "text/csv",
|
||||
}),
|
||||
betas: ["files-api-2025-04-14"],
|
||||
});
|
||||
|
||||
// 2. Pass to code execution
|
||||
// Code execution is GA; Files API is still beta (pass via RequestOptions)
|
||||
const response = await client.messages.create(
|
||||
{
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Analyze this sales data. Show trends and create a visualization.",
|
||||
},
|
||||
{ type: "container_upload", file_id: uploaded.id },
|
||||
],
|
||||
},
|
||||
],
|
||||
tools: [{ type: "code_execution_20260120", name: "code_execution" }],
|
||||
},
|
||||
{ headers: { "anthropic-beta": "files-api-2025-04-14" } },
|
||||
);
|
||||
```
|
||||
|
||||
### Retrieve Generated Files
|
||||
|
||||
```typescript
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
|
||||
const OUTPUT_DIR = "./claude_outputs";
|
||||
await fs.promises.mkdir(OUTPUT_DIR, { recursive: true });
|
||||
|
||||
for (const block of response.content) {
|
||||
if (block.type === "bash_code_execution_tool_result") {
|
||||
const result = block.content;
|
||||
if (result.type === "bash_code_execution_result" && result.content) {
|
||||
for (const fileRef of result.content) {
|
||||
if (fileRef.type === "bash_code_execution_output") {
|
||||
const metadata = await client.beta.files.retrieveMetadata(
|
||||
fileRef.file_id,
|
||||
);
|
||||
const downloadResponse = await client.beta.files.download(fileRef.file_id);
|
||||
const fileBytes = Buffer.from(await downloadResponse.arrayBuffer());
|
||||
const safeName = path.basename(metadata.filename);
|
||||
if (!safeName || safeName === "." || safeName === "..") {
|
||||
console.warn(`Skipping invalid filename: ${metadata.filename}`);
|
||||
continue;
|
||||
}
|
||||
const outputPath = path.join(OUTPUT_DIR, safeName);
|
||||
await fs.promises.writeFile(outputPath, fileBytes);
|
||||
console.log(`Saved: ${outputPath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Container Reuse
|
||||
|
||||
```typescript
|
||||
// First request: set up environment
|
||||
const response1 = await client.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Install tabulate and create data.json with sample user data",
|
||||
},
|
||||
],
|
||||
tools: [{ type: "code_execution_20260120", name: "code_execution" }],
|
||||
});
|
||||
|
||||
// Reuse container
|
||||
// container is nullable — set only when using server-side code execution
|
||||
const containerId = response1.container!.id;
|
||||
|
||||
const response2 = await client.messages.create({
|
||||
container: containerId,
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Read data.json and display as a formatted table",
|
||||
},
|
||||
],
|
||||
tools: [{ type: "code_execution_20260120", name: "code_execution" }],
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Memory Tool
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```typescript
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Remember that my preferred language is TypeScript.",
|
||||
},
|
||||
],
|
||||
tools: [{ type: "memory_20250818", name: "memory" }],
|
||||
});
|
||||
```
|
||||
|
||||
### SDK Memory Helper
|
||||
|
||||
Use `betaMemoryTool` with a `MemoryToolHandlers` implementation:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
betaMemoryTool,
|
||||
type MemoryToolHandlers,
|
||||
} from "@anthropic-ai/sdk/helpers/beta/memory";
|
||||
|
||||
const handlers: MemoryToolHandlers = {
|
||||
async view(command) { ... },
|
||||
async create(command) { ... },
|
||||
async str_replace(command) { ... },
|
||||
async insert(command) { ... },
|
||||
async delete(command) { ... },
|
||||
async rename(command) { ... },
|
||||
};
|
||||
|
||||
const memory = betaMemoryTool(handlers);
|
||||
|
||||
const runner = client.beta.messages.toolRunner({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
tools: [memory],
|
||||
messages: [{ role: "user", content: "Remember my preferences" }],
|
||||
});
|
||||
|
||||
for await (const message of runner) {
|
||||
console.log(message);
|
||||
}
|
||||
```
|
||||
|
||||
For full implementation examples, use WebFetch:
|
||||
|
||||
- `https://github.com/anthropics/anthropic-sdk-typescript/blob/main/examples/tools-helpers-memory.ts`
|
||||
|
||||
---
|
||||
|
||||
## Structured Outputs
|
||||
|
||||
### JSON Outputs (Zod — Recommended)
|
||||
|
||||
```typescript
|
||||
import Anthropic from "@anthropic-ai/sdk";
|
||||
import { z } from "zod";
|
||||
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
|
||||
|
||||
const ContactInfoSchema = z.object({
|
||||
name: z.string(),
|
||||
email: z.string(),
|
||||
plan: z.string(),
|
||||
interests: z.array(z.string()),
|
||||
demo_requested: z.boolean(),
|
||||
});
|
||||
|
||||
const client = new Anthropic();
|
||||
|
||||
const response = await client.messages.parse({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content:
|
||||
"Extract: Jane Doe (jane@co.com) wants Enterprise, interested in API and SDKs, wants a demo.",
|
||||
},
|
||||
],
|
||||
output_config: {
|
||||
format: zodOutputFormat(ContactInfoSchema),
|
||||
},
|
||||
});
|
||||
|
||||
// parsed_output is null if parsing failed — assert or guard
|
||||
console.log(response.parsed_output!.name); // "Jane Doe"
|
||||
```
|
||||
|
||||
### Strict Tool Use
|
||||
|
||||
```typescript
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-8",
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Book a flight to Tokyo for 2 passengers on March 15",
|
||||
},
|
||||
],
|
||||
tools: [
|
||||
{
|
||||
name: "book_flight",
|
||||
description: "Book a flight to a destination",
|
||||
strict: true,
|
||||
input_schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
destination: { type: "string" },
|
||||
date: { type: "string", format: "date" },
|
||||
passengers: {
|
||||
type: "integer",
|
||||
enum: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
},
|
||||
},
|
||||
required: ["destination", "date", "passengers"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,357 @@
|
||||
# Managed Agents — TypeScript
|
||||
|
||||
> **Bindings not shown here:** This README covers the most common managed-agents flows for TypeScript. If you need a class, method, namespace, field, or behavior that isn't shown, WebFetch the TypeScript SDK repo **or the relevant docs page** from `shared/live-sources.md` rather than guess. Do not extrapolate from cURL shapes or another language's SDK.
|
||||
|
||||
> **Agents are persistent — create once, reference by ID.** Store the agent ID returned by `agents.create` and pass it to every subsequent `sessions.create`; do not call `agents.create` in the request path. The Anthropic CLI is one convenient way to create agents and environments from version-controlled YAML — its URL is in `shared/live-sources.md`. The examples below show in-code creation for completeness; in production the create call belongs in setup, not in the request path.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @anthropic-ai/sdk
|
||||
```
|
||||
|
||||
## Client Initialization
|
||||
|
||||
```typescript
|
||||
import Anthropic from "@anthropic-ai/sdk";
|
||||
|
||||
// Default — resolves credentials from the environment:
|
||||
// ANTHROPIC_API_KEY, or ANTHROPIC_AUTH_TOKEN, or an `ant auth login` profile.
|
||||
// Prefer this for local dev; don't hardcode a key.
|
||||
const client = new Anthropic();
|
||||
|
||||
// Explicit API key (only when you must inject a specific key)
|
||||
const client = new Anthropic({ apiKey: "your-api-key" });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create an Environment
|
||||
|
||||
```typescript
|
||||
const environment = await client.beta.environments.create(
|
||||
{
|
||||
name: "my-dev-env",
|
||||
config: {
|
||||
type: "cloud",
|
||||
networking: { type: "unrestricted" },
|
||||
},
|
||||
},
|
||||
);
|
||||
console.log(environment.id); // env_...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create an Agent (required first step)
|
||||
|
||||
> ⚠️ **There is no inline agent config.** `model`/`system`/`tools` live on the agent object, not the session. Always start with `agents.create()` — the session only takes `agent: { type: "agent", id: agent.id }`.
|
||||
|
||||
### Minimal
|
||||
|
||||
```typescript
|
||||
// 1. Create the agent (reusable, versioned)
|
||||
const agent = await client.beta.agents.create(
|
||||
{
|
||||
name: "Coding Assistant",
|
||||
model: "claude-opus-4-8",
|
||||
tools: [{ type: "agent_toolset_20260401", default_config: { enabled: true } }],
|
||||
},
|
||||
);
|
||||
|
||||
// 2. Start a session
|
||||
const session = await client.beta.sessions.create(
|
||||
{
|
||||
agent: { type: "agent", id: agent.id, version: agent.version },
|
||||
environment_id: environment.id,
|
||||
},
|
||||
);
|
||||
console.log(session.id, session.status);
|
||||
```
|
||||
|
||||
### With system prompt and custom tools
|
||||
|
||||
```typescript
|
||||
const agent = await client.beta.agents.create(
|
||||
{
|
||||
name: "Code Reviewer",
|
||||
model: "claude-opus-4-8",
|
||||
system: "You are a senior code reviewer.",
|
||||
tools: [
|
||||
{ type: "agent_toolset_20260401", default_config: { enabled: true } },
|
||||
{
|
||||
type: "custom",
|
||||
name: "run_tests",
|
||||
description: "Run the test suite",
|
||||
input_schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
test_path: { type: "string", description: "Path to test file" },
|
||||
},
|
||||
required: ["test_path"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
const session = await client.beta.sessions.create(
|
||||
{
|
||||
agent: { type: "agent", id: agent.id, version: agent.version },
|
||||
environment_id: environment.id,
|
||||
title: "Code review session",
|
||||
resources: [
|
||||
{
|
||||
type: "github_repository",
|
||||
url: "https://github.com/owner/repo",
|
||||
mount_path: "/workspace/repo",
|
||||
authorization_token: process.env.GITHUB_TOKEN,
|
||||
branch: "main",
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Send a User Message
|
||||
|
||||
```typescript
|
||||
await client.beta.sessions.events.send(
|
||||
session.id,
|
||||
{
|
||||
events: [
|
||||
{
|
||||
type: "user.message",
|
||||
content: [{ type: "text", text: "Review the auth module" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
> 💡 **Stream-first:** Open the stream *before* (or concurrently with) sending the message. The stream only delivers events that occur after it opens — stream-after-send means early events arrive buffered in one batch. See [Steering Patterns](../../shared/managed-agents-events.md#steering-patterns).
|
||||
|
||||
---
|
||||
|
||||
## Stream Events (SSE)
|
||||
|
||||
```typescript
|
||||
// Stream-first: open stream and send concurrently
|
||||
const [events] = await Promise.all([
|
||||
collectStream(session.id),
|
||||
client.beta.sessions.events.send(
|
||||
session.id,
|
||||
{ events: [{ type: "user.message", content: [{ type: "text", text: "..." }] }] },
|
||||
),
|
||||
]);
|
||||
|
||||
// Standalone stream iteration:
|
||||
const stream = await client.beta.sessions.events.stream(
|
||||
session.id,
|
||||
);
|
||||
|
||||
for await (const event of stream) {
|
||||
switch (event.type) {
|
||||
case "agent.message":
|
||||
for (const block of event.content) {
|
||||
if (block.type === "text") {
|
||||
process.stdout.write(block.text);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "agent.custom_tool_use":
|
||||
// Custom tool invocation — session is now idle
|
||||
console.log(`\nCustom tool call: ${event.name}`);
|
||||
console.log(`Input: ${JSON.stringify(event.input)}`);
|
||||
break;
|
||||
case "session.status_idle":
|
||||
console.log("\n--- Agent idle ---");
|
||||
break;
|
||||
case "session.status_terminated":
|
||||
console.log("\n--- Session terminated ---");
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Provide Custom Tool Result
|
||||
|
||||
```typescript
|
||||
await client.beta.sessions.events.send(
|
||||
session.id,
|
||||
{
|
||||
events: [
|
||||
{
|
||||
type: "user.custom_tool_result",
|
||||
custom_tool_use_id: "sevt_abc123",
|
||||
content: [{ type: "text", text: "All 42 tests passed." }],
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Poll Events
|
||||
|
||||
```typescript
|
||||
const events = await client.beta.sessions.events.list(
|
||||
session.id,
|
||||
);
|
||||
for (const event of events.data) {
|
||||
console.log(`${event.type}: ${event.id}`);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Full Streaming Loop with Custom Tools
|
||||
|
||||
```typescript
|
||||
function runCustomTool(toolName: string, toolInput: unknown): string {
|
||||
if (toolName === "run_tests") {
|
||||
// Your tool implementation here
|
||||
return "All tests passed.";
|
||||
}
|
||||
return `Unknown tool: ${toolName}`;
|
||||
}
|
||||
|
||||
async function runSession(client: Anthropic, sessionId: string) {
|
||||
while (true) {
|
||||
const stream = await client.beta.sessions.events.stream(
|
||||
sessionId,
|
||||
);
|
||||
|
||||
const toolCalls: Anthropic.Beta.Sessions.BetaManagedAgentsAgentCustomToolUseEvent[] = [];
|
||||
|
||||
for await (const event of stream) {
|
||||
if (event.type === "agent.message") {
|
||||
for (const block of event.content) {
|
||||
if (block.type === "text") {
|
||||
process.stdout.write(block.text);
|
||||
}
|
||||
}
|
||||
} else if (event.type === "agent.custom_tool_use") {
|
||||
toolCalls.push(event);
|
||||
} else if (event.type === "session.status_idle") {
|
||||
break;
|
||||
} else if (event.type === "session.status_terminated") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (toolCalls.length === 0) break;
|
||||
|
||||
// Process custom tool calls
|
||||
const results = toolCalls.map((call) => ({
|
||||
type: "user.custom_tool_result" as const,
|
||||
custom_tool_use_id: call.id,
|
||||
content: [{ type: "text" as const, text: runCustomTool(call.name, call.input) }],
|
||||
}));
|
||||
|
||||
await client.beta.sessions.events.send(
|
||||
sessionId,
|
||||
{ events: results },
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Upload a File
|
||||
|
||||
```typescript
|
||||
import fs from "fs";
|
||||
|
||||
const file = await client.beta.files.upload({
|
||||
file: fs.createReadStream("data.csv"),
|
||||
});
|
||||
|
||||
// Use in a session
|
||||
const session = await client.beta.sessions.create(
|
||||
{
|
||||
agent: { type: "agent", id: agent.id, version: agent.version },
|
||||
environment_id: environment.id,
|
||||
resources: [{ type: "file", file_id: file.id, mount_path: "/workspace/data.csv" }],
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## List and Download Session Files
|
||||
|
||||
List files the agent wrote to `/mnt/session/outputs/` during a session, then download them.
|
||||
|
||||
```typescript
|
||||
import fs from "fs";
|
||||
|
||||
// List files associated with a session
|
||||
const files = await client.beta.files.list({
|
||||
scope_id: session.id,
|
||||
betas: ["managed-agents-2026-04-01"],
|
||||
});
|
||||
for (const f of files.data) {
|
||||
console.log(f.filename, f.size_bytes);
|
||||
|
||||
// Download and save to disk
|
||||
const resp = await client.beta.files.download(f.id);
|
||||
const buffer = Buffer.from(await resp.arrayBuffer());
|
||||
fs.writeFileSync(f.filename, buffer);
|
||||
}
|
||||
```
|
||||
|
||||
> 💡 There's a brief indexing lag (~1–3s) between `session.status_idle` and output files appearing in `files.list`. Retry once or twice if the list is empty.
|
||||
|
||||
---
|
||||
|
||||
## Session Management
|
||||
|
||||
```typescript
|
||||
// Get session details
|
||||
const session = await client.beta.sessions.retrieve("sesn_011CZxAbc123Def456");
|
||||
console.log(session.status, session.usage);
|
||||
|
||||
// List sessions
|
||||
const sessions = await client.beta.sessions.list();
|
||||
|
||||
// Delete a session
|
||||
await client.beta.sessions.delete("sesn_011CZxAbc123Def456");
|
||||
|
||||
// Archive a session
|
||||
await client.beta.sessions.archive("sesn_011CZxAbc123Def456");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MCP Server Integration
|
||||
|
||||
```typescript
|
||||
// Agent declares MCP server (no auth here — auth goes in a vault)
|
||||
const agent = await client.beta.agents.create({
|
||||
name: "MCP Agent",
|
||||
model: "claude-opus-4-8",
|
||||
mcp_servers: [
|
||||
{ type: "url", name: "my-tools", url: "https://my-mcp-server.example.com/sse" },
|
||||
],
|
||||
tools: [
|
||||
{ type: "agent_toolset_20260401", default_config: { enabled: true } },
|
||||
{ type: "mcp_toolset", mcp_server_name: "my-tools" },
|
||||
],
|
||||
});
|
||||
|
||||
// Session attaches vault(s) containing credentials for those MCP server URLs
|
||||
const session = await client.beta.sessions.create({
|
||||
agent: agent.id,
|
||||
environment_id: environment.id,
|
||||
vault_ids: [vault.id],
|
||||
});
|
||||
```
|
||||
|
||||
See `shared/managed-agents-tools.md` §Vaults for creating vaults and adding credentials.
|
||||
Reference in New Issue
Block a user