Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:24:01 +08:00
commit 7ca465850c
24 changed files with 5512 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
{
"name": "claude-api",
"description": "Integrate Claude API with streaming, prompt caching, tool use, vision, and extended thinking. Use when: building chatbots, AI assistants, content tools in Node.js/Workers/Next.js, or troubleshooting rate_limit_error, overloaded_error, invalid_request_error, 429 errors.",
"version": "1.0.0",
"author": {
"name": "Jeremy Dawes",
"email": "jeremy@jezweb.net"
},
"skills": [
"./"
]
}

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# claude-api
Integrate Claude API with streaming, prompt caching, tool use, vision, and extended thinking. Use when: building chatbots, AI assistants, content tools in Node.js/Workers/Next.js, or troubleshooting rate_limit_error, overloaded_error, invalid_request_error, 429 errors.

530
SKILL.md Normal file
View File

@@ -0,0 +1,530 @@
---
name: claude-api
description: |
Build with Claude Messages API using structured outputs (v0.69.0+, Nov 2025) for guaranteed JSON schema validation. Covers prompt caching (90% savings), streaming SSE, tool use, model deprecations (3.5/3.7 retired Oct 2025). Use when: building chatbots/agents with validated JSON responses, or troubleshooting rate_limit_error, structured output validation, prompt caching not activating, streaming SSE parsing.
license: MIT
metadata:
version: 2.0.0
last_verified: 2025-11-22
sdk_version: 0.70.1
token_savings: ~48%
errors_prevented: 12
breaking_changes: Oct 2025 - Claude 3.5/3.7 models retired, Nov 2025 - Structured outputs beta
keywords:
- claude api
- anthropic api
- messages api
- "@anthropic-ai/sdk"
- structured outputs
- output_format
- strict tool use
- json schema validation
- claude streaming
- prompt caching
- cache_control
- tool use
- vision
- extended thinking
- claude sonnet 4.5
- claude haiku 4.5
- claude opus 4
- function calling
- SSE
- rate limits
- 429 errors
- agent skills api
- context management
- clear thinking
- streaming sse parsing
- prompt caching not working
- structured output validation
- model deprecated
- model retired
---
# Claude API - Structured Outputs & Error Prevention Guide
**Package**: @anthropic-ai/sdk@0.70.1 (Nov 20, 2025)
**Breaking Changes**: Oct 2025 - Claude 3.5/3.7 models retired, Nov 2025 - Structured outputs beta
**Last Updated**: 2025-11-22
---
## What's New in v0.69.0+ (Nov 2025)
**Major Features:**
### 1. Structured Outputs (v0.69.0, Nov 14, 2025) - CRITICAL ⭐
**Guaranteed JSON schema conformance** - Claude's responses strictly follow your JSON schema with two modes:
**JSON Outputs (`output_format`)** - For data extraction and formatting:
```typescript
import Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Extract contact info: John Doe, john@example.com, 555-1234' }],
betas: ['structured-outputs-2025-11-13'],
output_format: {
type: 'json_schema',
json_schema: {
name: 'Contact',
strict: true,
schema: {
type: 'object',
properties: {
name: { type: 'string' },
email: { type: 'string' },
phone: { type: 'string' }
},
required: ['name', 'email', 'phone'],
additionalProperties: false
}
}
}
});
// Guaranteed valid JSON matching schema
const contact = JSON.parse(message.content[0].text);
console.log(contact.name); // "John Doe"
```
**Strict Tool Use (`strict: true`)** - For validated function parameters:
```typescript
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Get weather for San Francisco' }],
betas: ['structured-outputs-2025-11-13'],
tools: [{
name: 'get_weather',
description: 'Get current weather',
input_schema: {
type: 'object',
properties: {
location: { type: 'string' },
unit: { type: 'string', enum: ['celsius', 'fahrenheit'] }
},
required: ['location'],
additionalProperties: false
},
strict: true // ← Guarantees schema compliance
}]
});
```
**Requirements:**
- **Beta header**: `structured-outputs-2025-11-13` (via `betas` array)
- **Models**: Claude Sonnet 4.5, Claude Opus 4.1 only
- **SDK**: v0.69.0+ required
**Limitations:**
- ❌ No recursive schemas
- ❌ No numerical constraints (`minimum`, `maximum`)
- ❌ Limited regex support (no backreferences/lookahead)
- ❌ Incompatible with citations and message prefilling
- ⚠️ Grammar compilation adds latency on first request (cached 24hrs)
**When to Use:**
- Data extraction from unstructured text
- API response formatting
- Agentic workflows requiring validated tool inputs
- Eliminating JSON parse errors
### 2. Model Changes (Oct 2025) - BREAKING
**Retired (return errors):**
- ❌ Claude 3.5 Sonnet (all versions)
- ❌ Claude 3.7 Sonnet - DEPRECATED (Oct 28, 2025)
**Active Models (Nov 2025):**
| Model | ID | Context | Best For | Cost (per MTok) |
|-------|-----|---------|----------|-----------------|
| **Claude Sonnet 4.5** | claude-sonnet-4-5-20250929 | 200k | Balanced performance | $3/$15 (in/out) |
| **Claude Opus 4** | claude-opus-4-20250514 | 200k | Highest capability | $15/$75 |
| **Claude Haiku 4.5** | claude-3-5-haiku-20241022 | 200k | Near-frontier, fast | $1/$5 |
### 3. Context Management (Oct 28, 2025)
**Clear Thinking Blocks** - Automatic thinking block cleanup:
```typescript
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 4096,
messages: [{ role: 'user', content: 'Solve complex problem' }],
betas: ['clear_thinking_20251015']
});
// Thinking blocks automatically managed
```
### 4. Agent Skills API (Oct 16, 2025)
Pre-built skills for Office files (PowerPoint, Excel, Word, PDF):
```typescript
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Analyze this spreadsheet' }],
betas: ['skills-2025-10-02'],
// Requires code execution tool enabled
});
```
📚 **Docs**: https://platform.claude.com/docs/en/build-with-claude/structured-outputs
---
## Streaming Responses (SSE)
**CRITICAL Error Pattern** - Errors occur AFTER initial 200 response:
```typescript
const stream = anthropic.messages.stream({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hello' }],
});
stream
.on('error', (error) => {
// Error can occur AFTER stream starts
console.error('Stream error:', error);
// Implement fallback or retry logic
})
.on('abort', (error) => {
console.warn('Stream aborted:', error);
});
```
**Why this matters**: Unlike regular HTTP errors, SSE errors happen mid-stream after 200 OK, requiring error event listeners
---
## Prompt Caching (⭐ 90% Cost Savings)
**CRITICAL Rule** - `cache_control` MUST be on LAST block:
```typescript
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
system: [
{
type: 'text',
text: 'System instructions...',
},
{
type: 'text',
text: LARGE_CODEBASE, // 50k tokens
cache_control: { type: 'ephemeral' }, // ← MUST be on LAST block
},
],
messages: [{ role: 'user', content: 'Explain auth module' }],
});
// Monitor cache usage
console.log('Cache reads:', message.usage.cache_read_input_tokens);
console.log('Cache writes:', message.usage.cache_creation_input_tokens);
```
**Minimum requirements:**
- Claude Sonnet 4.5: 1,024 tokens minimum
- Claude Haiku 4.5: 2,048 tokens minimum
- 5-minute TTL (refreshes on each use)
- Cache shared only with IDENTICAL content
---
## Tool Use (Function Calling)
**CRITICAL Patterns:**
**Strict Tool Use** (with structured outputs):
```typescript
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
betas: ['structured-outputs-2025-11-13'],
tools: [{
name: 'get_weather',
description: 'Get weather data',
input_schema: {
type: 'object',
properties: {
location: { type: 'string' },
unit: { type: 'string', enum: ['celsius', 'fahrenheit'] }
},
required: ['location'],
additionalProperties: false
},
strict: true // ← Guarantees schema compliance
}],
messages: [{ role: 'user', content: 'Weather in NYC?' }]
});
```
**Tool Result Pattern** - `tool_use_id` MUST match:
```typescript
const toolResults = [];
for (const block of response.content) {
if (block.type === 'tool_use') {
const result = await executeToolFunction(block.name, block.input);
toolResults.push({
type: 'tool_result',
tool_use_id: block.id, // ← MUST match tool_use block id
content: JSON.stringify(result),
});
}
}
messages.push({
role: 'user',
content: toolResults,
});
```
**Error Handling** - Handle tool execution failures:
```typescript
try {
const result = await executeToolFunction(block.name, block.input);
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: JSON.stringify(result),
});
} catch (error) {
// Return error to Claude for handling
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
is_error: true,
content: `Tool execution failed: ${error.message}`,
});
}
```
---
## Vision (Image Understanding)
**CRITICAL Rules:**
- **Formats**: JPEG, PNG, WebP, GIF (non-animated)
- **Max size**: 5MB per image
- **Base64 overhead**: ~33% size increase
- **Context impact**: Images count toward token limit
- **Caching**: Consider for repeated image analysis
**Format validation** - Check before encoding:
```typescript
const validFormats = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
if (!validFormats.includes(mimeType)) {
throw new Error(`Unsupported format: ${mimeType}`);
}
```
---
## Extended Thinking Mode
**⚠️ Model Compatibility:**
- ❌ Claude 3.7 Sonnet - DEPRECATED (Oct 28, 2025)
- ❌ Claude 3.5 Sonnet - RETIRED (not supported)
- ✅ Claude Sonnet 4.5 - Extended thinking supported
- ✅ Claude Opus 4 - Extended thinking supported
**CRITICAL:**
- Thinking blocks are NOT cacheable
- Requires higher `max_tokens` (thinking consumes tokens)
- Check model before expecting thinking blocks
---
## Rate Limits
**CRITICAL Pattern** - Respect `retry-after` header with exponential backoff:
```typescript
async function makeRequestWithRetry(
requestFn: () => Promise<any>,
maxRetries = 3,
baseDelay = 1000
): Promise<any> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await requestFn();
} catch (error) {
if (error.status === 429) {
// CRITICAL: Use retry-after header if present
const retryAfter = error.response?.headers?.['retry-after'];
const delay = retryAfter
? parseInt(retryAfter) * 1000
: baseDelay * Math.pow(2, attempt);
console.warn(`Rate limited. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
throw new Error('Max retries exceeded');
}
```
**Rate limit headers:**
- `anthropic-ratelimit-requests-limit` - Total RPM allowed
- `anthropic-ratelimit-requests-remaining` - Remaining requests
- `anthropic-ratelimit-requests-reset` - Reset timestamp
---
## Error Handling
**Common Error Codes:**
| Status | Error Type | Cause | Solution |
|--------|-----------|-------|----------|
| 400 | invalid_request_error | Bad parameters | Validate request body |
| 401 | authentication_error | Invalid API key | Check env variable |
| 403 | permission_error | No access to feature | Check account tier |
| 404 | not_found_error | Invalid endpoint | Check API version |
| 429 | rate_limit_error | Too many requests | Implement retry logic |
| 500 | api_error | Internal error | Retry with backoff |
| 529 | overloaded_error | System overloaded | Retry later |
**CRITICAL:**
- Streaming errors occur AFTER initial 200 response
- Always implement error event listeners for streams
- Respect `retry-after` header on 429 errors
- Have fallback strategies for critical operations
---
## Known Issues Prevention
This skill prevents **12** documented issues:
### Issue #1: Rate Limit 429 Errors Without Backoff
**Error**: `429 Too Many Requests: Number of request tokens has exceeded your per-minute rate limit`
**Source**: https://docs.claude.com/en/api/errors
**Why It Happens**: Exceeding RPM, TPM, or daily token limits
**Prevention**: Implement exponential backoff with `retry-after` header respect
### Issue #2: Streaming SSE Parsing Errors
**Error**: Incomplete chunks, malformed SSE events
**Source**: Common SDK issue (GitHub #323)
**Why It Happens**: Network interruptions, improper event parsing
**Prevention**: Use SDK stream helpers, implement error event listeners
### Issue #3: Prompt Caching Not Activating
**Error**: High costs despite cache_control blocks
**Source**: https://platform.claude.com/docs/en/build-with-claude/prompt-caching
**Why It Happens**: `cache_control` placed incorrectly (must be at END)
**Prevention**: Always place `cache_control` on LAST block of cacheable content
### Issue #4: Tool Use Response Format Errors
**Error**: `invalid_request_error: tools[0].input_schema is invalid`
**Source**: API validation errors
**Why It Happens**: Invalid JSON Schema, missing required fields
**Prevention**: Validate schemas with JSON Schema validator, test thoroughly
### Issue #5: Vision Image Format Issues
**Error**: `invalid_request_error: image source must be base64 or url`
**Source**: API documentation
**Why It Happens**: Incorrect encoding, unsupported formats
**Prevention**: Validate format (JPEG/PNG/WebP/GIF), proper base64 encoding
### Issue #6: Token Counting Mismatches for Billing
**Error**: Unexpected high costs, context window exceeded
**Source**: Token counting differences
**Why It Happens**: Not accounting for special tokens, formatting
**Prevention**: Use official token counter, monitor usage headers
### Issue #7: System Prompt Ordering Issues
**Error**: System prompt ignored or overridden
**Source**: API behavior
**Why It Happens**: System prompt placed after messages array
**Prevention**: ALWAYS place system prompt before messages
### Issue #8: Context Window Exceeded (200k)
**Error**: `invalid_request_error: messages: too many tokens`
**Source**: Model limits
**Why It Happens**: Long conversations without pruning
**Prevention**: Implement message history pruning, use caching
### Issue #9: Extended Thinking on Wrong Model
**Error**: No thinking blocks in response
**Source**: Model capabilities
**Why It Happens**: Using retired/deprecated models (3.5/3.7 Sonnet)
**Prevention**: Only use extended thinking with Claude Sonnet 4.5 or Claude Opus 4
### Issue #10: API Key Exposure in Client Code
**Error**: CORS errors, security vulnerability
**Source**: Security best practices
**Why It Happens**: Making API calls from browser
**Prevention**: Server-side only, use environment variables
### Issue #11: Rate Limit Tier Confusion
**Error**: Lower limits than expected
**Source**: Account tier system
**Why It Happens**: Not understanding tier progression
**Prevention**: Check Console for current tier, auto-scales with usage
### Issue #12: Message Batches Beta Headers Missing
**Error**: `invalid_request_error: unknown parameter: batches`
**Source**: Beta API requirements
**Why It Happens**: Missing `anthropic-beta` header
**Prevention**: Include `anthropic-beta: message-batches-2024-09-24` header
---
## Official Documentation
- **Claude API**: https://platform.claude.com/docs/en/api
- **Messages API**: https://platform.claude.com/docs/en/api/messages
- **Structured Outputs**: https://platform.claude.com/docs/en/build-with-claude/structured-outputs
- **Prompt Caching**: https://platform.claude.com/docs/en/build-with-claude/prompt-caching
- **Tool Use**: https://platform.claude.com/docs/en/build-with-claude/tool-use
- **Vision**: https://platform.claude.com/docs/en/build-with-claude/vision
- **Rate Limits**: https://platform.claude.com/docs/en/api/rate-limits
- **Errors**: https://platform.claude.com/docs/en/api/errors
- **TypeScript SDK**: https://github.com/anthropics/anthropic-sdk-typescript
- **Context7 Library ID**: /anthropics/anthropic-sdk-typescript
---
## Package Versions
**Latest**: @anthropic-ai/sdk@0.70.1 (Nov 20, 2025)
```json
{
"dependencies": {
"@anthropic-ai/sdk": "^0.70.1"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.3.0",
"zod": "^3.23.0"
}
}
```
---
**Token Efficiency**:
- **Without skill**: ~8,000 tokens (basic setup, streaming, caching, tools, vision, errors)
- **With skill**: ~4,200 tokens (knowledge gaps + error prevention + critical patterns)
- **Savings**: ~48% (~3,800 tokens)
**Errors prevented**: 12 documented issues with exact solutions
**Key value**: Structured outputs (v0.69.0+), model deprecations (Oct 2025), prompt caching edge cases, streaming error patterns, rate limit retry logic
---
**Last verified**: 2025-11-22 | **Skill version**: 2.0.0 | **Changes**: Added structured outputs (v0.69.0), updated model table (retired 3.5/3.7), context management, agent skills API. Removed basic tutorials (~380 lines). Focused on knowledge gaps + error prevention + advanced patterns.

125
plugin.lock.json Normal file
View File

@@ -0,0 +1,125 @@
{
"$schema": "internal://schemas/plugin.lock.v1.json",
"pluginId": "gh:jezweb/claude-skills:skills/claude-api",
"normalized": {
"repo": null,
"ref": "refs/tags/v20251128.0",
"commit": "0b446dc7f17ae1ba47fea924d73fe5b8ac655302",
"treeHash": "0d9003459d6376a2af8544e39932cd7d1bda1d681490397dda3e54de8c5ce071",
"generatedAt": "2025-11-28T10:19:02.213122Z",
"toolVersion": "publish_plugins.py@0.2.0"
},
"origin": {
"remote": "git@github.com:zhongweili/42plugin-data.git",
"branch": "master",
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
},
"manifest": {
"name": "claude-api",
"description": "Integrate Claude API with streaming, prompt caching, tool use, vision, and extended thinking. Use when: building chatbots, AI assistants, content tools in Node.js/Workers/Next.js, or troubleshooting rate_limit_error, overloaded_error, invalid_request_error, 429 errors.",
"version": "1.0.0"
},
"content": {
"files": [
{
"path": "README.md",
"sha256": "030954f810c2215caa27c6e578f96237b015580f14045f03036cb64fb1f906cc"
},
{
"path": "SKILL.md",
"sha256": "583beabc079f4d049641170507d1526bf69ab7d8a488bafdaf13c14ce760325f"
},
{
"path": "references/rate-limits.md",
"sha256": "d365e3327c2f9343a7ea2e8c0b085e72624352df04ad7c3b790211bd90fbace9"
},
{
"path": "references/tool-use-patterns.md",
"sha256": "c08b81d8b74e6dbe481570575e4c6d5452ed1ea6ee58331773d362e4ad0b679f"
},
{
"path": "references/top-errors.md",
"sha256": "61e316b1dfd7c3459955529e1386471516c0836b9306bd77f717bc8e1181e909"
},
{
"path": "references/vision-capabilities.md",
"sha256": "232d5aab709580a4684450252943c1291efeaf8f39038dea4a762097f7de97a5"
},
{
"path": "references/prompt-caching-guide.md",
"sha256": "aad6332727664ba5345be4c433d877f01520bdd4d4ad44e8603405702fd931b2"
},
{
"path": "references/api-reference.md",
"sha256": "845efb91c97276fe6cc4ac8105eeea736701ef0b3ea2ccab278ea7f8605f2bde"
},
{
"path": "scripts/check-versions.sh",
"sha256": "09a2d059609ba75e4ffecc688c7075a981c263368c92c31b5ad766f931176aa1"
},
{
"path": ".claude-plugin/plugin.json",
"sha256": "fd8b0ba9d8e048ebc7efe84d487920bf856931c4438f06383ba0ae7ff480d20c"
},
{
"path": "templates/extended-thinking.ts",
"sha256": "32298e07dad6df9116b49280f3a5c763f362fb2b880550538484459e975962d8"
},
{
"path": "templates/tool-use-basic.ts",
"sha256": "e83db54f9d28b7cfe1e3187873fd955f68017df61dfd91de3208f5659827d819"
},
{
"path": "templates/tool-use-advanced.ts",
"sha256": "2f486d51f6011224c0ca5cb541fccd450257adfb0b468f051d3a20fcf021f558"
},
{
"path": "templates/wrangler.jsonc",
"sha256": "f200236b2af9644ae7b95f523cd5cf50afb96eea478bf65bdad15768600fe8cf"
},
{
"path": "templates/nextjs-api-route.ts",
"sha256": "fa3ee9a8eb426c59bf1be2e1587704497303854cfd675e26088603605421b0c6"
},
{
"path": "templates/streaming-chat.ts",
"sha256": "a67c02b6ac9eef07812e0abe777182e0f29a4b263ef688b6375e849ed46ead2f"
},
{
"path": "templates/basic-chat.ts",
"sha256": "3461d28e0d4ae9b34f03b24b6dcea3a033f261721e5b833a0065da04f58f9bc4"
},
{
"path": "templates/error-handling.ts",
"sha256": "352ec0214260c012b1bce78ecf39026c410769783f8ce25497859b6824f09064"
},
{
"path": "templates/package.json",
"sha256": "3fedabb44a2d420de2139167ffe4705e7104dfe54479e0fe8d69b673ff30a2d3"
},
{
"path": "templates/cloudflare-worker.ts",
"sha256": "34a4bea6597f104ced2d49de7508fed05cf0e4e5ac9cab49c2761506b7fa1c63"
},
{
"path": "templates/nodejs-example.ts",
"sha256": "aed836c3d2fdd05e52d7079b2aa09fa3c0750c9ff468e1bcdbf1533623aab85f"
},
{
"path": "templates/vision-image.ts",
"sha256": "51418952ec69fdd109c40d04cf0b42487de9edd3dbeb2223aacbfbe66471fef1"
},
{
"path": "templates/prompt-caching.ts",
"sha256": "df3b729e396f852f0e421d471a01259e3de96b71d85b7c99470b089c1b2cacef"
}
],
"dirSha256": "0d9003459d6376a2af8544e39932cd7d1bda1d681490397dda3e54de8c5ce071"
},
"security": {
"scannedAt": null,
"scannerVersion": null,
"flags": []
}
}

224
references/api-reference.md Normal file
View File

@@ -0,0 +1,224 @@
# Claude API Reference
Quick reference for the Anthropic Messages API endpoints and parameters.
## Base URL
```
https://api.anthropic.com/v1
```
## Authentication
All requests require:
```http
x-api-key: YOUR_API_KEY
anthropic-version: 2023-06-01
content-type: application/json
```
## Endpoints
### POST /messages
Create a message with Claude.
**Request Body:**
```typescript
{
model: string, // Required: "claude-sonnet-4-5-20250929", etc.
max_tokens: number, // Required: Maximum tokens to generate (1-8192)
messages: Message[], // Required: Conversation history
system?: string | SystemBlock[], // Optional: System prompt
temperature?: number, // Optional: 0-1 (default: 1)
top_p?: number, // Optional: 0-1 (default: 1)
top_k?: number, // Optional: Sampling parameter
stop_sequences?: string[], // Optional: Stop generation at these sequences
stream?: boolean, // Optional: Enable streaming (default: false)
tools?: Tool[], // Optional: Available tools
tool_choice?: ToolChoice, // Optional: Tool selection strategy
metadata?: Metadata // Optional: Request metadata
}
```
**Message Format:**
```typescript
{
role: "user" | "assistant",
content: string | ContentBlock[]
}
```
**Content Block Types:**
```typescript
// Text
{
type: "text",
text: string,
cache_control?: { type: "ephemeral" } // For prompt caching
}
// Image
{
type: "image",
source: {
type: "base64" | "url",
media_type: "image/jpeg" | "image/png" | "image/webp" | "image/gif",
data?: string, // base64 encoded
url?: string // publicly accessible URL
},
cache_control?: { type: "ephemeral" }
}
// Tool use (assistant messages only)
{
type: "tool_use",
id: string,
name: string,
input: object
}
// Tool result (user messages only)
{
type: "tool_result",
tool_use_id: string,
content: string | ContentBlock[],
is_error?: boolean
}
```
**Response:**
```typescript
{
id: string,
type: "message",
role: "assistant",
content: ContentBlock[],
model: string,
stop_reason: "end_turn" | "max_tokens" | "stop_sequence" | "tool_use",
stop_sequence?: string,
usage: {
input_tokens: number,
output_tokens: number,
cache_creation_input_tokens?: number,
cache_read_input_tokens?: number
}
}
```
## Model IDs
| Model | ID | Context Window |
|-------|-----|----------------|
| Claude Sonnet 4.5 | claude-sonnet-4-5-20250929 | 200k tokens |
| Claude 3.7 Sonnet | claude-3-7-sonnet-20250228 | 2M tokens |
| Claude Opus 4 | claude-opus-4-20250514 | 200k tokens |
| Claude 3.5 Haiku | claude-3-5-haiku-20241022 | 200k tokens |
## Tool Definition
```typescript
{
name: string, // Tool identifier
description: string, // What the tool does
input_schema: { // JSON Schema
type: "object",
properties: {
[key: string]: {
type: "string" | "number" | "boolean" | "array" | "object",
description?: string,
enum?: any[]
}
},
required?: string[]
}
}
```
## Streaming
Set `stream: true` in request. Returns Server-Sent Events (SSE):
**Event Types:**
- `message_start`: Message begins
- `content_block_start`: Content block begins
- `content_block_delta`: Text or JSON delta
- `content_block_stop`: Content block complete
- `message_delta`: Metadata update
- `message_stop`: Message complete
- `ping`: Keep-alive
**Event Format:**
```
event: message_start
data: {"type":"message_start","message":{...}}
event: content_block_delta
data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"Hello"}}
event: message_stop
data: {"type":"message_stop"}
```
## Error Responses
```typescript
{
type: "error",
error: {
type: string, // Error type identifier
message: string // Human-readable description
}
}
```
**Common Error Types:**
- `invalid_request_error` (400)
- `authentication_error` (401)
- `permission_error` (403)
- `not_found_error` (404)
- `rate_limit_error` (429)
- `api_error` (500)
- `overloaded_error` (529)
## Rate Limit Headers
Response includes:
```
anthropic-ratelimit-requests-limit: 50
anthropic-ratelimit-requests-remaining: 49
anthropic-ratelimit-requests-reset: 2025-10-25T12:00:00Z
anthropic-ratelimit-tokens-limit: 50000
anthropic-ratelimit-tokens-remaining: 49500
anthropic-ratelimit-tokens-reset: 2025-10-25T12:01:00Z
retry-after: 60 // Only on 429 errors
```
## SDK Installation
```bash
# TypeScript/JavaScript
npm install @anthropic-ai/sdk
# Python
pip install anthropic
# Java
# See https://github.com/anthropics/anthropic-sdk-java
# Go
go get github.com/anthropics/anthropic-sdk-go
```
## Official Documentation
- **API Reference**: https://docs.claude.com/en/api/messages
- **SDK Documentation**: https://github.com/anthropics/anthropic-sdk-typescript
- **Rate Limits**: https://docs.claude.com/en/api/rate-limits
- **Errors**: https://docs.claude.com/en/api/errors

View File

@@ -0,0 +1,354 @@
# Prompt Caching Guide
Complete guide to using prompt caching for cost optimization.
## Overview
Prompt caching reduces costs by up to 90% and latency by up to 85% by caching frequently used context.
### Benefits
- **Cost Savings**: Cache reads = 10% of input token price
- **Latency Reduction**: 85% faster time to first token
- **Use Cases**: Long documents, codebases, system instructions, conversation history
### Pricing
| Operation | Cost (per MTok) | vs Regular Input |
|-----------|-----------------|------------------|
| Regular input | $3 | 100% |
| Cache write | $3.75 | 125% |
| Cache read | $0.30 | 10% |
**Example**: 100k tokens cached, used 10 times
- Without caching: 100k × $3/MTok × 10 = $3.00
- With caching: (100k × $3.75/MTok) + (100k × $0.30/MTok × 9) = $0.375 + $0.27 = $0.645
- **Savings: $2.355 (78.5%)**
## Requirements
### Minimum Cacheable Content
- **Claude 3.5 Sonnet**: 1,024 tokens minimum
- **Claude 3.5 Haiku**: 2,048 tokens minimum
- **Claude 3.7 Sonnet**: 1,024 tokens minimum
### Cache Lifetime
- **Default**: 5 minutes
- **Extended**: 1 hour (configurable)
- Refreshes on each use
### Cache Matching
Cache hits require:
-**Identical content** (byte-for-byte)
-**Same position** in request
-**Within TTL** (5 min or 1 hour)
## Implementation
### Basic System Prompt Caching
```typescript
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
system: [
{
type: 'text',
text: LARGE_SYSTEM_INSTRUCTIONS, // >= 1024 tokens
cache_control: { type: 'ephemeral' },
},
],
messages: [
{ role: 'user', content: 'Your question here' }
],
});
```
### Caching in User Messages
```typescript
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: 'Analyze this document:',
},
{
type: 'text',
text: LARGE_DOCUMENT, // >= 1024 tokens
cache_control: { type: 'ephemeral' },
},
{
type: 'text',
text: 'What are the main themes?',
},
],
},
],
});
```
### Multi-Turn Conversation Caching
```typescript
// Turn 1 - Creates cache
const response1 = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
system: [
{
type: 'text',
text: SYSTEM_PROMPT,
cache_control: { type: 'ephemeral' },
},
],
messages: [
{ role: 'user', content: 'Hello!' }
],
});
// Turn 2 - Hits cache (same system prompt)
const response2 = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
system: [
{
type: 'text',
text: SYSTEM_PROMPT, // Identical - cache hit
cache_control: { type: 'ephemeral' },
},
],
messages: [
{ role: 'user', content: 'Hello!' },
{ role: 'assistant', content: response1.content[0].text },
{ role: 'user', content: 'Tell me more' },
],
});
```
### Caching Conversation History
```typescript
const messages = [
{ role: 'user', content: 'Message 1' },
{ role: 'assistant', content: 'Response 1' },
{ role: 'user', content: 'Message 2' },
{ role: 'assistant', content: 'Response 2' },
];
// Cache last assistant message
messages[messages.length - 1] = {
role: 'assistant',
content: [
{
type: 'text',
text: 'Response 2',
cache_control: { type: 'ephemeral' },
},
],
};
messages.push({ role: 'user', content: 'Message 3' });
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages,
});
```
## Best Practices
### ✅ Do
- Place `cache_control` on the **last block** of cacheable content
- Cache content >= 1024 tokens (3.5 Sonnet) or >= 2048 tokens (3.5 Haiku)
- Use caching for repeated context (system prompts, documents, code)
- Monitor cache usage in response headers
- Cache conversation history in long chats
### ❌ Don't
- Cache content below minimum token threshold
- Place `cache_control` in the middle of text
- Change cached content (breaks cache matching)
- Cache rarely used content (not cost-effective)
- Expect caching to work across different API keys
## Monitoring Cache Usage
```typescript
const response = await anthropic.messages.create({...});
console.log('Input tokens:', response.usage.input_tokens);
console.log('Cache creation:', response.usage.cache_creation_input_tokens);
console.log('Cache read:', response.usage.cache_read_input_tokens);
console.log('Output tokens:', response.usage.output_tokens);
// First request
// input_tokens: 1000
// cache_creation_input_tokens: 5000
// cache_read_input_tokens: 0
// Subsequent requests (within 5 min)
// input_tokens: 1000
// cache_creation_input_tokens: 0
// cache_read_input_tokens: 5000 // 90% cost savings!
```
## Common Patterns
### Pattern 1: Document Analysis Chatbot
```typescript
const document = fs.readFileSync('./document.txt', 'utf-8'); // 10k tokens
// All requests use same cached document
for (const question of questions) {
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: [
{ type: 'text', text: 'Document:' },
{
type: 'text',
text: document,
cache_control: { type: 'ephemeral' },
},
{ type: 'text', text: `Question: ${question}` },
],
},
],
});
// First request: cache_creation_input_tokens: 10000
// Subsequent: cache_read_input_tokens: 10000 (90% savings)
}
```
### Pattern 2: Code Review with Codebase Context
```typescript
const codebase = await loadCodebase(); // 50k tokens
const review = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
system: [
{ type: 'text', text: 'You are a code reviewer.' },
{
type: 'text',
text: `Codebase context:\n${codebase}`,
cache_control: { type: 'ephemeral' },
},
],
messages: [
{ role: 'user', content: 'Review this PR: ...' }
],
});
```
### Pattern 3: Customer Support with Knowledge Base
```typescript
const knowledgeBase = await loadKB(); // 20k tokens
// Cache persists across all customer queries
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
system: [
{ type: 'text', text: 'You are a customer support agent.' },
{
type: 'text',
text: knowledgeBase,
cache_control: { type: 'ephemeral' },
},
],
messages: customerConversation,
});
```
## Troubleshooting
### Cache Not Activating
**Problem**: `cache_read_input_tokens` is 0
**Solutions**:
1. Ensure content >= 1024 tokens (or 2048 for Haiku)
2. Verify `cache_control` is on **last block**
3. Check content is byte-for-byte identical
4. Confirm requests within 5-minute window
### Unexpected Cache Misses
**Problem**: Cache hits intermittently
**Solutions**:
1. Ensure content doesn't change (even whitespace)
2. Check TTL hasn't expired
3. Verify using same API key
4. Monitor cache headers in responses
### High Cache Creation Costs
**Problem**: Frequent `cache_creation_input_tokens`
**Solutions**:
1. Increase request frequency (use cache before expiry)
2. Consider if caching is cost-effective (need 2+ uses)
3. Extend cache TTL to 1 hour if supported
## Cost Calculator
```typescript
function calculateCachingSavings(
cachedTokens: number,
uncachedTokens: number,
requestCount: number
): {
withoutCaching: number;
withCaching: number;
savings: number;
savingsPercent: number;
} {
const inputCostPerMTok = 3;
const cacheCostPerMTok = 3.75;
const cacheReadCostPerMTok = 0.3;
const withoutCaching = ((cachedTokens + uncachedTokens) / 1_000_000) *
inputCostPerMTok * requestCount;
const cacheWrite = (cachedTokens / 1_000_000) * cacheCostPerMTok;
const cacheReads = (cachedTokens / 1_000_000) * cacheReadCostPerMTok * (requestCount - 1);
const uncachedInput = (uncachedTokens / 1_000_000) * inputCostPerMTok * requestCount;
const withCaching = cacheWrite + cacheReads + uncachedInput;
const savings = withoutCaching - withCaching;
const savingsPercent = (savings / withoutCaching) * 100;
return { withoutCaching, withCaching, savings, savingsPercent };
}
// Example: 10k cached tokens, 1k uncached, 20 requests
const result = calculateCachingSavings(10000, 1000, 20);
console.log(`Savings: $${result.savings.toFixed(4)} (${result.savingsPercent.toFixed(1)}%)`);
```
## Official Documentation
- **Prompt Caching Guide**: https://docs.claude.com/en/docs/build-with-claude/prompt-caching
- **Pricing**: https://www.anthropic.com/pricing
- **API Reference**: https://docs.claude.com/en/api/messages

80
references/rate-limits.md Normal file
View File

@@ -0,0 +1,80 @@
# Rate Limits Guide
Complete guide to Claude API rate limits and how to handle them.
## Overview
Claude API uses **token bucket algorithm** for rate limiting:
- Capacity continuously replenishes
- Three types: Requests per minute (RPM), Tokens per minute (TPM), Daily tokens
- Limits vary by account tier and model
## Rate Limit Tiers
| Tier | Requirements | Example Limits (Sonnet 3.5) |
|------|--------------|------------------------------|
| Tier 1 | New account | 50 RPM, 40k TPM |
| Tier 2 | $10 spend | 1000 RPM, 100k TPM |
| Tier 3 | $50 spend | 2000 RPM, 200k TPM |
| Tier 4 | $500 spend | 4000 RPM, 400k TPM |
**Note**: Limits vary by model. Check Console for exact limits.
## Response Headers
Every API response includes:
```
anthropic-ratelimit-requests-limit: 50
anthropic-ratelimit-requests-remaining: 49
anthropic-ratelimit-requests-reset: 2025-10-25T12:00:00Z
anthropic-ratelimit-tokens-limit: 50000
anthropic-ratelimit-tokens-remaining: 49500
anthropic-ratelimit-tokens-reset: 2025-10-25T12:01:00Z
```
On 429 errors:
```
retry-after: 60 // Seconds until retry allowed
```
## Handling Rate Limits
### Basic Exponential Backoff
```typescript
async function withRetry(requestFn, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await requestFn();
} catch (error) {
if (error.status === 429) {
const delay = 1000 * Math.pow(2, attempt);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
}
```
### Respecting retry-after Header
```typescript
const retryAfter = error.response?.headers?.['retry-after'];
const delay = retryAfter ? parseInt(retryAfter) * 1000 : baseDelay;
```
## Best Practices
1. **Monitor headers** - Check remaining requests/tokens
2. **Implement backoff** - Exponential delay on 429
3. **Respect retry-after** - Use provided wait time
4. **Batch requests** - Group when possible
5. **Use caching** - Reduce duplicate requests
6. **Upgrade tier** - Scale with usage
## Official Docs
https://docs.claude.com/en/api/rate-limits

View File

@@ -0,0 +1,114 @@
# Tool Use Patterns
Common patterns for using tools (function calling) with Claude API.
## Basic Pattern
```typescript
const tools = [{
name: 'get_weather',
description: 'Get current weather',
input_schema: {
type: 'object',
properties: {
location: { type: 'string' }
},
required: ['location']
}
}];
// 1. Send request with tools
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
tools,
messages: [{ role: 'user', content: 'Weather in NYC?' }]
});
// 2. Check if Claude wants to use tools
if (response.stop_reason === 'tool_use') {
for (const block of response.content) {
if (block.type === 'tool_use') {
// 3. Execute tool
const result = await executeToolFunction(block.name, block.input);
// 4. Return result
const toolResult = {
type: 'tool_result',
tool_use_id: block.id,
content: JSON.stringify(result)
};
}
}
}
```
## Tool Execution Loop
```typescript
async function chatWithTools(userMessage) {
const messages = [{ role: 'user', content: userMessage }];
while (true) {
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
tools,
messages,
});
messages.push({ role: 'assistant', content: response.content });
if (response.stop_reason === 'tool_use') {
// Execute tools and continue
const toolResults = await executeAllTools(response.content);
messages.push({ role: 'user', content: toolResults });
} else {
// Final response
return response.content.find(b => b.type === 'text')?.text;
}
}
}
```
## With Zod Validation
```typescript
import { betaZodTool } from '@anthropic-ai/sdk/helpers/zod';
import { z } from 'zod';
const weatherTool = betaZodTool({
name: 'get_weather',
inputSchema: z.object({
location: z.string(),
unit: z.enum(['celsius', 'fahrenheit']).optional(),
}),
description: 'Get weather',
run: async (input) => {
return `Weather in ${input.location}: 72°F`;
},
});
// Automatic execution
const finalMessage = await anthropic.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1000,
messages: [{ role: 'user', content: 'Weather in SF?' }],
tools: [weatherTool],
});
```
## Error Handling in Tools
```typescript
{
type: 'tool_result',
tool_use_id: block.id,
content: 'Error: API unavailable',
is_error: true // Mark as error
}
```
## Official Docs
https://docs.claude.com/en/docs/build-with-claude/tool-use

507
references/top-errors.md Normal file
View File

@@ -0,0 +1,507 @@
# Top 12 Common Errors and Solutions
Complete reference for troubleshooting Claude API errors.
---
## Error #1: Rate Limit 429 - Too Many Requests
**Error Message:**
```
429 Too Many Requests: Number of request tokens has exceeded your per-minute rate limit
```
**Source**: https://docs.claude.com/en/api/errors
**Why It Happens:**
- Exceeded requests per minute (RPM)
- Exceeded tokens per minute (TPM)
- Exceeded daily token quota
**Solution:**
```typescript
async function handleRateLimit(requestFn, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await requestFn();
} catch (error) {
if (error.status === 429) {
const retryAfter = error.response?.headers?.['retry-after'];
const delay = retryAfter ? parseInt(retryAfter) * 1000 : 1000 * Math.pow(2, attempt);
console.log(`Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
}
```
**Prevention:**
- Implement exponential backoff
- Respect `retry-after` header
- Monitor rate limit headers
- Upgrade account tier for higher limits
---
## Error #2: Streaming SSE Parsing Errors
**Error Message:**
```
Incomplete chunks, malformed events, connection dropped
```
**Source**: Community reports, SDK issues
**Why It Happens:**
- Network interruptions
- Improper SSE event parsing
- Errors occur AFTER initial 200 response
**Solution:**
```typescript
const stream = anthropic.messages.stream({...});
stream
.on('error', (error) => {
console.error('Stream error:', error);
// Implement retry or fallback
})
.on('abort', (error) => {
console.warn('Stream aborted');
})
.on('end', () => {
console.log('Stream completed');
});
await stream.finalMessage();
```
**Prevention:**
- Always implement error event listeners
- Handle stream abortion
- Use SDK helpers (don't parse SSE manually)
- Implement reconnection logic
---
## Error #3: Prompt Caching Not Activating
**Error Message:**
```
High costs despite cache_control blocks
cache_read_input_tokens: 0
```
**Source**: https://docs.claude.com/en/docs/build-with-claude/prompt-caching
**Why It Happens:**
- `cache_control` not on last block
- Content below minimum tokens (1024/2048)
- Content changed (breaks cache match)
- Outside 5-minute TTL
**Solution:**
```typescript
// ❌ Wrong - cache_control not at end
{
type: 'text',
text: DOCUMENT,
cache_control: { type: 'ephemeral' }, // Wrong position
},
{
type: 'text',
text: 'Additional text',
}
// ✅ Correct - cache_control at end
{
type: 'text',
text: DOCUMENT + '\n\nAdditional text',
cache_control: { type: 'ephemeral' }, // Correct position
}
```
**Prevention:**
- Place `cache_control` on LAST block
- Ensure content >= 1024 tokens
- Keep cached content identical
- Monitor `cache_read_input_tokens`
---
## Error #4: Tool Use Response Format Errors
**Error Message:**
```
invalid_request_error: tools[0].input_schema is invalid
```
**Source**: API validation
**Why It Happens:**
- Invalid JSON Schema
- Missing required fields
- Incorrect tool_use_id in tool_result
**Solution:**
```typescript
// ✅ Valid tool schema
{
name: 'get_weather',
description: 'Get current weather',
input_schema: {
type: 'object', // Must be 'object'
properties: {
location: {
type: 'string', // Valid JSON Schema types
description: 'City' // Optional but recommended
}
},
required: ['location'] // List required fields
}
}
// ✅ Valid tool result
{
type: 'tool_result',
tool_use_id: block.id, // Must match tool_use id
content: JSON.stringify(result) // Convert to string
}
```
**Prevention:**
- Validate schemas with JSON Schema validator
- Match `tool_use_id` exactly
- Stringify tool results
- Test thoroughly before production
---
## Error #5: Vision Image Format Issues
**Error Message:**
```
invalid_request_error: image source must be base64 or url
```
**Source**: API documentation
**Why It Happens:**
- Unsupported image format
- Incorrect base64 encoding
- Invalid media_type
**Solution:**
```typescript
import fs from 'fs';
const imageData = fs.readFileSync('./image.jpg');
const base64Image = imageData.toString('base64');
// ✅ Correct format
{
type: 'image',
source: {
type: 'base64',
media_type: 'image/jpeg', // Must match actual format
data: base64Image // Pure base64 (no data URI prefix)
}
}
```
**Supported Formats:**
- image/jpeg
- image/png
- image/webp
- image/gif
**Prevention:**
- Validate format before encoding
- Use correct media_type
- Remove data URI prefix if present
- Keep images under 5MB
---
## Error #6: Token Counting Mismatches
**Error Message:**
```
invalid_request_error: messages: too many tokens
```
**Source**: Token counting differences
**Why It Happens:**
- Not accounting for special tokens
- Formatting adds hidden tokens
- Context window exceeded
**Solution:**
```typescript
// Monitor token usage
const response = await anthropic.messages.create({...});
console.log('Input tokens:', response.usage.input_tokens);
console.log('Output tokens:', response.usage.output_tokens);
console.log('Total:', response.usage.input_tokens + response.usage.output_tokens);
// Check against context window
const contextWindow = 200000; // Claude 3.5 Sonnet
if (response.usage.input_tokens > contextWindow) {
console.warn('Approaching context limit');
}
```
**Prevention:**
- Use official token counter
- Monitor usage headers
- Implement message pruning
- Use prompt caching for long context
---
## Error #7: System Prompt Ordering Issues
**Error Message:**
```
System prompt ignored or overridden
```
**Source**: API behavior
**Why It Happens:**
- System prompt placed after messages
- System prompt in wrong format
**Solution:**
```typescript
// ❌ Wrong
const message = await anthropic.messages.create({
messages: [...],
system: 'You are helpful', // Wrong - after messages
});
// ✅ Correct
const message = await anthropic.messages.create({
system: 'You are helpful', // Correct - before messages
messages: [...],
});
```
**Prevention:**
- Always place `system` before `messages`
- Use system prompt for behavior instructions
- Test system prompt effectiveness
---
## Error #8: Context Window Exceeded
**Error Message:**
```
invalid_request_error: messages: too many tokens (210000 > 200000)
```
**Source**: Model limits
**Why It Happens:**
- Long conversations
- Large documents
- Not pruning message history
**Solution:**
```typescript
function pruneMessages(messages, maxTokens = 150000) {
// Keep most recent messages
let totalTokens = 0;
const prunedMessages = [];
for (let i = messages.length - 1; i >= 0; i--) {
const msgTokens = estimateTokens(messages[i].content);
if (totalTokens + msgTokens > maxTokens) break;
prunedMessages.unshift(messages[i]);
totalTokens += msgTokens;
}
return prunedMessages;
}
```
**Prevention:**
- Implement message history pruning
- Use summarization for old messages
- Use prompt caching
- Choose model with larger context (3.7 Sonnet: 2M tokens)
---
## Error #9: Extended Thinking on Wrong Model
**Error Message:**
```
No thinking blocks in response
```
**Source**: Model capabilities
**Why It Happens:**
- Using Claude 3.5 Sonnet (not supported)
- Should use Claude 3.7 Sonnet or Claude 4
**Solution:**
```typescript
// ❌ Wrong model - no extended thinking
model: 'claude-sonnet-4-5-20250929'
// ✅ Correct models for extended thinking
model: 'claude-3-7-sonnet-20250228' // Has extended thinking
model: 'claude-opus-4-20250514' // Has extended thinking
```
**Prevention:**
- Verify model capabilities
- Use 3.7 Sonnet or 4.x models for extended thinking
- Document model requirements
---
## Error #10: API Key Exposure in Client Code
**Error Message:**
```
CORS errors, security vulnerability
```
**Source**: Security best practices
**Why It Happens:**
- Making API calls from browser
- API key in client-side code
**Solution:**
```typescript
// ❌ Never do this
const anthropic = new Anthropic({
apiKey: 'sk-ant-...', // Exposed in browser!
});
// ✅ Use server-side endpoint
async function callClaude(messages) {
const response = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ messages }),
});
return response.json();
}
```
**Prevention:**
- Server-side only
- Use environment variables
- Implement authentication
- Never expose API key
---
## Error #11: Rate Limit Tier Confusion
**Error Message:**
```
Lower limits than expected
```
**Source**: Account tier system
**Why It Happens:**
- Not understanding tier progression
- Expecting higher limits without usage history
**Solution:**
- Check current tier in Console
- Tiers auto-scale with usage ($10, $50, $500 spend)
- Monitor rate limit headers
- Contact support for custom limits
**Tier Progression:**
- Tier 1: 50 RPM, 40k TPM
- Tier 2: 1000 RPM, 100k TPM ($10 spend)
- Tier 3: 2000 RPM, 200k TPM ($50 spend)
- Tier 4: 4000 RPM, 400k TPM ($500 spend)
**Prevention:**
- Review tier requirements
- Plan for gradual scale-up
- Implement proper rate limiting
---
## Error #12: Message Batches Beta Headers Missing
**Error Message:**
```
invalid_request_error: unknown parameter: batches
```
**Source**: Beta API requirements
**Why It Happens:**
- Missing `anthropic-beta` header
- Using wrong endpoint
**Solution:**
```typescript
const response = await fetch('https://api.anthropic.com/v1/messages/batches', {
method: 'POST',
headers: {
'x-api-key': API_KEY,
'anthropic-version': '2023-06-01',
'anthropic-beta': 'message-batches-2024-09-24', // Required!
'content-type': 'application/json',
},
body: JSON.stringify({...}),
});
```
**Prevention:**
- Include beta headers for beta features
- Check official docs for header requirements
- Test beta features in development first
---
## Quick Diagnosis Checklist
When encountering errors:
1. ✅ Check error status code (400, 401, 429, 500, etc.)
2. ✅ Read error message carefully
3. ✅ Verify API key is valid and in environment variable
4. ✅ Confirm model ID is correct
5. ✅ Check request format matches API spec
6. ✅ Monitor rate limit headers
7. ✅ Review recent code changes
8. ✅ Test with minimal example
9. ✅ Check official docs for breaking changes
10. ✅ Search GitHub issues for similar problems
---
## Getting Help
**Official Resources:**
- **Errors Reference**: https://docs.claude.com/en/api/errors
- **API Documentation**: https://docs.claude.com/en/api
- **Support**: https://support.claude.com/
**Community:**
- **GitHub Issues**: https://github.com/anthropics/anthropic-sdk-typescript/issues
- **Developer Forum**: https://support.claude.com/
**This Skill:**
- Check other reference files for detailed guides
- Review templates for working examples
- Verify setup checklist in SKILL.md

View File

@@ -0,0 +1,108 @@
# Vision Capabilities
Guide to using Claude's vision capabilities for image understanding.
## Supported Formats
- **JPEG** (image/jpeg)
- **PNG** (image/png)
- **WebP** (image/webp)
- **GIF** (image/gif) - non-animated only
**Max size**: 5MB per image
## Basic Usage
```typescript
import fs from 'fs';
const imageData = fs.readFileSync('./image.jpg').toString('base64');
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{
role: 'user',
content: [
{
type: 'image',
source: {
type: 'base64',
media_type: 'image/jpeg',
data: imageData
}
},
{
type: 'text',
text: 'What is in this image?'
}
]
}]
});
```
## Multiple Images
```typescript
content: [
{ type: 'text', text: 'Compare these images:' },
{ type: 'image', source: { type: 'base64', media_type: 'image/jpeg', data: img1 } },
{ type: 'image', source: { type: 'base64', media_type: 'image/png', data: img2 } },
{ type: 'text', text: 'What are the differences?' }
]
```
## With Tools
```typescript
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
tools: [searchProductTool],
messages: [{
role: 'user',
content: [
{ type: 'image', source: {...} },
{ type: 'text', text: 'Find similar products' }
]
}]
});
```
## With Prompt Caching
```typescript
{
type: 'image',
source: {
type: 'base64',
media_type: 'image/jpeg',
data: imageData
},
cache_control: { type: 'ephemeral' } // Cache image
}
```
## Validation
```typescript
function validateImage(path: string) {
const stats = fs.statSync(path);
const sizeMB = stats.size / (1024 * 1024);
if (sizeMB > 5) {
throw new Error('Image exceeds 5MB');
}
const ext = path.split('.').pop()?.toLowerCase();
if (!['jpg', 'jpeg', 'png', 'webp', 'gif'].includes(ext)) {
throw new Error('Unsupported format');
}
return true;
}
```
## Official Docs
https://docs.claude.com/en/docs/build-with-claude/vision

102
scripts/check-versions.sh Executable file
View File

@@ -0,0 +1,102 @@
#!/bin/bash
# Check Claude API package versions
# Usage: ./scripts/check-versions.sh
echo "=== Claude API Package Version Checker ==="
echo ""
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Check if npm is installed
if ! command -v npm &> /dev/null; then
echo -e "${RED}Error: npm is not installed${NC}"
exit 1
fi
# Check @anthropic-ai/sdk
echo "Checking @anthropic-ai/sdk..."
CURRENT_VERSION=$(npm view @anthropic-ai/sdk version 2>/dev/null)
if [ -z "$CURRENT_VERSION" ]; then
echo -e "${RED}Error: Could not fetch version info${NC}"
else
echo -e "${GREEN}Latest version: $CURRENT_VERSION${NC}"
# Expected version from skill
EXPECTED="0.67.0"
if [ "$CURRENT_VERSION" != "$EXPECTED" ]; then
echo -e "${YELLOW}Note: Skill was verified with version $EXPECTED${NC}"
echo -e "${YELLOW}Latest version is $CURRENT_VERSION${NC}"
echo ""
echo "To update skill documentation, run:"
echo " npm view @anthropic-ai/sdk version"
else
echo -e "${GREEN}✓ Version matches skill documentation${NC}"
fi
fi
echo ""
# Check other related packages
echo "Checking optional dependencies..."
echo ""
packages=("zod" "@types/node" "typescript")
for package in "${packages[@]}"; do
echo "- $package:"
VERSION=$(npm view $package version 2>/dev/null)
if [ -z "$VERSION" ]; then
echo -e " ${YELLOW}Could not fetch version${NC}"
else
echo -e " ${GREEN}Latest: $VERSION${NC}"
fi
done
echo ""
# Check if package.json exists locally
if [ -f "package.json" ]; then
echo "Checking local package.json..."
echo ""
if command -v jq &> /dev/null; then
# Use jq if available
ANTHROPIC_VERSION=$(jq -r '.dependencies."@anthropic-ai/sdk"' package.json 2>/dev/null)
if [ "$ANTHROPIC_VERSION" != "null" ] && [ ! -z "$ANTHROPIC_VERSION" ]; then
echo "@anthropic-ai/sdk: $ANTHROPIC_VERSION"
fi
else
# Fallback to grep
grep -E '"@anthropic-ai/sdk"' package.json
fi
echo ""
fi
# Check for breaking changes
echo "=== Checking for breaking changes ==="
echo ""
echo "Official changelog:"
echo "https://github.com/anthropics/anthropic-sdk-typescript/releases"
echo ""
# Check npm for recent updates
echo "Recent versions:"
npm view @anthropic-ai/sdk versions --json | tail -10
echo ""
echo "=== Version Check Complete ==="
echo ""
echo "To update your local installation:"
echo " npm install @anthropic-ai/sdk@latest"
echo ""
echo "To check what would be installed:"
echo " npm outdated"

115
templates/basic-chat.ts Normal file
View File

@@ -0,0 +1,115 @@
import Anthropic from '@anthropic-ai/sdk';
// Initialize the client
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY || '',
});
async function basicChat() {
try {
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'Hello, Claude! Tell me a fun fact about TypeScript.',
},
],
});
// Extract text from response
const textContent = message.content.find(block => block.type === 'text');
if (textContent && textContent.type === 'text') {
console.log('Claude:', textContent.text);
}
// Log usage information
console.log('\nUsage:');
console.log('- Input tokens:', message.usage.input_tokens);
console.log('- Output tokens:', message.usage.output_tokens);
} catch (error) {
if (error instanceof Anthropic.APIError) {
console.error(`API Error [${error.status}]:`, error.message);
} else {
console.error('Unexpected error:', error);
}
}
}
// Multi-turn conversation example
async function multiTurnChat() {
const messages: Array<{ role: 'user' | 'assistant'; content: string }> = [];
// First turn
messages.push({
role: 'user',
content: 'What is the capital of France?',
});
const response1 = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages,
});
const text1 = response1.content.find(b => b.type === 'text');
if (text1 && text1.type === 'text') {
messages.push({ role: 'assistant', content: text1.text });
console.log('Claude:', text1.text);
}
// Second turn
messages.push({
role: 'user',
content: 'What is its population?',
});
const response2 = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages,
});
const text2 = response2.content.find(b => b.type === 'text');
if (text2 && text2.type === 'text') {
console.log('Claude:', text2.text);
}
}
// System prompt example
async function chatWithSystemPrompt() {
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
system: 'You are a helpful Python coding assistant. Always provide type hints and docstrings.',
messages: [
{
role: 'user',
content: 'Write a function to calculate the factorial of a number.',
},
],
});
const textContent = message.content.find(block => block.type === 'text');
if (textContent && textContent.type === 'text') {
console.log(textContent.text);
}
}
// Run examples
if (require.main === module) {
console.log('=== Basic Chat ===\n');
basicChat()
.then(() => {
console.log('\n=== Multi-turn Chat ===\n');
return multiTurnChat();
})
.then(() => {
console.log('\n=== Chat with System Prompt ===\n');
return chatWithSystemPrompt();
})
.catch(console.error);
}
export { basicChat, multiTurnChat, chatWithSystemPrompt };

