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 runtime wiring inside the function
(agent options, tools, API keys). Put unsafe calls behind replay-mockable marked
spans; use a no-op only for a replay-only callback slot with no recorded call to
mock.
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.
Trace metadata
This integration exports its own trace under the same canonical trace id as the traced function, and that export carries metadata of its own. Metadata the caller recorded, throughseedTrace / seed_trace or setMetadata / set_metadata, is merged onto that export and takes precedence per key, so a seeded case’s provenance survives and reaches a later replay’s adaptInputs / adapt_inputs hook as ctx.metadata / ctx["metadata"].
Keys the integration set that the caller did not are kept, so ctx.metadata can carry keys the caller never wrote. A key set on both sides to different values keeps the caller’s value and warns once per trace.
The merge needs both sides on the same canonical trace id, which is what running the integration nested inside a traced function gives them. A processor-only setup, with no enclosing traced function, resolves its own trace id and has no caller metadata to merge. An export that lands after the traced function returned, as a streamed run’s does, still merges. Requires the TypeScript or Python SDK v0.52.3 or later.