clawd-code-sdk 0.3.8__tar.gz → 0.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/.gitignore +1 -1
  2. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/PKG-INFO +2 -1
  3. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/pyproject.toml +2 -1
  4. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/__init__.py +26 -4
  5. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/_internal/query.py +1 -35
  6. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/_internal/transport/subprocess_cli.py +9 -7
  7. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/client.py +50 -47
  8. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/__init__.py +22 -14
  9. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/base.py +7 -7
  10. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/messages.py +198 -27
  11. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/options.py +10 -21
  12. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/query.py +9 -8
  13. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/session.py +3 -1
  14. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_agents_and_settings.py +2 -2
  15. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_include_partial_messages.py +11 -25
  16. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_slash_commands.py +1 -1
  17. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_stderr_callback.py +2 -2
  18. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_structured_output.py +4 -4
  19. clawd_code_sdk-0.4.1/tests/mock_claude_server.py +92 -0
  20. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_client.py +6 -6
  21. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_integration.py +1 -1
  22. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_streaming_client.py +10 -50
  23. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_subprocess_buffering.py +8 -8
  24. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_transport.py +56 -66
  25. clawd_code_sdk-0.3.8/tests/mock_claude_server.py +0 -92
  26. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/LICENSE +0 -0
  27. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/README.md +0 -0
  28. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/_bundled/.gitignore +0 -0
  29. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/_errors.py +0 -0
  30. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/_internal/__init__.py +0 -0
  31. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/_internal/message_parser.py +0 -0
  32. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/_internal/transport/__init__.py +0 -0
  33. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/_version.py +0 -0
  34. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/anthropic_types.py +0 -0
  35. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/list_sessions.py +0 -0
  36. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/mcp_utils.py +0 -0
  37. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/agents.py +0 -0
  38. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/content_blocks.py +0 -0
  39. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/control.py +0 -0
  40. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/hooks.py +0 -0
  41. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/input_types.py +0 -0
  42. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/mcp.py +0 -0
  43. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/output_types.py +0 -0
  44. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/permissions.py +0 -0
  45. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/sandbox.py +0 -0
  46. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/server_info.py +0 -0
  47. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/py.typed +0 -0
  48. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/storage/ARCHITECTURE.md +0 -0
  49. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/storage/__init__.py +0 -0
  50. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/storage/helpers.py +0 -0
  51. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/storage/models.py +0 -0
  52. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/storage/replay.py +0 -0
  53. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/usage.py +0 -0
  54. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/__init__.py +0 -0
  55. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/conftest.py +0 -0
  56. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/__init__.py +0 -0
  57. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_dynamic_control.py +0 -0
  58. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_hook_events.py +0 -0
  59. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_hooks.py +0 -0
  60. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_mcp_tools.py +0 -0
  61. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_sdk_mcp_tools.py +0 -0
  62. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_subagent_invocation.py +0 -0
  63. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_tool_permissions.py +0 -0
  64. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/mcp_server.py +0 -0
  65. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_changelog.py +0 -0
  66. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_errors.py +0 -0
  67. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_image.png +0 -0
  68. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_message_parser.py +0 -0
  69. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_sdk_mcp_integration.py +0 -0
  70. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_session.py +0 -0
  71. {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_tool_callbacks.py +0 -0
@@ -27,7 +27,7 @@ ENV/
27
27
  env/
28
28
  .venv
29
29
  uv.lock
30
-
30
+ .env
31
31
  # IDEs
32
32
  .vscode/
33
33
  .idea/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clawd-code-sdk
3
- Version: 0.3.8
3
+ Version: 0.4.1
4
4
  Summary: Python SDK for Claude Code
5
5
  Project-URL: Documentation, https://github.com/phil65/claude-agent-sdk-python
6
6
  Project-URL: Homepage, https://github.com/phil65/claude-agent-sdk-python
@@ -21,6 +21,7 @@ Requires-Dist: anthropic>=0.77.0
21
21
  Requires-Dist: anyenv>=2.0.15
22
22
  Requires-Dist: anyio>=4.0.0
23
23
  Requires-Dist: mcp>=0.1.0
24
+ Requires-Dist: python-dotenv>=1.2.1
24
25
  Provides-Extra: dev
25
26
  Requires-Dist: anyio[trio]>=4.0.0; extra == 'dev'
26
27
  Requires-Dist: mypy>=1.0.0; extra == 'dev'
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "clawd-code-sdk"
3
- version = "0.3.8"
3
+ version = "0.4.1"
4
4
  description = "Python SDK for Claude Code"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -23,6 +23,7 @@ dependencies = [
23
23
  "anyenv>=2.0.15",
24
24
  "anyio>=4.0.0",
25
25
  "mcp>=0.1.0",
26
+ "python-dotenv>=1.2.1",
26
27
  ]
27
28
 
28
29
  [project.urls]
@@ -2,6 +2,10 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from dotenv import load_dotenv
6
+
7
+ load_dotenv()
8
+
5
9
 
6
10
  from ._errors import (
7
11
  APIError,
@@ -22,6 +26,7 @@ from .anthropic_types import ToolResultContentBlock
22
26
  from .client import ClaudeSDKClient
23
27
  from .list_sessions import list_sessions
24
28
  from .models import (
29
+ AccumulatedUsage,
25
30
  AgentDefinition,
26
31
  AgentHookHandler,
27
32
  AgentHooksConfig,
@@ -85,9 +90,17 @@ from .models import (
85
90
  ToolResultBlock,
86
91
  ToolUseBlock,
87
92
  UserMessage,
88
- UserPromptMessage,
89
- UserPromptMessageContent,
93
+ DocumentMediaType,
94
+ ImageMediaType,
95
+ PlainTextMediaType,
96
+ UserDocumentPrompt,
97
+ UserDocumentURLPrompt,
98
+ UserImagePrompt,
99
+ UserImageURLPrompt,
100
+ UserPlainTextDocumentPrompt,
101
+ UserPrompt,
90
102
  UserPromptSubmitHookInput,
103
+ UserTextPrompt,
91
104
  FromPR,
92
105
  NewSession,
93
106
  ResumeSession,
@@ -113,6 +126,7 @@ __cli_version__ = "2.1.11"
113
126
  __all__ = [
114
127
  # API Errors
115
128
  "APIError",
129
+ "AccumulatedUsage",
116
130
  # Agent support
117
131
  "AgentDefinition",
118
132
  # Hook support
@@ -138,6 +152,7 @@ __all__ = [
138
152
  # Session config
139
153
  "ContinueLatest",
140
154
  "ConversationTurn",
155
+ "DocumentMediaType",
141
156
  "FromPR",
142
157
  "HookCallback",
143
158
  "HookContext",
@@ -146,6 +161,7 @@ __all__ = [
146
161
  "HookJSONOutput",
147
162
  "HookMatcher",
148
163
  "HookMatcherConfig",
164
+ "ImageMediaType",
149
165
  "InitSystemMessage",
150
166
  "InvalidRequestError",
151
167
  "ListSessionsOptions",
@@ -164,6 +180,7 @@ __all__ = [
164
180
  "PermissionResultAllow",
165
181
  "PermissionResultDeny",
166
182
  "PermissionUpdate",
183
+ "PlainTextMediaType",
167
184
  "PostToolUseFailureHookInput",
168
185
  "PostToolUseFailureHookSpecificOutput",
169
186
  "PostToolUseHookInput",
@@ -212,10 +229,15 @@ __all__ = [
212
229
  "ToolUseBlock",
213
230
  # Transport
214
231
  "Transport",
232
+ "UserDocumentPrompt",
233
+ "UserDocumentURLPrompt",
234
+ "UserImagePrompt",
235
+ "UserImageURLPrompt",
215
236
  "UserMessage",
216
- "UserPromptMessage",
217
- "UserPromptMessageContent",
237
+ "UserPlainTextDocumentPrompt",
238
+ "UserPrompt",
218
239
  "UserPromptSubmitHookInput",
240
+ "UserTextPrompt",
219
241
  "__version__",
220
242
  # MCP Server Support
221
243
  "create_sdk_mcp_server",
@@ -33,7 +33,7 @@ from clawd_code_sdk.models.server_info import ClaudeCodeServerInfo
33
33
 
34
34
 
35
35
  if TYPE_CHECKING:
36
- from collections.abc import AsyncGenerator, AsyncIterable, AsyncIterator, Iterator
36
+ from collections.abc import AsyncGenerator, AsyncIterator, Iterator
37
37
 
38
38
  from anyio.abc import CancelScope, TaskGroup
39
39
  from mcp.server import Server as McpServer
@@ -45,7 +45,6 @@ if TYPE_CHECKING:
45
45
  from clawd_code_sdk.models.hooks import HookCallback, HookEvent, HookMatcher
46
46
  from clawd_code_sdk.models.input_types import AskUserQuestionInput
47
47
  from clawd_code_sdk.models.mcp import JSONRPCMessage, JSONRPCResponse, RequestId
48
- from clawd_code_sdk.models.messages import UserPromptMessage
49
48
  from clawd_code_sdk.models.permissions import CanUseTool, OnUserQuestion, PermissionResult
50
49
 
51
50
  logger = logging.getLogger(__name__)
@@ -473,39 +472,6 @@ class Query:
473
472
  req = {"subtype": "rewind_files", "user_message_id": user_message_id}
474
473
  return await self._send_control_request(req)
475
474
 
476
- async def stream_input(self, stream: AsyncIterable[UserPromptMessage]) -> None:
477
- """Stream input messages to transport.
478
-
479
- If SDK MCP servers or hooks are present, waits for the first result
480
- before closing stdin to allow bidirectional control protocol communication.
481
- """
482
- try:
483
- async for message in stream:
484
- if self._closed:
485
- break
486
- await self.transport.write(anyenv.dump_json(message) + "\n")
487
-
488
- # If we have SDK MCP servers or hooks that need bidirectional communication,
489
- # wait for first result before closing the channel
490
- if self.sdk_mcp_servers or self.hooks:
491
- logger.debug(
492
- "Waiting for first result before closing stdin "
493
- "(sdk_mcp_servers=%s, has_hooks=%s)",
494
- len(self.sdk_mcp_servers),
495
- bool(self.hooks),
496
- )
497
- try:
498
- with anyio.move_on_after(self._stream_close_timeout):
499
- await self._first_result_event.wait()
500
- logger.debug("Received first result, closing input stream")
501
- except Exception:
502
- logger.debug("Timed out waiting for first result, closing input stream")
503
-
504
- # After all messages sent (and result received if needed), end input
505
- await self.transport.end_input()
506
- except Exception as e:
507
- logger.debug("Error streaming input: %s", e)
508
-
509
475
  async def receive_messages(self) -> AsyncGenerator[dict[str, Any]]:
510
476
  """Receive SDK messages (not control messages)."""
511
477
  async for message in self._message_receive:
@@ -26,15 +26,19 @@ from clawd_code_sdk._errors import (
26
26
  )
27
27
  from clawd_code_sdk._internal.transport import Transport
28
28
  from clawd_code_sdk._version import __version__
29
+ from clawd_code_sdk.models.base import (
30
+ ThinkingConfigAdaptive,
31
+ ThinkingConfigDisabled,
32
+ ThinkingConfigEnabled,
33
+ )
29
34
 
30
35
 
31
36
  if TYPE_CHECKING:
32
- from collections.abc import AsyncIterable, AsyncIterator
37
+ from collections.abc import AsyncIterator
33
38
 
34
39
  from anyio.abc import Process
35
40
 
36
41
  from clawd_code_sdk.models import ClaudeAgentOptions
37
- from clawd_code_sdk.models.messages import UserPromptMessage
38
42
 
39
43
  logger = logging.getLogger(__name__)
40
44
 
@@ -56,10 +60,8 @@ class SubprocessCLITransport(Transport):
56
60
 
57
61
  def __init__(
58
62
  self,
59
- prompt: str | AsyncIterable[UserPromptMessage],
60
63
  options: ClaudeAgentOptions,
61
64
  ):
62
- self._prompt = prompt
63
65
  self._options = options
64
66
  self._cli_path = str(options.cli_path) if options.cli_path is not None else _find_cli()
65
67
  self._cwd = str(options.cwd) if options.cwd else None
@@ -207,11 +209,11 @@ class SubprocessCLITransport(Transport):
207
209
 
208
210
  # Resolve thinking config → --max-thinking-tokens
209
211
  match self._options.thinking:
210
- case {"type": "adaptive"}:
212
+ case ThinkingConfigAdaptive():
211
213
  cmd.extend(["--max-thinking-tokens", "32000"])
212
- case {"type": "enabled", "budget_tokens": budget}:
214
+ case ThinkingConfigEnabled(budget_tokens=budget):
213
215
  cmd.extend(["--max-thinking-tokens", str(budget)])
214
- case {"type": "disabled"}:
216
+ case ThinkingConfigDisabled():
215
217
  cmd.extend(["--max-thinking-tokens", "0"])
216
218
 
217
219
  if self._options.effort is not None:
@@ -2,7 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from collections.abc import AsyncIterable
6
5
  from dataclasses import replace
7
6
  import os
8
7
  from typing import TYPE_CHECKING, Any, Self
@@ -12,12 +11,17 @@ from pydantic import TypeAdapter
12
11
 
13
12
  from clawd_code_sdk._errors import CLIConnectionError
14
13
  from clawd_code_sdk.models import (
14
+ AccumulatedUsage,
15
15
  ClaudeAgentOptions,
16
16
  ResultMessage,
17
- UserPromptMessage,
18
17
  )
19
18
  from clawd_code_sdk.models.mcp import McpStatusResponse
20
- from clawd_code_sdk.models.messages import AssistantMessage
19
+ from clawd_code_sdk.models.messages import (
20
+ AssistantMessage,
21
+ ResultErrorMessage,
22
+ ResultSuccessMessage,
23
+ UserTextPrompt,
24
+ )
21
25
 
22
26
 
23
27
  if TYPE_CHECKING:
@@ -27,6 +31,7 @@ if TYPE_CHECKING:
27
31
  from clawd_code_sdk._internal.query import Query
28
32
  from clawd_code_sdk.models import Message, PermissionMode
29
33
  from clawd_code_sdk.models.mcp import McpServerConfig
34
+ from clawd_code_sdk.models.messages import UserPrompt
30
35
  from clawd_code_sdk.models.server_info import ClaudeCodeServerInfo
31
36
 
32
37
 
@@ -59,6 +64,10 @@ class ClaudeSDKClient:
59
64
  self._custom_transport = transport
60
65
  self._transport: Transport | None = None
61
66
  self._query: Query | None = None
67
+ self.session_usage: AccumulatedUsage = AccumulatedUsage()
68
+ """Cumulative token usage across all queries in this session."""
69
+ self.query_usage: AccumulatedUsage = AccumulatedUsage()
70
+ """Token usage for the current/last query only (reset on each query() call)."""
62
71
  os.environ["CLAUDE_CODE_ENTRYPOINT"] = "sdk-py-client"
63
72
 
64
73
  def _ensure_connected(self) -> Query:
@@ -67,38 +76,22 @@ class ClaudeSDKClient:
67
76
  raise CLIConnectionError("Not connected. Call connect() first.")
68
77
  return self._query
69
78
 
70
- async def connect(self, prompt: str | AsyncIterable[UserPromptMessage] | None = None) -> None:
71
- """Connect to Claude with a prompt or message stream."""
79
+ async def connect(self) -> None:
80
+ """Connect to Claude Code CLI and initialize the session."""
72
81
  from clawd_code_sdk._internal.query import Query
73
82
  from clawd_code_sdk._internal.transport.subprocess_cli import SubprocessCLITransport
74
83
 
75
- # Auto-connect with empty async iterable if no prompt is provided
76
- async def _empty_stream() -> AsyncIterator[UserPromptMessage]:
77
- # Never yields, but indicates that this function is an iterator and
78
- # keeps the connection open.
79
- # This yield is never reached but makes this an async generator
80
- return
81
- yield {} # type: ignore[unreachable]
82
-
83
- actual_prompt = _empty_stream() if prompt is None else prompt
84
84
  # Validate and configure permission settings (matching TypeScript SDK logic)
85
85
  self.options.validate()
86
86
 
87
87
  if self.options.can_use_tool:
88
- # canUseTool callback requires streaming mode (AsyncIterable prompt)
89
- if isinstance(prompt, str):
90
- raise ValueError(
91
- "can_use_tool callback requires streaming mode. "
92
- "Please provide prompt as an AsyncIterable instead of a string."
93
- )
94
-
95
88
  # Automatically set permission_prompt_tool_name to "stdio" for control protocol
96
89
  options = replace(self.options, permission_prompt_tool_name="stdio")
97
90
  else:
98
91
  options = self.options
99
92
 
100
93
  # Use provided custom transport or create subprocess transport
101
- tp = self._custom_transport or SubprocessCLITransport(prompt=actual_prompt, options=options)
94
+ tp = self._custom_transport or SubprocessCLITransport(options=options)
102
95
  self._transport = tp
103
96
  await self._transport.connect()
104
97
  # Extract SDK MCP servers from options
@@ -151,12 +144,6 @@ class ClaudeSDKClient:
151
144
  # Start reading messages and initialize
152
145
  await self._query.start()
153
146
  await self._query.initialize()
154
- # Send the initial prompt
155
- match prompt:
156
- case str() as text:
157
- await self.query(text)
158
- case AsyncIterable() if self._query._tg:
159
- self._query._tg.start_soon(self._query.stream_input, prompt)
160
147
 
161
148
  async def receive_messages(self) -> AsyncIterator[Message]:
162
149
  """Receive all messages from Claude."""
@@ -165,34 +152,50 @@ class ClaudeSDKClient:
165
152
  query = self._ensure_connected()
166
153
  async for data in query.receive_messages():
167
154
  message = parse_message(data)
168
- if isinstance(message, AssistantMessage):
169
- message.raise_if_api_error()
155
+ match message:
156
+ case AssistantMessage():
157
+ message.raise_if_api_error()
158
+ case ResultSuccessMessage() | ResultErrorMessage():
159
+ self.query_usage.accumulate(message.usage)
160
+ self.session_usage.accumulate(message.usage)
170
161
  yield message
171
162
 
172
163
  async def query(
173
164
  self,
174
- prompt: str | AsyncIterable[UserPromptMessage],
165
+ *prompts: str | UserPrompt,
175
166
  session_id: str = "default",
167
+ parent_tool_use_id: str | None = None,
176
168
  ) -> None:
177
- """Send a new request in streaming mode."""
169
+ """Send a new user message with one or more content blocks.
170
+
171
+ Args:
172
+ *prompts: One or more content blocks. Strings are converted to
173
+ UserTextPrompt automatically. Pass multiple to combine, e.g.
174
+ ``query(image_prompt, "What's in this image?")``.
175
+ session_id: Session identifier for the message.
176
+ parent_tool_use_id: If responding to a tool use, the tool_use block ID.
177
+ """
178
+ self.query_usage.reset()
178
179
  self._ensure_connected()
179
180
  if not self._transport:
180
181
  raise CLIConnectionError("Not connected. Call connect() first.")
181
- # Handle string prompts
182
- if isinstance(prompt, str):
183
- message = UserPromptMessage(
184
- type="user",
185
- message={"role": "user", "content": prompt},
186
- parent_tool_use_id=None,
187
- session_id=session_id,
188
- )
189
- await self._transport.write(anyenv.dump_json(message) + "\n")
182
+ if not prompts:
183
+ return
184
+ # Collect content blocks
185
+ blocks = [UserTextPrompt(text=p) if isinstance(p, str) else p for p in prompts]
186
+ # Single text block → plain string, otherwise list of content block dicts
187
+ message_content: str | list[dict[str, Any]]
188
+ if len(blocks) == 1 and isinstance(blocks[0], UserTextPrompt):
189
+ message_content = blocks[0].text
190
190
  else:
191
- # Handle AsyncIterable prompts - stream them
192
- async for msg in prompt:
193
- # Ensure session_id is set on each message
194
- msg.setdefault("session_id", session_id)
195
- await self._transport.write(anyenv.dump_json(msg) + "\n")
191
+ message_content = [b.to_content_block() for b in blocks]
192
+ wire_message = {
193
+ "type": "user",
194
+ "message": {"role": "user", "content": message_content},
195
+ "parent_tool_use_id": parent_tool_use_id,
196
+ "session_id": session_id,
197
+ }
198
+ await self._transport.write(anyenv.dump_json(wire_message) + "\n")
196
199
 
197
200
  async def interrupt(self) -> None:
198
201
  """Send interrupt signal (only works with streaming mode)."""
@@ -390,7 +393,7 @@ if __name__ == "__main__":
390
393
  os.environ["ANTHROPIC_API_KEY"] = ""
391
394
 
392
395
  async def main() -> None:
393
- opts = ClaudeAgentOptions(thinking=ThinkingConfigAdaptive(type="adaptive"))
396
+ opts = ClaudeAgentOptions(thinking=ThinkingConfigAdaptive())
394
397
  client = ClaudeSDKClient(opts)
395
398
  await client.connect()
396
399
  await client.query("ultrathink")
@@ -210,6 +210,7 @@ from .content_blocks import (
210
210
  )
211
211
 
212
212
  from .messages import (
213
+ AccumulatedUsage,
213
214
  AssistantMessage,
214
215
  AssistantMessageError,
215
216
  BaseSystemMessage,
@@ -240,8 +241,16 @@ from .messages import (
240
241
  TriggerMetadata,
241
242
  Usage,
242
243
  UserMessage,
243
- UserPromptMessage,
244
- UserPromptMessageContent,
244
+ DocumentMediaType,
245
+ ImageMediaType,
246
+ PlainTextMediaType,
247
+ UserDocumentPrompt,
248
+ UserDocumentURLPrompt,
249
+ UserImagePrompt,
250
+ UserImageURLPrompt,
251
+ UserPlainTextDocumentPrompt,
252
+ UserPrompt,
253
+ UserTextPrompt,
245
254
  system_message_adapter,
246
255
  )
247
256
  from .options import ClaudeAgentOptions
@@ -272,14 +281,12 @@ __all__ = [
272
281
  # mcp
273
282
  "JSONRPC_VERSION",
274
283
  "TOOL_INPUT_TYPES",
275
- # output_types (actual tool_use_result wire format)
276
284
  "TOOL_USE_RESULT_TYPES",
285
+ "AccumulatedUsage",
277
286
  "AgentAsyncLaunchedOutput",
278
287
  "AgentCacheCreation",
279
288
  "AgentCompletedOutput",
280
- # agents
281
289
  "AgentDefinition",
282
- # hooks
283
290
  "AgentHookHandler",
284
291
  "AgentHooksConfig",
285
292
  "AgentInput",
@@ -288,14 +295,12 @@ __all__ = [
288
295
  "AgentOutputUsage",
289
296
  "AgentServerToolUse",
290
297
  "AgentSubAgentEnteredOutput",
291
- # base
292
298
  "ApiKeySource",
293
299
  "AskUserQuestion",
294
300
  "AskUserQuestionInput",
295
301
  "AskUserQuestionItem",
296
302
  "AskUserQuestionOption",
297
303
  "AskUserQuestionOutput",
298
- # messages
299
304
  "AssistantMessage",
300
305
  "AssistantMessageError",
301
306
  "AsyncHookJSONOutput",
@@ -308,21 +313,18 @@ __all__ = [
308
313
  "BashOutput",
309
314
  "BashOutputInput",
310
315
  "BashOutputOutput",
311
- # backwards-compat aliases (old tool_use_results.py names)
312
316
  "BashToolUseResult",
313
- # permissions
314
317
  "CanUseTool",
315
- # options
316
318
  "ClaudeAgentOptions",
317
319
  "CommandHookHandler",
318
320
  "CompactBoundarySystemMessage",
319
321
  "ConfigOutput",
320
322
  "ContentBlock",
321
323
  "ContinueLatest",
322
- # control
323
324
  "ControlErrorResponse",
324
325
  "ControlRequestUnion",
325
326
  "ControlResponse",
327
+ "DocumentMediaType",
326
328
  "EditOutput",
327
329
  "EditToolUseResult",
328
330
  "EnterPlanModeInput",
@@ -352,6 +354,7 @@ __all__ = [
352
354
  "HookResponseSystemMessage",
353
355
  "HookSpecificOutput",
354
356
  "HookStartedSystemMessage",
357
+ "ImageMediaType",
355
358
  "InitSystemMessage",
356
359
  "JSONRPCError",
357
360
  "JSONRPCErrorResponse",
@@ -396,6 +399,7 @@ __all__ = [
396
399
  "PermissionRuleValue",
397
400
  "PermissionUpdate",
398
401
  "PermissionUpdateDestination",
402
+ "PlainTextMediaType",
399
403
  "PostToolUseFailureHookInput",
400
404
  "PostToolUseFailureHookSpecificOutput",
401
405
  "PostToolUseHookInput",
@@ -438,7 +442,6 @@ __all__ = [
438
442
  "SDKHookCallbackRequest",
439
443
  "SDKPermissionDenial",
440
444
  "SDKSessionInfo",
441
- # sandbox
442
445
  "SandboxIgnoreViolations",
443
446
  "SandboxNetworkConfig",
444
447
  "SandboxSettings",
@@ -496,11 +499,16 @@ __all__ = [
496
499
  "UnsubscribeMcpResourceOutput",
497
500
  "UnsubscribePollingOutput",
498
501
  "Usage",
502
+ "UserDocumentPrompt",
503
+ "UserDocumentURLPrompt",
504
+ "UserImagePrompt",
505
+ "UserImageURLPrompt",
499
506
  "UserMessage",
500
- "UserPromptMessage",
501
- "UserPromptMessageContent",
507
+ "UserPlainTextDocumentPrompt",
508
+ "UserPrompt",
502
509
  "UserPromptSubmitHookInput",
503
510
  "UserPromptSubmitHookSpecificOutput",
511
+ "UserTextPrompt",
504
512
  "WebFetchInput",
505
513
  "WebFetchOutput",
506
514
  "WebSearchHit",
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import sys
6
- from typing import Literal, TypedDict
6
+ from typing import Literal
7
7
 
8
8
  from pydantic import BaseModel, ConfigDict
9
9
  from pydantic.alias_generators import to_camel
@@ -70,23 +70,23 @@ class ClaudeCodeBaseModel(BaseModel):
70
70
 
71
71
 
72
72
  # Thinking configuration types
73
- class ThinkingConfigAdaptive(TypedDict):
73
+ class ThinkingConfigAdaptive(ClaudeCodeBaseModel):
74
74
  """Adaptive thinking configuration - model decides thinking budget."""
75
75
 
76
- type: Literal["adaptive"]
76
+ type: Literal["adaptive"] = "adaptive"
77
77
 
78
78
 
79
- class ThinkingConfigEnabled(TypedDict):
79
+ class ThinkingConfigEnabled(ClaudeCodeBaseModel):
80
80
  """Enabled thinking configuration with explicit token budget."""
81
81
 
82
- type: Literal["enabled"]
82
+ type: Literal["enabled"] = "enabled"
83
83
  budget_tokens: int
84
84
 
85
85
 
86
- class ThinkingConfigDisabled(TypedDict):
86
+ class ThinkingConfigDisabled(ClaudeCodeBaseModel):
87
87
  """Disabled thinking configuration."""
88
88
 
89
- type: Literal["disabled"]
89
+ type: Literal["disabled"] = "disabled"
90
90
 
91
91
 
92
92
  ThinkingConfig = ThinkingConfigAdaptive | ThinkingConfigEnabled | ThinkingConfigDisabled