Supported Languages
Quick Start
What Gets Captured
The handler captures three types of spans:Token Usage
For LLM spans, token usage is extracted from the message stream:inputTokens- Input tokensoutputTokens- Output tokenscacheReadTokens- Cache read tokens (if applicable)cacheCreationTokens- Cache creation tokens (if applicable)model- Model name
TypeScript
Installation
Method Signature
traceFunctionKey(string, required) - Groups all traces from this handler under one key in Bitfab
BitfabClaudeAgentHandler instance with methods for instrumenting options and wrapping streams.
Handler Methods
instrumentOptions(options)
Injects Bitfab hooks into the SDK options object. Mutates the options in-place and returns them.
- PreToolUse → Creates a
functionspan with the tool input - PostToolUse → Completes the span with the tool response
- PostToolUseFailure → Completes the span with an error
- SubagentStart → Creates an
agentspan - SubagentStop → Completes the agent span
wrapQuery(stream, opts?)
Wraps the query() async iterator to capture LLM turns from the message stream. Messages are yielded unchanged. Tool and subagent spans come from the hooks injected by instrumentOptions.
Pass { input } (the prompt) to record a replayable root agent span - see Replay.
llm span containing:
- Input: Full conversation history snapshot up to this turn
- Output: Assistant message content blocks
- Context: Model name, token usage
wrapResponse(stream)
Identical to wrapQuery - wraps any Claude Agent SDK message stream. Provided for naming symmetry with the Python SDK (whose ClaudeSDKClient.receiveResponse() it wraps). In TypeScript, prefer wrapQuery around query().
The TypeScript Claude Agent SDK exposes a single
query() entry point. There is no ClaudeSDKClient class - that exists only in the Python SDK.Usage
Nesting with Core Tracing
Wrapping the agent call inwithSpan with the same key records the call’s
arguments as a replayable root and nests every handler span underneath it (see
Replay).
Error Handling
- All hook callbacks are wrapped in try/catch - errors are silently ignored and return an empty object.
- Stream processing continues even if individual message capture fails.
- The handler never throws or affects the SDK’s execution.
Python
Installation
Method Signature
trace_function_key(str, required) - Groups all traces from this handler under one key in Bitfab
BitfabClaudeAgentHandler instance with methods for instrumenting options and wrapping streams.
Handler Methods
instrument_options(options)
Injects Bitfab hooks into the SDK options object. Returns the modified options.
wrap_response(stream, input=...)
Wraps the receive_response() async iterator to capture LLM turns. Pass input (the prompt) to record a replayable root agent span - see Replay:
wrap_query(stream, input=...)
Same, for the query() API:
Usage
Nesting with Core Tracing
Bind the key once withget_function so the root and the handler share it (no repeated string to keep in sync):
@bitfab.span("my-pipeline") / bitfab.get_claude_agent_handler("my-pipeline") forms work too; get_function just keys both from one place.
Error Handling
- All hook callbacks are wrapped in try/except - errors are logged at DEBUG level and return an empty dict.
- Stream processing continues even if individual message capture fails.
- The handler never raises or affects the SDK’s execution.
Replay
Pass the prompt asinput to the wrap call and the handler records a root
agent span carrying it, with every LLM / tool / subagent span nested
underneath. That root is the replayable unit: replay(key, fn) re-feeds each
historical prompt to a callable that re-issues the query. No @bitfab.span /
withSpan wrapper is required.
The prompt is not present anywhere in the message stream, so the handler
cannot recover it on its own - you must pass it as
input. Without input
the run still traces, but it has no replayable root.input serializable (a prompt string, a small params object) - it is the
recorded root input replay re-feeds. Rebuild any runtime environment inside the
function (agent options, tools, API keys); use no-op substitutes for
side-effectful wiring.
If you already wrap the agent call in a @bitfab.span / withSpan with the
same key (for example, to also capture surrounding work), that outer span
is the replayable root and the handler nests under it automatically - passing
input is then unnecessary. Full details: Replaying functions in the
Python SDK and TypeScript SDK pages.