View File

@@ -0,0 +1,268 @@
// Cloudflare Worker with Claude API
// Uses fetch API (no SDK needed)
export interface Env {
ANTHROPIC_API_KEY: string;
}
// Basic chat endpoint
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
// CORS headers
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
};
if (request.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders });
}
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
try {
const { messages } = await request.json<{ messages: any[] }>();
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'x-api-key': env.ANTHROPIC_API_KEY,
'anthropic-version': '2023-06-01',
'content-type': 'application/json',
},
body: JSON.stringify({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages,
}),
});
const data = await response.json();
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
...corsHeaders,
},
});
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{
status: 500,
headers: {
'Content-Type': 'application/json',
...corsHeaders,
},
}
);
}
},
};
// Streaming endpoint example
export const streamingEndpoint = {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
try {
const { messages } = await request.json<{ messages: any[] }>();
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'x-api-key': env.ANTHROPIC_API_KEY,
'anthropic-version': '2023-06-01',
'content-type': 'application/json',
},
body: JSON.stringify({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages,
stream: true,
}),
});
// Return SSE stream directly
return new Response(response.body, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*',
},
});
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{ status: 500 }
);
}
},
};
// With rate limiting using Durable Objects
interface RateLimiterState {
requests: number;
resetTime: number;
}
export class RateLimiter implements DurableObject {
state: DurableObjectState;
storage: DurableObjectStorage;
constructor(state: DurableObjectState, env: Env) {
this.state = state;
this.storage = state.storage;
}
async fetch(request: Request): Promise<Response> {
const now = Date.now();
const limitData = await this.storage.get<RateLimiterState>('limiter') || {
requests: 0,
resetTime: now + 60000, // 1 minute
};
// Reset if time window expired
if (now > limitData.resetTime) {
limitData.requests = 0;
limitData.resetTime = now + 60000;
}
// Check limit (e.g., 10 requests per minute)
if (limitData.requests >= 10) {
return new Response('Rate limit exceeded', { status: 429 });
}
limitData.requests++;
await this.storage.put('limiter', limitData);
return new Response('OK', { status: 200 });
}
}
// Complete worker with error handling and rate limiting
export const productionWorker = {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
// Route handling
if (url.pathname === '/chat') {
return handleChat(request, env);
}
if (url.pathname === '/stream') {
return handleStream(request, env);
}
return new Response('Not Found', { status: 404 });
},
};
async function handleChat(request: Request, env: Env): Promise<Response> {
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
try {
const { messages } = await request.json<{ messages: any[] }>();
// Validate input
if (!Array.isArray(messages) || messages.length === 0) {
return new Response(
JSON.stringify({ error: 'Invalid messages array' }),
{ status: 400 }
);
}
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'x-api-key': env.ANTHROPIC_API_KEY,
'anthropic-version': '2023-06-01',
'content-type': 'application/json',
},
body: JSON.stringify({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages,
}),
});
if (!response.ok) {
const error = await response.text();
console.error('Claude API error:', error);
return new Response(
JSON.stringify({ error: 'API request failed' }),
{ status: response.status }
);
}
const data = await response.json();
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
} catch (error) {
console.error('Worker error:', error);
return new Response(
JSON.stringify({ error: 'Internal server error' }),
{ status: 500 }
);
}
}
async function handleStream(request: Request, env: Env): Promise<Response> {
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
try {
const { messages } = await request.json<{ messages: any[] }>();
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'x-api-key': env.ANTHROPIC_API_KEY,
'anthropic-version': '2023-06-01',
'content-type': 'application/json',
},
body: JSON.stringify({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages,
stream: true,
}),
});
if (!response.ok) {
return new Response(
JSON.stringify({ error: 'Stream failed' }),
{ status: response.status }
);
}
return new Response(response.body, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*',
},
});
} catch (error) {
console.error('Stream error:', error);
return new Response(
JSON.stringify({ error: 'Stream error' }),
{ status: 500 }
);
}
}

373
templates/error-handling.ts Normal file
View File

@@ -0,0 +1,373 @@
import Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
// Example 1: Basic error handling
async function basicErrorHandling(prompt: string) {
try {
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: prompt }],
});
return message;
} catch (error) {
if (error instanceof Anthropic.APIError) {
console.error(`API Error [${error.status}]:`, error.message);
console.error('Error type:', error.type);
console.error('Error details:', error.error);
// Handle specific error types
switch (error.status) {
case 400:
console.error('Invalid request. Check your parameters.');
break;
case 401:
console.error('Authentication failed. Check your API key.');
break;
case 403:
console.error('Permission denied. Check your account tier.');
break;
case 404:
console.error('Resource not found. Check the endpoint.');
break;
case 429:
console.error('Rate limit exceeded. Implement retry logic.');
break;
case 500:
console.error('Server error. Retry with exponential backoff.');
break;
case 529:
console.error('API overloaded. Retry later.');
break;
default:
console.error('Unexpected error occurred.');
}
} else {
console.error('Non-API error:', error);
}
throw error;
}
}
// Example 2: Rate limit handler with retry
async function handleRateLimits(
requestFn: () => Promise<any>,
maxRetries = 3,
baseDelay = 1000
): Promise<any> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await requestFn();
} catch (error) {
if (error instanceof Anthropic.APIError && error.status === 429) {
// Check retry-after header
const retryAfter = error.response?.headers?.['retry-after'];
const delay = retryAfter
? parseInt(retryAfter) * 1000
: baseDelay * Math.pow(2, attempt);
if (attempt < maxRetries - 1) {
console.warn(`Rate limited. Retrying in ${delay}ms... (Attempt ${attempt + 1}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
}
throw error;
}
}
throw new Error('Max retries exceeded');
}
// Example 3: Comprehensive error handler
class APIErrorHandler {
private maxRetries: number;
private baseDelay: number;
private onError?: (error: Error) => void;
constructor(options: {
maxRetries?: number;
baseDelay?: number;
onError?: (error: Error) => void;
} = {}) {
this.maxRetries = options.maxRetries || 3;
this.baseDelay = options.baseDelay || 1000;
this.onError = options.onError;
}
async execute<T>(requestFn: () => Promise<T>): Promise<T> {
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
try {
return await requestFn();
} catch (error) {
if (this.onError) {
this.onError(error);
}
if (error instanceof Anthropic.APIError) {
if (this.shouldRetry(error) && attempt < this.maxRetries - 1) {
const delay = this.calculateDelay(error, attempt);
console.warn(`Retrying after ${delay}ms... (${attempt + 1}/${this.maxRetries})`);
await this.sleep(delay);
continue;
}
}
throw this.enhanceError(error);
}
}
throw new Error('Max retries exceeded');
}
private shouldRetry(error: Anthropic.APIError): boolean {
// Retry on rate limits, server errors, and overload
return error.status === 429 || error.status === 500 || error.status === 529;
}
private calculateDelay(error: Anthropic.APIError, attempt: number): number {
// Use retry-after header if available
const retryAfter = error.response?.headers?.['retry-after'];
if (retryAfter) {
return parseInt(retryAfter) * 1000;
}
// Exponential backoff
return this.baseDelay * Math.pow(2, attempt);
}
private enhanceError(error: any): Error {
if (error instanceof Anthropic.APIError) {
const enhancedError = new Error(`Claude API Error: ${error.message}`);
(enhancedError as any).originalError = error;
(enhancedError as any).status = error.status;
(enhancedError as any).type = error.type;
return enhancedError;
}
return error;
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Example 4: Streaming error handling
async function streamWithErrorHandling(prompt: string) {
try {
const stream = anthropic.messages.stream({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: prompt }],
});
let hasError = false;
stream.on('error', (error) => {
hasError = true;
console.error('Stream error:', error);
if (error instanceof Anthropic.APIError) {
console.error(`Status: ${error.status}`);
console.error(`Type: ${error.type}`);
}
// Implement fallback or retry logic here
});
stream.on('abort', (error) => {
console.warn('Stream aborted:', error);
});
stream.on('text', (text) => {
if (!hasError) {
process.stdout.write(text);
}
});
await stream.finalMessage();
if (hasError) {
throw new Error('Stream completed with errors');
}
} catch (error) {
console.error('Failed to complete stream:', error);
throw error;
}
}
// Example 5: Validation errors
function validateRequest(params: {
messages: any[];
max_tokens?: number;
model?: string;
}): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (!Array.isArray(params.messages) || params.messages.length === 0) {
errors.push('Messages must be a non-empty array');
}
if (params.max_tokens && (params.max_tokens < 1 || params.max_tokens > 8192)) {
errors.push('max_tokens must be between 1 and 8192');
}
if (params.model && !params.model.startsWith('claude-')) {
errors.push('Invalid model name');
}
for (const [index, message] of params.messages.entries()) {
if (!message.role || !['user', 'assistant'].includes(message.role)) {
errors.push(`Message ${index}: Invalid role. Must be "user" or "assistant"`);
}
if (!message.content) {
errors.push(`Message ${index}: Missing content`);
}
}
return {
valid: errors.length === 0,
errors,
};
}
// Example 6: Circuit breaker pattern
class CircuitBreaker {
private failures: number = 0;
private lastFailureTime: number = 0;
private state: 'closed' | 'open' | 'half-open' = 'closed';
private readonly threshold: number;
private readonly timeout: number;
constructor(options: { threshold?: number; timeout?: number } = {}) {
this.threshold = options.threshold || 5;
this.timeout = options.timeout || 60000; // 1 minute
}
async execute<T>(requestFn: () => Promise<T>): Promise<T> {
if (this.state === 'open') {
if (Date.now() - this.lastFailureTime > this.timeout) {
console.log('Circuit breaker: Transitioning to half-open');
this.state = 'half-open';
} else {
throw new Error('Circuit breaker is open. Service unavailable.');
}
}
try {
const result = await requestFn();
// Success - reset failures
if (this.state === 'half-open') {
console.log('Circuit breaker: Transitioning to closed');
this.state = 'closed';
}
this.failures = 0;
return result;
} catch (error) {
this.failures++;
this.lastFailureTime = Date.now();
if (this.failures >= this.threshold) {
console.error(`Circuit breaker: Opening after ${this.failures} failures`);
this.state = 'open';
}
throw error;
}
}
getState(): { state: string; failures: number } {
return {
state: this.state,
failures: this.failures,
};
}
}
// Example 7: Usage with all patterns
async function robustAPICall(prompt: string) {
const errorHandler = new APIErrorHandler({
maxRetries: 3,
baseDelay: 1000,
onError: (error) => {
console.error('Error logged:', error);
// Could send to monitoring service here
},
});
const circuitBreaker = new CircuitBreaker({
threshold: 5,
timeout: 60000,
});
try {
const validation = validateRequest({
messages: [{ role: 'user', content: prompt }],
max_tokens: 1024,
model: 'claude-sonnet-4-5-20250929',
});
if (!validation.valid) {
throw new Error(`Validation failed: ${validation.errors.join(', ')}`);
}
const result = await circuitBreaker.execute(() =>
errorHandler.execute(() =>
anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: prompt }],
})
)
);
return result;
} catch (error) {
console.error('Robust API call failed:', error);
console.error('Circuit breaker state:', circuitBreaker.getState());
throw error;
}
}
// Run examples
if (require.main === module) {
console.log('=== Error Handling Examples ===\n');
// Test basic error handling
basicErrorHandling('Hello, Claude!')
.then(() => {
console.log('\n=== Testing Rate Limit Handler ===\n');
return handleRateLimits(() =>
anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Test message' }],
})
);
})
.then(() => {
console.log('\n=== Testing Robust API Call ===\n');
return robustAPICall('What is 2+2?');
})
.catch(console.error);
}
export {
basicErrorHandling,
handleRateLimits,
APIErrorHandler,
streamWithErrorHandling,
validateRequest,
CircuitBreaker,
robustAPICall,
};

View File

@@ -0,0 +1,320 @@
import Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY || '',
});
/**
* IMPORTANT: Extended thinking is ONLY available in:
* - Claude 3.7 Sonnet (claude-3-7-sonnet-20250228)
* - Claude 4 models (Opus 4, Sonnet 4)
*
* NOT available in Claude 3.5 Sonnet
*/
// Example 1: Basic extended thinking
async function basicExtendedThinking() {
const message = await anthropic.messages.create({
model: 'claude-3-7-sonnet-20250228', // Must use 3.7 or 4.x
max_tokens: 4096, // Higher token limit for thinking
messages: [
{
role: 'user',
content: `A ball is thrown upward with an initial velocity of 20 m/s.
How high does it go? (Use g = 9.8 m/s²)`,
},
],
});
console.log('=== Response with Extended Thinking ===\n');
// Display thinking blocks separately from answer
for (const block of message.content) {
if (block.type === 'thinking') {
console.log('🤔 Claude is thinking:');
console.log(block.text);
console.log('\n' + '='.repeat(50) + '\n');
} else if (block.type === 'text') {
console.log('💡 Final Answer:');
console.log(block.text);
}
}
console.log('\nStop reason:', message.stop_reason);
console.log('Token usage:', message.usage);
}
// Example 2: Complex problem solving
async function complexProblemSolving() {
const message = await anthropic.messages.create({
model: 'claude-3-7-sonnet-20250228',
max_tokens: 8192, // Even higher for complex reasoning
messages: [
{
role: 'user',
content: `Debug this Python code and explain what's wrong:
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-1)
print(fibonacci(10))
Why is it slow and what's the correct implementation?`,
},
],
});
for (const block of message.content) {
if (block.type === 'thinking') {
console.log('🔍 Debugging process:');
console.log(block.text);
console.log();
} else if (block.type === 'text') {
console.log('✅ Solution:');
console.log(block.text);
}
}
}
// Example 3: Multi-step reasoning
async function multiStepReasoning() {
const message = await anthropic.messages.create({
model: 'claude-3-7-sonnet-20250228',
max_tokens: 6144,
messages: [
{
role: 'user',
content: `I have a 10-liter jug and a 6-liter jug. How can I measure exactly 8 liters of water?
Think through this step by step.`,
},
],
});
for (const block of message.content) {
if (block.type === 'thinking') {
console.log('🧠 Reasoning steps:');
console.log(block.text);
console.log();
} else if (block.type === 'text') {
console.log('📝 Final solution:');
console.log(block.text);
}
}
}
// Example 4: Comparing with and without extended thinking
async function compareThinkingModes() {
const problem = 'What is the sum of all prime numbers less than 100?';
// Without extended thinking (Claude 3.5 Sonnet)
console.log('=== Without Extended Thinking (Claude 3.5 Sonnet) ===\n');
const response1 = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
messages: [{ role: 'user', content: problem }],
});
const text1 = response1.content.find(b => b.type === 'text');
if (text1 && text1.type === 'text') {
console.log(text1.text);
}
console.log('\nTokens used:', response1.usage.input_tokens + response1.usage.output_tokens);
// With extended thinking (Claude 3.7 Sonnet)
console.log('\n\n=== With Extended Thinking (Claude 3.7 Sonnet) ===\n');
const response2 = await anthropic.messages.create({
model: 'claude-3-7-sonnet-20250228',
max_tokens: 4096,
messages: [{ role: 'user', content: problem }],
});
for (const block of response2.content) {
if (block.type === 'thinking') {
console.log('🤔 Thinking process:');
console.log(block.text);
console.log();
} else if (block.type === 'text') {
console.log('💡 Answer:');
console.log(block.text);
}
}
console.log('\nTokens used:', response2.usage.input_tokens + response2.usage.output_tokens);
}
// Example 5: Extended thinking with tools
async function extendedThinkingWithTools() {
const tools: Anthropic.Tool[] = [
{
name: 'calculate',
description: 'Perform mathematical calculations',
input_schema: {
type: 'object',
properties: {
expression: {
type: 'string',
description: 'Mathematical expression to evaluate',
},
},
required: ['expression'],
},
},
];
const messages: Anthropic.MessageParam[] = [
{
role: 'user',
content:
'Calculate the compound interest on $1000 invested at 5% annual interest for 10 years, compounded monthly',
},
];
const response = await anthropic.messages.create({
model: 'claude-3-7-sonnet-20250228',
max_tokens: 4096,
tools,
messages,
});
console.log('=== Extended Thinking with Tools ===\n');
for (const block of response.content) {
if (block.type === 'thinking') {
console.log('🤔 Planning:');
console.log(block.text);
console.log();
} else if (block.type === 'tool_use') {
console.log('🔧 Tool use:', block.name);
console.log('Parameters:', block.input);
console.log();
} else if (block.type === 'text') {
console.log('💡 Response:');
console.log(block.text);
}
}
}
// Example 6: Error when using wrong model
async function demonstrateWrongModelError() {
try {
console.log('=== Attempting extended thinking on Claude 3.5 Sonnet ===\n');
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929', // Wrong model!
max_tokens: 4096,
messages: [
{
role: 'user',
content: 'Solve this complex math problem step by step',
},
],
});
// No thinking blocks will be present
const hasThinking = message.content.some(block => block.type === 'thinking');
if (!hasThinking) {
console.log('⚠️ No thinking blocks found!');
console.log('Extended thinking is only available in Claude 3.7 Sonnet or Claude 4 models.');
}
for (const block of message.content) {
if (block.type === 'text') {
console.log('Regular response:', block.text);
}
}
} catch (error) {
console.error('Error:', error);
}
}
// Example 7: Check model capabilities
function getModelCapabilities(modelId: string): {
supportsExtendedThinking: boolean;
contextWindow: number;
} {
const models: Record<
string,
{ supportsExtendedThinking: boolean; contextWindow: number }
> = {
'claude-sonnet-4-5-20250929': {
supportsExtendedThinking: false,
contextWindow: 200_000,
},
'claude-3-7-sonnet-20250228': {
supportsExtendedThinking: true,
contextWindow: 2_000_000,
},
'claude-opus-4-20250514': {
supportsExtendedThinking: true,
contextWindow: 200_000,
},
'claude-3-5-haiku-20241022': {
supportsExtendedThinking: false,
contextWindow: 200_000,
},
};
return (
models[modelId] || {
supportsExtendedThinking: false,
contextWindow: 200_000,
}
);
}
// Helper: Validate model for extended thinking
function validateModelForExtendedThinking(modelId: string): void {
const capabilities = getModelCapabilities(modelId);
if (!capabilities.supportsExtendedThinking) {
throw new Error(
`Model ${modelId} does not support extended thinking. Use Claude 3.7 Sonnet or Claude 4 models.`
);
}
console.log(`✅ Model ${modelId} supports extended thinking`);
console.log(`Context window: ${capabilities.contextWindow.toLocaleString()} tokens`);
}
// Run examples
if (require.main === module) {
console.log('=== Extended Thinking Examples ===\n');
// Validate model first
try {
validateModelForExtendedThinking('claude-3-7-sonnet-20250228');
} catch (error) {
console.error(error.message);
process.exit(1);
}
basicExtendedThinking()
.then(() => {
console.log('\n\n=== Complex Problem ===\n');
return complexProblemSolving();
})
.then(() => {
console.log('\n\n=== Multi-step Reasoning ===\n');
return multiStepReasoning();
})
.then(() => {
console.log('\n\n=== Wrong Model Demo ===\n');
return demonstrateWrongModelError();
})
.catch(console.error);
}
export {
basicExtendedThinking,
complexProblemSolving,
multiStepReasoning,
compareThinkingModes,
extendedThinkingWithTools,
demonstrateWrongModelError,
getModelCapabilities,
validateModelForExtendedThinking,
};

