Skip to content
Merged
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
35 changes: 31 additions & 4 deletions python/packages/claude/agent_framework_claude/_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@
from agent_framework._types import normalize_tools
from agent_framework.exceptions import ServiceException, ServiceInitializationError
from claude_agent_sdk import (
ClaudeAgentOptions as SDKOptions,
)
from claude_agent_sdk import (
AssistantMessage,
ClaudeSDKClient,
ResultMessage,
SdkMcpTool,
create_sdk_mcp_server,
)
from claude_agent_sdk.types import StreamEvent
from claude_agent_sdk import (
ClaudeAgentOptions as SDKOptions,
)
from claude_agent_sdk.types import StreamEvent, TextBlock
from pydantic import ValidationError

from ._settings import ClaudeAgentSettings
Expand Down Expand Up @@ -639,7 +640,33 @@ async def run_stream(
contents=[Content.from_text_reasoning(text=thinking, raw_representation=message)],
raw_representation=message,
)
elif isinstance(message, AssistantMessage):
# Handle AssistantMessage - check for API errors
# Note: In streaming mode, the content was already yielded via StreamEvent,
# so we only check for errors here, not re-emit content.
if message.error:
# Map error types to descriptive messages
error_messages = {
"authentication_failed": "Authentication failed with Claude API",
"billing_error": "Billing error with Claude API",
"rate_limit": "Rate limit exceeded for Claude API",
"invalid_request": "Invalid request to Claude API",
"server_error": "Claude API server error",
"unknown": "Unknown error from Claude API",
}
error_msg = error_messages.get(message.error, f"Claude API error: {message.error}")
# Extract any error details from content blocks
if message.content:
for block in message.content:
if isinstance(block, TextBlock):
error_msg = f"{error_msg}: {block.text}"
break
raise ServiceException(error_msg)
elif isinstance(message, ResultMessage):
# Check for errors in result message
if message.is_error:
error_msg = message.result or "Unknown error from Claude API"
raise ServiceException(f"Claude API error: {error_msg}")
session_id = message.session_id

# Update thread with session ID
Expand Down
55 changes: 55 additions & 0 deletions python/packages/claude/tests/test_claude_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,61 @@ async def test_run_stream_yields_updates(self) -> None:
assert updates[0].text == "Streaming "
assert updates[1].text == "response"

async def test_run_stream_raises_on_assistant_message_error(self) -> None:
"""Test run_stream raises ServiceException when AssistantMessage has an error."""
from agent_framework.exceptions import ServiceException
from claude_agent_sdk import AssistantMessage, ResultMessage, TextBlock

messages = [
AssistantMessage(
content=[TextBlock(text="Error details from API")],
model="claude-sonnet",
error="invalid_request",
),
ResultMessage(
subtype="success",
duration_ms=100,
duration_api_ms=50,
is_error=False,
num_turns=1,
session_id="error-session",
),
]
mock_client = self._create_mock_client(messages)

with patch("agent_framework_claude._agent.ClaudeSDKClient", return_value=mock_client):
agent = ClaudeAgent()
with pytest.raises(ServiceException) as exc_info:
async for _ in agent.run_stream("Hello"):
pass
assert "Invalid request to Claude API" in str(exc_info.value)
assert "Error details from API" in str(exc_info.value)

async def test_run_stream_raises_on_result_message_error(self) -> None:
"""Test run_stream raises ServiceException when ResultMessage.is_error is True."""
from agent_framework.exceptions import ServiceException
from claude_agent_sdk import ResultMessage

messages = [
ResultMessage(
subtype="error",
duration_ms=100,
duration_api_ms=50,
is_error=True,
num_turns=0,
session_id="error-session",
result="Model 'claude-sonnet-4.5' not found",
),
]
mock_client = self._create_mock_client(messages)

with patch("agent_framework_claude._agent.ClaudeSDKClient", return_value=mock_client):
agent = ClaudeAgent()
with pytest.raises(ServiceException) as exc_info:
async for _ in agent.run_stream("Hello"):
pass
assert "Model 'claude-sonnet-4.5' not found" in str(exc_info.value)


# region Test ClaudeAgent Session Management

Expand Down
28 changes: 14 additions & 14 deletions python/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading