diff --git a/src/claude_agent_sdk/__init__.py b/src/claude_agent_sdk/__init__.py index 31f3df5f1..b92a147d1 100644 --- a/src/claude_agent_sdk/__init__.py +++ b/src/claude_agent_sdk/__init__.py @@ -126,6 +126,7 @@ SubagentStartHookInput, SubagentStartHookSpecificOutput, SubagentStopHookInput, + SubagentTokenUsageEvent, SystemMessage, TaskBudget, TaskNotificationMessage, @@ -560,6 +561,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> Any: "RateLimitStatus", "RateLimitType", "StreamEvent", + "SubagentTokenUsageEvent", "Message", "ClaudeAgentOptions", "TaskBudget", diff --git a/src/claude_agent_sdk/_internal/message_parser.py b/src/claude_agent_sdk/_internal/message_parser.py index b8aecfdb9..311fa0ff6 100644 --- a/src/claude_agent_sdk/_internal/message_parser.py +++ b/src/claude_agent_sdk/_internal/message_parser.py @@ -17,6 +17,7 @@ ServerToolResultBlock, ServerToolUseBlock, StreamEvent, + SubagentTokenUsageEvent, SystemMessage, TaskNotificationMessage, TaskProgressMessage, @@ -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. diff --git a/src/claude_agent_sdk/types.py b/src/claude_agent_sdk/types.py index 705648030..09990d9b0 100644 --- a/src/claude_agent_sdk/types.py +++ b/src/claude_agent_sdk/types.py @@ -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[ @@ -1320,6 +1348,7 @@ class HookEventMessage(SystemMessage): | ResultMessage | StreamEvent | RateLimitEvent + | SubagentTokenUsageEvent )