View File

@@ -0,0 +1,304 @@
// Next.js API Routes for Claude API
// ============================================
// App Router (app/api/chat/route.ts)
// ============================================
import Anthropic from '@anthropic-ai/sdk';
import { NextRequest, NextResponse } from 'next/server';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
// POST /api/chat - Non-streaming
export async function POST(request: NextRequest) {
try {
const { messages } = await request.json();
// Validate input
if (!Array.isArray(messages) || messages.length === 0) {
return NextResponse.json({ error: 'Invalid messages' }, { status: 400 });
}
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages,
});
return NextResponse.json(message);
} catch (error) {
console.error('Chat error:', error);
if (error instanceof Anthropic.APIError) {
return NextResponse.json(
{ error: error.message },
{ status: error.status || 500 }
);
}
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}
// ============================================
// App Router with Streaming (app/api/stream/route.ts)
// ============================================
export async function POST_STREAMING(request: NextRequest) {
try {
const { messages } = await request.json();
const stream = anthropic.messages.stream({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages,
});
// Convert to ReadableStream
const readableStream = new ReadableStream({
async start(controller) {
try {
for await (const event of stream) {
if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
const text = event.delta.text;
controller.enqueue(new TextEncoder().encode(text));
}
if (event.type === 'message_stop') {
controller.close();
}
}
} catch (error) {
console.error('Stream error:', error);
controller.error(error);
}
},
});
return new Response(readableStream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
} catch (error) {
console.error('Stream setup error:', error);
return NextResponse.json({ error: 'Stream failed' }, { status: 500 });
}
}
// ============================================
// Pages Router (pages/api/chat.ts)
// ============================================
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const { messages } = req.body;
if (!Array.isArray(messages)) {
return res.status(400).json({ error: 'Invalid request' });
}
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages,
});
res.status(200).json(message);
} catch (error) {
console.error('API error:', error);
if (error instanceof Anthropic.APIError) {
return res.status(error.status || 500).json({ error: error.message });
}
res.status(500).json({ error: 'Internal server error' });
}
}
// ============================================
// Pages Router with Streaming (pages/api/stream.ts)
// ============================================
export async function streamHandler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const { messages } = req.body;
// Set SSE headers
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const stream = anthropic.messages.stream({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages,
});
stream.on('text', (text) => {
res.write(`data: ${JSON.stringify({ text })}\n\n`);
});
stream.on('error', (error) => {
console.error('Stream error:', error);
res.write(`data: ${JSON.stringify({ error: error.message })}\n\n`);
res.end();
});
stream.on('end', () => {
res.write('data: [DONE]\n\n');
res.end();
});
await stream.finalMessage();
} catch (error) {
console.error('Stream setup error:', error);
res.status(500).json({ error: 'Stream failed' });
}
}
// ============================================
// With Tool Use (App Router)
// ============================================
export async function POST_WITH_TOOLS(request: NextRequest) {
try {
const { messages } = await request.json();
const tools: Anthropic.Tool[] = [
{
name: 'get_weather',
description: 'Get the current weather',
input_schema: {
type: 'object',
properties: {
location: { type: 'string' },
},
required: ['location'],
},
},
];
let conversationMessages = messages;
while (true) {
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
tools,
messages: conversationMessages,
});
conversationMessages.push({
role: 'assistant',
content: response.content,
});
if (response.stop_reason === 'tool_use') {
const toolResults = [];
for (const block of response.content) {
if (block.type === 'tool_use') {
// Execute tool
const result = await executeToolFunction(block.name, block.input);
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: JSON.stringify(result),
});
}
}
conversationMessages.push({
role: 'user',
content: toolResults,
});
} else {
// Final response
return NextResponse.json(response);
}
}
} catch (error) {
console.error('Tool use error:', error);
return NextResponse.json({ error: 'Tool execution failed' }, { status: 500 });
}
}
async function executeToolFunction(name: string, input: any): Promise<any> {
if (name === 'get_weather') {
// Mock implementation
return { temperature: 72, condition: 'Sunny' };
}
return { error: 'Unknown tool' };
}
// ============================================
// With Prompt Caching (App Router)
// ============================================
export async function POST_WITH_CACHING(request: NextRequest) {
try {
const { messages, systemPrompt } = await request.json();
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
system: [
{
type: 'text',
text: systemPrompt,
cache_control: { type: 'ephemeral' },
},
],
messages,
});
return NextResponse.json({
message,
usage: message.usage,
});
} catch (error) {
console.error('Caching error:', error);
return NextResponse.json({ error: 'Request failed' }, { status: 500 });
}
}
// ============================================
// Middleware for Rate Limiting
// ============================================
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '1 m'),
});
export async function POST_WITH_RATE_LIMIT(request: NextRequest) {
// Get identifier (IP or user ID)
const identifier = request.ip ?? 'anonymous';
const { success } = await ratelimit.limit(identifier);
if (!success) {
return NextResponse.json({ error: 'Rate limit exceeded' }, { status: 429 });
}
// Continue with normal handler
return POST(request);
}

