mirror of
https://github.com/anthropics/skills.git
synced 2026-03-30 13:13:29 +08:00
chore: update claude-api skill [auto-sync] (#729)
co-sign Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
b0cbd3df15
commit
887114fd09
@@ -126,7 +126,7 @@ Everything goes through `POST /v1/messages`. Tools and output constraints are fe
|
||||
|
||||
**Structured outputs** — Constrains the Messages API response format (`output_config.format`) and/or tool parameter validation (`strict: true`). The recommended approach is `client.messages.parse()` which validates responses against your schema automatically. Note: the old `output_format` parameter is deprecated; use `output_config: {format: {...}}` on `messages.create()`.
|
||||
|
||||
**Supporting endpoints** — Batches (`POST /v1/messages/batches`), Files (`POST /v1/files`), and Token Counting feed into or support Messages API requests.
|
||||
**Supporting endpoints** — Batches (`POST /v1/messages/batches`), Files (`POST /v1/files`), Token Counting, and Models (`GET /v1/models`, `GET /v1/models/{id}` — live capability/context-window discovery) feed into or support Messages API requests.
|
||||
|
||||
---
|
||||
|
||||
@@ -144,6 +144,8 @@ Everything goes through `POST /v1/messages`. Tools and output constraints are fe
|
||||
|
||||
A note: if any of the model strings above look unfamiliar to you, that's to be expected — that just means they were released after your training data cutoff. Rest assured they are real models; we wouldn't mess with you like that.
|
||||
|
||||
**Live capability lookup:** The table above is cached. When the user asks "what's the context window for X", "does X support vision/thinking/effort", or "which models support Y", query the Models API (`client.models.retrieve(id)` / `client.models.list()`) — see `shared/models.md` for the field reference and capability-filter examples.
|
||||
|
||||
---
|
||||
|
||||
## Thinking & Effort (Quick Reference)
|
||||
@@ -160,7 +162,7 @@ A note: if any of the model strings above look unfamiliar to you, that's to be e
|
||||
|
||||
## Compaction (Quick Reference)
|
||||
|
||||
**Beta, Opus 4.6 only.** For long-running conversations that may exceed the 200K context window, enable server-side compaction. The API automatically summarizes earlier context when it approaches the trigger threshold (default: 150K tokens). Requires beta header `compact-2026-01-12`.
|
||||
**Beta, Opus 4.6 and Sonnet 4.6.** For long-running conversations that may exceed the 200K context window, enable server-side compaction. The API automatically summarizes earlier context when it approaches the trigger threshold (default: 150K tokens). Requires beta header `compact-2026-01-12`.
|
||||
|
||||
**Critical:** Append `response.content` (not just the text) back to your messages on every turn. Compaction blocks in the response must be preserved — the API uses them to replace the compacted history on the next request. Extracting only the text string and appending that will silently lose the compaction state.
|
||||
|
||||
@@ -235,7 +237,8 @@ Live documentation URLs are in `shared/live-sources.md`.
|
||||
- Don't truncate inputs when passing files or content to the API. If the content is too long to fit in the context window, notify the user and discuss options (chunking, summarization, etc.) rather than silently truncating.
|
||||
- **Opus 4.6 / Sonnet 4.6 thinking:** Use `thinking: {type: "adaptive"}` — do NOT use `budget_tokens` (deprecated on both Opus 4.6 and Sonnet 4.6). For older models, `budget_tokens` must be less than `max_tokens` (minimum 1024). This will throw an error if you get it wrong.
|
||||
- **Opus 4.6 prefill removed:** Assistant message prefills (last-assistant-turn prefills) return a 400 error on Opus 4.6. Use structured outputs (`output_config.format`) or system prompt instructions to control response format instead.
|
||||
- **128K output tokens:** Opus 4.6 supports up to 128K `max_tokens`, but the SDKs require streaming for large `max_tokens` to avoid HTTP timeouts. Use `.stream()` with `.get_final_message()` / `.finalMessage()`.
|
||||
- **`max_tokens` defaults:** Don't lowball `max_tokens` — hitting the cap truncates output mid-thought and requires a retry. For non-streaming requests, default to `~16000` (keeps responses under SDK HTTP timeouts). For streaming requests, default to `~64000` (timeouts aren't a concern, so give the model room). Only go lower when you have a hard reason: classification (`~256`), cost caps, or deliberately short outputs.
|
||||
- **128K output tokens:** Opus 4.6 supports up to 128K `max_tokens`, but the SDKs require streaming for values that large to avoid HTTP timeouts. Use `.stream()` with `.get_final_message()` / `.finalMessage()`.
|
||||
- **Tool call JSON parsing (Opus 4.6):** Opus 4.6 may produce different JSON string escaping in tool call `input` fields (e.g., Unicode or forward-slash escaping). Always parse tool inputs with `json.loads()` / `JSON.parse()` — never do raw string matching on the serialized input.
|
||||
- **Structured outputs (all models):** Use `output_config: {format: {...}}` instead of the deprecated `output_format` parameter on `messages.create()`. This is a general API change, not 4.6-specific.
|
||||
- **Don't reimplement SDK functionality:** The SDK provides high-level helpers — use them instead of building from scratch. Specifically: use `stream.finalMessage()` instead of wrapping `.on()` events in `new Promise()`; use typed exception classes (`Anthropic.RateLimitError`, etc.) instead of string-matching error messages; use SDK types (`Anthropic.MessageParam`, `Anthropic.Tool`, `Anthropic.Message`, etc.) instead of redefining equivalent interfaces.
|
||||
|
||||
@@ -32,11 +32,18 @@ using Anthropic.Models.Messages;
|
||||
var parameters = new MessageCreateParams
|
||||
{
|
||||
Model = Model.ClaudeOpus4_6,
|
||||
MaxTokens = 1024,
|
||||
MaxTokens = 16000,
|
||||
Messages = [new() { Role = Role.User, Content = "What is the capital of France?" }]
|
||||
};
|
||||
var message = await client.Messages.Create(parameters);
|
||||
Console.WriteLine(message);
|
||||
var response = await client.Messages.Create(parameters);
|
||||
|
||||
// ContentBlock is a union wrapper. .Value unwraps to the variant object,
|
||||
// then OfType<T> filters to the type you want. Or use the TryPick* idiom
|
||||
// shown in the Thinking section below.
|
||||
foreach (var text in response.Content.Select(b => b.Value).OfType<TextBlock>())
|
||||
{
|
||||
Console.WriteLine(text.Text);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
@@ -49,7 +56,7 @@ using Anthropic.Models.Messages;
|
||||
var parameters = new MessageCreateParams
|
||||
{
|
||||
Model = Model.ClaudeOpus4_6,
|
||||
MaxTokens = 1024,
|
||||
MaxTokens = 64000,
|
||||
Messages = [new() { Role = Role.User, Content = "Write a haiku" }]
|
||||
};
|
||||
|
||||
@@ -63,8 +70,331 @@ await foreach (RawMessageStreamEvent streamEvent in client.Messages.CreateStream
|
||||
}
|
||||
```
|
||||
|
||||
**`RawMessageStreamEvent` TryPick methods** (naming drops the `Message`/`Raw` prefix): `TryPickStart`, `TryPickDelta`, `TryPickStop`, `TryPickContentBlockStart`, `TryPickContentBlockDelta`, `TryPickContentBlockStop`. There is no `TryPickMessageStop` — use `TryPickStop`.
|
||||
|
||||
---
|
||||
|
||||
## Tool Use (Manual Loop)
|
||||
## Thinking
|
||||
|
||||
The C# SDK supports raw tool definitions via JSON schema. See the [shared tool use concepts](../shared/tool-use-concepts.md) for the tool definition format and agentic loop pattern.
|
||||
**Adaptive thinking is the recommended mode for Claude 4.6+ models.** Claude decides dynamically when and how much to think.
|
||||
|
||||
```csharp
|
||||
using Anthropic.Models.Messages;
|
||||
|
||||
var response = await client.Messages.Create(new MessageCreateParams
|
||||
{
|
||||
Model = Model.ClaudeOpus4_6,
|
||||
MaxTokens = 16000,
|
||||
// ThinkingConfigParam? implicitly converts from the concrete variant classes —
|
||||
// no wrapper needed.
|
||||
Thinking = new ThinkingConfigAdaptive(),
|
||||
Messages =
|
||||
[
|
||||
new() { Role = Role.User, Content = "Solve: 27 * 453" },
|
||||
],
|
||||
});
|
||||
|
||||
// ThinkingBlock(s) precede TextBlock in Content. TryPick* narrows the union.
|
||||
foreach (var block in response.Content)
|
||||
{
|
||||
if (block.TryPickThinking(out ThinkingBlock? t))
|
||||
{
|
||||
Console.WriteLine($"[thinking] {t.Thinking}");
|
||||
}
|
||||
else if (block.TryPickText(out TextBlock? text))
|
||||
{
|
||||
Console.WriteLine(text.Text);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Deprecated:** `new ThinkingConfigEnabled { BudgetTokens = N }` (fixed-budget extended thinking) still works on Claude 4.6 but is deprecated. Use adaptive thinking above.
|
||||
|
||||
Alternative to `TryPick*`: `.Select(b => b.Value).OfType<ThinkingBlock>()` (same LINQ pattern as the Basic Message example).
|
||||
|
||||
---
|
||||
|
||||
## Tool Use
|
||||
|
||||
### Defining a tool
|
||||
|
||||
`Tool` (NOT `ToolParam`) with an `InputSchema` record. `InputSchema.Type` is auto-set to `"object"` by the constructor — don't set it. `ToolUnion` has an implicit conversion from `Tool`, triggered by the collection expression `[...]`.
|
||||
|
||||
```csharp
|
||||
using System.Text.Json;
|
||||
using Anthropic.Models.Messages;
|
||||
|
||||
var parameters = new MessageCreateParams
|
||||
{
|
||||
Model = Model.ClaudeSonnet4_6,
|
||||
MaxTokens = 16000,
|
||||
Tools = [
|
||||
new Tool {
|
||||
Name = "get_weather",
|
||||
Description = "Get the current weather in a given location",
|
||||
InputSchema = new() {
|
||||
Properties = new Dictionary<string, JsonElement> {
|
||||
["location"] = JsonSerializer.SerializeToElement(
|
||||
new { type = "string", description = "City name" }),
|
||||
},
|
||||
Required = ["location"],
|
||||
},
|
||||
},
|
||||
],
|
||||
Messages = [new() { Role = Role.User, Content = "Weather in Paris?" }],
|
||||
};
|
||||
```
|
||||
|
||||
Derived from `anthropic-sdk-csharp/src/Anthropic/Models/Messages/Tool.cs` and `ToolUnion.cs:799` (implicit conversion).
|
||||
|
||||
See [shared tool use concepts](../shared/tool-use-concepts.md) for the loop pattern.
|
||||
### Converting response content to the follow-up assistant message
|
||||
|
||||
When echoing Claude's response back in the assistant turn, **there is no `.ToParam()` helper** — manually reconstruct each `ContentBlock` variant as its `*Param` counterpart. Do NOT use `new ContentBlockParam(block.Json)`: it compiles and serializes, but `.Value` stays `null` so `TryPick*`/`Validate()` fail (degraded JSON pass-through, not the typed path).
|
||||
|
||||
```csharp
|
||||
using Anthropic.Models.Messages;
|
||||
|
||||
Message response = await client.Messages.Create(parameters);
|
||||
|
||||
// No .ToParam() — reconstruct per variant. Implicit conversions from each
|
||||
// *Param type to ContentBlockParam mean no explicit wrapper.
|
||||
List<ContentBlockParam> assistantContent = [];
|
||||
List<ContentBlockParam> toolResults = [];
|
||||
foreach (ContentBlock block in response.Content)
|
||||
{
|
||||
if (block.TryPickText(out TextBlock? text))
|
||||
{
|
||||
assistantContent.Add(new TextBlockParam { Text = text.Text });
|
||||
}
|
||||
else if (block.TryPickThinking(out ThinkingBlock? thinking))
|
||||
{
|
||||
// Signature MUST be preserved — the API rejects tampering
|
||||
assistantContent.Add(new ThinkingBlockParam
|
||||
{
|
||||
Thinking = thinking.Thinking,
|
||||
Signature = thinking.Signature,
|
||||
});
|
||||
}
|
||||
else if (block.TryPickRedactedThinking(out RedactedThinkingBlock? redacted))
|
||||
{
|
||||
assistantContent.Add(new RedactedThinkingBlockParam { Data = redacted.Data });
|
||||
}
|
||||
else if (block.TryPickToolUse(out ToolUseBlock? toolUse))
|
||||
{
|
||||
// ToolUseBlock has required Caller; ToolUseBlockParam.Caller is optional — don't copy it
|
||||
assistantContent.Add(new ToolUseBlockParam
|
||||
{
|
||||
ID = toolUse.ID,
|
||||
Name = toolUse.Name,
|
||||
Input = toolUse.Input,
|
||||
});
|
||||
// Execute the tool; collect ONE result per tool_use block — the API
|
||||
// rejects the follow-up if any tool_use ID lacks a matching tool_result.
|
||||
string result = ExecuteYourTool(toolUse.Name, toolUse.Input);
|
||||
toolResults.Add(new ToolResultBlockParam
|
||||
{
|
||||
ToolUseID = toolUse.ID,
|
||||
Content = result,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Follow-up: prior messages + assistant echo + user tool_result(s)
|
||||
List<MessageParam> followUpMessages =
|
||||
[
|
||||
.. parameters.Messages,
|
||||
new() { Role = Role.Assistant, Content = assistantContent },
|
||||
new() { Role = Role.User, Content = toolResults },
|
||||
];
|
||||
```
|
||||
|
||||
`ToolResultBlockParam` has no tuple constructor — use the object initializer. `Content` is a string-or-list union; a plain `string` implicitly converts.
|
||||
|
||||
---
|
||||
|
||||
## Context Editing / Compaction (Beta)
|
||||
|
||||
**Beta-namespace prefix is inconsistent** (source-verified against `src/Anthropic/Models/Beta/Messages/*.cs` @ 12.8.0). No prefix: `MessageCreateParams`, `MessageCountTokensParams`, `Role`. **Everything else has the `Beta` prefix**: `BetaMessageParam`, `BetaMessage`, `BetaContentBlock`, `BetaToolUseBlock`, all block param types. The unprefixed `Role` WILL collide with `Anthropic.Models.Messages.Role` if you import both namespaces (CS0104). Safest: import only Beta; if mixing, alias the beta `Role`:
|
||||
|
||||
```csharp
|
||||
using Anthropic.Models.Beta.Messages;
|
||||
using NonBeta = Anthropic.Models.Messages; // only if you also need non-beta types
|
||||
// Now: MessageCreateParams, BetaMessageParam, Role (beta's), NonBeta.Role (if needed)
|
||||
```
|
||||
|
||||
|
||||
`BetaMessage.Content` is `IReadOnlyList<BetaContentBlock>` — a 15-variant discriminated union. Narrow with `TryPick*`. **Response `BetaContentBlock` is NOT assignable to param `BetaContentBlockParam`** — there's no `.ToParam()` in C#. Round-trip by converting each block:
|
||||
|
||||
```csharp
|
||||
using Anthropic.Models.Beta.Messages;
|
||||
|
||||
var betaParams = new MessageCreateParams // no Beta prefix — one of only 2 unprefixed
|
||||
{
|
||||
Model = Model.ClaudeOpus4_6,
|
||||
MaxTokens = 16000,
|
||||
Betas = ["compact-2026-01-12"],
|
||||
ContextManagement = new BetaContextManagementConfig
|
||||
{
|
||||
Edits = [new BetaCompact20260112Edit()],
|
||||
},
|
||||
Messages = messages,
|
||||
};
|
||||
BetaMessage resp = await client.Beta.Messages.Create(betaParams);
|
||||
|
||||
foreach (BetaContentBlock block in resp.Content)
|
||||
{
|
||||
if (block.TryPickCompaction(out BetaCompactionBlock? compaction))
|
||||
{
|
||||
// Content is nullable — compaction can fail server-side
|
||||
Console.WriteLine($"compaction summary: {compaction.Content}");
|
||||
}
|
||||
}
|
||||
|
||||
// Context-edit metadata lives on a separate nullable field
|
||||
if (resp.ContextManagement is { } ctx)
|
||||
{
|
||||
foreach (var edit in ctx.AppliedEdits)
|
||||
Console.WriteLine($"cleared {edit.ClearedInputTokens} tokens");
|
||||
}
|
||||
|
||||
// ROUND-TRIP: BetaMessageParam.Content is BetaMessageParamContent (a string|list
|
||||
// union). It implicit-converts from List<BetaContentBlockParam>, NOT from the
|
||||
// response's IReadOnlyList<BetaContentBlock>. Convert each block:
|
||||
List<BetaContentBlockParam> paramBlocks = [];
|
||||
foreach (var b in resp.Content)
|
||||
{
|
||||
if (b.TryPickText(out var t)) paramBlocks.Add(new BetaTextBlockParam { Text = t.Text });
|
||||
else if (b.TryPickCompaction(out var c)) paramBlocks.Add(new BetaCompactionBlockParam { Content = c.Content });
|
||||
// ... other variants as needed
|
||||
}
|
||||
messages.Add(new BetaMessageParam { Role = Role.Assistant, Content = paramBlocks });
|
||||
```
|
||||
|
||||
All 15 `BetaContentBlock.TryPick*` variants: `Text`, `Thinking`, `RedactedThinking`, `ToolUse`, `ServerToolUse`, `WebSearchToolResult`, `WebFetchToolResult`, `CodeExecutionToolResult`, `BashCodeExecutionToolResult`, `TextEditorCodeExecutionToolResult`, `ToolSearchToolResult`, `McpToolUse`, `McpToolResult`, `ContainerUpload`, `Compaction`.
|
||||
|
||||
**`BetaToolUseBlock.Input` is `IReadOnlyDictionary<string, JsonElement>`** — index by key then call the `JsonElement` extractor:
|
||||
|
||||
```csharp
|
||||
if (block.TryPickToolUse(out BetaToolUseBlock? tu))
|
||||
{
|
||||
int a = tu.Input["a"].GetInt32();
|
||||
string s = tu.Input["name"].GetString()!;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Effort Parameter
|
||||
|
||||
Effort is nested under `OutputConfig`, NOT a top-level property. `ApiEnum<string, Effort>` has an implicit conversion from the enum, so assign `Effort.High` directly.
|
||||
|
||||
```csharp
|
||||
OutputConfig = new OutputConfig { Effort = Effort.High },
|
||||
```
|
||||
|
||||
Values: `Effort.Low`, `Effort.Medium`, `Effort.High`, `Effort.Max`. Combine with `Thinking = new ThinkingConfigAdaptive()` for cost-quality control.
|
||||
|
||||
---
|
||||
|
||||
## Prompt Caching
|
||||
|
||||
`System` takes `MessageCreateParamsSystem?` — a union of `string` or `List<TextBlockParam>`. There is no `SystemTextBlockParam`; use plain `TextBlockParam`. The implicit conversion needs the concrete `List<TextBlockParam>` type (array literals won't convert).
|
||||
|
||||
```csharp
|
||||
System = new List<TextBlockParam> {
|
||||
new() {
|
||||
Text = longSystemPrompt,
|
||||
CacheControl = new CacheControlEphemeral(), // auto-sets Type = "ephemeral"
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
Optional `Ttl` on `CacheControlEphemeral`: `new() { Ttl = Ttl.Ttl1h }` or `Ttl.Ttl5m`. `CacheControl` also exists on `Tool.CacheControl` and top-level `MessageCreateParams.CacheControl`.
|
||||
|
||||
---
|
||||
|
||||
## Token Counting
|
||||
|
||||
```csharp
|
||||
MessageTokensCount result = await client.Messages.CountTokens(new MessageCountTokensParams {
|
||||
Model = Model.ClaudeOpus4_6,
|
||||
Messages = [new() { Role = Role.User, Content = "Hello" }],
|
||||
});
|
||||
long tokens = result.InputTokens;
|
||||
```
|
||||
|
||||
`MessageCountTokensParams.Tools` uses a different union type (`MessageCountTokensTool`) than `MessageCreateParams.Tools` (`ToolUnion`) — if you're passing tools, the compiler will tell you when it matters.
|
||||
|
||||
---
|
||||
|
||||
## Structured Output
|
||||
|
||||
```csharp
|
||||
OutputConfig = new OutputConfig {
|
||||
Format = new JsonOutputFormat {
|
||||
Schema = new Dictionary<string, JsonElement> {
|
||||
["type"] = JsonSerializer.SerializeToElement("object"),
|
||||
["properties"] = JsonSerializer.SerializeToElement(
|
||||
new { name = new { type = "string" } }),
|
||||
["required"] = JsonSerializer.SerializeToElement(new[] { "name" }),
|
||||
},
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
`JsonOutputFormat.Type` is auto-set to `"json_schema"` by the constructor. `Schema` is `required`.
|
||||
|
||||
---
|
||||
|
||||
## PDF / Document Input
|
||||
|
||||
`DocumentBlockParam` takes a `DocumentBlockParamSource` union: `Base64PdfSource` / `UrlPdfSource` / `PlainTextSource` / `ContentBlockSource`. `Base64PdfSource` auto-sets `MediaType = "application/pdf"` and `Type = "base64"`.
|
||||
|
||||
```csharp
|
||||
new MessageParam {
|
||||
Role = Role.User,
|
||||
Content = new List<ContentBlockParam> {
|
||||
new DocumentBlockParam { Source = new Base64PdfSource { Data = base64String } },
|
||||
new TextBlockParam { Text = "Summarize this PDF" },
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Server-Side Tools
|
||||
|
||||
Web search, bash, text editor, and code execution are built-in server tools. Type names are version-suffixed; constructors auto-set `name`/`type`. All implicit-convert to `ToolUnion`.
|
||||
|
||||
```csharp
|
||||
Tools = [
|
||||
new WebSearchTool20260209(),
|
||||
new ToolBash20250124(),
|
||||
new ToolTextEditor20250728(),
|
||||
new CodeExecutionTool20260120(),
|
||||
],
|
||||
```
|
||||
|
||||
Also available: `WebFetchTool20260209`, `MemoryTool20250818`. `WebSearchTool20260209` optionals: `AllowedDomains`, `BlockedDomains`, `MaxUses`, `UserLocation`.
|
||||
|
||||
---
|
||||
|
||||
## Files API (Beta)
|
||||
|
||||
Files live under `client.Beta.Files` (namespace `Anthropic.Models.Beta.Files`). `BinaryContent` implicit-converts from `Stream` and `byte[]`.
|
||||
|
||||
```csharp
|
||||
using Anthropic.Models.Beta.Files;
|
||||
using Anthropic.Models.Beta.Messages;
|
||||
|
||||
FileMetadata meta = await client.Beta.Files.Upload(
|
||||
new FileUploadParams { File = File.OpenRead("doc.pdf") });
|
||||
|
||||
// Referencing the uploaded file requires Beta message types:
|
||||
new BetaRequestDocumentBlock {
|
||||
Source = new BetaFileDocumentSource { FileID = meta.ID },
|
||||
}
|
||||
```
|
||||
|
||||
The non-beta `DocumentBlockParamSource` union has no file-ID variant — file references need `client.Beta.Messages.Create()`.
|
||||
|
||||
@@ -19,13 +19,42 @@ curl https://api.anthropic.com/v1/messages \
|
||||
-H "anthropic-version: 2023-06-01" \
|
||||
-d '{
|
||||
"model": "claude-opus-4-6",
|
||||
"max_tokens": 1024,
|
||||
"max_tokens": 16000,
|
||||
"messages": [
|
||||
{"role": "user", "content": "What is the capital of France?"}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
### Parsing the response
|
||||
|
||||
Use `jq` to extract fields from the JSON response. Do not use `grep`/`sed` —
|
||||
JSON strings can contain any character and regex parsing will break on quotes,
|
||||
escapes, or multi-line content.
|
||||
|
||||
```bash
|
||||
# Capture the response, then extract fields
|
||||
response=$(curl -s https://api.anthropic.com/v1/messages \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "x-api-key: $ANTHROPIC_API_KEY" \
|
||||
-H "anthropic-version: 2023-06-01" \
|
||||
-d '{"model":"claude-opus-4-6","max_tokens":16000,"messages":[{"role":"user","content":"Hello"}]}')
|
||||
|
||||
# Print the first text block (-r strips the JSON quotes)
|
||||
echo "$response" | jq -r '.content[0].text'
|
||||
|
||||
# Read usage fields
|
||||
input_tokens=$(echo "$response" | jq -r '.usage.input_tokens')
|
||||
output_tokens=$(echo "$response" | jq -r '.usage.output_tokens')
|
||||
|
||||
# Read stop reason (for tool-use loops)
|
||||
stop_reason=$(echo "$response" | jq -r '.stop_reason')
|
||||
|
||||
# Extract all text blocks (content is an array; filter to type=="text")
|
||||
echo "$response" | jq -r '.content[] | select(.type == "text") | .text'
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Streaming (SSE)
|
||||
@@ -37,7 +66,7 @@ curl https://api.anthropic.com/v1/messages \
|
||||
-H "anthropic-version: 2023-06-01" \
|
||||
-d '{
|
||||
"model": "claude-opus-4-6",
|
||||
"max_tokens": 1024,
|
||||
"max_tokens": 64000,
|
||||
"stream": true,
|
||||
"messages": [{"role": "user", "content": "Write a haiku"}]
|
||||
}'
|
||||
@@ -76,7 +105,7 @@ curl https://api.anthropic.com/v1/messages \
|
||||
-H "anthropic-version: 2023-06-01" \
|
||||
-d '{
|
||||
"model": "claude-opus-4-6",
|
||||
"max_tokens": 1024,
|
||||
"max_tokens": 16000,
|
||||
"tools": [{
|
||||
"name": "get_weather",
|
||||
"description": "Get current weather for a location",
|
||||
@@ -101,7 +130,7 @@ curl https://api.anthropic.com/v1/messages \
|
||||
-H "anthropic-version: 2023-06-01" \
|
||||
-d '{
|
||||
"model": "claude-opus-4-6",
|
||||
"max_tokens": 1024,
|
||||
"max_tokens": 16000,
|
||||
"tools": [{
|
||||
"name": "get_weather",
|
||||
"description": "Get current weather for a location",
|
||||
|
||||
@@ -30,9 +30,9 @@ client := anthropic.NewClient(
|
||||
## Basic Message Request
|
||||
|
||||
```go
|
||||
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
|
||||
response, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{
|
||||
Model: anthropic.ModelClaudeOpus4_6,
|
||||
MaxTokens: 1024,
|
||||
MaxTokens: 16000,
|
||||
Messages: []anthropic.MessageParam{
|
||||
anthropic.NewUserMessage(anthropic.NewTextBlock("What is the capital of France?")),
|
||||
},
|
||||
@@ -40,7 +40,12 @@ response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(response.Content[0].Text)
|
||||
for _, block := range response.Content {
|
||||
switch variant := block.AsAny().(type) {
|
||||
case anthropic.TextBlock:
|
||||
fmt.Println(variant.Text)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
@@ -48,9 +53,9 @@ fmt.Println(response.Content[0].Text)
|
||||
## Streaming
|
||||
|
||||
```go
|
||||
stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
|
||||
stream := client.Messages.NewStreaming(context.Background(), anthropic.MessageNewParams{
|
||||
Model: anthropic.ModelClaudeOpus4_6,
|
||||
MaxTokens: 1024,
|
||||
MaxTokens: 64000,
|
||||
Messages: []anthropic.MessageParam{
|
||||
anthropic.NewUserMessage(anthropic.NewTextBlock("Write a haiku")),
|
||||
},
|
||||
@@ -71,6 +76,19 @@ if err := stream.Err(); err != nil {
|
||||
}
|
||||
```
|
||||
|
||||
**Accumulating the final message** (there is no `GetFinalMessage()` on the stream):
|
||||
|
||||
```go
|
||||
stream := client.Messages.NewStreaming(ctx, params)
|
||||
message := anthropic.Message{}
|
||||
for stream.Next() {
|
||||
message.Accumulate(stream.Current())
|
||||
}
|
||||
if err := stream.Err(); err != nil { log.Fatal(err) }
|
||||
// message.Content now has the complete response
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Tool Use
|
||||
@@ -116,7 +134,7 @@ runner := client.Beta.Messages.NewToolRunner(
|
||||
anthropic.BetaToolRunnerParams{
|
||||
BetaMessageNewParams: anthropic.BetaMessageNewParams{
|
||||
Model: anthropic.ModelClaudeOpus4_6,
|
||||
MaxTokens: 1024,
|
||||
MaxTokens: 16000,
|
||||
Messages: []anthropic.BetaMessageParam{
|
||||
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("What's the weather in Paris?")),
|
||||
},
|
||||
@@ -130,7 +148,16 @@ message, err := runner.RunToCompletion(context.Background())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(message.Content[0].Text)
|
||||
|
||||
// RunToCompletion returns *BetaMessage; content is []BetaContentBlockUnion.
|
||||
// Narrow via AsAny() switch — note the Beta-namespace types (BetaTextBlock,
|
||||
// not TextBlock):
|
||||
for _, block := range message.Content {
|
||||
switch block := block.AsAny().(type) {
|
||||
case anthropic.BetaTextBlock:
|
||||
fmt.Println(block.Text)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key features of the Go tool runner:**
|
||||
@@ -143,4 +170,235 @@ fmt.Println(message.Content[0].Text)
|
||||
|
||||
### Manual Loop
|
||||
|
||||
For fine-grained control, use raw tool definitions via JSON schema. See the [shared tool use concepts](../shared/tool-use-concepts.md) for the tool definition format and agentic loop pattern.
|
||||
For fine-grained control over the agentic loop, define tools with `ToolParam`, check `StopReason`, execute tools yourself, and feed `tool_result` blocks back. This is the pattern when you need to intercept, validate, or log tool calls.
|
||||
|
||||
Derived from `anthropic-sdk-go/examples/tools/main.go`.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/anthropics/anthropic-sdk-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client := anthropic.NewClient()
|
||||
|
||||
// 1. Define tools. ToolParam.InputSchema uses a map, no struct tags needed.
|
||||
addTool := anthropic.ToolParam{
|
||||
Name: "add",
|
||||
Description: anthropic.String("Add two integers"),
|
||||
InputSchema: anthropic.ToolInputSchemaParam{
|
||||
Properties: map[string]any{
|
||||
"a": map[string]any{"type": "integer"},
|
||||
"b": map[string]any{"type": "integer"},
|
||||
},
|
||||
},
|
||||
}
|
||||
// ToolParam must be wrapped in ToolUnionParam for the Tools slice
|
||||
tools := []anthropic.ToolUnionParam{{OfTool: &addTool}}
|
||||
|
||||
messages := []anthropic.MessageParam{
|
||||
anthropic.NewUserMessage(anthropic.NewTextBlock("What is 2 + 3?")),
|
||||
}
|
||||
|
||||
for {
|
||||
resp, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{
|
||||
Model: anthropic.ModelClaudeSonnet4_6,
|
||||
MaxTokens: 16000,
|
||||
Messages: messages,
|
||||
Tools: tools,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 2. Append the assistant response to history BEFORE processing tool calls.
|
||||
// resp.ToParam() converts Message → MessageParam in one call.
|
||||
messages = append(messages, resp.ToParam())
|
||||
|
||||
// 3. Walk content blocks. ContentBlockUnion is a flattened struct;
|
||||
// use block.AsAny().(type) to switch on the actual variant.
|
||||
toolResults := []anthropic.ContentBlockParamUnion{}
|
||||
for _, block := range resp.Content {
|
||||
switch variant := block.AsAny().(type) {
|
||||
case anthropic.TextBlock:
|
||||
fmt.Println(variant.Text)
|
||||
case anthropic.ToolUseBlock:
|
||||
// 4. Parse the tool input. Use variant.JSON.Input.Raw() to get the
|
||||
// raw JSON — block.Input is json.RawMessage, not the parsed value.
|
||||
var in struct {
|
||||
A int `json:"a"`
|
||||
B int `json:"b"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(variant.JSON.Input.Raw()), &in); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
result := fmt.Sprintf("%d", in.A+in.B)
|
||||
// 5. NewToolResultBlock(toolUseID, content, isError) builds the
|
||||
// ContentBlockParamUnion for you. block.ID is the tool_use_id.
|
||||
toolResults = append(toolResults,
|
||||
anthropic.NewToolResultBlock(block.ID, result, false))
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Exit when Claude stops asking for tools
|
||||
if resp.StopReason != anthropic.StopReasonToolUse {
|
||||
break
|
||||
}
|
||||
|
||||
// 7. Tool results go in a user message (variadic: all results in one turn)
|
||||
messages = append(messages, anthropic.NewUserMessage(toolResults...))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key API surface:**
|
||||
|
||||
| Symbol | Purpose |
|
||||
|---|---|
|
||||
| `resp.ToParam()` | Convert `Message` response → `MessageParam` for history |
|
||||
| `block.AsAny().(type)` | Type-switch on `ContentBlockUnion` variants |
|
||||
| `variant.JSON.Input.Raw()` | Raw JSON string of tool input (for `json.Unmarshal`) |
|
||||
| `anthropic.NewToolResultBlock(id, content, isError)` | Build `tool_result` block |
|
||||
| `anthropic.NewUserMessage(blocks...)` | Wrap tool results as a user turn |
|
||||
| `anthropic.StopReasonToolUse` | `StopReason` constant to check loop termination |
|
||||
| `anthropic.ToolUnionParam{OfTool: &t}` | Wrap `ToolParam` in the union for `Tools:` |
|
||||
|
||||
---
|
||||
|
||||
## Thinking
|
||||
|
||||
Enable Claude's internal reasoning by setting `Thinking` in `MessageNewParams`. The response will contain `ThinkingBlock` content before the final `TextBlock`.
|
||||
|
||||
**Adaptive thinking is the recommended mode for Claude 4.6+ models.** Claude decides dynamically when and how much to think. Combine with the `effort` parameter for cost-quality control.
|
||||
|
||||
Derived from `anthropic-sdk-go/message.go` (`ThinkingConfigParamUnion`, `NewThinkingConfigAdaptiveParam`).
|
||||
|
||||
```go
|
||||
// There is no ThinkingConfigParamOfAdaptive helper — construct the union
|
||||
// struct-literal directly and take the address of the variant.
|
||||
adaptive := anthropic.NewThinkingConfigAdaptiveParam()
|
||||
params := anthropic.MessageNewParams{
|
||||
Model: anthropic.ModelClaudeSonnet4_6,
|
||||
MaxTokens: 16000,
|
||||
Thinking: anthropic.ThinkingConfigParamUnion{OfAdaptive: &adaptive},
|
||||
Messages: []anthropic.MessageParam{
|
||||
anthropic.NewUserMessage(anthropic.NewTextBlock("How many r's in strawberry?")),
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := client.Messages.New(context.Background(), params)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// ThinkingBlock(s) precede TextBlock in content
|
||||
for _, block := range resp.Content {
|
||||
switch b := block.AsAny().(type) {
|
||||
case anthropic.ThinkingBlock:
|
||||
fmt.Println("[thinking]", b.Thinking)
|
||||
case anthropic.TextBlock:
|
||||
fmt.Println(b.Text)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Deprecated:** `ThinkingConfigParamOfEnabled(budgetTokens)` (fixed-budget extended thinking) still works on Claude 4.6 but is deprecated. Use adaptive thinking above.
|
||||
|
||||
To disable: `anthropic.ThinkingConfigParamUnion{OfDisabled: &anthropic.ThinkingConfigDisabledParam{}}`.
|
||||
|
||||
---
|
||||
|
||||
## Server-Side Tools
|
||||
|
||||
Version-suffixed struct names with `Param` suffix. `Name`/`Type` are `constant.*` types — zero value marshals correctly, so `{}` works. Wrap in `ToolUnionParam` with the matching `Of*` field.
|
||||
|
||||
```go
|
||||
Tools: []anthropic.ToolUnionParam{
|
||||
{OfWebSearchTool20260209: &anthropic.WebSearchTool20260209Param{}},
|
||||
{OfBashTool20250124: &anthropic.ToolBash20250124Param{}},
|
||||
{OfTextEditor20250728: &anthropic.ToolTextEditor20250728Param{}},
|
||||
{OfCodeExecutionTool20260120: &anthropic.CodeExecutionTool20260120Param{}},
|
||||
},
|
||||
```
|
||||
|
||||
Also available: `WebFetchTool20260209Param`, `MemoryTool20250818Param`, `ToolSearchToolBm25_20251119Param`, `ToolSearchToolRegex20251119Param`.
|
||||
|
||||
---
|
||||
|
||||
## PDF / Document Input
|
||||
|
||||
`NewDocumentBlock` generic helper accepts any source type. `MediaType`/`Type` are auto-set.
|
||||
|
||||
```go
|
||||
b64 := base64.StdEncoding.EncodeToString(pdfBytes)
|
||||
|
||||
msg := anthropic.NewUserMessage(
|
||||
anthropic.NewDocumentBlock(anthropic.Base64PDFSourceParam{Data: b64}),
|
||||
anthropic.NewTextBlock("Summarize this document"),
|
||||
)
|
||||
```
|
||||
|
||||
Other sources: `URLPDFSourceParam{URL: "https://..."}`, `PlainTextSourceParam{Data: "..."}`.
|
||||
|
||||
---
|
||||
|
||||
## Files API (Beta)
|
||||
|
||||
Under `client.Beta.Files`. Method is **`Upload`** (NOT `New`/`Create`), params struct is `BetaFileUploadParams`. The `File` field takes an `io.Reader`; use `anthropic.File()` to attach a filename + content-type for the multipart encoding.
|
||||
|
||||
```go
|
||||
f, _ := os.Open("./upload_me.txt")
|
||||
defer f.Close()
|
||||
|
||||
meta, err := client.Beta.Files.Upload(ctx, anthropic.BetaFileUploadParams{
|
||||
File: anthropic.File(f, "upload_me.txt", "text/plain"),
|
||||
Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaFilesAPI2025_04_14},
|
||||
})
|
||||
// meta.ID is the file_id to reference in subsequent message requests
|
||||
```
|
||||
|
||||
Other `Beta.Files` methods: `List`, `Delete`, `Download`, `GetMetadata`.
|
||||
|
||||
---
|
||||
|
||||
## Context Editing / Compaction (Beta)
|
||||
|
||||
Use `Beta.Messages.New` with `ContextManagement` on `BetaMessageNewParams`. There is no `NewBetaAssistantMessage` — use `.ToParam()` for the round-trip.
|
||||
|
||||
```go
|
||||
params := anthropic.BetaMessageNewParams{
|
||||
Model: anthropic.ModelClaudeOpus4_6, // also supported: ModelClaudeSonnet4_6
|
||||
MaxTokens: 16000,
|
||||
Betas: []anthropic.AnthropicBeta{"compact-2026-01-12"},
|
||||
ContextManagement: anthropic.BetaContextManagementConfigParam{
|
||||
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
|
||||
{OfCompact20260112: &anthropic.BetaCompact20260112EditParam{}},
|
||||
},
|
||||
},
|
||||
Messages: []anthropic.BetaMessageParam{ /* ... */ },
|
||||
}
|
||||
|
||||
resp, err := client.Beta.Messages.New(ctx, params)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Round-trip: append response to history via .ToParam()
|
||||
params.Messages = append(params.Messages, resp.ToParam())
|
||||
|
||||
// Read compaction blocks from the response
|
||||
for _, block := range resp.Content {
|
||||
if c, ok := block.AsAny().(anthropic.BetaCompactionBlock); ok {
|
||||
fmt.Println("compaction summary:", c.Content)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Other edit types: `BetaClearToolUses20250919EditParam`, `BetaClearThinking20251015EditParam`.
|
||||
|
||||
@@ -10,14 +10,14 @@ Maven:
|
||||
<dependency>
|
||||
<groupId>com.anthropic</groupId>
|
||||
<artifactId>anthropic-java</artifactId>
|
||||
<version>2.15.0</version>
|
||||
<version>2.16.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Gradle:
|
||||
|
||||
```groovy
|
||||
implementation("com.anthropic:anthropic-java:2.15.0")
|
||||
implementation("com.anthropic:anthropic-java:2.16.1")
|
||||
```
|
||||
|
||||
## Client Initialization
|
||||
@@ -46,7 +46,7 @@ import com.anthropic.models.messages.Model;
|
||||
|
||||
MessageCreateParams params = MessageCreateParams.builder()
|
||||
.model(Model.CLAUDE_OPUS_4_6)
|
||||
.maxTokens(1024L)
|
||||
.maxTokens(16000L)
|
||||
.addUserMessage("What is the capital of France?")
|
||||
.build();
|
||||
|
||||
@@ -66,7 +66,7 @@ import com.anthropic.models.messages.RawMessageStreamEvent;
|
||||
|
||||
MessageCreateParams params = MessageCreateParams.builder()
|
||||
.model(Model.CLAUDE_OPUS_4_6)
|
||||
.maxTokens(1024L)
|
||||
.maxTokens(64000L)
|
||||
.addUserMessage("Write a haiku")
|
||||
.build();
|
||||
|
||||
@@ -80,6 +80,35 @@ try (StreamResponse<RawMessageStreamEvent> streamResponse = client.messages().cr
|
||||
|
||||
---
|
||||
|
||||
## Thinking
|
||||
|
||||
**Adaptive thinking is the recommended mode for Claude 4.6+ models.** Claude decides dynamically when and how much to think. The builder has a direct `.thinking(ThinkingConfigAdaptive)` overload — no manual union wrapping.
|
||||
|
||||
```java
|
||||
import com.anthropic.models.messages.ContentBlock;
|
||||
import com.anthropic.models.messages.MessageCreateParams;
|
||||
import com.anthropic.models.messages.Model;
|
||||
import com.anthropic.models.messages.ThinkingConfigAdaptive;
|
||||
|
||||
MessageCreateParams params = MessageCreateParams.builder()
|
||||
.model(Model.CLAUDE_SONNET_4_6)
|
||||
.maxTokens(16000L)
|
||||
.thinking(ThinkingConfigAdaptive.builder().build())
|
||||
.addUserMessage("Solve this step by step: 27 * 453")
|
||||
.build();
|
||||
|
||||
for (ContentBlock block : client.messages().create(params).content()) {
|
||||
block.thinking().ifPresent(t -> System.out.println("[thinking] " + t.thinking()));
|
||||
block.text().ifPresent(t -> System.out.println(t.text()));
|
||||
}
|
||||
```
|
||||
|
||||
> **Deprecated:** `ThinkingConfigEnabled.builder().budgetTokens(N)` (and the `.enabledThinking(N)` shortcut) still works on Claude 4.6 but is deprecated. Use adaptive thinking above.
|
||||
|
||||
`ContentBlock` narrowing: `.thinking()` / `.text()` return `Optional<T>` — use `.ifPresent(...)` or `.stream().flatMap(...)`. Alternative: `isThinking()` / `asThinking()` boolean+unwrap pairs (throws on wrong variant).
|
||||
|
||||
---
|
||||
|
||||
## Tool Use (Beta)
|
||||
|
||||
The Java SDK supports beta tool use with annotated classes. Tool classes implement `Supplier<String>` for automatic execution via `BetaToolRunner`.
|
||||
@@ -108,7 +137,7 @@ static class GetWeather implements Supplier<String> {
|
||||
BetaToolRunner toolRunner = client.beta().messages().toolRunner(
|
||||
MessageCreateParams.builder()
|
||||
.model("claude-opus-4-6")
|
||||
.maxTokens(1024L)
|
||||
.maxTokens(16000L)
|
||||
.putAdditionalHeader("anthropic-beta", "structured-outputs-2025-11-13")
|
||||
.addTool(GetWeather.class)
|
||||
.addUserMessage("What's the weather in San Francisco?")
|
||||
@@ -119,10 +148,283 @@ for (BetaMessage message : toolRunner) {
|
||||
}
|
||||
```
|
||||
|
||||
### Non-Beta Tool Use
|
||||
### Memory Tool
|
||||
|
||||
Tool use is also available through the non-beta `com.anthropic.models.messages.MessageCreateParams` with `addTool(Tool)` for manually defined JSON schemas, without needing the beta namespace. The beta namespace is only needed for the class-annotation convenience layer (`@JsonClassDescription`, `BetaToolRunner`).
|
||||
The Java SDK provides `BetaMemoryToolHandler` for implementing the memory tool backend. You supply a handler that manages file storage, and the `BetaToolRunner` handles memory tool calls automatically.
|
||||
|
||||
### Manual Loop
|
||||
```java
|
||||
import com.anthropic.helpers.BetaMemoryToolHandler;
|
||||
import com.anthropic.helpers.BetaToolRunner;
|
||||
import com.anthropic.models.beta.messages.BetaMemoryTool20250818;
|
||||
import com.anthropic.models.beta.messages.BetaMessage;
|
||||
import com.anthropic.models.beta.messages.MessageCreateParams;
|
||||
import com.anthropic.models.beta.messages.ToolRunnerCreateParams;
|
||||
|
||||
For manual tool loops, define tools as JSON schema in the request, handle `tool_use` blocks in the response, send `tool_result` back, and loop until `stop_reason` is `"end_turn"`. See the [shared tool use concepts](../shared/tool-use-concepts.md) for the agentic loop pattern.
|
||||
// Implement BetaMemoryToolHandler with your storage backend (e.g., filesystem)
|
||||
BetaMemoryToolHandler memoryHandler = new FileSystemMemoryToolHandler(sandboxRoot);
|
||||
|
||||
MessageCreateParams createParams = MessageCreateParams.builder()
|
||||
.model("claude-opus-4-6")
|
||||
.maxTokens(4096L)
|
||||
.addTool(BetaMemoryTool20250818.builder().build())
|
||||
.addUserMessage("Remember that my favorite color is blue")
|
||||
.build();
|
||||
|
||||
BetaToolRunner toolRunner = client.beta().messages().toolRunner(
|
||||
ToolRunnerCreateParams.builder()
|
||||
.betaMemoryToolHandler(memoryHandler)
|
||||
.initialMessageParams(createParams)
|
||||
.build());
|
||||
|
||||
for (BetaMessage message : toolRunner) {
|
||||
System.out.println(message);
|
||||
}
|
||||
```
|
||||
|
||||
See the [shared memory tool concepts](../shared/tool-use-concepts.md) for more details on the memory tool.
|
||||
|
||||
### Non-Beta Tool Declaration (manual JSON schema)
|
||||
|
||||
`Tool.InputSchema.Properties` is a freeform `Map<String, JsonValue>` wrapper — build property schemas via `putAdditionalProperty`. `type: "object"` is the default. The builder has a direct `.addTool(Tool)` overload that wraps in `ToolUnion` automatically.
|
||||
|
||||
```java
|
||||
import com.anthropic.core.JsonValue;
|
||||
import com.anthropic.models.messages.Tool;
|
||||
|
||||
Tool tool = Tool.builder()
|
||||
.name("get_weather")
|
||||
.description("Get the current weather in a given location")
|
||||
.inputSchema(Tool.InputSchema.builder()
|
||||
.properties(Tool.InputSchema.Properties.builder()
|
||||
.putAdditionalProperty("location", JsonValue.from(Map.of("type", "string")))
|
||||
.build())
|
||||
.required(List.of("location"))
|
||||
.build())
|
||||
.build();
|
||||
|
||||
MessageCreateParams params = MessageCreateParams.builder()
|
||||
.model(Model.CLAUDE_SONNET_4_6)
|
||||
.maxTokens(16000L)
|
||||
.addTool(tool)
|
||||
.addUserMessage("Weather in Paris?")
|
||||
.build();
|
||||
```
|
||||
|
||||
For manual tool loops, handle `tool_use` blocks in the response, send `tool_result` back, loop until `stop_reason` is `"end_turn"`. See [shared tool use concepts](../shared/tool-use-concepts.md).
|
||||
|
||||
### Building `MessageParam` with Content Blocks (Tool Result Round-Trip)
|
||||
|
||||
`MessageParam.Content` is an inner union class (string | list). Use the builder's `.contentOfBlockParams(List<ContentBlockParam>)` alias — there is NO separate `MessageParamContent` class with a static `ofBlockParams`:
|
||||
|
||||
```java
|
||||
import com.anthropic.models.messages.MessageParam;
|
||||
import com.anthropic.models.messages.ContentBlockParam;
|
||||
import com.anthropic.models.messages.ToolResultBlockParam;
|
||||
|
||||
List<ContentBlockParam> results = List.of(
|
||||
ContentBlockParam.ofToolResult(ToolResultBlockParam.builder()
|
||||
.toolUseId(toolUseBlock.id())
|
||||
.content(yourResultString)
|
||||
.build())
|
||||
);
|
||||
|
||||
MessageParam toolResultMsg = MessageParam.builder()
|
||||
.role(MessageParam.Role.USER)
|
||||
.contentOfBlockParams(results) // builder alias for Content.ofBlockParams(...)
|
||||
.build();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Effort Parameter
|
||||
|
||||
Effort is nested inside `OutputConfig` — there is NO `.effort()` directly on `MessageCreateParams.Builder`.
|
||||
|
||||
```java
|
||||
import com.anthropic.models.messages.OutputConfig;
|
||||
|
||||
.outputConfig(OutputConfig.builder()
|
||||
.effort(OutputConfig.Effort.HIGH) // or LOW, MEDIUM, MAX
|
||||
.build())
|
||||
```
|
||||
|
||||
Combine with `Thinking = ThinkingConfigAdaptive` for cost-quality control.
|
||||
|
||||
---
|
||||
|
||||
## Prompt Caching
|
||||
|
||||
System message as a list of `TextBlockParam` with `CacheControlEphemeral`. Use `.systemOfTextBlockParams(...)` — the plain `.system(String)` overload can't carry cache control.
|
||||
|
||||
```java
|
||||
import com.anthropic.models.messages.TextBlockParam;
|
||||
import com.anthropic.models.messages.CacheControlEphemeral;
|
||||
|
||||
.systemOfTextBlockParams(List.of(
|
||||
TextBlockParam.builder()
|
||||
.text(longSystemPrompt)
|
||||
.cacheControl(CacheControlEphemeral.builder()
|
||||
.ttl(CacheControlEphemeral.Ttl.TTL_1H) // optional; also TTL_5M
|
||||
.build())
|
||||
.build()))
|
||||
```
|
||||
|
||||
There's also a top-level `.cacheControl(CacheControlEphemeral)` on `MessageCreateParams.Builder` and on `Tool.builder()`.
|
||||
|
||||
---
|
||||
|
||||
## Token Counting
|
||||
|
||||
```java
|
||||
import com.anthropic.models.messages.MessageCountTokensParams;
|
||||
|
||||
long tokens = client.messages().countTokens(
|
||||
MessageCountTokensParams.builder()
|
||||
.model(Model.CLAUDE_SONNET_4_6)
|
||||
.addUserMessage("Hello")
|
||||
.build()
|
||||
).inputTokens();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Structured Output
|
||||
|
||||
The class-based overload auto-derives the JSON schema from your POJO and gives you a typed `.text()` return — no manual schema, no manual parsing.
|
||||
|
||||
```java
|
||||
import com.anthropic.models.messages.StructuredMessageCreateParams;
|
||||
|
||||
record Book(String title, String author) {}
|
||||
record BookList(List<Book> books) {}
|
||||
|
||||
StructuredMessageCreateParams<BookList> params = MessageCreateParams.builder()
|
||||
.model(Model.CLAUDE_SONNET_4_6)
|
||||
.maxTokens(16000L)
|
||||
.outputConfig(BookList.class) // returns a typed builder
|
||||
.addUserMessage("List 3 classic novels")
|
||||
.build();
|
||||
|
||||
client.messages().create(params).content().stream()
|
||||
.flatMap(cb -> cb.text().stream())
|
||||
.forEach(typed -> {
|
||||
// typed.text() returns BookList, not String
|
||||
for (Book b : typed.text().books()) System.out.println(b.title());
|
||||
});
|
||||
```
|
||||
|
||||
Supports Jackson annotations: `@JsonPropertyDescription`, `@JsonIgnore`, `@ArraySchema(minItems=...)`. Manual schema path: `OutputConfig.builder().format(JsonOutputFormat.builder().schema(...).build())`.
|
||||
|
||||
---
|
||||
|
||||
## PDF / Document Input
|
||||
|
||||
`DocumentBlockParam` builder has source shortcuts. Wrap in `ContentBlockParam.ofDocument()` and pass via `.addUserMessageOfBlockParams()`.
|
||||
|
||||
```java
|
||||
import com.anthropic.models.messages.DocumentBlockParam;
|
||||
import com.anthropic.models.messages.ContentBlockParam;
|
||||
import com.anthropic.models.messages.TextBlockParam;
|
||||
|
||||
DocumentBlockParam doc = DocumentBlockParam.builder()
|
||||
.base64Source(base64String) // or .urlSource("https://...") or .textSource("...")
|
||||
.title("My Document") // optional
|
||||
.build();
|
||||
|
||||
.addUserMessageOfBlockParams(List.of(
|
||||
ContentBlockParam.ofDocument(doc),
|
||||
ContentBlockParam.ofText(TextBlockParam.builder().text("Summarize this").build())))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Server-Side Tools
|
||||
|
||||
Version-suffixed types; `name`/`type` auto-set by builder. Direct `.addTool()` overloads exist for every type — no manual `ToolUnion` wrapping.
|
||||
|
||||
```java
|
||||
import com.anthropic.models.messages.WebSearchTool20260209;
|
||||
import com.anthropic.models.messages.ToolBash20250124;
|
||||
import com.anthropic.models.messages.ToolTextEditor20250728;
|
||||
import com.anthropic.models.messages.CodeExecutionTool20260120;
|
||||
|
||||
.addTool(WebSearchTool20260209.builder()
|
||||
.maxUses(5L) // optional
|
||||
.allowedDomains(List.of("example.com")) // optional
|
||||
.build())
|
||||
.addTool(ToolBash20250124.builder().build())
|
||||
.addTool(ToolTextEditor20250728.builder().build())
|
||||
.addTool(CodeExecutionTool20260120.builder().build())
|
||||
```
|
||||
|
||||
Also available: `WebFetchTool20260209`, `MemoryTool20250818`, `ToolSearchToolBm25_20251119`.
|
||||
|
||||
### Beta namespace (MCP, compaction)
|
||||
|
||||
For beta-only features use `com.anthropic.models.beta.messages.*` — class names have a `Beta` prefix AND live in the beta package. The beta `MessageCreateParams.Builder` has direct `.addTool(BetaToolBash20250124)` overloads AND `.addMcpServer()`:
|
||||
|
||||
```java
|
||||
import com.anthropic.models.beta.messages.MessageCreateParams;
|
||||
import com.anthropic.models.beta.messages.BetaToolBash20250124;
|
||||
import com.anthropic.models.beta.messages.BetaCodeExecutionTool20260120;
|
||||
import com.anthropic.models.beta.messages.BetaRequestMcpServerUrlDefinition;
|
||||
|
||||
MessageCreateParams params = MessageCreateParams.builder()
|
||||
.model(Model.CLAUDE_OPUS_4_6)
|
||||
.maxTokens(16000L)
|
||||
.addBeta("mcp-client-2025-11-20")
|
||||
.addTool(BetaToolBash20250124.builder().build())
|
||||
.addTool(BetaCodeExecutionTool20260120.builder().build())
|
||||
.addMcpServer(BetaRequestMcpServerUrlDefinition.builder()
|
||||
.name("my-server")
|
||||
.url("https://example.com/mcp")
|
||||
.build())
|
||||
.addUserMessage("...")
|
||||
.build();
|
||||
|
||||
client.beta().messages().create(params);
|
||||
```
|
||||
|
||||
`BetaTool*` types are NOT interchangeable with non-beta `Tool*` — pick one namespace per request.
|
||||
|
||||
**Reading server-tool blocks in the response:** `ServerToolUseBlock` has `.id()`, `.name()` (enum), and `._input()` returning raw `JsonValue` — there is NO typed `.input()`. For code execution results, unwrap two levels:
|
||||
|
||||
```java
|
||||
for (ContentBlock block : response.content()) {
|
||||
block.serverToolUse().ifPresent(stu -> {
|
||||
System.out.println("tool: " + stu.name() + " input: " + stu._input());
|
||||
});
|
||||
block.codeExecutionToolResult().ifPresent(r -> {
|
||||
r.content().resultBlock().ifPresent(result -> {
|
||||
System.out.println("stdout: " + result.stdout());
|
||||
System.out.println("stderr: " + result.stderr());
|
||||
System.out.println("exit: " + result.returnCode());
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Files API (Beta)
|
||||
|
||||
Under `client.beta().files()`. File references in messages need the beta message types (non-beta `DocumentBlockParam.Source` has no file-ID variant).
|
||||
|
||||
```java
|
||||
import com.anthropic.models.beta.files.FileUploadParams;
|
||||
import com.anthropic.models.beta.files.FileMetadata;
|
||||
import com.anthropic.models.beta.messages.BetaRequestDocumentBlock;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
FileMetadata meta = client.beta().files().upload(
|
||||
FileUploadParams.builder()
|
||||
.file(Paths.get("/path/to/doc.pdf")) // or .file(InputStream) or .file(byte[])
|
||||
.build());
|
||||
|
||||
// Reference in a beta message:
|
||||
BetaRequestDocumentBlock doc = BetaRequestDocumentBlock.builder()
|
||||
.fileSource(meta.id())
|
||||
.build();
|
||||
```
|
||||
|
||||
Other methods: `.list()`, `.delete(String fileId)`, `.download(String fileId)`, `.retrieveMetadata(String fileId)`.
|
||||
|
||||
@@ -20,20 +20,20 @@ $client = new Client(apiKey: getenv("ANTHROPIC_API_KEY"));
|
||||
### Amazon Bedrock
|
||||
|
||||
```php
|
||||
use Anthropic\BedrockClient;
|
||||
use Anthropic\Bedrock;
|
||||
|
||||
$client = new BedrockClient(
|
||||
region: 'us-east-1',
|
||||
);
|
||||
// Constructor is private — use the static factory. Reads AWS credentials from env.
|
||||
$client = Bedrock\Client::fromEnvironment(region: 'us-east-1');
|
||||
```
|
||||
|
||||
### Google Vertex AI
|
||||
|
||||
```php
|
||||
use Anthropic\VertexClient;
|
||||
use Anthropic\Vertex;
|
||||
|
||||
$client = new VertexClient(
|
||||
region: 'us-east5',
|
||||
// Constructor is private. Parameter is `location`, not `region`.
|
||||
$client = Vertex\Client::fromEnvironment(
|
||||
location: 'us-east5',
|
||||
projectId: 'my-project-id',
|
||||
);
|
||||
```
|
||||
@@ -41,10 +41,12 @@ $client = new VertexClient(
|
||||
### Anthropic Foundry
|
||||
|
||||
```php
|
||||
use Anthropic\FoundryClient;
|
||||
use Anthropic\Foundry;
|
||||
|
||||
$client = new FoundryClient(
|
||||
authToken: getenv("ANTHROPIC_AUTH_TOKEN"),
|
||||
// Constructor is private. baseUrl or resource is required.
|
||||
$client = Foundry\Client::withCredentials(
|
||||
authToken: getenv('ANTHROPIC_FOUNDRY_AUTH_TOKEN'),
|
||||
baseUrl: 'https://<resource>.services.ai.azure.com/anthropic',
|
||||
);
|
||||
```
|
||||
|
||||
@@ -55,29 +57,56 @@ $client = new FoundryClient(
|
||||
```php
|
||||
$message = $client->messages->create(
|
||||
model: 'claude-opus-4-6',
|
||||
maxTokens: 1024,
|
||||
maxTokens: 16000,
|
||||
messages: [
|
||||
['role' => 'user', 'content' => 'What is the capital of France?'],
|
||||
],
|
||||
);
|
||||
echo $message->content[0]->text;
|
||||
|
||||
// content is an array of polymorphic blocks (TextBlock, ToolUseBlock,
|
||||
// ThinkingBlock). Accessing ->text on content[0] without checking the block
|
||||
// type will throw if the first block is not a TextBlock (e.g., when extended
|
||||
// thinking is enabled and a ThinkingBlock comes first). Always guard:
|
||||
foreach ($message->content as $block) {
|
||||
if ($block->type === 'text') {
|
||||
echo $block->text;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you only want the first text block:
|
||||
|
||||
```php
|
||||
foreach ($message->content as $block) {
|
||||
if ($block->type === 'text') {
|
||||
echo $block->text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Streaming
|
||||
|
||||
> **Requires SDK v0.5.0+.** v0.4.0 and earlier used a single `$params` array; calling with named parameters throws `Unknown named parameter $model`. Upgrade: `composer require "anthropic-ai/sdk:^0.6"`
|
||||
|
||||
```php
|
||||
use Anthropic\Messages\RawContentBlockDeltaEvent;
|
||||
use Anthropic\Messages\TextDelta;
|
||||
|
||||
$stream = $client->messages->createStream(
|
||||
model: 'claude-opus-4-6',
|
||||
maxTokens: 1024,
|
||||
maxTokens: 64000,
|
||||
messages: [
|
||||
['role' => 'user', 'content' => 'Write a haiku'],
|
||||
],
|
||||
);
|
||||
|
||||
foreach ($stream as $event) {
|
||||
echo $event;
|
||||
if ($event instanceof RawContentBlockDeltaEvent && $event->delta instanceof TextDelta) {
|
||||
echo $event->delta->text;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -85,4 +114,128 @@ foreach ($stream as $event) {
|
||||
|
||||
## Tool Use (Manual Loop)
|
||||
|
||||
The PHP SDK supports raw tool definitions via JSON schema. See the [shared tool use concepts](../shared/tool-use-concepts.md) for the tool definition format and agentic loop pattern.
|
||||
Tools are passed as arrays. **The SDK uses camelCase keys** (`inputSchema`, `toolUseID`, `stopReason`) and auto-maps to the API's snake_case on the wire — since v0.5.0. See [shared tool use concepts](../shared/tool-use-concepts.md) for the loop pattern.
|
||||
|
||||
```php
|
||||
use Anthropic\Messages\ToolUseBlock;
|
||||
|
||||
$tools = [
|
||||
[
|
||||
'name' => 'get_weather',
|
||||
'description' => 'Get the current weather in a given location',
|
||||
'inputSchema' => [ // camelCase, not input_schema
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'location' => ['type' => 'string', 'description' => 'City and state'],
|
||||
],
|
||||
'required' => ['location'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$messages = [['role' => 'user', 'content' => 'What is the weather in SF?']];
|
||||
|
||||
$response = $client->messages->create(
|
||||
model: 'claude-opus-4-6',
|
||||
maxTokens: 16000,
|
||||
tools: $tools,
|
||||
messages: $messages,
|
||||
);
|
||||
|
||||
while ($response->stopReason === 'tool_use') { // camelCase property
|
||||
$toolResults = [];
|
||||
foreach ($response->content as $block) {
|
||||
if ($block instanceof ToolUseBlock) {
|
||||
// $block->name : string — tool name to dispatch on
|
||||
// $block->input : array<string,mixed> — parsed JSON input
|
||||
// $block->id : string — pass back as toolUseID
|
||||
$result = executeYourTool($block->name, $block->input);
|
||||
$toolResults[] = [
|
||||
'type' => 'tool_result',
|
||||
'toolUseID' => $block->id, // camelCase, not tool_use_id
|
||||
'content' => $result,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Append assistant turn + user turn with tool results
|
||||
$messages[] = ['role' => 'assistant', 'content' => $response->content];
|
||||
$messages[] = ['role' => 'user', 'content' => $toolResults];
|
||||
|
||||
$response = $client->messages->create(
|
||||
model: 'claude-opus-4-6',
|
||||
maxTokens: 16000,
|
||||
tools: $tools,
|
||||
messages: $messages,
|
||||
);
|
||||
}
|
||||
|
||||
// Final text response
|
||||
foreach ($response->content as $block) {
|
||||
if ($block->type === 'text') {
|
||||
echo $block->text;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`$block->type === 'tool_use'` also works; `instanceof ToolUseBlock` narrows for PHPStan.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Extended Thinking
|
||||
|
||||
**Adaptive thinking is the recommended mode for Claude 4.6+ models.** Claude decides dynamically when and how much to think.
|
||||
|
||||
```php
|
||||
use Anthropic\Messages\ThinkingBlock;
|
||||
|
||||
$message = $client->messages->create(
|
||||
model: 'claude-opus-4-6',
|
||||
maxTokens: 16000,
|
||||
thinking: ['type' => 'adaptive'],
|
||||
messages: [
|
||||
['role' => 'user', 'content' => 'Solve: 27 * 453'],
|
||||
],
|
||||
);
|
||||
|
||||
// ThinkingBlock(s) precede TextBlock in content
|
||||
foreach ($message->content as $block) {
|
||||
if ($block instanceof ThinkingBlock) {
|
||||
echo "Thinking:\n{$block->thinking}\n\n";
|
||||
// $block->signature is an opaque string — preserve verbatim if
|
||||
// passing thinking blocks back in multi-turn conversations
|
||||
} elseif ($block->type === 'text') {
|
||||
echo "Answer: {$block->text}\n";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Deprecated:** `['type' => 'enabled', 'budgetTokens' => N]` (fixed-budget extended thinking) still works on Claude 4.6 but is deprecated. Use adaptive thinking above.
|
||||
|
||||
`$block->type === 'thinking'` also works for the check; `instanceof` narrows for PHPStan.
|
||||
|
||||
---
|
||||
|
||||
## Beta Features & Server-Side Tools
|
||||
|
||||
**`betas:` is NOT a param on `$client->messages->create()`** — it only exists on the beta namespace. Use it for features that need an explicit opt-in header:
|
||||
|
||||
```php
|
||||
use Anthropic\Beta\Messages\BetaRequestMCPServerURLDefinition;
|
||||
|
||||
$response = $client->beta->messages->create(
|
||||
model: 'claude-opus-4-6',
|
||||
maxTokens: 16000,
|
||||
mcpServers: [
|
||||
BetaRequestMCPServerURLDefinition::with(
|
||||
name: 'my-server',
|
||||
url: 'https://example.com/mcp',
|
||||
),
|
||||
],
|
||||
betas: ['mcp-client-2025-11-20'], // only valid on ->beta->messages
|
||||
messages: [['role' => 'user', 'content' => 'Use the MCP tools']],
|
||||
);
|
||||
```
|
||||
|
||||
**Server-side tools** (bash, web_search, text_editor, code_execution) are GA and work on both paths — `Anthropic\Messages\ToolBash20250124` / `WebSearchTool20260209` / `ToolTextEditor20250728` / `CodeExecutionTool20260120` for non-beta, `Anthropic\Beta\Messages\BetaToolBash20250124` / `BetaWebSearchTool20260209` / `BetaToolTextEditor20250728` / `BetaCodeExecutionTool20260120` for beta. No `betas:` header needed for these.
|
||||
|
||||
@@ -115,8 +115,7 @@ Permission modes:
|
||||
- `"default"`: Prompt for dangerous operations
|
||||
- `"plan"`: Planning only, no execution
|
||||
- `"acceptEdits"`: Auto-accept file edits
|
||||
- `"dontAsk"`: Don't prompt (useful for CI/CD)
|
||||
- `"bypassPermissions"`: Skip all prompts (requires `allow_dangerously_skip_permissions=True` in options)
|
||||
- `"bypassPermissions"`: Skip all prompts (use with caution)
|
||||
|
||||
---
|
||||
|
||||
@@ -164,7 +163,9 @@ async for message in query(
|
||||
print(message.result)
|
||||
```
|
||||
|
||||
Available hook events: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `Notification`, `UserPromptSubmit`, `SessionStart`, `SessionEnd`, `Stop`, `SubagentStart`, `SubagentStop`, `PreCompact`, `PermissionRequest`, `Setup`, `TeammateIdle`, `TaskCompleted`, `ConfigChange`
|
||||
Hook callback inputs for tool-lifecycle events (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`) include `agent_id` and `agent_type` fields, allowing hooks to identify which agent (main or subagent) triggered the tool call.
|
||||
|
||||
Available hook events: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `UserPromptSubmit`, `Stop`, `SubagentStop`, `PreCompact`, `Notification`, `SubagentStart`, `PermissionRequest`
|
||||
|
||||
---
|
||||
|
||||
@@ -183,7 +184,6 @@ async for message in query(prompt="...", options=ClaudeAgentOptions(...)):
|
||||
| `tools` | list | Built-in tools to make available (restricts the default set) |
|
||||
| `disallowed_tools` | list | Tools to explicitly disallow |
|
||||
| `permission_mode` | string | How to handle permission prompts |
|
||||
| `allow_dangerously_skip_permissions`| bool | Must be `True` to use `permission_mode="bypassPermissions"` |
|
||||
| `mcp_servers` | dict | MCP servers to connect to |
|
||||
| `hooks` | dict | Hooks for customizing behavior |
|
||||
| `system_prompt` | string | Custom system prompt |
|
||||
@@ -210,8 +210,26 @@ async for message in query(
|
||||
):
|
||||
if isinstance(message, ResultMessage):
|
||||
print(message.result)
|
||||
print(f"Stop reason: {message.stop_reason}") # e.g., "end_turn", "max_turns"
|
||||
elif isinstance(message, SystemMessage) and message.subtype == "init":
|
||||
session_id = message.session_id # Capture for resuming later
|
||||
session_id = message.data.get("session_id") # Capture for resuming later
|
||||
```
|
||||
|
||||
Typed task message subclasses are available for better type safety when handling subagent task events:
|
||||
- `TaskStartedMessage` — emitted when a subagent task is registered
|
||||
- `TaskProgressMessage` — real-time progress updates with cumulative usage metrics
|
||||
- `TaskNotificationMessage` — task completion notifications
|
||||
|
||||
`RateLimitEvent` is emitted when the rate limit status transitions (e.g., from `allowed` to `allowed_warning` or `rejected`). Use it to warn users or back off gracefully:
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import query, ClaudeAgentOptions, RateLimitEvent
|
||||
|
||||
async for message in query(prompt="...", options=ClaudeAgentOptions()):
|
||||
if isinstance(message, RateLimitEvent):
|
||||
print(f"Rate limit status: {message.rate_limit_info.status}")
|
||||
if message.rate_limit_info.resets_at:
|
||||
print(f"Resets at: {message.rate_limit_info.resets_at}")
|
||||
```
|
||||
|
||||
---
|
||||
@@ -260,6 +278,64 @@ except CLIConnectionError as e:
|
||||
|
||||
---
|
||||
|
||||
## Session History
|
||||
|
||||
Retrieve past session data with top-level functions:
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import list_sessions, get_session_messages
|
||||
|
||||
# List all past sessions (sync function — no await)
|
||||
sessions = list_sessions()
|
||||
for session in sessions:
|
||||
print(f"{session.session_id}: {session.cwd}")
|
||||
|
||||
# Get messages from a specific session (sync function — no await)
|
||||
messages = get_session_messages(session_id="...")
|
||||
for msg in messages:
|
||||
print(msg)
|
||||
```
|
||||
|
||||
### Session Mutations
|
||||
|
||||
Rename or tag sessions (sync functions — no await):
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import rename_session, tag_session
|
||||
|
||||
# Rename a session
|
||||
rename_session(session_id="...", title="My refactoring session")
|
||||
|
||||
# Tag a session (tags are Unicode-sanitized automatically)
|
||||
tag_session(session_id="...", tag="experiment")
|
||||
|
||||
# Clear a tag
|
||||
tag_session(session_id="...", tag=None)
|
||||
|
||||
# Optionally scope to a specific project directory
|
||||
rename_session(session_id="...", title="New title", directory="/path/to/project")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MCP Server Management
|
||||
|
||||
Manage MCP servers at runtime using `ClaudeSDKClient`:
|
||||
|
||||
```python
|
||||
async with ClaudeSDKClient(options=options) as client:
|
||||
# Reconnect a disconnected MCP server
|
||||
await client.reconnect_mcp_server("my-server")
|
||||
|
||||
# Toggle an MCP server on/off
|
||||
await client.toggle_mcp_server("my-server", enabled=False)
|
||||
|
||||
# Get status of all MCP servers
|
||||
status = await client.get_mcp_status() # returns McpStatusResponse
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always specify allowed_tools** — Explicitly list which tools the agent can use
|
||||
|
||||
@@ -24,7 +24,7 @@ anyio.run(main)
|
||||
|
||||
## Custom Tools
|
||||
|
||||
Custom tools require an MCP server. Use `ClaudeSDKClient` for full control, or pass the server to `query()` via `mcp_servers`.
|
||||
Custom tools require an MCP server. Use `ClaudeSDKClient` for full control (custom SDK MCP tools require `ClaudeSDKClient` — `query()` only supports external stdio/http MCP servers).
|
||||
|
||||
```python
|
||||
import anyio
|
||||
@@ -216,8 +216,7 @@ async def main():
|
||||
prompt="Set up the development environment",
|
||||
options=ClaudeAgentOptions(
|
||||
allowed_tools=["Bash", "Write"],
|
||||
permission_mode="bypassPermissions",
|
||||
allow_dangerously_skip_permissions=True
|
||||
permission_mode="bypassPermissions"
|
||||
)
|
||||
):
|
||||
pass
|
||||
@@ -278,7 +277,7 @@ async def main():
|
||||
options=ClaudeAgentOptions(allowed_tools=["Read", "Glob"])
|
||||
):
|
||||
if isinstance(message, SystemMessage) and message.subtype == "init":
|
||||
session_id = message.session_id
|
||||
session_id = message.data.get("session_id")
|
||||
|
||||
# Resume with full context from the first query
|
||||
async for message in query(
|
||||
@@ -293,6 +292,47 @@ anyio.run(main)
|
||||
|
||||
---
|
||||
|
||||
## Session History
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import list_sessions, get_session_messages
|
||||
|
||||
# List past sessions (sync function — no await)
|
||||
sessions = list_sessions()
|
||||
for session in sessions:
|
||||
print(f"Session {session.session_id} in {session.cwd}")
|
||||
|
||||
# Retrieve messages from the most recent session (sync function — no await)
|
||||
if sessions:
|
||||
messages = get_session_messages(session_id=sessions[0].session_id)
|
||||
for msg in messages:
|
||||
print(msg)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Session Mutations
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import rename_session, tag_session
|
||||
|
||||
session_id = "your-session-id"
|
||||
|
||||
# Rename a session
|
||||
rename_session(session_id=session_id, title="Refactoring auth module")
|
||||
|
||||
# Tag a session for filtering
|
||||
tag_session(session_id=session_id, tag="experiment-v2")
|
||||
|
||||
# Clear a tag
|
||||
tag_session(session_id=session_id, tag=None)
|
||||
|
||||
# Scope to a specific project directory
|
||||
rename_session(session_id=session_id, title="New title", directory="/path/to/project")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Custom System Prompt
|
||||
|
||||
```python
|
||||
|
||||
@@ -28,12 +28,16 @@ async_client = anthropic.AsyncAnthropic()
|
||||
```python
|
||||
response = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
messages=[
|
||||
{"role": "user", "content": "What is the capital of France?"}
|
||||
]
|
||||
)
|
||||
print(response.content[0].text)
|
||||
# response.content is a list of content block objects (TextBlock, ThinkingBlock,
|
||||
# ToolUseBlock, ...). Check .type before accessing .text.
|
||||
for block in response.content:
|
||||
if block.type == "text":
|
||||
print(block.text)
|
||||
```
|
||||
|
||||
---
|
||||
@@ -43,7 +47,7 @@ print(response.content[0].text)
|
||||
```python
|
||||
response = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
system="You are a helpful coding assistant. Always provide examples in Python.",
|
||||
messages=[{"role": "user", "content": "How do I read a JSON file?"}]
|
||||
)
|
||||
@@ -63,7 +67,7 @@ with open("image.png", "rb") as f:
|
||||
|
||||
response = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
messages=[{
|
||||
"role": "user",
|
||||
"content": [
|
||||
@@ -86,7 +90,7 @@ response = client.messages.create(
|
||||
```python
|
||||
response = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
messages=[{
|
||||
"role": "user",
|
||||
"content": [
|
||||
@@ -116,7 +120,7 @@ Use top-level `cache_control` to automatically cache the last cacheable block in
|
||||
```python
|
||||
response = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
cache_control={"type": "ephemeral"}, # auto-caches the last cacheable block
|
||||
system="You are an expert on this large document...",
|
||||
messages=[{"role": "user", "content": "Summarize the key points"}]
|
||||
@@ -130,7 +134,7 @@ For fine-grained control, add `cache_control` to specific content blocks:
|
||||
```python
|
||||
response = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
system=[{
|
||||
"type": "text",
|
||||
"text": "You are an expert on this large document...",
|
||||
@@ -142,7 +146,7 @@ response = client.messages.create(
|
||||
# With explicit TTL (time-to-live)
|
||||
response = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
system=[{
|
||||
"type": "text",
|
||||
"text": "You are an expert on this large document...",
|
||||
@@ -228,13 +232,15 @@ class ConversationManager:
|
||||
|
||||
response = self.client.messages.create(
|
||||
model=self.model,
|
||||
max_tokens=kwargs.get("max_tokens", 1024),
|
||||
max_tokens=kwargs.get("max_tokens", 16000),
|
||||
system=self.system,
|
||||
messages=self.messages,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
assistant_message = response.content[0].text
|
||||
assistant_message = next(
|
||||
(b.text for b in response.content if b.type == "text"), ""
|
||||
)
|
||||
self.messages.append({"role": "assistant", "content": assistant_message})
|
||||
|
||||
return assistant_message
|
||||
@@ -259,7 +265,7 @@ response2 = conversation.send("What's my name?") # Claude remembers "Alice"
|
||||
|
||||
### Compaction (long conversations)
|
||||
|
||||
> **Beta, Opus 4.6 only.** When conversations approach the 200K context window, compaction automatically summarizes earlier context server-side. The API returns a `compaction` block; you must pass it back on subsequent requests — append `response.content`, not just the text.
|
||||
> **Beta, Opus 4.6 and Sonnet 4.6.** When conversations approach the 200K context window, compaction automatically summarizes earlier context server-side. The API returns a `compaction` block; you must pass it back on subsequent requests — append `response.content`, not just the text.
|
||||
|
||||
```python
|
||||
import anthropic
|
||||
@@ -273,7 +279,7 @@ def chat(user_message: str) -> str:
|
||||
response = client.beta.messages.create(
|
||||
betas=["compact-2026-01-12"],
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=4096,
|
||||
max_tokens=16000,
|
||||
messages=messages,
|
||||
context_management={
|
||||
"edits": [{"type": "compact_20260112"}]
|
||||
@@ -316,7 +322,7 @@ The `stop_reason` field in the response indicates why the model stopped generati
|
||||
# Automatic caching (simplest — caches the last cacheable block)
|
||||
response = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
cache_control={"type": "ephemeral"},
|
||||
system=large_document_text, # e.g., 50KB of context
|
||||
messages=[{"role": "user", "content": "Summarize the key points"}]
|
||||
@@ -332,14 +338,14 @@ response = client.messages.create(
|
||||
# Default to Opus for most tasks
|
||||
response = client.messages.create(
|
||||
model="claude-opus-4-6", # $5.00/$25.00 per 1M tokens
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
messages=[{"role": "user", "content": "Explain quantum computing"}]
|
||||
)
|
||||
|
||||
# Use Sonnet for high-volume production workloads
|
||||
standard_response = client.messages.create(
|
||||
model="claude-sonnet-4-6", # $3.00/$15.00 per 1M tokens
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
messages=[{"role": "user", "content": "Summarize this document"}]
|
||||
)
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ message_batch = client.messages.batches.create(
|
||||
custom_id="request-1",
|
||||
params=MessageCreateParamsNonStreaming(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
messages=[{"role": "user", "content": "Summarize climate change impacts"}]
|
||||
)
|
||||
),
|
||||
@@ -35,7 +35,7 @@ message_batch = client.messages.batches.create(
|
||||
custom_id="request-2",
|
||||
params=MessageCreateParamsNonStreaming(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
messages=[{"role": "user", "content": "Explain quantum computing basics"}]
|
||||
)
|
||||
),
|
||||
@@ -75,7 +75,9 @@ print(f"Errored: {batch.request_counts.errored}")
|
||||
for result in client.messages.batches.results(message_batch.id):
|
||||
match result.result.type:
|
||||
case "succeeded":
|
||||
print(f"[{result.custom_id}] {result.result.message.content[0].text[:100]}")
|
||||
msg = result.result.message
|
||||
text = next((b.text for b in msg.content if b.type == "text"), "")
|
||||
print(f"[{result.custom_id}] {text[:100]}")
|
||||
case "errored":
|
||||
if result.result.error.type == "invalid_request":
|
||||
print(f"[{result.custom_id}] Validation error - fix request and retry")
|
||||
@@ -116,7 +118,7 @@ message_batch = client.messages.batches.create(
|
||||
custom_id=f"analysis-{i}",
|
||||
params=MessageCreateParamsNonStreaming(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
system=shared_system,
|
||||
messages=[{"role": "user", "content": question}]
|
||||
)
|
||||
@@ -175,7 +177,8 @@ while True:
|
||||
results = {}
|
||||
for result in client.messages.batches.results(batch.id):
|
||||
if result.result.type == "succeeded":
|
||||
results[result.custom_id] = result.result.message.content[0].text
|
||||
msg = result.result.message
|
||||
results[result.custom_id] = next((b.text for b in msg.content if b.type == "text"), "")
|
||||
|
||||
for custom_id, classification in sorted(results.items()):
|
||||
print(f"{custom_id}: {classification}")
|
||||
|
||||
@@ -37,7 +37,7 @@ print(f"Size: {uploaded.size_bytes} bytes")
|
||||
```python
|
||||
response = client.beta.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
messages=[{
|
||||
"role": "user",
|
||||
"content": [
|
||||
@@ -52,7 +52,9 @@ response = client.beta.messages.create(
|
||||
}],
|
||||
betas=["files-api-2025-04-14"],
|
||||
)
|
||||
print(response.content[0].text)
|
||||
for block in response.content:
|
||||
if block.type == "text":
|
||||
print(block.text)
|
||||
```
|
||||
|
||||
### Image
|
||||
@@ -64,7 +66,7 @@ image_file = client.beta.files.upload(
|
||||
|
||||
response = client.beta.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
messages=[{
|
||||
"role": "user",
|
||||
"content": [
|
||||
@@ -141,7 +143,7 @@ questions = [
|
||||
for question in questions:
|
||||
response = client.beta.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
messages=[{
|
||||
"role": "user",
|
||||
"content": [
|
||||
@@ -155,7 +157,8 @@ for question in questions:
|
||||
betas=["files-api-2025-04-14"],
|
||||
)
|
||||
print(f"\nQ: {question}")
|
||||
print(f"A: {response.content[0].text[:200]}")
|
||||
text = next((b.text for b in response.content if b.type == "text"), "")
|
||||
print(f"A: {text[:200]}")
|
||||
|
||||
# 3. Clean up when done
|
||||
client.beta.files.delete(uploaded.id)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
```python
|
||||
with client.messages.stream(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=64000,
|
||||
messages=[{"role": "user", "content": "Write a story"}]
|
||||
) as stream:
|
||||
for text in stream.text_stream:
|
||||
@@ -17,7 +17,7 @@ with client.messages.stream(
|
||||
```python
|
||||
async with async_client.messages.stream(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=64000,
|
||||
messages=[{"role": "user", "content": "Write a story"}]
|
||||
) as stream:
|
||||
async for text in stream.text_stream:
|
||||
@@ -35,7 +35,7 @@ Claude may return text, thinking blocks, or tool use. Handle each appropriately:
|
||||
```python
|
||||
with client.messages.stream(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=16000,
|
||||
max_tokens=64000,
|
||||
thinking={"type": "adaptive"},
|
||||
messages=[{"role": "user", "content": "Analyze this problem"}]
|
||||
) as stream:
|
||||
@@ -62,7 +62,7 @@ The Python tool runner currently returns complete messages. Use streaming for in
|
||||
```python
|
||||
with client.messages.stream(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=4096,
|
||||
max_tokens=64000,
|
||||
tools=tools,
|
||||
messages=messages
|
||||
) as stream:
|
||||
@@ -80,7 +80,7 @@ with client.messages.stream(
|
||||
```python
|
||||
with client.messages.stream(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=64000,
|
||||
messages=[{"role": "user", "content": "Hello"}]
|
||||
) as stream:
|
||||
for text in stream.text_stream:
|
||||
@@ -127,7 +127,7 @@ def stream_with_progress(client, **kwargs):
|
||||
try:
|
||||
with client.messages.stream(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=64000,
|
||||
messages=[{"role": "user", "content": "Write a story"}]
|
||||
) as stream:
|
||||
for text in stream.text_stream:
|
||||
|
||||
@@ -28,7 +28,7 @@ def get_weather(location: str, unit: str = "celsius") -> str:
|
||||
# The tool runner handles the agentic loop automatically
|
||||
runner = client.beta.messages.tool_runner(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=4096,
|
||||
max_tokens=16000,
|
||||
tools=[get_weather],
|
||||
messages=[{"role": "user", "content": "What's the weather in Paris?"}],
|
||||
)
|
||||
@@ -70,9 +70,10 @@ async with stdio_client(StdioServerParameters(command="mcp-server")) as (read, w
|
||||
await mcp_client.initialize()
|
||||
|
||||
tools_result = await mcp_client.list_tools()
|
||||
runner = await client.beta.messages.tool_runner(
|
||||
# tool_runner is sync — returns the runner, not a coroutine
|
||||
runner = client.beta.messages.tool_runner(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
messages=[{"role": "user", "content": "Use the available tools"}],
|
||||
tools=[async_mcp_tool(t, mcp_client) for t in tools_result.tools],
|
||||
)
|
||||
@@ -90,7 +91,7 @@ from anthropic.lib.tools.mcp import mcp_message
|
||||
prompt = await mcp_client.get_prompt(name="my-prompt")
|
||||
response = await client.beta.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
messages=[mcp_message(m) for m in prompt.messages],
|
||||
)
|
||||
```
|
||||
@@ -103,7 +104,7 @@ from anthropic.lib.tools.mcp import mcp_resource_to_content
|
||||
resource = await mcp_client.read_resource(uri="file:///path/to/doc.txt")
|
||||
response = await client.beta.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
messages=[{
|
||||
"role": "user",
|
||||
"content": [
|
||||
@@ -142,7 +143,7 @@ messages = [{"role": "user", "content": user_input}]
|
||||
while True:
|
||||
response = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=4096,
|
||||
max_tokens=16000,
|
||||
tools=tools,
|
||||
messages=messages
|
||||
)
|
||||
@@ -189,7 +190,7 @@ final_text = next(b.text for b in response.content if b.type == "text")
|
||||
```python
|
||||
response = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
tools=tools,
|
||||
messages=[{"role": "user", "content": "What's the weather in Paris?"}]
|
||||
)
|
||||
@@ -204,7 +205,7 @@ for block in response.content:
|
||||
|
||||
followup = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
tools=tools,
|
||||
messages=[
|
||||
{"role": "user", "content": "What's the weather in Paris?"},
|
||||
@@ -241,7 +242,7 @@ for block in response.content:
|
||||
if tool_results:
|
||||
followup = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
tools=tools,
|
||||
messages=[
|
||||
*previous_messages,
|
||||
@@ -271,7 +272,7 @@ tool_result = {
|
||||
```python
|
||||
response = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
tools=tools,
|
||||
tool_choice={"type": "tool", "name": "get_weather"}, # Force specific tool
|
||||
messages=[{"role": "user", "content": "What's the weather in Paris?"}]
|
||||
@@ -291,7 +292,7 @@ client = anthropic.Anthropic()
|
||||
|
||||
response = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=4096,
|
||||
max_tokens=16000,
|
||||
messages=[{
|
||||
"role": "user",
|
||||
"content": "Calculate the mean and standard deviation of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
|
||||
@@ -319,7 +320,7 @@ uploaded = client.beta.files.upload(file=open("sales_data.csv", "rb"))
|
||||
# Code execution is GA; Files API is still beta (pass via extra_headers)
|
||||
response = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=4096,
|
||||
max_tokens=16000,
|
||||
extra_headers={"anthropic-beta": "files-api-2025-04-14"},
|
||||
messages=[{
|
||||
"role": "user",
|
||||
@@ -364,7 +365,7 @@ for block in response.content:
|
||||
# First request: set up environment
|
||||
response1 = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=4096,
|
||||
max_tokens=16000,
|
||||
messages=[{"role": "user", "content": "Install tabulate and create data.json with sample data"}],
|
||||
tools=[{"type": "code_execution_20260120", "name": "code_execution"}]
|
||||
)
|
||||
@@ -376,7 +377,7 @@ container_id = response1.container.id
|
||||
response2 = client.messages.create(
|
||||
container=container_id,
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=4096,
|
||||
max_tokens=16000,
|
||||
messages=[{"role": "user", "content": "Read data.json and display as a formatted table"}],
|
||||
tools=[{"type": "code_execution_20260120", "name": "code_execution"}]
|
||||
)
|
||||
@@ -416,7 +417,7 @@ client = anthropic.Anthropic()
|
||||
|
||||
response = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=2048,
|
||||
max_tokens=16000,
|
||||
messages=[{"role": "user", "content": "Remember that my preferred language is Python."}],
|
||||
tools=[{"type": "memory_20250818", "name": "memory"}],
|
||||
)
|
||||
@@ -442,7 +443,7 @@ memory = MyMemoryTool()
|
||||
# Use with tool runner
|
||||
runner = client.beta.messages.tool_runner(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=2048,
|
||||
max_tokens=16000,
|
||||
tools=[memory],
|
||||
messages=[{"role": "user", "content": "Remember my preferences"}],
|
||||
)
|
||||
@@ -477,7 +478,7 @@ client = anthropic.Anthropic()
|
||||
|
||||
response = client.messages.parse(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
messages=[{
|
||||
"role": "user",
|
||||
"content": "Extract: Jane Doe (jane@co.com) wants Enterprise, interested in API and SDKs, wants a demo."
|
||||
@@ -496,7 +497,7 @@ print(contact.interests) # ["API", "SDKs"]
|
||||
```python
|
||||
response = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
messages=[{
|
||||
"role": "user",
|
||||
"content": "Extract info: John Smith (john@example.com) wants the Enterprise plan."
|
||||
@@ -520,7 +521,9 @@ response = client.messages.create(
|
||||
)
|
||||
|
||||
import json
|
||||
data = json.loads(response.content[0].text)
|
||||
# output_config.format guarantees the first block is text with valid JSON
|
||||
text = next(b.text for b in response.content if b.type == "text")
|
||||
data = json.loads(text)
|
||||
```
|
||||
|
||||
### Strict Tool Use
|
||||
@@ -528,7 +531,7 @@ data = json.loads(response.content[0].text)
|
||||
```python
|
||||
response = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
messages=[{"role": "user", "content": "Book a flight to Tokyo for 2 passengers on March 15"}],
|
||||
tools=[{
|
||||
"name": "book_flight",
|
||||
@@ -553,7 +556,7 @@ response = client.messages.create(
|
||||
```python
|
||||
response = client.messages.create(
|
||||
model="claude-opus-4-6",
|
||||
max_tokens=1024,
|
||||
max_tokens=16000,
|
||||
messages=[{"role": "user", "content": "Plan a trip to Paris next month"}],
|
||||
output_config={
|
||||
"format": {
|
||||
|
||||
@@ -27,12 +27,17 @@ client = Anthropic::Client.new(api_key: "your-api-key")
|
||||
```ruby
|
||||
message = client.messages.create(
|
||||
model: :"claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{ role: "user", content: "What is the capital of France?" }
|
||||
]
|
||||
)
|
||||
puts message.content.first.text
|
||||
# content is an array of polymorphic block objects (TextBlock, ThinkingBlock,
|
||||
# ToolUseBlock, ...). .type is a Symbol — compare with :text, not "text".
|
||||
# .text raises NoMethodError on non-TextBlock entries.
|
||||
message.content.each do |block|
|
||||
puts block.text if block.type == :text
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
@@ -42,7 +47,7 @@ puts message.content.first.text
|
||||
```ruby
|
||||
stream = client.messages.stream(
|
||||
model: :"claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 64000,
|
||||
messages: [{ role: "user", content: "Write a haiku" }]
|
||||
)
|
||||
|
||||
@@ -74,7 +79,7 @@ end
|
||||
|
||||
client.beta.messages.tool_runner(
|
||||
model: :"claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 16000,
|
||||
tools: [GetWeather.new],
|
||||
messages: [{ role: "user", content: "What's the weather in San Francisco?" }]
|
||||
).each_message do |message|
|
||||
|
||||
@@ -35,7 +35,8 @@ This file documents HTTP error codes returned by the Claude API, their common ca
|
||||
"error": {
|
||||
"type": "invalid_request_error",
|
||||
"message": "messages: roles must alternate between \"user\" and \"assistant\""
|
||||
}
|
||||
},
|
||||
"request_id": "req_011CSHoEeqs5C35K2UUqR7Fy"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,57 @@
|
||||
# Claude Model Catalog
|
||||
|
||||
**Only use exact model IDs listed in this file.** Never guess or construct model IDs — incorrect IDs will cause API errors. Use aliases wherever available. For the latest information, WebFetch the Models Overview URL in `shared/live-sources.md`.
|
||||
**Only use exact model IDs listed in this file.** Never guess or construct model IDs — incorrect IDs will cause API errors. Use aliases wherever available. For the latest information, WebFetch the Models Overview URL in `shared/live-sources.md`, or query the Models API directly (see Programmatic Model Discovery below).
|
||||
|
||||
## Programmatic Model Discovery
|
||||
|
||||
For **live** capability data — context window, max output tokens, feature support (thinking, vision, effort, structured outputs, etc.) — query the Models API instead of relying on the cached tables below. Use this when the user asks "what's the context window for X", "does model X support vision/thinking/effort", "which models support feature Y", or wants to select a model by capability at runtime.
|
||||
|
||||
```python
|
||||
m = client.models.retrieve("claude-opus-4-6")
|
||||
m.id # "claude-opus-4-6"
|
||||
m.display_name # "Claude Opus 4.6"
|
||||
m.max_input_tokens # context window (int)
|
||||
m.max_tokens # max output tokens (int)
|
||||
|
||||
# capabilities is an untyped nested dict — bracket access, check ["supported"] at the leaf
|
||||
caps = m.capabilities
|
||||
caps["image_input"]["supported"] # vision
|
||||
caps["thinking"]["types"]["adaptive"]["supported"] # adaptive thinking
|
||||
caps["effort"]["max"]["supported"] # effort: max (also low/medium/high)
|
||||
caps["structured_outputs"]["supported"]
|
||||
caps["context_management"]["compact_20260112"]["supported"]
|
||||
|
||||
# filter across all models — iterate the page object directly (auto-paginates); do NOT use .data
|
||||
[m for m in client.models.list()
|
||||
if m.capabilities["thinking"]["types"]["adaptive"]["supported"]
|
||||
and m.max_input_tokens >= 200_000]
|
||||
```
|
||||
|
||||
Top-level fields (`id`, `display_name`, `max_input_tokens`, `max_tokens`) are typed attributes. `capabilities` is a dict — use bracket access, not attribute access. The API returns the full capability tree for every model with `supported: true/false` at each leaf, so bracket chains are safe without `.get()` guards. TypeScript SDK: same method names, also auto-paginates on iteration.
|
||||
|
||||
### Raw HTTP
|
||||
|
||||
```bash
|
||||
curl https://api.anthropic.com/v1/models/claude-opus-4-6 \
|
||||
-H "x-api-key: $ANTHROPIC_API_KEY" \
|
||||
-H "anthropic-version: 2023-06-01"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "claude-opus-4-6",
|
||||
"display_name": "Claude Opus 4.6",
|
||||
"max_input_tokens": 1000000,
|
||||
"max_tokens": 128000,
|
||||
"capabilities": {
|
||||
"image_input": {"supported": true},
|
||||
"structured_outputs": {"supported": true},
|
||||
"thinking": {"supported": true, "types": {"enabled": {"supported": true}, "adaptive": {"supported": true}}},
|
||||
"effort": {"supported": true, "low": {"supported": true}, …, "max": {"supported": true}},
|
||||
…
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Current Models (recommended)
|
||||
|
||||
@@ -28,9 +79,9 @@
|
||||
|
||||
## Deprecated Models (retiring soon)
|
||||
|
||||
| Friendly Name | Alias (use this) | Full ID | Status |
|
||||
|-------------------|---------------------|-------------------------------|------------|
|
||||
| Claude Haiku 3 | — | `claude-3-haiku-20240307` | Deprecated |
|
||||
| Friendly Name | Alias (use this) | Full ID | Status | Retires |
|
||||
|-------------------|---------------------|-------------------------------|------------|--------------|
|
||||
| Claude Haiku 3 | — | `claude-3-haiku-20240307` | Deprecated | Apr 19, 2026 |
|
||||
|
||||
## Retired Models (no longer available)
|
||||
|
||||
|
||||
@@ -239,7 +239,7 @@ The memory tool enables Claude to store and retrieve information across conversa
|
||||
- Client-side tool — you control storage via your implementation
|
||||
- Supports commands: `view`, `create`, `str_replace`, `insert`, `delete`, `rename`
|
||||
- Operates on files in a `/memories` directory
|
||||
- The SDKs provide helper classes/functions for implementing the memory backend
|
||||
- The Python, TypeScript, and Java SDKs provide helper classes/functions for implementing the memory backend
|
||||
|
||||
> **Security:** Never store API keys, passwords, tokens, or other secrets in memory files. Be cautious with personally identifiable information (PII) — check data privacy regulations (GDPR, CCPA) before persisting user data. The reference implementations have no built-in access control; in multi-user systems, implement per-user memory directories and authentication in your tool handlers.
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ Permission modes:
|
||||
- `"default"`: Prompt for dangerous operations
|
||||
- `"plan"`: Planning only, no execution
|
||||
- `"acceptEdits"`: Auto-accept file edits
|
||||
- `"dontAsk"`: Don't prompt (useful for CI/CD)
|
||||
- `"dontAsk"`: Don't prompt — **denies** anything not pre-approved (not an auto-approve mode)
|
||||
- `"bypassPermissions"`: Skip all prompts (requires `allowDangerouslySkipPermissions: true` in options)
|
||||
|
||||
---
|
||||
@@ -137,7 +137,9 @@ for await (const message of query({
|
||||
}
|
||||
```
|
||||
|
||||
Available hook events: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `Notification`, `UserPromptSubmit`, `SessionStart`, `SessionEnd`, `Stop`, `SubagentStart`, `SubagentStop`, `PreCompact`, `PermissionRequest`, `Setup`, `TeammateIdle`, `TaskCompleted`, `ConfigChange`
|
||||
Hook event inputs for tool-lifecycle events (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`) include `agent_id` and `agent_type` fields, allowing hooks to identify which agent (main or subagent) triggered the tool call.
|
||||
|
||||
Available hook events: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `Notification`, `UserPromptSubmit`, `SessionStart`, `SessionEnd`, `Stop`, `SubagentStart`, `SubagentStop`, `PreCompact`, `PermissionRequest`, `Setup`, `TeammateIdle`, `TaskCompleted`, `ConfigChange`, `Elicitation`, `ElicitationResult`, `WorktreeCreate`, `WorktreeRemove`, `InstructionsLoaded`
|
||||
|
||||
---
|
||||
|
||||
@@ -153,13 +155,13 @@ query({ prompt: "...", options: { ... } })
|
||||
| ----------------------------------- | ------ | -------------------------------------------------------------------------- |
|
||||
| `cwd` | string | Working directory for file operations |
|
||||
| `allowedTools` | array | Tools the agent can use (e.g., `["Read", "Edit", "Bash"]`) |
|
||||
| `tools` | array | Built-in tools to make available (restricts the default set) |
|
||||
| `tools` | array \| preset | Built-in tools to make available (`string[]` or `{type:'preset', preset:'claude_code'}`) |
|
||||
| `disallowedTools` | array | Tools to explicitly disallow |
|
||||
| `permissionMode` | string | How to handle permission prompts |
|
||||
| `allowDangerouslySkipPermissions` | bool | Must be `true` to use `permissionMode: "bypassPermissions"` |
|
||||
| `mcpServers` | object | MCP servers to connect to |
|
||||
| `hooks` | object | Hooks for customizing behavior |
|
||||
| `systemPrompt` | string | Custom system prompt |
|
||||
| `systemPrompt` | string \| preset | Custom system prompt (`string` or `{type:'preset', preset:'claude_code', append?:string}`) |
|
||||
| `maxTurns` | number | Maximum agent turns before stopping |
|
||||
| `maxBudgetUsd` | number | Maximum budget in USD for the query |
|
||||
| `model` | string | Model ID (default: determined by CLI) |
|
||||
@@ -169,6 +171,7 @@ query({ prompt: "...", options: { ... } })
|
||||
| `betas` | array | Beta features to enable (e.g., `["context-1m-2025-08-07"]`) |
|
||||
| `settingSources` | array | Settings to load (e.g., `["project"]`). Default: none (no CLAUDE.md files) |
|
||||
| `env` | object | Environment variables to set for the session |
|
||||
| `agentProgressSummaries` | bool | Enable periodic AI-generated progress summaries on `task_progress` events |
|
||||
|
||||
---
|
||||
|
||||
@@ -203,12 +206,85 @@ for await (const message of query({
|
||||
})) {
|
||||
if ("result" in message) {
|
||||
console.log(message.result);
|
||||
console.log(`Stop reason: ${message.stop_reason}`); // e.g., "end_turn", "tool_use", "max_tokens"
|
||||
} else if (message.type === "system" && message.subtype === "init") {
|
||||
const sessionId = message.session_id; // Capture for resuming later
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Task-related system messages are also emitted for subagent operations:
|
||||
- `task_started` — emitted when a subagent task is registered
|
||||
- `task_progress` — real-time progress updates with cumulative usage metrics, tool counts, and duration (enable `agentProgressSummaries` option for periodic AI-generated summaries via the `summary` field)
|
||||
- `task_notification` — task completion notifications (includes `tool_use_id` for correlating with originating tool calls)
|
||||
|
||||
---
|
||||
|
||||
## Session History
|
||||
|
||||
Retrieve past session data:
|
||||
|
||||
```typescript
|
||||
import { listSessions, getSessionMessages, getSessionInfo } from "@anthropic-ai/claude-agent-sdk";
|
||||
|
||||
// List all past sessions (supports pagination via limit/offset)
|
||||
const sessions = await listSessions({ limit: 20, offset: 0 });
|
||||
for (const session of sessions) {
|
||||
console.log(`${session.sessionId}: ${session.cwd} (tag: ${session.tag})`);
|
||||
}
|
||||
|
||||
// Get metadata for a single session
|
||||
const sessionId = sessions[0]?.sessionId;
|
||||
const info = await getSessionInfo(sessionId);
|
||||
console.log(info.tag, info.createdAt);
|
||||
|
||||
// Get messages from a specific session (supports pagination via limit/offset)
|
||||
const messages = await getSessionMessages(sessionId, { limit: 50, offset: 0 });
|
||||
for (const msg of messages) {
|
||||
console.log(msg);
|
||||
}
|
||||
```
|
||||
|
||||
### Session Mutations
|
||||
|
||||
Rename, tag, or fork sessions:
|
||||
|
||||
```typescript
|
||||
import { renameSession, tagSession, forkSession } from "@anthropic-ai/claude-agent-sdk";
|
||||
|
||||
// Rename a session
|
||||
await renameSession(sessionId, "My refactoring session");
|
||||
|
||||
// Tag a session
|
||||
await tagSession(sessionId, "experiment");
|
||||
|
||||
// Clear a tag
|
||||
await tagSession(sessionId, null);
|
||||
|
||||
// Fork a session — branch a conversation from a specific point
|
||||
const { sessionId: forkedId } = await forkSession(sessionId);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MCP Server Management
|
||||
|
||||
Manage MCP servers at runtime on a running query:
|
||||
|
||||
```typescript
|
||||
// Reconnect a disconnected MCP server
|
||||
await queryHandle.reconnectMcpServer("my-server");
|
||||
|
||||
// Toggle an MCP server on/off
|
||||
await queryHandle.toggleMcpServer("my-server", false); // (name, enabled) — both required
|
||||
|
||||
// Get status of ALL configured MCP servers — returns an ARRAY
|
||||
const statuses: McpServerStatus[] = await queryHandle.mcpServerStatus();
|
||||
for (const s of statuses) {
|
||||
console.log(s.name, s.scope, s.tools.length, s.error);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
@@ -128,6 +128,65 @@ for await (const message of query({
|
||||
|
||||
---
|
||||
|
||||
## Session History
|
||||
|
||||
```typescript
|
||||
import { listSessions, getSessionMessages, getSessionInfo } from "@anthropic-ai/claude-agent-sdk";
|
||||
|
||||
async function main() {
|
||||
// List past sessions (supports pagination via limit/offset)
|
||||
const sessions = await listSessions();
|
||||
for (const session of sessions) {
|
||||
console.log(`Session ${session.sessionId} in ${session.cwd} (tag: ${session.tag})`);
|
||||
}
|
||||
|
||||
// Get metadata for a single session
|
||||
if (sessions.length > 0) {
|
||||
const info = await getSessionInfo(sessions[0].sessionId);
|
||||
console.log(`Created: ${info.createdAt}, Tag: ${info.tag}`);
|
||||
}
|
||||
|
||||
// Retrieve messages from the most recent session
|
||||
if (sessions.length > 0) {
|
||||
const messages = await getSessionMessages(sessions[0].sessionId, { limit: 50 });
|
||||
for (const msg of messages) {
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Session Mutations
|
||||
|
||||
```typescript
|
||||
import { renameSession, tagSession, forkSession } from "@anthropic-ai/claude-agent-sdk";
|
||||
|
||||
async function main() {
|
||||
const sessionId = "your-session-id";
|
||||
|
||||
// Rename a session
|
||||
await renameSession(sessionId, "Refactoring auth module");
|
||||
|
||||
// Tag a session for filtering
|
||||
await tagSession(sessionId, "experiment-v2");
|
||||
|
||||
// Clear a tag
|
||||
await tagSession(sessionId, null);
|
||||
|
||||
// Fork a conversation to branch from a point
|
||||
const { sessionId: forkedId } = await forkSession(sessionId);
|
||||
console.log(`Forked session: ${forkedId}`);
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Custom System Prompt
|
||||
|
||||
```typescript
|
||||
|
||||
@@ -25,10 +25,16 @@ const client = new Anthropic({ apiKey: "your-api-key" });
|
||||
```typescript
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 16000,
|
||||
messages: [{ role: "user", content: "What is the capital of France?" }],
|
||||
});
|
||||
console.log(response.content[0].text);
|
||||
// response.content is ContentBlock[] — a discriminated union. Narrow by .type
|
||||
// before accessing .text (TypeScript will error on content[0].text without this).
|
||||
for (const block of response.content) {
|
||||
if (block.type === "text") {
|
||||
console.log(block.text);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
@@ -38,7 +44,7 @@ console.log(response.content[0].text);
|
||||
```typescript
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 16000,
|
||||
system:
|
||||
"You are a helpful coding assistant. Always provide examples in Python.",
|
||||
messages: [{ role: "user", content: "How do I read a JSON file?" }],
|
||||
@@ -54,7 +60,7 @@ const response = await client.messages.create({
|
||||
```typescript
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
@@ -79,7 +85,7 @@ const imageData = fs.readFileSync("image.png").toString("base64");
|
||||
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
@@ -106,7 +112,7 @@ Use top-level `cache_control` to automatically cache the last cacheable block in
|
||||
```typescript
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 16000,
|
||||
cache_control: { type: "ephemeral" }, // auto-caches the last cacheable block
|
||||
system: "You are an expert on this large document...",
|
||||
messages: [{ role: "user", content: "Summarize the key points" }],
|
||||
@@ -120,7 +126,7 @@ For fine-grained control, add `cache_control` to specific content blocks:
|
||||
```typescript
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 16000,
|
||||
system: [
|
||||
{
|
||||
type: "text",
|
||||
@@ -134,7 +140,7 @@ const response = await client.messages.create({
|
||||
// With explicit TTL (time-to-live)
|
||||
const response2 = await client.messages.create({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 16000,
|
||||
system: [
|
||||
{
|
||||
type: "text",
|
||||
@@ -215,14 +221,14 @@ const messages: Anthropic.MessageParam[] = [
|
||||
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 16000,
|
||||
messages: messages,
|
||||
});
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
|
||||
- Messages must alternate between `user` and `assistant`
|
||||
- Consecutive same-role messages are allowed — the API combines them into a single turn
|
||||
- First message must be `user`
|
||||
- Use SDK types (`Anthropic.MessageParam`, `Anthropic.Message`, `Anthropic.Tool`, etc.) for all API data structures — don't redefine equivalent interfaces
|
||||
|
||||
@@ -230,7 +236,7 @@ const response = await client.messages.create({
|
||||
|
||||
### Compaction (long conversations)
|
||||
|
||||
> **Beta, Opus 4.6 only.** When conversations approach the 200K context window, compaction automatically summarizes earlier context server-side. The API returns a `compaction` block; you must pass it back on subsequent requests — append `response.content`, not just the text.
|
||||
> **Beta, Opus 4.6 and Sonnet 4.6.** When conversations approach the 200K context window, compaction automatically summarizes earlier context server-side. The API returns a `compaction` block; you must pass it back on subsequent requests — append `response.content`, not just the text.
|
||||
|
||||
```typescript
|
||||
import Anthropic from "@anthropic-ai/sdk";
|
||||
@@ -244,7 +250,7 @@ async function chat(userMessage: string): Promise<string> {
|
||||
const response = await client.beta.messages.create({
|
||||
betas: ["compact-2026-01-12"],
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 4096,
|
||||
max_tokens: 16000,
|
||||
messages,
|
||||
context_management: {
|
||||
edits: [{ type: "compact_20260112" }],
|
||||
@@ -254,7 +260,9 @@ async function chat(userMessage: string): Promise<string> {
|
||||
// Append full content — compaction blocks must be preserved
|
||||
messages.push({ role: "assistant", content: response.content });
|
||||
|
||||
const textBlock = response.content.find((block) => block.type === "text");
|
||||
const textBlock = response.content.find(
|
||||
(b): b is Anthropic.Beta.BetaTextBlock => b.type === "text",
|
||||
);
|
||||
return textBlock?.text ?? "";
|
||||
}
|
||||
|
||||
@@ -289,7 +297,7 @@ The `stop_reason` field in the response indicates why the model stopped generati
|
||||
// Automatic caching (simplest — caches the last cacheable block)
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 16000,
|
||||
cache_control: { type: "ephemeral" },
|
||||
system: largeDocumentText, // e.g., 50KB of context
|
||||
messages: [{ role: "user", content: "Summarize the key points" }],
|
||||
|
||||
@@ -25,7 +25,7 @@ const messageBatch = await client.messages.batches.create({
|
||||
custom_id: "request-1",
|
||||
params: {
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{ role: "user", content: "Summarize climate change impacts" },
|
||||
],
|
||||
@@ -35,7 +35,7 @@ const messageBatch = await client.messages.batches.create({
|
||||
custom_id: "request-2",
|
||||
params: {
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{ role: "user", content: "Explain quantum computing basics" },
|
||||
],
|
||||
|
||||
@@ -42,7 +42,7 @@ console.log(`Size: ${uploaded.size_bytes} bytes`);
|
||||
```typescript
|
||||
const response = await client.beta.messages.create({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
```typescript
|
||||
const stream = client.messages.stream({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 64000,
|
||||
messages: [{ role: "user", content: "Write a story" }],
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@ for await (const event of stream) {
|
||||
```typescript
|
||||
const stream = client.messages.stream({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 16000,
|
||||
max_tokens: 64000,
|
||||
thinking: { type: "adaptive" },
|
||||
messages: [{ role: "user", content: "Analyze this problem" }],
|
||||
});
|
||||
@@ -83,7 +83,7 @@ const getWeather = betaZodTool({
|
||||
|
||||
const runner = client.beta.messages.toolRunner({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 4096,
|
||||
max_tokens: 64000,
|
||||
tools: [getWeather],
|
||||
messages: [
|
||||
{ role: "user", content: "What's the weather in Paris and London?" },
|
||||
@@ -118,7 +118,7 @@ for await (const messageStream of runner) {
|
||||
```typescript
|
||||
const stream = client.messages.stream({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 64000,
|
||||
messages: [{ role: "user", content: "Hello" }],
|
||||
});
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ const getWeather = betaZodTool({
|
||||
// The tool runner handles the agentic loop and returns the final message
|
||||
const finalMessage = await client.beta.messages.toolRunner({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 4096,
|
||||
max_tokens: 16000,
|
||||
tools: [getWeather],
|
||||
messages: [{ role: "user", content: "What's the weather in Paris?" }],
|
||||
});
|
||||
@@ -62,19 +62,16 @@ let messages: Anthropic.MessageParam[] = [{ role: "user", content: userInput }];
|
||||
while (true) {
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 4096,
|
||||
max_tokens: 16000,
|
||||
tools: tools,
|
||||
messages: messages,
|
||||
});
|
||||
|
||||
if (response.stop_reason === "end_turn") break;
|
||||
|
||||
// Server-side tool hit iteration limit; re-send to continue
|
||||
// Server-side tool hit iteration limit; append assistant turn and re-send to continue
|
||||
if (response.stop_reason === "pause_turn") {
|
||||
messages = [
|
||||
{ role: "user", content: userInput },
|
||||
{ role: "assistant", content: response.content },
|
||||
];
|
||||
messages.push({ role: "assistant", content: response.content });
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -112,7 +109,7 @@ let messages: Anthropic.MessageParam[] = [{ role: "user", content: userInput }];
|
||||
while (true) {
|
||||
const stream = client.messages.stream({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 4096,
|
||||
max_tokens: 64000,
|
||||
tools,
|
||||
messages,
|
||||
});
|
||||
@@ -128,12 +125,9 @@ while (true) {
|
||||
|
||||
if (message.stop_reason === "end_turn") break;
|
||||
|
||||
// Server-side tool hit iteration limit; re-send to continue
|
||||
// Server-side tool hit iteration limit; append assistant turn and re-send to continue
|
||||
if (message.stop_reason === "pause_turn") {
|
||||
messages = [
|
||||
{ role: "user", content: userInput },
|
||||
{ role: "assistant", content: message.content },
|
||||
];
|
||||
messages.push({ role: "assistant", content: message.content });
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -170,7 +164,7 @@ while (true) {
|
||||
```typescript
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 16000,
|
||||
tools: tools,
|
||||
messages: [{ role: "user", content: "What's the weather in Paris?" }],
|
||||
});
|
||||
@@ -181,7 +175,7 @@ for (const block of response.content) {
|
||||
|
||||
const followup = await client.messages.create({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 16000,
|
||||
tools: tools,
|
||||
messages: [
|
||||
{ role: "user", content: "What's the weather in Paris?" },
|
||||
@@ -205,7 +199,7 @@ for (const block of response.content) {
|
||||
```typescript
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 16000,
|
||||
tools: tools,
|
||||
tool_choice: { type: "tool", name: "get_weather" },
|
||||
messages: [{ role: "user", content: "What's the weather in Paris?" }],
|
||||
@@ -214,6 +208,45 @@ const response = await client.messages.create({
|
||||
|
||||
---
|
||||
|
||||
## Server-Side Tools
|
||||
|
||||
Version-suffixed `type` literals; `name` is fixed per interface. Pass plain object literals — the `ToolUnion` type is satisfied structurally. **The `name`/`type` pair must match the interface**: mixing `str_replace_based_edit_tool` (20250728 name) with `text_editor_20250124` (which expects `str_replace_editor`) is a TS2322.
|
||||
|
||||
**Don't type-annotate as `Tool[]`** — `Tool` is just the custom-tool variant. Let structural typing infer from the `tools` param, or annotate as `Anthropic.Messages.ToolUnion[]` if you must:
|
||||
|
||||
```typescript
|
||||
// ✓ let inference work — no annotation
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 16000,
|
||||
tools: [
|
||||
{ type: "text_editor_20250728", name: "str_replace_based_edit_tool" },
|
||||
{ type: "bash_20250124", name: "bash" },
|
||||
{ type: "web_search_20260209", name: "web_search" },
|
||||
{ type: "code_execution_20260120", name: "code_execution" },
|
||||
],
|
||||
messages: [{ role: "user", content: "..." }],
|
||||
});
|
||||
|
||||
// ✗ this is a TS2352 — Tool is the CUSTOM tool variant only
|
||||
// const tools: Anthropic.Tool[] = [{ type: "text_editor_20250728", ... }]
|
||||
```
|
||||
|
||||
| Interface | `name` | `type` |
|
||||
|---|---|---|
|
||||
| `ToolTextEditor20250124` | `str_replace_editor` | `text_editor_20250124` |
|
||||
| `ToolTextEditor20250429` | `str_replace_based_edit_tool` | `text_editor_20250429` |
|
||||
| `ToolTextEditor20250728` | `str_replace_based_edit_tool` | `text_editor_20250728` |
|
||||
| `ToolBash20250124` | `bash` | `bash_20250124` |
|
||||
| `WebSearchTool20260209` | `web_search` | `web_search_20260209` |
|
||||
| `WebFetchTool20260209` | `web_fetch` | `web_fetch_20260209` |
|
||||
| `CodeExecutionTool20260120` | `code_execution` | `code_execution_20260120` |
|
||||
|
||||
**Don't mix beta and non-beta types**: if you call `client.beta.messages.create()`, the response `content` is `BetaContentBlock[]` — you cannot pass that to a non-beta `ContentBlockParam[]` without narrowing each element.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Code Execution
|
||||
|
||||
### Basic Usage
|
||||
@@ -225,7 +258,7 @@ const client = new Anthropic();
|
||||
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 4096,
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
@@ -237,6 +270,21 @@ const response = await client.messages.create({
|
||||
});
|
||||
```
|
||||
|
||||
### Reading Local Files (ESM note)
|
||||
|
||||
`__dirname` doesn't exist in ES modules. For script-relative paths use `import.meta.url`:
|
||||
|
||||
```typescript
|
||||
import { readFileSync } from "fs";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname, join } from "path";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const pdfBytes = readFileSync(join(__dirname, "sample.pdf"));
|
||||
```
|
||||
|
||||
Or use a CWD-relative path if the script runs from a known directory: `readFileSync("./sample.pdf")`.
|
||||
|
||||
### Upload Files for Analysis
|
||||
|
||||
```typescript
|
||||
@@ -258,7 +306,7 @@ const uploaded = await client.beta.files.upload({
|
||||
const response = await client.messages.create(
|
||||
{
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 4096,
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
@@ -295,8 +343,8 @@ for (const block of response.content) {
|
||||
const metadata = await client.beta.files.retrieveMetadata(
|
||||
fileRef.file_id,
|
||||
);
|
||||
const response = await client.beta.files.download(fileRef.file_id);
|
||||
const fileBytes = Buffer.from(await response.arrayBuffer());
|
||||
const downloadResponse = await client.beta.files.download(fileRef.file_id);
|
||||
const fileBytes = Buffer.from(await downloadResponse.arrayBuffer());
|
||||
const safeName = path.basename(metadata.filename);
|
||||
if (!safeName || safeName === "." || safeName === "..") {
|
||||
console.warn(`Skipping invalid filename: ${metadata.filename}`);
|
||||
@@ -318,7 +366,7 @@ for (const block of response.content) {
|
||||
// First request: set up environment
|
||||
const response1 = await client.messages.create({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 4096,
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
@@ -329,12 +377,13 @@ const response1 = await client.messages.create({
|
||||
});
|
||||
|
||||
// Reuse container
|
||||
const containerId = response1.container.id;
|
||||
// container is nullable — set only when using server-side code execution
|
||||
const containerId = response1.container!.id;
|
||||
|
||||
const response2 = await client.messages.create({
|
||||
container: containerId,
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 4096,
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
@@ -354,7 +403,7 @@ const response2 = await client.messages.create({
|
||||
```typescript
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 2048,
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
@@ -388,7 +437,7 @@ const memory = betaMemoryTool(handlers);
|
||||
|
||||
const runner = client.beta.messages.toolRunner({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 2048,
|
||||
max_tokens: 16000,
|
||||
tools: [memory],
|
||||
messages: [{ role: "user", content: "Remember my preferences" }],
|
||||
});
|
||||
@@ -425,7 +474,7 @@ const client = new Anthropic();
|
||||
|
||||
const response = await client.messages.parse({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
@@ -438,7 +487,8 @@ const response = await client.messages.parse({
|
||||
},
|
||||
});
|
||||
|
||||
console.log(response.parsed_output.name); // "Jane Doe"
|
||||
// parsed_output is null if parsing failed — assert or guard
|
||||
console.log(response.parsed_output!.name); // "Jane Doe"
|
||||
```
|
||||
|
||||
### Strict Tool Use
|
||||
@@ -446,7 +496,7 @@ console.log(response.parsed_output.name); // "Jane Doe"
|
||||
```typescript
|
||||
const response = await client.messages.create({
|
||||
model: "claude-opus-4-6",
|
||||
max_tokens: 1024,
|
||||
max_tokens: 16000,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
|
||||
Reference in New Issue
Block a user