diff --git a/.claude/skills/claude-platform-on-aws/SKILL.md b/.claude/skills/claude-platform-on-aws/SKILL.md new file mode 100644 index 00000000..89f71533 --- /dev/null +++ b/.claude/skills/claude-platform-on-aws/SKILL.md @@ -0,0 +1,90 @@ +--- +name: claude-platform-on-aws +description: Convert Anthropic 1P (direct API) code samples to Claude Platform on AWS (CPOA). Use when migrating from api.anthropic.com to aws-external-anthropic.{region}.api.aws, converting auth to IAM SigV4 or CPOA API key, adding workspace headers, adapting self-hosted sandbox workers for CPOA, or when someone asks about differences between Anthropic 1P and CPOA authentication/endpoints. Triggers on phrases like "convert to CPOA", "migrate to Claude Platform on AWS", "CPOA auth", "workspace header", "aws-external-anthropic", "SigV4 anthropic". +--- + +# Claude Platform on AWS — Migration Skill + +Convert Anthropic 1P API code to Claude Platform on AWS (CPOA). + +## Key Differences: 1P vs CPOA + +| Aspect | Anthropic 1P | CPOA | +|--------|-------------|------| +| Base URL | `https://api.anthropic.com` | `https://aws-external-anthropic.{region}.api.aws` | +| Auth | API key (`sk-ant-api03-...`) in `x-api-key` header | IAM SigV4 OR CPOA API key (generated in AWS Console) | +| Required headers | `x-api-key`, `anthropic-version` | `anthropic-workspace-id` (handled by platform SDK clients) | +| SDK client | `Anthropic` | `AnthropicAWS` (Python) / `AnthropicAws` (TypeScript) | +| Billing | Anthropic billing | AWS Marketplace | +| Inference | Anthropic infra | Anthropic infra (same — CPOA only changes auth/billing layer) | +| Env keys (self-hosted workers) | `sk-ant-oat01-...` (from Claude Console) | NOT used — workers authenticate via IAM or CPOA API key | +| VPC access | Direct HTTPS to api.anthropic.com | PrivateLink available (VPC→AWS hop only; inference still on Anthropic) | +| Credential rotation | Manual API key management | Automatic via STS (IAM mode) | + +## Conversion Steps + +### 1. Identify Auth Mode + +CPOA supports two auth modes: + +- **IAM SigV4** (preferred for production): Uses execution role credentials via standard AWS provider chain. No secrets to manage. +- **API key** (dev/quick-start): Generated in AWS Console under Claude Platform on AWS → API keys. Short-term tokens generated from AWS credentials default to 12 hours (capped at the lesser of requested duration, AWS credentials' expiry, and 12 hours). + +### 2. Install the Platform SDK + +The platform-specific SDK client handles SigV4 signing, base URL construction, and the `anthropic-workspace-id` header automatically. + +```bash +# Python +pip install -U "anthropic[aws]" + +# TypeScript +npm install @anthropic-ai/aws-sdk +``` + +### 3. Convert Client Initialization + +See `references/code-patterns.md` for language-specific conversion patterns (Python, Node.js, curl). + +The platform client reads `AWS_REGION` and `ANTHROPIC_AWS_WORKSPACE_ID` from environment variables. Set them: + +```bash +export AWS_REGION='us-west-2' +export ANTHROPIC_AWS_WORKSPACE_ID='wrkspc_XXXXX' +``` + +### 4. Update Self-Hosted Sandbox Workers + +For self-hosted environments on CPOA: +- Environment keys from the Claude Console **do not work** on the CPOA endpoint +- Workers authenticate via IAM (attach `AnthropicSelfHostedEnvironmentAccess` policy) or CPOA API key +- Use the `EnvironmentWorker` SDK helper (from `anthropic.lib.environments`) or call `GET /v1/environments/{id}/work/poll` directly + +### 5. IAM Permissions + +Different policies for different use cases: + +- **Inference** (`/v1/messages`): Attach `AnthropicInferenceAccess` managed policy (includes `CreateInference`) +- **Self-hosted workers**: Attach `AnthropicSelfHostedEnvironmentAccess` managed policy (includes `ProcessEnvironmentWork`, `GetEnvironment`, `GetSession`, `UpdateSession`, `GetSkill`, `CallWithBearerToken` — but NOT `CreateInference`) + +A role with only `AnthropicSelfHostedEnvironmentAccess` will 403 on `/v1/messages`. Combine both policies if the worker also needs to make inference calls. + +### 6. Security Comparison + +CPOA advantages for sandboxed/multi-tenant environments: +- No long-lived secrets — IAM creds rotate hourly via STS +- IAM policy scoping — restrict to specific actions +- Condition keys — `aws:SourceVpc`, `aws:SourceAccount` +- CloudTrail audit — native AWS logging +- SCP enforcement at org level + +Tradeoff: more operational complexity (IAM setup, workspace subscription, outbound web identity federation enablement). + +## Common Pitfalls + +1. **Missing workspace header** → 400 error. Use the platform SDK client (`AnthropicAWS`/`AnthropicAws`) which adds it automatically, or set `ANTHROPIC_AWS_WORKSPACE_ID` env var. +2. **Outbound web identity federation disabled** → Every request fails with "Outbound web identity federation is disabled for your account". Run `aws iam enable-outbound-web-identity-federation` once per AWS account. +3. **Using Claude Console environment keys on CPOA** → Won't work. Workers on CPOA authenticate via IAM or CPOA API key instead. +4. **Confusing IAM policies** → `AnthropicSelfHostedEnvironmentAccess` does NOT grant inference. Need `AnthropicInferenceAccess` for `/v1/messages`. +5. **Region not set** → Unlike `AnthropicBedrock` (which falls back to `us-east-1`), the platform client raises an error if no region is set. +6. **Account not CPOA-subscribed** → SigV4 will fail; ensure the AWS account has completed Claude Platform on AWS sign-up. diff --git a/.claude/skills/claude-platform-on-aws/references/code-patterns.md b/.claude/skills/claude-platform-on-aws/references/code-patterns.md new file mode 100644 index 00000000..59f253a4 --- /dev/null +++ b/.claude/skills/claude-platform-on-aws/references/code-patterns.md @@ -0,0 +1,286 @@ +# Code Patterns: 1P → CPOA Conversion + +## Python + +### Before (Anthropic 1P) + +```python +from anthropic import Anthropic + +client = Anthropic(api_key="sk-ant-api03-...") + +response = client.messages.create( + model="claude-sonnet-5", + max_tokens=1024, + messages=[{"role": "user", "content": "Hello"}], +) +``` + +### After (CPOA — API Key Mode) + +```python +from anthropic import AnthropicAWS # pip install -U "anthropic[aws]" + +# API key generated in AWS Console under Claude Platform on AWS → API keys +# Region + workspace from AWS_REGION / ANTHROPIC_AWS_WORKSPACE_ID env vars +client = AnthropicAWS(api_key="") + +response = client.messages.create( + model="claude-sonnet-5", + max_tokens=1024, + messages=[{"role": "user", "content": "Hello"}], +) +``` + +### After (CPOA — SigV4 IAM Mode) + +```python +from anthropic import AnthropicAWS # pip install -U "anthropic[aws]" + +# No API key → SigV4 via default AWS credential chain +# (env → ~/.aws → IRSA → ECS → IMDS) +client = AnthropicAWS(aws_region="us-west-2") + +response = client.messages.create( + model="claude-sonnet-5", + max_tokens=1024, + messages=[{"role": "user", "content": "Hello"}], +) +``` + +### After (CPOA — Short-Term Token) + +```python +from token_generator_for_aws_external_anthropic import TokenGenerator +from anthropic import AnthropicAWS + +# Token defaults to 12h, capped at min(requested, AWS creds expiry, 12h) +token = TokenGenerator(region="us-west-2").get_token() + +client = AnthropicAWS(api_key=token, aws_region="us-west-2") + +response = client.messages.create( + model="claude-sonnet-5", + max_tokens=1024, + messages=[{"role": "user", "content": "Hello"}], +) +``` + +## Node.js / TypeScript + +### Before (Anthropic 1P) + +```typescript +import Anthropic from "@anthropic-ai/sdk"; + +const client = new Anthropic({ apiKey: "sk-ant-api03-..." }); + +const response = await client.messages.create({ + model: "claude-sonnet-5", + max_tokens: 1024, + messages: [{ role: "user", content: "Hello" }], +}); +``` + +### After (CPOA — API Key Mode) + +```typescript +import AnthropicAws from "@anthropic-ai/aws-sdk"; // npm install @anthropic-ai/aws-sdk + +// API key generated in AWS Console under Claude Platform on AWS → API keys +const client = new AnthropicAws({ + apiKey: "", + awsRegion: "us-west-2", +}); + +const response = await client.messages.create({ + model: "claude-sonnet-5", + max_tokens: 1024, + messages: [{ role: "user", content: "Hello" }], +}); +``` + +### After (CPOA — SigV4 IAM Mode) + +```typescript +import AnthropicAws from "@anthropic-ai/aws-sdk"; + +// No API key → SigV4 via default AWS credential chain +const client = new AnthropicAws({ awsRegion: "us-west-2" }); + +const response = await client.messages.create({ + model: "claude-sonnet-5", + max_tokens: 1024, + messages: [{ role: "user", content: "Hello" }], +}); +``` + +### After (CPOA — Short-Term Token) + +```typescript +import { getTokenProvider } from "@aws/token-generator-for-aws-external-anthropic"; +import AnthropicAws from "@anthropic-ai/aws-sdk"; + +const tokenProvider = getTokenProvider({ region: "us-west-2" }); +const token = await tokenProvider(); + +const client = new AnthropicAws({ apiKey: token, awsRegion: "us-west-2" }); + +const response = await client.messages.create({ + model: "claude-sonnet-5", + max_tokens: 1024, + messages: [{ role: "user", content: "Hello" }], +}); +``` + +## curl + +### Before (Anthropic 1P) + +```bash +curl -X POST https://api.anthropic.com/v1/messages \ + -H "x-api-key: sk-ant-api03-..." \ + -H "anthropic-version: 2023-06-01" \ + -H "content-type: application/json" \ + -d '{"model":"claude-sonnet-5","max_tokens":1024,"messages":[{"role":"user","content":"Hello"}]}' +``` + +### After (CPOA — API Key Mode) + +```bash +curl -X POST https://aws-external-anthropic.us-west-2.api.aws/v1/messages \ + -H "x-api-key: " \ + -H "anthropic-workspace-id: wrkspc_XXXXX" \ + -H "anthropic-version: 2023-06-01" \ + -H "content-type: application/json" \ + -d '{"model":"claude-sonnet-5","max_tokens":1024,"messages":[{"role":"user","content":"Hello"}]}' +``` + +### After (CPOA — SigV4 Mode) + +```bash +# Uses curl's --aws-sigv4 flag (requires curl 7.75+) +curl -X POST "https://aws-external-anthropic.us-west-2.api.aws/v1/messages" \ + --aws-sigv4 "aws:amz:us-west-2:aws-external-anthropic" \ + --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \ + -H "x-amz-security-token: $AWS_SESSION_TOKEN" \ + -H "anthropic-workspace-id: $ANTHROPIC_AWS_WORKSPACE_ID" \ + -H "anthropic-version: 2023-06-01" \ + -H "content-type: application/json" \ + -d '{"model":"claude-sonnet-5","max_tokens":1024,"messages":[{"role":"user","content":"Hello"}]}' +``` + +## Self-Hosted Sandbox Workers + +### Before (1P — Environment Key) + +```python +import asyncio +import os +from anthropic import AsyncAnthropic +from anthropic.lib.environments import EnvironmentWorker + +async def main() -> None: + environment_key = os.environ["ANTHROPIC_ENVIRONMENT_KEY"] # sk-ant-oat01-... + environment_id = os.environ["ANTHROPIC_ENVIRONMENT_ID"] + async with AsyncAnthropic(auth_token=environment_key) as client: + await EnvironmentWorker( + client, + environment_id=environment_id, + environment_key=environment_key, + workdir="/workspace", + ).run() + +asyncio.run(main()) +``` + +### After (CPOA — Worker with IAM Auth) + +```python +import asyncio +import os +from anthropic import AsyncAnthropic +from anthropic.lib.environments import EnvironmentWorker + +async def main() -> None: + # On CPOA: no environment key needed — authenticate via IAM + # Requires AnthropicSelfHostedEnvironmentAccess IAM policy + environment_id = os.environ["ANTHROPIC_ENVIRONMENT_ID"] + async with AsyncAnthropic() as client: # SigV4 via AWS credential chain + await EnvironmentWorker( + client, + environment_id=environment_id, + workdir="/workspace", + ).run() + +asyncio.run(main()) +``` + +### After (CPOA — Worker with API Key) + +```python +import asyncio +import os +from anthropic import AsyncAnthropic +from anthropic.lib.environments import EnvironmentWorker + +async def main() -> None: + # On CPOA: use API key generated in AWS Console + api_key = os.environ["ANTHROPIC_AWS_API_KEY"] + environment_id = os.environ["ANTHROPIC_ENVIRONMENT_ID"] + async with AsyncAnthropic(auth_token=api_key) as client: + await EnvironmentWorker( + client, + environment_id=environment_id, + workdir="/workspace", + ).run() + +asyncio.run(main()) +``` + +### Session Creation (CPOA) + +```python +from anthropic import AnthropicAWS + +client = AnthropicAWS(aws_region="us-west-2") + +# Create session targeting an agent in a self-hosted environment +session = client.beta.sessions.create( + agent="agent_XXXXX", + environment_id="env_XXXXX", +) + +# Send user message +client.beta.sessions.events.send( + session_id=session.id, + events=[{ + "type": "user.message", + "content": [{"type": "text", "text": "Analyze the data in /mnt/data/"}], + }], +) + +# Session transitions: idle → running (work queued for worker pickup) +``` + +## Environment Variables Mapping + +| 1P Variable | CPOA Variable | Notes | +|-------------|---------------|-------| +| `ANTHROPIC_API_KEY` | (constructor `api_key`) | CPOA keys generated in AWS Console | +| (none) | `ANTHROPIC_AWS_WORKSPACE_ID` | Required — SDK reads automatically | +| (none) | `AWS_REGION` | Required — no fallback default | +| `ANTHROPIC_BASE_URL` | (handled by SDK) | `AnthropicAWS`/`AnthropicAws` sets it from region | +| `ANTHROPIC_ENVIRONMENT_KEY` | (not used on CPOA) | 1P uses `sk-ant-oat01-...`; CPOA workers use IAM or API key instead | + +## Validation Checklist + +After conversion, verify: + +1. [ ] Using platform SDK client (`AnthropicAWS` / `AnthropicAws`), not base `Anthropic` with manual URL +2. [ ] `AWS_REGION` and `ANTHROPIC_AWS_WORKSPACE_ID` environment variables set +3. [ ] No references to `sk-ant-api03-` or `sk-ant-oat01-` keys (1P credentials) +4. [ ] Outbound web identity federation enabled (`aws iam enable-outbound-web-identity-federation`) +5. [ ] Correct IAM policy: `AnthropicInferenceAccess` for `/v1/messages`, `AnthropicSelfHostedEnvironmentAccess` for workers +6. [ ] `/v1/models` endpoint returns 200 (basic connectivity test) +7. [ ] Short-term token refresh logic in place if using generated tokens (no auto-refresh)