381
templates/nodejs-example.ts Normal file
View File

@@ -0,0 +1,381 @@
// Complete Node.js examples for Claude API
import Anthropic from '@anthropic-ai/sdk';
import * as dotenv from 'dotenv';
import * as readline from 'readline';
dotenv.config();
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
// Example 1: Simple CLI chatbot
async function simpleCLIChatbot() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const messages: Anthropic.MessageParam[] = [];
console.log('Claude CLI Chatbot (type "exit" to quit)\n');
const chat = async () => {
rl.question('You: ', async (userInput) => {
if (userInput.toLowerCase() === 'exit') {
console.log('Goodbye!');
rl.close();
return;
}
messages.push({ role: 'user', content: userInput });
try {
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages,
});
const textContent = response.content.find(b => b.type === 'text');
if (textContent && textContent.type === 'text') {
console.log(`\nClaude: ${textContent.text}\n`);
messages.push({ role: 'assistant', content: textContent.text });
}
} catch (error) {
console.error('Error:', error.message);
}
chat();
});
};
chat();
}
// Example 2: Streaming CLI chatbot
async function streamingCLIChatbot() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
console.log('Streaming Claude CLI Chatbot (type "exit" to quit)\n');
const chat = async () => {
rl.question('You: ', async (userInput) => {
if (userInput.toLowerCase() === 'exit') {
console.log('Goodbye!');
rl.close();
return;
}
try {
process.stdout.write('\nClaude: ');
const stream = anthropic.messages.stream({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: userInput }],
});
let fullText = '';
stream.on('text', (text) => {
process.stdout.write(text);
fullText += text;
});
await stream.finalMessage();
console.log('\n');
} catch (error) {
console.error('\nError:', error.message);
}
chat();
});
};
chat();
}
// Example 3: Batch processing from file
import * as fs from 'fs';
async function batchProcessing(inputFile: string, outputFile: string) {
const lines = fs.readFileSync(inputFile, 'utf-8').split('\n').filter(Boolean);
const results = [];
console.log(`Processing ${lines.length} prompts...`);
for (let i = 0; i < lines.length; i++) {
const prompt = lines[i];
console.log(`\n[${i + 1}/${lines.length}] Processing: ${prompt.substring(0, 50)}...`);
try {
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: prompt }],
});
const textContent = message.content.find(b => b.type === 'text');
if (textContent && textContent.type === 'text') {
results.push({
prompt,
response: textContent.text,
tokens: message.usage,
});
}
// Rate limiting pause
await new Promise(resolve => setTimeout(resolve, 1000));
} catch (error) {
console.error(`Error processing prompt ${i + 1}:`, error.message);
results.push({
prompt,
error: error.message,
});
}
}
fs.writeFileSync(outputFile, JSON.stringify(results, null, 2));
console.log(`\nResults written to ${outputFile}`);
}
// Example 4: Document summarization
async function summarizeDocument(filePath: string) {
const document = fs.readFileSync(filePath, 'utf-8');
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
system: 'You are an expert document summarizer. Provide concise, accurate summaries.',
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: 'Summarize the following document in 3-5 bullet points:',
},
{
type: 'text',
text: document,
cache_control: { type: 'ephemeral' },
},
],
},
],
});
const textContent = message.content.find(b => b.type === 'text');
if (textContent && textContent.type === 'text') {
console.log('Summary:');
console.log(textContent.text);
console.log('\nToken usage:', message.usage);
}
}
// Example 5: Code review assistant
async function codeReview(codeContent: string, language: string) {
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
system: `You are an expert ${language} code reviewer. Analyze code for:
- Bugs and potential issues
- Performance optimizations
- Security vulnerabilities
- Best practices
- Code style and readability`,
messages: [
{
role: 'user',
content: `Review this ${language} code:\n\n\`\`\`${language}\n${codeContent}\n\`\`\``,
},
],
});
const textContent = message.content.find(b => b.type === 'text');
if (textContent && textContent.type === 'text') {
console.log('Code Review:');
console.log(textContent.text);
}
}
// Example 6: Translation service
async function translateText(text: string, from: string, to: string) {
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: `Translate the following text from ${from} to ${to}:\n\n${text}`,
},
],
});
const textContent = message.content.find(b => b.type === 'text');
if (textContent && textContent.type === 'text') {
return textContent.text;
}
return null;
}
// Example 7: Parallel requests
async function parallelRequests(prompts: string[]) {
console.log(`Processing ${prompts.length} prompts in parallel...`);
const promises = prompts.map(async (prompt, index) => {
try {
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 512,
messages: [{ role: 'user', content: prompt }],
});
const textContent = message.content.find(b => b.type === 'text');
return {
index,
prompt,
response: textContent && textContent.type === 'text' ? textContent.text : null,
};
} catch (error) {
return {
index,
prompt,
error: error.message,
};
}
});
const results = await Promise.all(promises);
return results;
}
// Example 8: Retry logic with exponential backoff
async function requestWithRetry(
prompt: string,
maxRetries = 3,
baseDelay = 1000
): Promise<string> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: prompt }],
});
const textContent = message.content.find(b => b.type === 'text');
if (textContent && textContent.type === 'text') {
return textContent.text;
}
throw new Error('No text content in response');
} catch (error) {
if (error instanceof Anthropic.APIError && error.status === 429) {
if (attempt < maxRetries - 1) {
const delay = baseDelay * Math.pow(2, attempt);
console.warn(`Rate limited. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
}
throw error;
}
}
throw new Error('Max retries exceeded');
}
// Example 9: Conversation logger
class ConversationLogger {
private messages: Anthropic.MessageParam[] = [];
private logFile: string;
constructor(logFile: string) {
this.logFile = logFile;
}
async chat(userMessage: string): Promise<string> {
this.messages.push({ role: 'user', content: userMessage });
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: this.messages,
});
const textContent = response.content.find(b => b.type === 'text');
if (textContent && textContent.type === 'text') {
this.messages.push({ role: 'assistant', content: textContent.text });
this.save();
return textContent.text;
}
throw new Error('No response');
}
private save() {
fs.writeFileSync(this.logFile, JSON.stringify(this.messages, null, 2));
}
load() {
if (fs.existsSync(this.logFile)) {
this.messages = JSON.parse(fs.readFileSync(this.logFile, 'utf-8'));
}
}
}
// Run examples
if (require.main === module) {
const args = process.argv.slice(2);
const example = args[0];
switch (example) {
case 'cli':
simpleCLIChatbot();
break;
case 'stream':
streamingCLIChatbot();
break;
case 'batch':
batchProcessing(args[1], args[2] || 'output.json');
break;
case 'summarize':
summarizeDocument(args[1]);
break;
case 'review':
const code = fs.readFileSync(args[1], 'utf-8');
codeReview(code, args[2] || 'typescript');
break;
case 'translate':
translateText(args[1], args[2], args[3]).then(console.log);
break;
default:
console.log('Available examples:');
console.log('- cli: Interactive chatbot');
console.log('- stream: Streaming chatbot');
console.log('- batch <input> [output]: Batch processing');
console.log('- summarize <file>: Document summarization');
console.log('- review <file> [language]: Code review');
console.log('- translate <text> <from> <to>: Translation');
}
}
export {
simpleCLIChatbot,
streamingCLIChatbot,
batchProcessing,
summarizeDocument,
codeReview,
translateText,
parallelRequests,
requestWithRetry,
ConversationLogger,
};

48
templates/package.json Normal file
View File

@@ -0,0 +1,48 @@
{
"name": "claude-api-examples",
"version": "1.0.0",
"description": "Examples for using the Claude API with various platforms",
"main": "index.ts",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"cli": "tsx src/nodejs-example.ts cli",
"stream": "tsx src/nodejs-example.ts stream",
"test": "vitest",
"lint": "eslint . --ext .ts",
"format": "prettier --write \"**/*.{ts,tsx,md,json}\"",
"typecheck": "tsc --noEmit"
},
"keywords": [
"claude",
"anthropic",
"ai",
"llm",
"chatbot"
],
"author": "",
"license": "MIT",
"dependencies": {
"@anthropic-ai/sdk": "^0.67.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"dotenv": "^16.3.1",
"eslint": "^8.50.0",
"prettier": "^3.0.3",
"tsx": "^4.0.0",
"typescript": "^5.3.0",
"vitest": "^1.0.0",
"zod": "^3.23.0"
},
"optionalDependencies": {
"@upstash/ratelimit": "^1.0.0",
"@upstash/redis": "^1.25.0"
},
"engines": {
"node": ">=18.0.0"
}
}

272
templates/prompt-caching.ts Normal file
View File

@@ -0,0 +1,272 @@
import Anthropic from '@anthropic-ai/sdk';
import fs from 'fs';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY || '',
});
// Example 1: Basic prompt caching with system prompt
async function cacheSystemPrompt() {
// Simulate a large system prompt (must be >= 1024 tokens for caching)
const largeSystemPrompt = `
You are an expert software architect with deep knowledge of:
- Microservices architecture and design patterns
- Cloud-native applications (AWS, GCP, Azure)
- Containerization (Docker, Kubernetes)
- CI/CD pipelines and DevOps practices
- Database design (SQL and NoSQL)
- API design (REST, GraphQL, gRPC)
- Security best practices and compliance
- Performance optimization and scalability
- Monitoring and observability (Prometheus, Grafana)
- Event-driven architectures and message queues
${' '.repeat(10000)} // Padding to ensure > 1024 tokens
Always provide detailed, production-ready advice with code examples.
`.trim();
// First request - creates cache
const message1 = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
system: [
{
type: 'text',
text: largeSystemPrompt,
cache_control: { type: 'ephemeral' },
},
],
messages: [
{
role: 'user',
content: 'How do I design a scalable authentication system?',
},
],
});
console.log('=== First Request (Cache Creation) ===');
console.log('Cache creation tokens:', message1.usage.cache_creation_input_tokens);
console.log('Cache read tokens:', message1.usage.cache_read_input_tokens);
console.log('Input tokens:', message1.usage.input_tokens);
console.log('Output tokens:', message1.usage.output_tokens);
// Second request - hits cache (within 5 minutes)
const message2 = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
system: [
{
type: 'text',
text: largeSystemPrompt, // MUST be identical to hit cache
cache_control: { type: 'ephemeral' },
},
],
messages: [
{
role: 'user',
content: 'What about rate limiting strategies?',
},
],
});
console.log('\n=== Second Request (Cache Hit) ===');
console.log('Cache creation tokens:', message2.usage.cache_creation_input_tokens);
console.log('Cache read tokens:', message2.usage.cache_read_input_tokens);
console.log('Input tokens:', message2.usage.input_tokens);
console.log('Output tokens:', message2.usage.output_tokens);
console.log('Savings: ~90% on cached content');
}
// Example 2: Caching large documents
async function cacheLargeDocument() {
// Read a large document (e.g., documentation, codebase)
const largeDocument = fs.readFileSync('./large-document.txt', 'utf-8');
// Ensure document is >= 1024 tokens
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: 'Analyze the following documentation:',
},
{
type: 'text',
text: largeDocument,
cache_control: { type: 'ephemeral' },
},
{
type: 'text',
text: 'What are the main API endpoints?',
},
],
},
],
});
console.log('=== Document Analysis with Caching ===');
console.log('Cache creation:', message.usage.cache_creation_input_tokens);
console.log('Cache read:', message.usage.cache_read_input_tokens);
}
// Example 3: Multi-turn conversation with caching (chatbot pattern)
async function multiTurnCachingConversation() {
const systemInstructions = `
You are a customer support AI for TechCorp, specializing in:
- Product troubleshooting
- Account management
- Billing inquiries
- Technical specifications
${' '.repeat(10000)} // Ensure > 1024 tokens
Knowledge Base:
- Product A: Cloud storage service
- Product B: Analytics platform
- Product C: AI API service
Always be polite, helpful, and provide actionable solutions.
`.trim();
// Conversation state
const messages: Anthropic.MessageParam[] = [];
// Turn 1
messages.push({
role: 'user',
content: 'How do I reset my password?',
});
const response1 = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
system: [
{
type: 'text',
text: systemInstructions,
cache_control: { type: 'ephemeral' },
},
],
messages,
});
const text1 = response1.content.find(b => b.type === 'text');
if (text1 && text1.type === 'text') {
messages.push({ role: 'assistant', content: text1.text });
console.log('Turn 1 - Cache creation:', response1.usage.cache_creation_input_tokens);
}
// Turn 2 - cache hit
messages.push({
role: 'user',
content: 'What about two-factor authentication?',
});
const response2 = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
system: [
{
type: 'text',
text: systemInstructions,
cache_control: { type: 'ephemeral' },
},
],
messages,
});
console.log('Turn 2 - Cache read:', response2.usage.cache_read_input_tokens);
console.log('Turn 2 - New input tokens:', response2.usage.input_tokens);
}
// Example 4: Caching with conversation history
async function cacheConversationHistory() {
const messages: Anthropic.MessageParam[] = [
{ role: 'user', content: 'Tell me about TypeScript' },
{ role: 'assistant', content: 'TypeScript is a superset of JavaScript...' },
{ role: 'user', content: 'What about interfaces?' },
{ role: 'assistant', content: 'Interfaces in TypeScript define contracts...' },
{ role: 'user', content: 'Can you give examples?' },
];
// Cache the conversation history
const messagesWithCache: Anthropic.MessageParam[] = messages.slice(0, -1).map((msg, idx) => {
if (idx === messages.length - 2) {
// Cache the last assistant message
return {
...msg,
content: [
{
type: 'text',
text: typeof msg.content === 'string' ? msg.content : '',
cache_control: { type: 'ephemeral' },
},
],
};
}
return msg;
});
messagesWithCache.push(messages[messages.length - 1]);
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: messagesWithCache,
});
console.log('=== Caching Conversation History ===');
console.log('Cache usage:', response.usage);
}
// Example 5: Cost comparison calculator
function calculateCachingSavings(inputTokens: number, outputTokens: number, turnCount: number) {
const inputCostPerMTok = 3; // $3 per million tokens
const outputCostPerMTok = 15; // $15 per million tokens
const cacheCostPerMTok = 3.75; // $3.75 per million tokens (write)
const cacheReadCostPerMTok = 0.3; // $0.30 per million tokens (read)
// Without caching
const costWithoutCache =
(inputTokens / 1_000_000) * inputCostPerMTok * turnCount +
(outputTokens / 1_000_000) * outputCostPerMTok * turnCount;
// With caching
const cacheWriteCost = (inputTokens / 1_000_000) * cacheCostPerMTok; // First request
const cacheReadCost = (inputTokens / 1_000_000) * cacheReadCostPerMTok * (turnCount - 1); // Subsequent
const outputCost = (outputTokens / 1_000_000) * outputCostPerMTok * turnCount;
const costWithCache = cacheWriteCost + cacheReadCost + outputCost;
const savings = costWithoutCache - costWithCache;
const savingsPercent = (savings / costWithoutCache) * 100;
console.log('\n=== Cost Comparison ===');
console.log(`Input tokens: ${inputTokens}, Output tokens: ${outputTokens}, Turns: ${turnCount}`);
console.log(`Without caching: $${costWithoutCache.toFixed(4)}`);
console.log(`With caching: $${costWithCache.toFixed(4)}`);
console.log(`Savings: $${savings.toFixed(4)} (${savingsPercent.toFixed(1)}%)`);
}
// Run examples
if (require.main === module) {
cacheSystemPrompt()
.then(() => multiTurnCachingConversation())
.then(() => {
// Example cost calculation
calculateCachingSavings(100000, 5000, 10); // 100k input, 5k output, 10 turns
})
.catch(console.error);
}
export {
cacheSystemPrompt,
cacheLargeDocument,
multiTurnCachingConversation,
cacheConversationHistory,
calculateCachingSavings,
};

194
templates/streaming-chat.ts Normal file
View File

@@ -0,0 +1,194 @@
import Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY || '',
});
// Method 1: Using SDK stream helper with event listeners
async function streamWithEvents() {
console.log('Claude:');
const stream = anthropic.messages.stream({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'Write a short poem about coding.',
},
],
});
stream
.on('text', (text) => {
process.stdout.write(text);
})
.on('message', (message) => {
console.log('\n\nFinal message:', message);
console.log('Stop reason:', message.stop_reason);
})
.on('error', (error) => {
console.error('\nStream error:', error);
})
.on('abort', (error) => {
console.warn('\nStream aborted:', error);
})
.on('end', () => {
console.log('\n\nStream ended');
});
// Wait for stream to complete
const finalMessage = await stream.finalMessage();
return finalMessage;
}
// Method 2: Manual iteration over stream events
async function streamWithManualIteration() {
console.log('Claude:');
const stream = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'Explain quantum computing in simple terms.',
},
],
stream: true,
});
let fullText = '';
try {
for await (const event of stream) {
if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
const text = event.delta.text;
fullText += text;
process.stdout.write(text);
}
if (event.type === 'message_stop') {
console.log('\n\nStream complete');
}
}
} catch (error) {
console.error('\nStream error:', error);
throw error;
}
return fullText;
}
// Method 3: Streaming with abort control
async function streamWithAbort() {
console.log('Claude (can be aborted):');
const stream = anthropic.messages.stream({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
messages: [
{
role: 'user',
content: 'Write a long essay about the history of computers.',
},
],
});
let charCount = 0;
const maxChars = 200; // Abort after 200 characters
stream.on('text', (text) => {
process.stdout.write(text);
charCount += text.length;
// Abort stream after reaching limit
if (charCount >= maxChars) {
console.log('\n\n[Aborting stream after', charCount, 'characters]');
stream.abort();
}
});
stream.on('abort', () => {
console.log('Stream was aborted successfully');
});
stream.on('error', (error) => {
console.error('Stream error:', error);
});
try {
await stream.done();
} catch (error) {
// Handle abort error
if (error.name === 'APIUserAbortError') {
console.log('Stream aborted by user');
} else {
throw error;
}
}
}
// Method 4: Streaming with retry logic
async function streamWithRetry(maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const stream = anthropic.messages.stream({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'Tell me a fun fact about space.',
},
],
});
let fullText = '';
stream.on('text', (text) => {
fullText += text;
process.stdout.write(text);
});
stream.on('error', (error) => {
console.error(`\nStream error on attempt ${attempt + 1}:`, error);
throw error;
});
await stream.finalMessage();
console.log('\n\nStream completed successfully');
return fullText;
} catch (error) {
if (attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000;
console.log(`\nRetrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
console.error('\nMax retries exceeded');
throw error;
}
}
}
}
// Run examples
if (require.main === module) {
console.log('=== Stream with Events ===\n');
streamWithEvents()
.then(() => {
console.log('\n\n=== Stream with Manual Iteration ===\n');
return streamWithManualIteration();
})
.then(() => {
console.log('\n\n=== Stream with Abort ===\n');
return streamWithAbort();
})
.then(() => {
console.log('\n\n=== Stream with Retry ===\n');
return streamWithRetry();
})
.catch(console.error);
}
export { streamWithEvents, streamWithManualIteration, streamWithAbort, streamWithRetry };

