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.
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/.gitignore +1 -1
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/PKG-INFO +2 -1
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/pyproject.toml +2 -1
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/__init__.py +26 -4
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/_internal/query.py +1 -35
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/_internal/transport/subprocess_cli.py +9 -7
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/client.py +50 -47
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/__init__.py +22 -14
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/base.py +7 -7
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/messages.py +198 -27
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/options.py +10 -21
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/query.py +9 -8
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/session.py +3 -1
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_agents_and_settings.py +2 -2
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_include_partial_messages.py +11 -25
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_slash_commands.py +1 -1
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_stderr_callback.py +2 -2
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_structured_output.py +4 -4
- clawd_code_sdk-0.4.1/tests/mock_claude_server.py +92 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_client.py +6 -6
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_integration.py +1 -1
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_streaming_client.py +10 -50
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_subprocess_buffering.py +8 -8
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_transport.py +56 -66
- clawd_code_sdk-0.3.8/tests/mock_claude_server.py +0 -92
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/LICENSE +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/README.md +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/_bundled/.gitignore +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/_errors.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/_internal/__init__.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/_internal/message_parser.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/_internal/transport/__init__.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/_version.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/anthropic_types.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/list_sessions.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/mcp_utils.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/agents.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/content_blocks.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/control.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/hooks.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/input_types.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/mcp.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/output_types.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/permissions.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/sandbox.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/models/server_info.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/py.typed +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/storage/ARCHITECTURE.md +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/storage/__init__.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/storage/helpers.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/storage/models.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/storage/replay.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/src/clawd_code_sdk/usage.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/__init__.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/conftest.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/__init__.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_dynamic_control.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_hook_events.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_hooks.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_mcp_tools.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_sdk_mcp_tools.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_subagent_invocation.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/e2e/test_tool_permissions.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/mcp_server.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_changelog.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_errors.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_image.png +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_message_parser.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_sdk_mcp_integration.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_session.py +0 -0
- {clawd_code_sdk-0.3.8 → clawd_code_sdk-0.4.1}/tests/test_tool_callbacks.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clawd-code-sdk
|
|
3
|
-
Version: 0.
|
|
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
|
+
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
|
-
|
|
89
|
-
|
|
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
|
-
"
|
|
217
|
-
"
|
|
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,
|
|
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
|
|
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
|
|
212
|
+
case ThinkingConfigAdaptive():
|
|
211
213
|
cmd.extend(["--max-thinking-tokens", "32000"])
|
|
212
|
-
case
|
|
214
|
+
case ThinkingConfigEnabled(budget_tokens=budget):
|
|
213
215
|
cmd.extend(["--max-thinking-tokens", str(budget)])
|
|
214
|
-
case
|
|
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
|
|
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
|
|
71
|
-
"""Connect to Claude
|
|
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(
|
|
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
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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(
|
|
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
|
-
|
|
244
|
-
|
|
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
|
-
"
|
|
501
|
-
"
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|