chore: import upstream snapshot with attribution
Component Security Validation / Security Audit (push) Has been cancelled
Deploy to Cloudflare Pages / deploy (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:38:58 +08:00
commit bb5c75ce05
8824 changed files with 1946442 additions and 0 deletions
@@ -0,0 +1,375 @@
# Claude API — PHP
> **Note:** The PHP SDK is the official Anthropic SDK for PHP. A beta tool runner is available via `$client->beta->messages->toolRunner()`. Structured output helpers are supported via `StructuredOutputModel` classes. Agent SDK is not available. Bedrock, Vertex AI, and Foundry clients are supported.
## Installation
```bash
composer require "anthropic-ai/sdk"
```
## Client Initialization
```php
use Anthropic\Client;
// Using API key from environment variable
$client = new Client(apiKey: getenv("ANTHROPIC_API_KEY"));
```
### Amazon Bedrock
```php
use Anthropic\Bedrock;
// 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\Vertex;
// Constructor is private. Parameter is `location`, not `region`.
$client = Vertex\Client::fromEnvironment(
location: 'us-east5',
projectId: 'my-project-id',
);
```
### Anthropic Foundry
```php
use Anthropic\Foundry;
// 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',
);
```
---
## Basic Message Request
```php
$message = $client->messages->create(
model: 'claude-opus-4-7',
maxTokens: 16000,
messages: [
['role' => 'user', 'content' => 'What is the capital of France?'],
],
);
// 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.7"`
```php
use Anthropic\Messages\RawContentBlockDeltaEvent;
use Anthropic\Messages\TextDelta;
$stream = $client->messages->createStream(
model: 'claude-opus-4-7',
maxTokens: 64000,
messages: [
['role' => 'user', 'content' => 'Write a haiku'],
],
);
foreach ($stream as $event) {
if ($event instanceof RawContentBlockDeltaEvent && $event->delta instanceof TextDelta) {
echo $event->delta->text;
}
}
```
---
## Tool Use
### Tool Runner (Beta)
**Beta:** The PHP SDK provides a tool runner via `$client->beta->messages->toolRunner()`. Define tools with `BetaRunnableTool` — a definition array plus a `run` closure:
```php
use Anthropic\Lib\Tools\BetaRunnableTool;
$weatherTool = new BetaRunnableTool(
definition: [
'name' => 'get_weather',
'description' => 'Get the current weather for a location.',
'input_schema' => [
'type' => 'object',
'properties' => [
'location' => ['type' => 'string', 'description' => 'City and state'],
],
'required' => ['location'],
],
],
run: function (array $input): string {
return "The weather in {$input['location']} is sunny and 72°F.";
},
);
$runner = $client->beta->messages->toolRunner(
maxTokens: 16000,
messages: [['role' => 'user', 'content' => 'What is the weather in Paris?']],
model: 'claude-opus-4-7',
tools: [$weatherTool],
);
foreach ($runner as $message) {
foreach ($message->content as $block) {
if ($block->type === 'text') {
echo $block->text;
}
}
}
```
### Manual Loop
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-7',
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-7',
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-7',
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.
---
## Prompt Caching
`system:` takes an array of text blocks; set `cacheControl` on the last block. Array-shape syntax (camelCase keys) is idiomatic. For placement patterns and the silent-invalidator audit checklist, see `shared/prompt-caching.md`.
```php
$message = $client->messages->create(
model: 'claude-opus-4-7',
maxTokens: 16000,
system: [
['type' => 'text', 'text' => $longSystemPrompt, 'cacheControl' => ['type' => 'ephemeral']],
],
messages: [['role' => 'user', 'content' => 'Summarize the key points']],
);
```
For 1-hour TTL: `'cacheControl' => ['type' => 'ephemeral', 'ttl' => '1h']`. There's also a top-level `cacheControl:` on `messages->create(...)` that auto-places on the last cacheable block.
Verify hits via `$message->usage->cacheCreationInputTokens` / `$message->usage->cacheReadInputTokens`.
---
## Structured Outputs
### Using StructuredOutputModel (Recommended)
Define a PHP class implementing `StructuredOutputModel` and pass it as `outputConfig`:
```php
use Anthropic\Lib\Contracts\StructuredOutputModel;
use Anthropic\Lib\Concerns\StructuredOutputModelTrait;
use Anthropic\Lib\Attributes\Constrained;
class Person implements StructuredOutputModel
{
use StructuredOutputModelTrait;
#[Constrained(description: 'Full name')]
public string $name;
public int $age;
public ?string $email = null; // nullable = optional field
}
$message = $client->messages->create(
model: 'claude-opus-4-7',
maxTokens: 16000,
messages: [['role' => 'user', 'content' => 'Generate a profile for Alice, age 30']],
outputConfig: ['format' => Person::class],
);
$person = $message->parsedOutput(); // Person instance
echo $person->name;
```
Types are inferred from PHP type hints. Use `#[Constrained(description: '...')]` to add descriptions. Nullable properties (`?string`) become optional fields.
### Raw Schema
```php
$message = $client->messages->create(
model: 'claude-opus-4-7',
maxTokens: 16000,
messages: [['role' => 'user', 'content' => 'Extract: John (john@co.com), Enterprise plan']],
outputConfig: [
'format' => [
'type' => 'json_schema',
'schema' => [
'type' => 'object',
'properties' => [
'name' => ['type' => 'string'],
'email' => ['type' => 'string'],
'plan' => ['type' => 'string'],
],
'required' => ['name', 'email', 'plan'],
'additionalProperties' => false,
],
],
],
);
// First text block contains valid JSON
foreach ($message->content as $block) {
if ($block->type === 'text') {
$data = json_decode($block->text, true);
break;
}
}
```
---
## 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-7',
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.
@@ -0,0 +1,435 @@
# Managed Agents — PHP
> **Bindings not shown here:** This README covers the most common managed-agents flows for PHP. If you need a class, method, namespace, field, or behavior that isn't shown, WebFetch the PHP SDK repo **or the relevant docs page** from `shared/live-sources.md` rather than guess. Do not extrapolate from cURL shapes or another language's SDK.
> **Agents are persistent — create once, reference by ID.** Store the agent ID returned by `$client->beta->agents->create` and pass it to every subsequent `->sessions->create`; do not call `agents->create` in the request path. The Anthropic CLI is one convenient way to create agents and environments from version-controlled YAML — its URL is in `shared/live-sources.md`. The examples below show in-code creation for completeness; in production the create call belongs in setup, not in the request path.
## Installation
```bash
composer require "anthropic-ai/sdk"
```
## Client Initialization
```php
use Anthropic\Client;
// Default (uses ANTHROPIC_API_KEY env var)
$client = new Client();
// Explicit API key
$client = new Client(apiKey: 'your-api-key');
```
---
## Create an Environment
```php
$environment = $client->beta->environments->create(
name: 'my-dev-env',
config: ['type' => 'cloud', 'networking' => ['type' => 'unrestricted']],
);
echo "Environment ID: {$environment->id}\n"; // env_...
```
---
## Create an Agent (required first step)
> ⚠️ **There is no inline agent config.** `model`/`system`/`tools` live on the agent object, not the session. Always start with `$client->beta->agents->create()` — the session takes either `agent: $agent->id` or the typed `BetaManagedAgentsAgentParams::with(type: 'agent', id: $agent->id, version: $agent->version)`.
### Minimal
```php
use Anthropic\Beta\Agents\BetaManagedAgentsAgentToolset20260401Params;
// 1. Create the agent (reusable, versioned)
$agent = $client->beta->agents->create(
name: 'Coding Assistant',
model: 'claude-opus-4-7',
system: 'You are a helpful coding assistant.',
tools: [
BetaManagedAgentsAgentToolset20260401Params::with(
type: 'agent_toolset_20260401',
),
],
);
// 2. Start a session
$session = $client->beta->sessions->create(
agent: ['type' => 'agent', 'id' => $agent->id, 'version' => $agent->version],
environmentID: $environment->id,
title: 'Quickstart session',
);
echo "Session ID: {$session->id}\n";
```
### Updating an Agent
Updates create new versions; the agent object is immutable per version.
```php
$updatedAgent = $client->beta->agents->update(
$agent->id,
version: $agent->version,
system: 'You are a helpful coding agent. Always write tests.',
);
echo "New version: {$updatedAgent->version}\n";
// List all versions
foreach ($client->beta->agents->versions->list($agent->id)->pagingEachItem() as $version) {
echo "Version {$version->version}: {$version->updatedAt->format(DateTimeInterface::ATOM)}\n";
}
// Archive the agent
$archived = $client->beta->agents->archive($agent->id);
echo "Archived at: {$archived->archivedAt->format(DateTimeInterface::ATOM)}\n";
```
---
## Send a User Message
```php
$client->beta->sessions->events->send(
$session->id,
events: [
[
'type' => 'user.message',
'content' => [['type' => 'text', 'text' => 'Review the auth module']],
],
],
);
```
> 💡 **Stream-first:** Open the stream *before* (or concurrently with) sending the message. The stream only delivers events that occur after it opens — stream-after-send means early events arrive buffered in one batch. See [Steering Patterns](../../shared/managed-agents-events.md#steering-patterns).
---
## Stream Events (SSE)
> ️ **Streaming transporter:** PHP's default buffered PSR-18 client never returns for the open-ended session event stream. Use a streaming Guzzle transporter for `streamStream()` calls — other calls keep the default client.
```php
$streamingClient = new GuzzleHttp\Client(['stream' => true]);
// Open the stream first, then send the user message
$stream = $client->beta->sessions->events->streamStream(
$session->id,
requestOptions: ['transporter' => $streamingClient],
);
$client->beta->sessions->events->send(
$session->id,
events: [
[
'type' => 'user.message',
'content' => [['type' => 'text', 'text' => 'Summarize the repo README']],
],
],
);
foreach ($stream as $event) {
match ($event->type) {
'agent.message' => array_walk(
$event->content,
static fn($block) => $block->type === 'text' ? print($block->text) : null,
),
'agent.tool_use' => print("\n[Using tool: {$event->name}]\n"),
'session.error' => printf("\n[Error: %s]", $event->error?->message ?? 'unknown'),
default => null,
};
if ($event->type === 'session.status_idle' || $event->type === 'session.error') {
break;
}
}
$stream->close();
```
### Reconnecting and Tailing
When reconnecting mid-session, list past events first to dedupe, then tail live events:
```php
$stream = $client->beta->sessions->events->streamStream(
$session->id,
requestOptions: ['transporter' => $streamingClient],
);
// Stream is open and buffering. List history before tailing live.
$seenEventIds = [];
foreach ($client->beta->sessions->events->list($session->id)->pagingEachItem() as $event) {
$seenEventIds[$event->id] = true;
}
// Tail live events, skipping anything already seen
foreach ($stream as $event) {
if (isset($seenEventIds[$event->id])) {
continue;
}
$seenEventIds[$event->id] = true;
match ($event->type) {
'agent.message' => array_walk(
$event->content,
static fn($block) => $block->type === 'text' ? print($block->text) : null,
),
default => null,
};
if ($event->type === 'session.status_idle') {
break;
}
}
$stream->close();
```
---
## Provide Custom Tool Result
> ️ The PHP managed-agents bindings for `user.custom_tool_result` are not yet documented in this skill or in the apps source examples. Refer to `shared/managed-agents-events.md` for the wire format and the `anthropic-ai/sdk` PHP repository for the corresponding params.
---
## Poll Events
```php
foreach ($client->beta->sessions->events->list($session->id)->pagingEachItem() as $event) {
echo "{$event->type}: {$event->id}\n";
}
```
---
## Upload a File
> ️ **PHP file upload:** The PHP SDK's beta managed-agents file upload binding is not shown in the apps source examples; the canonical PHP example uses raw cURL against `POST /v1/files`. If your codebase prefers the SDK, WebFetch the `anthropic-ai/sdk` PHP repository for the latest binding before writing code.
```php
use Anthropic\Beta\Sessions\BetaManagedAgentsFileResourceParams;
// Raw cURL upload (canonical example from the apps source)
$csvPath = 'data.csv';
$ch = curl_init('https://api.anthropic.com/v1/files');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'x-api-key: ' . getenv('ANTHROPIC_API_KEY'),
'anthropic-version: 2023-06-01',
'anthropic-beta: files-api-2025-04-14',
],
CURLOPT_POSTFIELDS => ['file' => new CURLFile($csvPath, 'text/csv', 'data.csv')],
]);
$file = json_decode(curl_exec($ch));
echo "File ID: {$file->id}\n";
// Mount in a session
$session = $client->beta->sessions->create(
agent: $agent->id,
environmentID: $environment->id,
resources: [
BetaManagedAgentsFileResourceParams::with(
type: 'file',
fileID: $file->id,
mountPath: '/workspace/data.csv',
),
],
);
```
### Add and Manage Resources on an Existing Session
```php
// Attach an additional file to an open session
$resource = $client->beta->sessions->resources->add(
$session->id,
type: 'file',
fileID: $file->id,
);
echo "{$resource->id}\n"; // "sesrsc_01ABC..."
// List resources on the session
$listed = $client->beta->sessions->resources->list($session->id);
foreach ($listed->data as $entry) {
echo "{$entry->id} {$entry->type}\n";
}
// Detach a resource
$client->beta->sessions->resources->delete($resource->id, sessionID: $session->id);
```
---
## List and Download Session Files
> ️ Listing and downloading files an agent wrote during a session is not yet documented for PHP in this skill or in the apps source examples. See `shared/managed-agents-events.md` and the `anthropic-ai/sdk` PHP repository for the file list/download bindings.
---
## Session Management
```php
// List environments
$environments = $client->beta->environments->list();
// Retrieve a specific environment
$env = $client->beta->environments->retrieve($environment->id);
// Archive an environment (read-only, existing sessions continue)
$client->beta->environments->archive($environment->id);
// Delete an environment (only if no sessions reference it)
$client->beta->environments->delete($environment->id);
// Delete a session
$client->beta->sessions->delete($session->id);
```
---
## MCP Server Integration
```php
use Anthropic\Beta\Agents\BetaManagedAgentsAgentToolset20260401Params;
use Anthropic\Beta\Agents\BetaManagedAgentsMCPToolsetParams;
use Anthropic\Beta\Agents\BetaManagedAgentsUrlmcpServerParams;
use Anthropic\Beta\Sessions\BetaManagedAgentsAgentParams;
// Agent declares MCP server (no auth here — auth goes in a vault)
$agent = $client->beta->agents->create(
name: 'GitHub Assistant',
model: 'claude-opus-4-7',
mcpServers: [
BetaManagedAgentsUrlmcpServerParams::with(
type: 'url',
name: 'github',
url: 'https://api.githubcopilot.com/mcp/',
),
],
tools: [
BetaManagedAgentsAgentToolset20260401Params::with(type: 'agent_toolset_20260401'),
BetaManagedAgentsMCPToolsetParams::with(
type: 'mcp_toolset',
mcpServerName: 'github',
),
],
);
// Session attaches vault(s) containing credentials for those MCP server URLs
$session = $client->beta->sessions->create(
agent: BetaManagedAgentsAgentParams::with(
type: 'agent',
id: $agent->id,
version: $agent->version,
),
environmentID: $environment->id,
vaultIDs: [$vault->id],
);
```
See `shared/managed-agents-tools.md` §Vaults for creating vaults and adding credentials.
---
## Vaults
```php
// Create a vault
$vault = $client->beta->vaults->create(
displayName: 'Alice',
metadata: ['external_user_id' => 'usr_abc123'],
);
echo $vault->id . "\n"; // "vlt_01ABC..."
// Add an OAuth credential
$credential = $client->beta->vaults->credentials->create(
vaultID: $vault->id,
displayName: "Alice's Slack",
auth: [
'type' => 'mcp_oauth',
'mcp_server_url' => 'https://mcp.slack.com/mcp',
'access_token' => 'xoxp-...',
'expires_at' => '2026-04-15T00:00:00Z',
'refresh' => [
'token_endpoint' => 'https://slack.com/api/oauth.v2.access',
'client_id' => '1234567890.0987654321',
'scope' => 'channels:read chat:write',
'refresh_token' => 'xoxe-1-...',
'token_endpoint_auth' => [
'type' => 'client_secret_post',
'client_secret' => 'abc123...',
],
],
],
);
// Rotate the credential (e.g., after a token refresh)
$client->beta->vaults->credentials->update(
$credential->id,
vaultID: $vault->id,
auth: [
'type' => 'mcp_oauth',
'access_token' => 'xoxp-new-...',
'expires_at' => '2026-05-15T00:00:00Z',
'refresh' => ['refresh_token' => 'xoxe-1-new-...'],
],
);
// Archive a vault
$client->beta->vaults->archive($vault->id);
```
---
## GitHub Repository Integration
Mount a GitHub repository as a session resource (a vault holds the GitHub MCP credential):
```php
$session = $client->beta->sessions->create(
agent: $agent->id,
environmentID: $environment->id,
vaultIDs: [$vault->id],
resources: [
[
'type' => 'github_repository',
'url' => 'https://github.com/org/repo',
'mountPath' => '/workspace/repo',
'authorizationToken' => 'ghp_your_github_token',
],
],
);
```
Multiple repositories on the same session:
```php
$resources = [
[
'type' => 'github_repository',
'url' => 'https://github.com/org/frontend',
'mountPath' => '/workspace/frontend',
'authorizationToken' => 'ghp_your_github_token',
],
[
'type' => 'github_repository',
'url' => 'https://github.com/org/backend',
'mountPath' => '/workspace/backend',
'authorizationToken' => 'ghp_your_github_token',
],
];
```
Rotating a repository's authorization token:
```php
$listed = $client->beta->sessions->resources->list($session->id);
$repoResourceId = $listed->data[0]->id;
$client->beta->sessions->resources->update(
$repoResourceId,
sessionID: $session->id,
authorizationToken: 'ghp_your_new_github_token',
);
```