Skip to content
Draft
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
13 changes: 13 additions & 0 deletions src/claude_agent_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@
from typing import Any, Generic, TypeVar

from ._errors import (
APIError,
AuthenticationError,
BillingError,
ClaudeSDKError,
CLIConnectionError,
CLIJSONDecodeError,
CLINotFoundError,
InvalidRequestError,
ProcessError,
RateLimitError,
ServerError,
)
from ._internal.transport import Transport
from ._version import __version__
Expand Down Expand Up @@ -362,4 +368,11 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> Any:
"CLINotFoundError",
"ProcessError",
"CLIJSONDecodeError",
# API Errors
"APIError",
"AuthenticationError",
"BillingError",
"RateLimitError",
"InvalidRequestError",
"ServerError",
]
38 changes: 38 additions & 0 deletions src/claude_agent_sdk/_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,41 @@ class MessageParseError(ClaudeSDKError):
def __init__(self, message: str, data: dict[str, Any] | None = None):
self.data = data
super().__init__(message)


class APIError(ClaudeSDKError):
"""Base exception for API errors from the Anthropic API.

Raised when the API returns an error (400, 401, 429, 529, etc.) instead of
being silently returned as a text message.
"""

def __init__(
self,
message: str,
error_type: str | None = None,
error_text: str | None = None,
):
self.error_type = error_type
self.error_text = error_text
super().__init__(message)


class AuthenticationError(APIError):
"""Raised when API authentication fails (401)."""


class BillingError(APIError):
"""Raised when there's a billing issue with the API account."""


class RateLimitError(APIError):
"""Raised when API rate limits are exceeded (429)."""


class InvalidRequestError(APIError):
"""Raised when the API request is invalid (400)."""


class ServerError(APIError):
"""Raised when the API server encounters an error (500, 529)."""
58 changes: 57 additions & 1 deletion src/claude_agent_sdk/_internal/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,70 @@
from dataclasses import replace
from typing import Any

from .._errors import (
APIError,
AuthenticationError,
BillingError,
InvalidRequestError,
RateLimitError,
ServerError,
)
from ..types import (
AssistantMessage,
ClaudeAgentOptions,
HookEvent,
HookMatcher,
Message,
TextBlock,
)
from .message_parser import parse_message
from .query import Query
from .transport import Transport
from .transport.subprocess_cli import SubprocessCLITransport

# Map error types to exception classes
_ERROR_TYPE_TO_EXCEPTION: dict[str, type[APIError]] = {
"authentication_failed": AuthenticationError,
"billing_error": BillingError,
"rate_limit": RateLimitError,
"invalid_request": InvalidRequestError,
"server_error": ServerError,
"unknown": APIError,
}


def _raise_if_api_error(message: Message) -> None:
"""Check if a message contains an API error and raise the appropriate exception.

Args:
message: The parsed message to check

Raises:
APIError: If the message contains an API error
"""
if isinstance(message, AssistantMessage) and message.error:
# Extract error text from message content
error_text = None
if message.content:
for block in message.content:
if isinstance(block, TextBlock):
error_text = block.text
break

# Get the appropriate exception class
exc_class = _ERROR_TYPE_TO_EXCEPTION.get(message.error, APIError)

# Build error message
error_message = f"API error ({message.error})"
if error_text:
error_message = f"{error_message}: {error_text}"

raise exc_class(
message=error_message,
error_type=message.error,
error_text=error_text,
)


class InternalClient:
"""Internal client implementation."""
Expand Down Expand Up @@ -118,7 +171,10 @@ async def process_query(

# Yield parsed messages
async for data in query.receive_messages():
yield parse_message(data)
message = parse_message(data)
# Check for API errors and raise appropriate exceptions
_raise_if_api_error(message)
yield message

finally:
await query.close()
Loading
Loading