View File

@@ -0,0 +1,296 @@
import Anthropic from '@anthropic-ai/sdk';
import { betaZodTool } from '@anthropic-ai/sdk/helpers/zod';
import { z } from 'zod';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY || '',
});
// Example 1: Using Zod schemas with betaZodTool
const weatherTool = betaZodTool({
name: 'get_weather',
inputSchema: z.object({
location: z.string().describe('The city and state, e.g. San Francisco, CA'),
unit: z.enum(['celsius', 'fahrenheit']).optional().describe('Temperature unit'),
}),
description: 'Get the current weather in a given location',
run: async (input) => {
// Mock implementation - replace with actual API call
console.log(`Fetching weather for ${input.location}...`);
const temp = input.unit === 'celsius' ? 22 : 72;
return `The weather in ${input.location} is sunny and ${temp}°${input.unit || 'F'}`;
},
});
const searchTool = betaZodTool({
name: 'search_web',
inputSchema: z.object({
query: z.string().describe('The search query'),
max_results: z.number().int().min(1).max(10).optional().describe('Maximum number of results'),
}),
description: 'Search the web for information',
run: async (input) => {
console.log(`Searching for: ${input.query}...`);
// Mock implementation
return `Found ${input.max_results || 5} results for "${input.query}":
1. Example result 1
2. Example result 2
3. Example result 3`;
},
});
const calculatorTool = betaZodTool({
name: 'calculate',
inputSchema: z.object({
expression: z.string().describe('Mathematical expression to evaluate'),
}),
description: 'Evaluate a mathematical expression',
run: async (input) => {
try {
// WARNING: eval is dangerous - this is just for demonstration
// In production, use a safe math parser like math.js
const result = eval(input.expression);
return `${input.expression} = ${result}`;
} catch (error) {
throw new Error(`Invalid expression: ${input.expression}`);
}
},
});
// Example 2: Using toolRunner for automatic execution
async function automaticToolExecution() {
const finalMessage = await anthropic.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1000,
messages: [
{
role: 'user',
content: 'What is the weather in Tokyo? Also, search for "best sushi restaurants in Tokyo"',
},
],
tools: [weatherTool, searchTool],
});
console.log('\nFinal response:');
for (const block of finalMessage.content) {
if (block.type === 'text') {
console.log(block.text);
}
}
return finalMessage;
}
// Example 3: Streaming with tools
async function streamingWithTools() {
const runner = anthropic.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1000,
messages: [
{
role: 'user',
content: 'Calculate 123 * 456, then tell me about the result',
},
],
tools: [calculatorTool],
stream: true,
});
console.log('Streaming response:');
// Iterate through messages as they arrive
for await (const messageStream of runner) {
// Each message can have multiple events
for await (const event of messageStream) {
if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
process.stdout.write(event.delta.text);
}
}
console.log('\n\nMessage completed');
}
// Get final result
const result = await runner;
console.log('\nFinal result:', result);
}
// Example 4: Complex tool chain
const databaseTool = betaZodTool({
name: 'query_database',
inputSchema: z.object({
query: z.string().describe('SQL query to execute'),
}),
description: 'Query the database',
run: async (input) => {
console.log(`Executing SQL: ${input.query}`);
// Mock database response
return JSON.stringify([
{ id: 1, name: 'Product A', price: 29.99 },
{ id: 2, name: 'Product B', price: 49.99 },
]);
},
});
const emailTool = betaZodTool({
name: 'send_email',
inputSchema: z.object({
to: z.string().email().describe('Recipient email address'),
subject: z.string().describe('Email subject'),
body: z.string().describe('Email body'),
}),
description: 'Send an email',
run: async (input) => {
console.log(`Sending email to ${input.to}...`);
// Mock email sending
return `Email sent successfully to ${input.to}`;
},
});
async function complexToolChain() {
const finalMessage = await anthropic.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2000,
messages: [
{
role: 'user',
content:
'Query the database for all products, calculate their average price, and send me an email with the results to test@example.com',
},
],
tools: [databaseTool, calculatorTool, emailTool],
});
console.log('\nTool chain completed');
for (const block of finalMessage.content) {
if (block.type === 'text') {
console.log(block.text);
}
}
}
// Example 5: Tool with max iterations limit
async function toolsWithMaxIterations() {
try {
const finalMessage = await anthropic.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1000,
max_iterations: 3, // Limit tool execution loops
messages: [
{
role: 'user',
content: 'Search for "quantum computing" and then search for each result',
},
],
tools: [searchTool],
});
console.log('Completed with max_iterations limit');
for (const block of finalMessage.content) {
if (block.type === 'text') {
console.log(block.text);
}
}
} catch (error) {
console.error('Max iterations reached or error occurred:', error);
}
}
// Example 6: Custom tool runner with manual control
async function manualToolRunner() {
const runner = anthropic.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1000,
messages: [
{
role: 'user',
content: 'What is 15 * 27?',
},
],
tools: [calculatorTool],
});
// Manually iterate through messages
for await (const message of runner) {
console.log('\nReceived message');
console.log('Stop reason:', message.stop_reason);
// Generate tool response if needed
const toolResponse = await runner.generateToolResponse();
if (toolResponse) {
console.log('Tool results:', toolResponse.content);
}
// Can inspect and modify the conversation here
console.log('Current params:', runner.params);
}
// Wait for completion
const finalMessage = await runner.done();
console.log('\nFinal message:', finalMessage);
}
// Example 7: Error recovery in tools
const unreliableTool = betaZodTool({
name: 'unreliable_api',
inputSchema: z.object({
data: z.string(),
}),
description: 'An API that sometimes fails',
run: async (input) => {
// Randomly fail to demonstrate error handling
if (Math.random() < 0.3) {
throw new Error('API temporarily unavailable');
}
return `Processed: ${input.data}`;
},
});
async function toolWithErrorRecovery() {
try {
const finalMessage = await anthropic.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1000,
messages: [
{
role: 'user',
content: 'Process this data with the unreliable API: "test data"',
},
],
tools: [unreliableTool],
});
console.log('Success:', finalMessage.content);
} catch (error) {
console.error('Tool execution failed:', error);
// Implement retry logic or fallback
}
}
// Run examples
if (require.main === module) {
console.log('=== Automatic Tool Execution ===\n');
automaticToolExecution()
.then(() => {
console.log('\n\n=== Streaming with Tools ===\n');
return streamingWithTools();
})
.then(() => {
console.log('\n\n=== Complex Tool Chain ===\n');
return complexToolChain();
})
.then(() => {
console.log('\n\n=== Manual Tool Runner ===\n');
return manualToolRunner();
})
.catch(console.error);
}
export {
automaticToolExecution,
streamingWithTools,
complexToolChain,
toolsWithMaxIterations,
manualToolRunner,
toolWithErrorRecovery,
};

