mirror of
https://github.com/anthropics/claude-code-sdk-python.git
synced 2025-07-07 22:55:01 +00:00

The CLI only sends total_cost_usd, not cost_usd. This PR: - Removes the non-existent cost_usd field from ResultMessage - Makes total_cost_usd optional since it may be missing - Handles GeneratorExit gracefully in subprocess transport - Removes unnecessary cancel scope call Fixes the error users were experiencing: - KeyError: 'cost_usd' - Field required [type=missing, input_value=...] The anyio cancel scope error still appears in logs but doesn't affect functionality. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
97 lines
3.1 KiB
Python
97 lines
3.1 KiB
Python
"""Internal client implementation."""
|
|
|
|
from collections.abc import AsyncIterator
|
|
from typing import Any
|
|
|
|
from ..types import (
|
|
AssistantMessage,
|
|
ClaudeCodeOptions,
|
|
ContentBlock,
|
|
Message,
|
|
ResultMessage,
|
|
SystemMessage,
|
|
TextBlock,
|
|
ToolResultBlock,
|
|
ToolUseBlock,
|
|
UserMessage,
|
|
)
|
|
from .transport.subprocess_cli import SubprocessCLITransport
|
|
|
|
|
|
class InternalClient:
|
|
"""Internal client implementation."""
|
|
|
|
def __init__(self) -> None:
|
|
"""Initialize the internal client."""
|
|
|
|
async def process_query(
|
|
self, prompt: str, options: ClaudeCodeOptions
|
|
) -> AsyncIterator[Message]:
|
|
"""Process a query through transport."""
|
|
|
|
transport = SubprocessCLITransport(prompt=prompt, options=options)
|
|
|
|
try:
|
|
await transport.connect()
|
|
|
|
async for data in transport.receive_messages():
|
|
message = self._parse_message(data)
|
|
if message:
|
|
yield message
|
|
|
|
finally:
|
|
await transport.disconnect()
|
|
|
|
def _parse_message(self, data: dict[str, Any]) -> Message | None:
|
|
"""Parse message from CLI output, trusting the structure."""
|
|
|
|
match data["type"]:
|
|
case "user":
|
|
return UserMessage(content=data["message"]["content"])
|
|
|
|
case "assistant":
|
|
content_blocks: list[ContentBlock] = []
|
|
for block in data["message"]["content"]:
|
|
match block["type"]:
|
|
case "text":
|
|
content_blocks.append(TextBlock(text=block["text"]))
|
|
case "tool_use":
|
|
content_blocks.append(
|
|
ToolUseBlock(
|
|
id=block["id"],
|
|
name=block["name"],
|
|
input=block["input"],
|
|
)
|
|
)
|
|
case "tool_result":
|
|
content_blocks.append(
|
|
ToolResultBlock(
|
|
tool_use_id=block["tool_use_id"],
|
|
content=block.get("content"),
|
|
is_error=block.get("is_error"),
|
|
)
|
|
)
|
|
|
|
return AssistantMessage(content=content_blocks)
|
|
|
|
case "system":
|
|
return SystemMessage(
|
|
subtype=data["subtype"],
|
|
data=data,
|
|
)
|
|
|
|
case "result":
|
|
return ResultMessage(
|
|
subtype=data["subtype"],
|
|
duration_ms=data["duration_ms"],
|
|
duration_api_ms=data["duration_api_ms"],
|
|
is_error=data["is_error"],
|
|
num_turns=data["num_turns"],
|
|
session_id=data["session_id"],
|
|
total_cost_usd=data.get("total_cost_usd"),
|
|
usage=data.get("usage"),
|
|
result=data.get("result"),
|
|
)
|
|
|
|
case _:
|
|
return None
|