Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/claude_agent_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
SubagentStartHookInput,
SubagentStartHookSpecificOutput,
SubagentStopHookInput,
SubagentTokenUsageEvent,
SystemMessage,
TaskBudget,
TaskNotificationMessage,
Expand Down Expand Up @@ -560,6 +561,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> Any:
"RateLimitStatus",
"RateLimitType",
"StreamEvent",
"SubagentTokenUsageEvent",
"Message",
"ClaudeAgentOptions",
"TaskBudget",
Expand Down
19 changes: 19 additions & 0 deletions src/claude_agent_sdk/_internal/message_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
ServerToolResultBlock,
ServerToolUseBlock,
StreamEvent,
SubagentTokenUsageEvent,
SystemMessage,
TaskNotificationMessage,
TaskProgressMessage,
Expand Down Expand Up @@ -336,6 +337,24 @@ def parse_message(data: dict[str, Any]) -> Message | None:
f"Missing required field in rate_limit_event message: {e}", data
) from e

case "subagent_token_usage":
try:
usage = data.get("usage") or {}
return SubagentTokenUsageEvent(
input_tokens=usage.get("input_tokens", 0),
output_tokens=usage.get("output_tokens", 0),
cache_read_tokens=usage.get("cache_read_input_tokens", 0),
cache_creation_tokens=usage.get("cache_creation_input_tokens", 0),
agent_id=data.get("agent_id"),
session_id=data["session_id"],
uuid=data["uuid"],
timestamp=data.get("timestamp"),
)
except KeyError as e:
raise MessageParseError(
f"Missing required field in subagent_token_usage message: {e}", data
) from e

case _:
# Forward-compatible: skip unrecognized message types so newer
# CLI versions don't crash older SDK versions.
Expand Down
29 changes: 29 additions & 0 deletions src/claude_agent_sdk/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,34 @@ class StreamEvent:
parent_tool_use_id: str | None = None


@dataclass
class SubagentTokenUsageEvent:
"""Token usage event emitted mid-stream per subagent during execution.

Enables real-time cost attribution and live budget enforcement in
multi-agent systems without waiting for the final ``ResultMessage``.

Attributes:
input_tokens: Number of input tokens consumed by this subagent turn.
output_tokens: Number of output tokens generated by this subagent turn.
cache_read_tokens: Tokens read from the prompt cache (reduces cost).
cache_creation_tokens: Tokens written to the prompt cache.
agent_id: Subagent identifier. None on the main thread.
session_id: Session identifier the event belongs to.
uuid: Unique identifier for this event.
timestamp: ISO-8601 timestamp string when the usage was recorded.
"""

input_tokens: int
output_tokens: int
session_id: str
uuid: str
cache_read_tokens: int = 0
cache_creation_tokens: int = 0
agent_id: str | None = None
timestamp: str | None = None


# Rate limit types — see https://docs.claude.com/en/docs/claude-code/rate-limits
RateLimitStatus = Literal["allowed", "allowed_warning", "rejected"]
RateLimitType = Literal[
Expand Down Expand Up @@ -1320,6 +1348,7 @@ class HookEventMessage(SystemMessage):
| ResultMessage
| StreamEvent
| RateLimitEvent
| SubagentTokenUsageEvent
)


Expand Down