310
templates/tool-use-basic.ts Normal file
View File

@@ -0,0 +1,310 @@
import Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY || '',
});
// Define tools
const tools: Anthropic.Tool[] = [
{
name: 'get_weather',
description: 'Get the current weather in a given location',
input_schema: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'The city and state, e.g. San Francisco, CA',
},
unit: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: 'The unit of temperature, either "celsius" or "fahrenheit"',
},
},
required: ['location'],
},
},
{
name: 'get_time',
description: 'Get the current time in a given timezone',
input_schema: {
type: 'object',
properties: {
timezone: {
type: 'string',
description: 'The IANA timezone name, e.g. America/Los_Angeles',
},
},
required: ['timezone'],
},
},
];
// Mock tool implementations
function executeWeatherTool(location: string, unit?: string): string {
// In production, call actual weather API
const temp = unit === 'celsius' ? 22 : 72;
return `The weather in ${location} is sunny and ${temp}°${unit === 'celsius' ? 'C' : 'F'}`;
}
function executeTimeTool(timezone: string): string {
// In production, get actual time for timezone
const time = new Date().toLocaleTimeString('en-US', { timeZone: timezone });
return `The current time in ${timezone} is ${time}`;
}
// Example 1: Basic tool use detection
async function basicToolUse() {
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
tools,
messages: [
{
role: 'user',
content: 'What is the weather like in San Francisco?',
},
],
});
console.log('Stop reason:', message.stop_reason);
if (message.stop_reason === 'tool_use') {
console.log('\nClaude wants to use tools:');
for (const block of message.content) {
if (block.type === 'tool_use') {
console.log(`- Tool: ${block.name}`);
console.log(` ID: ${block.id}`);
console.log(` Input:`, block.input);
}
}
}
return message;
}
// Example 2: Tool execution loop
async function toolExecutionLoop(userMessage: string) {
const messages: Anthropic.MessageParam[] = [
{ role: 'user', content: userMessage },
];
while (true) {
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
tools,
messages,
});
console.log('\nStop reason:', response.stop_reason);
// Add assistant response to messages
messages.push({
role: 'assistant',
content: response.content,
});
// Check if Claude wants to use tools
if (response.stop_reason === 'tool_use') {
const toolResults: Anthropic.ToolResultBlockParam[] = [];
// Execute each tool
for (const block of response.content) {
if (block.type === 'tool_use') {
console.log(`\nExecuting tool: ${block.name}`);
console.log('Input:', block.input);
let result: string;
// Execute the appropriate tool
if (block.name === 'get_weather') {
result = executeWeatherTool(
block.input.location as string,
block.input.unit as string | undefined
);
} else if (block.name === 'get_time') {
result = executeTimeTool(block.input.timezone as string);
} else {
result = `Unknown tool: ${block.name}`;
}
console.log('Result:', result);
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: result,
});
}
}
// Add tool results to messages
messages.push({
role: 'user',
content: toolResults,
});
} else {
// Final response - no more tools needed
const textBlock = response.content.find(block => block.type === 'text');
if (textBlock && textBlock.type === 'text') {
console.log('\nFinal response:', textBlock.text);
return textBlock.text;
}
break;
}
}
}
// Example 3: Multiple tools in one turn
async function multipleToolsInOneTurn() {
const messages: Anthropic.MessageParam[] = [
{
role: 'user',
content: 'What is the weather in New York and what time is it in Tokyo?',
},
];
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
tools,
messages,
});
console.log('Claude requested', response.content.filter(b => b.type === 'tool_use').length, 'tools');
if (response.stop_reason === 'tool_use') {
const toolResults: Anthropic.ToolResultBlockParam[] = [];
for (const block of response.content) {
if (block.type === 'tool_use') {
console.log(`\n- ${block.name}:`, block.input);
let result: string;
if (block.name === 'get_weather') {
result = executeWeatherTool(block.input.location as string);
} else if (block.name === 'get_time') {
result = executeTimeTool(block.input.timezone as string);
} else {
result = 'Unknown tool';
}
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: result,
});
}
}
// Continue conversation with tool results
messages.push({ role: 'assistant', content: response.content });
messages.push({ role: 'user', content: toolResults });
const finalResponse = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
tools,
messages,
});
const textBlock = finalResponse.content.find(b => b.type === 'text');
if (textBlock && textBlock.type === 'text') {
console.log('\nFinal answer:', textBlock.text);
}
}
}
// Example 4: Error handling in tool execution
async function toolExecutionWithErrorHandling(userMessage: string) {
const messages: Anthropic.MessageParam[] = [
{ role: 'user', content: userMessage },
];
try {
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
tools,
messages,
});
messages.push({ role: 'assistant', content: response.content });
if (response.stop_reason === 'tool_use') {
const toolResults: Anthropic.ToolResultBlockParam[] = [];
for (const block of response.content) {
if (block.type === 'tool_use') {
try {
let result: string;
if (block.name === 'get_weather') {
result = executeWeatherTool(block.input.location as string);
} else if (block.name === 'get_time') {
result = executeTimeTool(block.input.timezone as string);
} else {
throw new Error(`Unknown tool: ${block.name}`);
}
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: result,
});
} catch (error) {
// Return error to Claude
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: `Error executing tool: ${error.message}`,
is_error: true,
});
}
}
}
messages.push({ role: 'user', content: toolResults });
// Get final response
const finalResponse = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
tools,
messages,
});
const textBlock = finalResponse.content.find(b => b.type === 'text');
if (textBlock && textBlock.type === 'text') {
console.log('Final response:', textBlock.text);
}
}
} catch (error) {
console.error('API error:', error);
throw error;
}
}
// Run examples
if (require.main === module) {
console.log('=== Basic Tool Use ===\n');
basicToolUse()
.then(() => {
console.log('\n\n=== Tool Execution Loop ===\n');
return toolExecutionLoop('What is the weather in London and what time is it there?');
})
.then(() => {
console.log('\n\n=== Multiple Tools ===\n');
return multipleToolsInOneTurn();
})
.then(() => {
console.log('\n\n=== Error Handling ===\n');
return toolExecutionWithErrorHandling('What is the weather in Mars?');
})
.catch(console.error);
}
export { basicToolUse, toolExecutionLoop, multipleToolsInOneTurn, toolExecutionWithErrorHandling };

