Capture Anthropic SDK calls — Messages and streaming — as Carbon events.
wrapAnthropicSdk instruments an existing Anthropic client. Your call sites stay the same; each request is captured as an LLM event with model, prompts, output, token usage, duration, and status.
import Anthropic from "@anthropic-ai/sdk";import { Carbon } from "@carbon-js/sdk";import { wrapAnthropicSdk } from "@carbon-js/sdk/ai";const carbon = new Carbon();const anthropic = wrapAnthropicSdk(new Anthropic(), carbon);const message = await anthropic.messages.create({ model: "claude-haiku-4-5", max_tokens: 1024, messages: [{ role: "user", content: "What is the capital of France?" }],});
wrapAnthropicSdk mutates the client you pass in and returns the same instance, typed to accept the optional carbon
field on requests. Wrap the client once at startup and share the wrapped instance.
Deploying to a serverless platform — Next.js, Vercel, AWS Lambda? Flush buffered events before the function exits so none are lost. See Serverless.
Add a carbon field to any request to set a trace ID, context identifiers, or custom properties. The SDK strips the field before the request reaches Anthropic.
The Anthropic Messages API does not execute tools. messages.create and messages.stream capture the LLM turn, including the tool_use blocks the model requests, but running those tools is your code’s job — so tool executions are always instrumented manually.Wrap each tool with wrapTool so its execution is captured. These methods don’t open a trace of their own, so create a trace ID with carbon.createTraceId() and pass it to both the model call and every tool call — that shared ID links the LLM event and its tool events into one trace:
const getWeather = carbon.wrapTool({ name: "get_weather", run: async ({ city }: { city: string }) => fetchWeather(city),});const traceId = carbon.createTraceId();const message = await anthropic.messages.create({ model: "claude-haiku-4-5", max_tokens: 1024, messages, tools: [/* get_weather definition */], carbon: { traceId },});// Run each tool the model requested — each wrapped call is captured on the same trace.for (const block of message.content) { if (block.type === "tool_use" && block.name === "get_weather") { await getWeather(block.input as { city: string }, { traceId }); }}