392
templates/vision-image.ts Normal file
View File

@@ -0,0 +1,392 @@
import Anthropic from '@anthropic-ai/sdk';
import fs from 'fs';
import path from 'path';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY || '',
});
// Example 1: Single image analysis
async function analyzeSingleImage(imagePath: string) {
// Read and encode image as base64
const imageData = fs.readFileSync(imagePath);
const base64Image = imageData.toString('base64');
// Determine media type from file extension
const ext = path.extname(imagePath).toLowerCase();
const mediaTypeMap: Record<string, string> = {
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png',
'.webp': 'image/webp',
'.gif': 'image/gif',
};
const mediaType = mediaTypeMap[ext] || 'image/jpeg';
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: [
{
type: 'image',
source: {
type: 'base64',
media_type: mediaType,
data: base64Image,
},
},
{
type: 'text',
text: 'What is in this image? Describe it in detail.',
},
],
},
],
});
const textContent = message.content.find(block => block.type === 'text');
if (textContent && textContent.type === 'text') {
console.log('Claude:', textContent.text);
}
return message;
}
// Example 2: Multiple images comparison
async function compareImages(image1Path: string, image2Path: string) {
const image1Data = fs.readFileSync(image1Path).toString('base64');
const image2Data = fs.readFileSync(image2Path).toString('base64');
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: 'Compare these two images. What are the similarities and differences?',
},
{
type: 'image',
source: {
type: 'base64',
media_type: 'image/jpeg',
data: image1Data,
},
},
{
type: 'image',
source: {
type: 'base64',
media_type: 'image/jpeg',
data: image2Data,
},
},
],
},
],
});
const textContent = message.content.find(block => block.type === 'text');
if (textContent && textContent.type === 'text') {
console.log('Comparison:', textContent.text);
}
}
// Example 3: Vision with tools
const searchTool: Anthropic.Tool = {
name: 'search_product',
description: 'Search for similar products',
input_schema: {
type: 'object',
properties: {
keywords: {
type: 'array',
items: { type: 'string' },
description: 'Keywords to search for',
},
},
required: ['keywords'],
},
};
async function visionWithTools(imagePath: string) {
const imageData = fs.readFileSync(imagePath).toString('base64');
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
tools: [searchTool],
messages: [
{
role: 'user',
content: [
{
type: 'image',
source: {
type: 'base64',
media_type: 'image/jpeg',
data: imageData,
},
},
{
type: 'text',
text: 'Identify the objects in this image and search for similar products',
},
],
},
],
});
console.log('Stop reason:', message.stop_reason);
if (message.stop_reason === 'tool_use') {
for (const block of message.content) {
if (block.type === 'tool_use') {
console.log('Tool requested:', block.name);
console.log('Search keywords:', block.input);
}
}
}
}
// Example 4: Multi-turn conversation with images
async function multiTurnVision(imagePath: string) {
const imageData = fs.readFileSync(imagePath).toString('base64');
const messages: Anthropic.MessageParam[] = [
{
role: 'user',
content: [
{
type: 'image',
source: {
type: 'base64',
media_type: 'image/jpeg',
data: imageData,
},
},
{
type: 'text',
text: 'What objects are visible in this image?',
},
],
},
];
// First turn
const response1 = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages,
});
const text1 = response1.content.find(b => b.type === 'text');
if (text1 && text1.type === 'text') {
console.log('Claude:', text1.text);
messages.push({ role: 'assistant', content: text1.text });
}
// Second turn - follow-up question (image still in context)
messages.push({
role: 'user',
content: 'What color is the largest object?',
});
const response2 = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages,
});
const text2 = response2.content.find(b => b.type === 'text');
if (text2 && text2.type === 'text') {
console.log('Claude:', text2.text);
}
}
// Example 5: Vision with prompt caching
async function visionWithCaching(imagePath: string) {
const imageData = fs.readFileSync(imagePath).toString('base64');
// First request - cache the image
const response1 = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: [
{
type: 'image',
source: {
type: 'base64',
media_type: 'image/jpeg',
data: imageData,
},
cache_control: { type: 'ephemeral' },
},
{
type: 'text',
text: 'Describe the main objects in this image',
},
],
},
],
});
console.log('First request - cache creation:', response1.usage.cache_creation_input_tokens);
const text1 = response1.content.find(b => b.type === 'text');
if (text1 && text1.type === 'text') {
console.log('Response 1:', text1.text);
}
// Second request - use cached image (within 5 minutes)
const response2 = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: [
{
type: 'image',
source: {
type: 'base64',
media_type: 'image/jpeg',
data: imageData, // Same image
},
cache_control: { type: 'ephemeral' },
},
{
type: 'text',
text: 'What colors are prominent in this image?',
},
],
},
],
});
console.log('Second request - cache read:', response2.usage.cache_read_input_tokens);
console.log('Token savings: ~90%');
}
// Example 6: Image URL (if accessible)
async function analyzeImageFromURL(imageUrl: string) {
// Note: Image must be publicly accessible
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: [
{
type: 'image',
source: {
type: 'url',
url: imageUrl,
},
},
{
type: 'text',
text: 'Analyze this image',
},
],
},
],
});
const textContent = message.content.find(block => block.type === 'text');
if (textContent && textContent.type === 'text') {
console.log('Analysis:', textContent.text);
}
}
// Example 7: Image validation helper
function validateImage(filePath: string): { valid: boolean; error?: string } {
if (!fs.existsSync(filePath)) {
return { valid: false, error: 'File does not exist' };
}
const stats = fs.statSync(filePath);
const fileSizeMB = stats.size / (1024 * 1024);
if (fileSizeMB > 5) {
return { valid: false, error: 'Image exceeds 5MB limit' };
}
const ext = path.extname(filePath).toLowerCase();
const supportedFormats = ['.jpg', '.jpeg', '.png', '.webp', '.gif'];
if (!supportedFormats.includes(ext)) {
return { valid: false, error: `Unsupported format. Use: ${supportedFormats.join(', ')}` };
}
return { valid: true };
}
// Example 8: Batch image analysis
async function analyzeMultipleImages(imagePaths: string[]) {
const results = [];
for (const imagePath of imagePaths) {
console.log(`\nAnalyzing: ${imagePath}`);
const validation = validateImage(imagePath);
if (!validation.valid) {
console.error(`Error: ${validation.error}`);
continue;
}
try {
const result = await analyzeSingleImage(imagePath);
results.push({ imagePath, result });
} catch (error) {
console.error(`Failed to analyze ${imagePath}:`, error);
}
// Rate limiting pause
await new Promise(resolve => setTimeout(resolve, 1000));
}
return results;
}
// Run examples (with placeholder paths)
if (require.main === module) {
const exampleImagePath = './example-image.jpg';
// Check if example image exists
if (fs.existsSync(exampleImagePath)) {
console.log('=== Single Image Analysis ===\n');
analyzeSingleImage(exampleImagePath)
.then(() => {
console.log('\n=== Vision with Caching ===\n');
return visionWithCaching(exampleImagePath);
})
.catch(console.error);
} else {
console.log('Example image not found. Create example-image.jpg to test.');
console.log('\nValidation example:');
const validation = validateImage('./non-existent.jpg');
console.log(validation);
}
}
export {
analyzeSingleImage,
compareImages,
visionWithTools,
multiTurnVision,
visionWithCaching,
analyzeImageFromURL,
validateImage,
analyzeMultipleImages,
};

80
templates/wrangler.jsonc Normal file
View File

@@ -0,0 +1,80 @@
// wrangler.jsonc - Cloudflare Workers configuration for Claude API
{
"name": "claude-api-worker",
"main": "src/index.ts",
"compatibility_date": "2025-01-01",
// Bindings
"vars": {
"ENVIRONMENT": "production"
},
// Environment variables (secrets)
// Set with: wrangler secret put ANTHROPIC_API_KEY
// "ANTHROPIC_API_KEY": "sk-ant-..." // Never commit this!
// KV namespace (optional - for caching)
"kv_namespaces": [
{
"binding": "CACHE",
"id": "your-kv-namespace-id",
"preview_id": "your-kv-preview-id"
}
],
// D1 database (optional - for conversation storage)
"d1_databases": [
{
"binding": "DB",
"database_name": "claude-conversations",
"database_id": "your-d1-database-id"
}
],
// Durable Objects (optional - for rate limiting)
"durable_objects": {
"bindings": [
{
"name": "RATE_LIMITER",
"class_name": "RateLimiter",
"script_name": "claude-api-worker"
}
]
},
"migrations": [
{
"tag": "v1",
"new_classes": ["RateLimiter"]
}
],
// Routes (if deploying to custom domain)
"routes": [
{
"pattern": "api.example.com/chat/*",
"zone_name": "example.com"
}
],
// Limits and features
"limits": {
"cpu_ms": 50 // 50ms CPU time
},
"compatibility_flags": [
"nodejs_compat" // If using Node.js APIs
],
// Observability
"observability": {
"enabled": true
},
// Tail consumers (for logging)
"tail_consumers": [
{
"service": "logging-worker"
}
]
}