claude-agent-sdk 0.1.0__tar.gz → 0.1.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.

Potentially problematic release.


This version of claude-agent-sdk might be problematic. Click here for more details.

Files changed (30) hide show
  1. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/PKG-INFO +13 -4
  2. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/README.md +12 -3
  3. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/pyproject.toml +1 -1
  4. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/src/claude_agent_sdk/_internal/query.py +10 -5
  5. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/src/claude_agent_sdk/_internal/transport/subprocess_cli.py +45 -0
  6. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/src/claude_agent_sdk/_version.py +1 -1
  7. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/src/claude_agent_sdk/client.py +18 -8
  8. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/src/claude_agent_sdk/types.py +36 -0
  9. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/tests/test_tool_callbacks.py +4 -4
  10. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/tests/test_transport.py +54 -15
  11. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/tests/test_types.py +2 -2
  12. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/.gitignore +0 -0
  13. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/LICENSE +0 -0
  14. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/src/claude_agent_sdk/__init__.py +0 -0
  15. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/src/claude_agent_sdk/_errors.py +0 -0
  16. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/src/claude_agent_sdk/_internal/__init__.py +0 -0
  17. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/src/claude_agent_sdk/_internal/client.py +0 -0
  18. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/src/claude_agent_sdk/_internal/message_parser.py +0 -0
  19. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/src/claude_agent_sdk/_internal/transport/__init__.py +0 -0
  20. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/src/claude_agent_sdk/py.typed +0 -0
  21. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/src/claude_agent_sdk/query.py +0 -0
  22. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/tests/conftest.py +0 -0
  23. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/tests/test_changelog.py +0 -0
  24. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/tests/test_client.py +0 -0
  25. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/tests/test_errors.py +0 -0
  26. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/tests/test_integration.py +0 -0
  27. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/tests/test_message_parser.py +0 -0
  28. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/tests/test_sdk_mcp_integration.py +0 -0
  29. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/tests/test_streaming_client.py +0 -0
  30. {claude_agent_sdk-0.1.0 → claude_agent_sdk-0.1.1}/tests/test_subprocess_buffering.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-agent-sdk
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Python SDK for Claude Code
5
5
  Project-URL: Homepage, https://github.com/anthropics/claude-agent-sdk-python
6
6
  Project-URL: Documentation, https://docs.anthropic.com/en/docs/claude-code/sdk
@@ -43,8 +43,8 @@ pip install claude-agent-sdk
43
43
 
44
44
  **Prerequisites:**
45
45
  - Python 3.10+
46
- - Node.js
47
- - Claude Code: `npm install -g @anthropic-ai/claude-code`
46
+ - Node.js
47
+ - Claude Code 2.0.0+: `npm install -g @anthropic-ai/claude-code`
48
48
 
49
49
  ## Quick Start
50
50
 
@@ -92,7 +92,7 @@ options = ClaudeAgentOptions(
92
92
  )
93
93
 
94
94
  async for message in query(
95
- prompt="Create a hello.py file",
95
+ prompt="Create a hello.py file",
96
96
  options=options
97
97
  ):
98
98
  # Process tool use and results
@@ -304,6 +304,15 @@ See [examples/quick_start.py](examples/quick_start.py) for a complete working ex
304
304
 
305
305
  See [examples/streaming_mode.py](examples/streaming_mode.py) for comprehensive examples involving `ClaudeSDKClient`. You can even run interactive examples in IPython from [examples/streaming_mode_ipython.py](examples/streaming_mode_ipython.py).
306
306
 
307
+ ## Migrating from Claude Code SDK
308
+
309
+ If you're upgrading from the Claude Code SDK (versions < 0.1.0), please see the [CHANGELOG.md](CHANGELOG.md#010) for details on breaking changes and new features, including:
310
+
311
+ - `ClaudeCodeOptions` → `ClaudeAgentOptions` rename
312
+ - Merged system prompt configuration
313
+ - Settings isolation and explicit control
314
+ - New programmatic subagents and session forking features
315
+
307
316
  ## License
308
317
 
309
318
  MIT
@@ -10,8 +10,8 @@ pip install claude-agent-sdk
10
10
 
11
11
  **Prerequisites:**
12
12
  - Python 3.10+
13
- - Node.js
14
- - Claude Code: `npm install -g @anthropic-ai/claude-code`
13
+ - Node.js
14
+ - Claude Code 2.0.0+: `npm install -g @anthropic-ai/claude-code`
15
15
 
16
16
  ## Quick Start
17
17
 
@@ -59,7 +59,7 @@ options = ClaudeAgentOptions(
59
59
  )
60
60
 
61
61
  async for message in query(
62
- prompt="Create a hello.py file",
62
+ prompt="Create a hello.py file",
63
63
  options=options
64
64
  ):
65
65
  # Process tool use and results
@@ -271,6 +271,15 @@ See [examples/quick_start.py](examples/quick_start.py) for a complete working ex
271
271
 
272
272
  See [examples/streaming_mode.py](examples/streaming_mode.py) for comprehensive examples involving `ClaudeSDKClient`. You can even run interactive examples in IPython from [examples/streaming_mode_ipython.py](examples/streaming_mode_ipython.py).
273
273
 
274
+ ## Migrating from Claude Code SDK
275
+
276
+ If you're upgrading from the Claude Code SDK (versions < 0.1.0), please see the [CHANGELOG.md](CHANGELOG.md#010) for details on breaking changes and new features, including:
277
+
278
+ - `ClaudeCodeOptions` → `ClaudeAgentOptions` rename
279
+ - Merged system prompt configuration
280
+ - Settings isolation and explicit control
281
+ - New programmatic subagents and session forking features
282
+
274
283
  ## License
275
284
 
276
285
  MIT
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "claude-agent-sdk"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  description = "Python SDK for Claude Code"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -213,13 +213,18 @@ class Query:
213
213
 
214
214
  # Convert PermissionResult to expected dict format
215
215
  if isinstance(response, PermissionResultAllow):
216
- response_data = {"allow": True}
216
+ response_data = {"behavior": "allow"}
217
217
  if response.updated_input is not None:
218
- response_data["input"] = response.updated_input
219
- # TODO: Handle updatedPermissions when control protocol supports it
218
+ response_data["updatedInput"] = response.updated_input
219
+ if response.updated_permissions is not None:
220
+ response_data["updatedPermissions"] = [
221
+ permission.to_dict()
222
+ for permission in response.updated_permissions
223
+ ]
220
224
  elif isinstance(response, PermissionResultDeny):
221
- response_data = {"allow": False, "reason": response.message}
222
- # TODO: Handle interrupt flag when control protocol supports it
225
+ response_data = {"behavior": "deny", "message": response.message}
226
+ if response.interrupt:
227
+ response_data["interrupt"] = response.interrupt
223
228
  else:
224
229
  raise TypeError(
225
230
  f"Tool permission callback must return PermissionResult (PermissionResultAllow or PermissionResultDeny), got {type(response)}"
@@ -3,7 +3,9 @@
3
3
  import json
4
4
  import logging
5
5
  import os
6
+ import re
6
7
  import shutil
8
+ import sys
7
9
  from collections.abc import AsyncIterable, AsyncIterator
8
10
  from contextlib import suppress
9
11
  from dataclasses import asdict
@@ -25,6 +27,7 @@ from . import Transport
25
27
  logger = logging.getLogger(__name__)
26
28
 
27
29
  _DEFAULT_MAX_BUFFER_SIZE = 1024 * 1024 # 1MB buffer limit
30
+ MINIMUM_CLAUDE_CODE_VERSION = "2.0.0"
28
31
 
29
32
 
30
33
  class SubprocessCLITransport(Transport):
@@ -202,6 +205,8 @@ class SubprocessCLITransport(Transport):
202
205
  if self._process:
203
206
  return
204
207
 
208
+ await self._check_claude_version()
209
+
205
210
  cmd = self._build_command()
206
211
  try:
207
212
  # Merge environment variables: system -> user -> SDK required
@@ -448,6 +453,46 @@ class SubprocessCLITransport(Transport):
448
453
  )
449
454
  raise self._exit_error
450
455
 
456
+ async def _check_claude_version(self) -> None:
457
+ """Check Claude Code version and warn if below minimum."""
458
+ version_process = None
459
+ try:
460
+ with anyio.fail_after(2): # 2 second timeout
461
+ version_process = await anyio.open_process(
462
+ [self._cli_path, "-v"],
463
+ stdout=PIPE,
464
+ stderr=PIPE,
465
+ )
466
+
467
+ if version_process.stdout:
468
+ stdout_bytes = await version_process.stdout.receive()
469
+ version_output = stdout_bytes.decode().strip()
470
+
471
+ match = re.match(r"([0-9]+\.[0-9]+\.[0-9]+)", version_output)
472
+ if match:
473
+ version = match.group(1)
474
+ version_parts = [int(x) for x in version.split(".")]
475
+ min_parts = [
476
+ int(x) for x in MINIMUM_CLAUDE_CODE_VERSION.split(".")
477
+ ]
478
+
479
+ if version_parts < min_parts:
480
+ warning = (
481
+ f"Warning: Claude Code version {version} is unsupported in the Agent SDK. "
482
+ f"Minimum required version is {MINIMUM_CLAUDE_CODE_VERSION}. "
483
+ "Some features may not work correctly."
484
+ )
485
+ logger.warning(warning)
486
+ print(warning, file=sys.stderr)
487
+ except Exception:
488
+ pass
489
+ finally:
490
+ if version_process:
491
+ with suppress(Exception):
492
+ version_process.terminate()
493
+ with suppress(Exception):
494
+ await version_process.wait()
495
+
451
496
  def is_ready(self) -> bool:
452
497
  """Check if transport is ready for communication."""
453
498
  return self._ready
@@ -1,3 +1,3 @@
1
1
  """Version information for claude-agent-sdk."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.1.1"
@@ -6,6 +6,7 @@ from collections.abc import AsyncIterable, AsyncIterator
6
6
  from dataclasses import replace
7
7
  from typing import Any
8
8
 
9
+ from . import Transport
9
10
  from ._errors import CLIConnectionError
10
11
  from .types import ClaudeAgentOptions, HookEvent, HookMatcher, Message, ResultMessage
11
12
 
@@ -51,12 +52,17 @@ class ClaudeSDKClient:
51
52
  exist.
52
53
  """
53
54
 
54
- def __init__(self, options: ClaudeAgentOptions | None = None):
55
+ def __init__(
56
+ self,
57
+ options: ClaudeAgentOptions | None = None,
58
+ transport: Transport | None = None,
59
+ ):
55
60
  """Initialize Claude SDK client."""
56
61
  if options is None:
57
62
  options = ClaudeAgentOptions()
58
63
  self.options = options
59
- self._transport: Any | None = None
64
+ self._custom_transport = transport
65
+ self._transport: Transport | None = None
60
66
  self._query: Any | None = None
61
67
  os.environ["CLAUDE_CODE_ENTRYPOINT"] = "sdk-py-client"
62
68
 
@@ -115,10 +121,14 @@ class ClaudeSDKClient:
115
121
  else:
116
122
  options = self.options
117
123
 
118
- self._transport = SubprocessCLITransport(
119
- prompt=actual_prompt,
120
- options=options,
121
- )
124
+ # Use provided custom transport or create subprocess transport
125
+ if self._custom_transport:
126
+ self._transport = self._custom_transport
127
+ else:
128
+ self._transport = SubprocessCLITransport(
129
+ prompt=actual_prompt,
130
+ options=options,
131
+ )
122
132
  await self._transport.connect()
123
133
 
124
134
  # Extract SDK MCP servers from options
@@ -222,7 +232,7 @@ class ClaudeSDKClient:
222
232
 
223
233
  Args:
224
234
  model: The model to use, or None to use default. Examples:
225
- - 'claude-sonnet-4-20250514'
235
+ - 'claude-sonnet-4-5'
226
236
  - 'claude-opus-4-1-20250805'
227
237
  - 'claude-opus-4-20250514'
228
238
 
@@ -233,7 +243,7 @@ class ClaudeSDKClient:
233
243
  await client.query("Help me understand this problem")
234
244
 
235
245
  # Switch to a different model for implementation
236
- await client.set_model('claude-3-5-sonnet-20241022')
246
+ await client.set_model('claude-sonnet-4-5')
237
247
  await client.query("Now implement the solution")
238
248
  ```
239
249
  """
@@ -70,6 +70,42 @@ class PermissionUpdate:
70
70
  directories: list[str] | None = None
71
71
  destination: PermissionUpdateDestination | None = None
72
72
 
73
+ def to_dict(self) -> dict[str, Any]:
74
+ """Convert PermissionUpdate to dictionary format matching TypeScript control protocol."""
75
+ result: dict[str, Any] = {
76
+ "type": self.type,
77
+ }
78
+
79
+ # Add destination for all variants
80
+ if self.destination is not None:
81
+ result["destination"] = self.destination
82
+
83
+ # Handle different type variants
84
+ if self.type in ["addRules", "replaceRules", "removeRules"]:
85
+ # Rules-based variants require rules and behavior
86
+ if self.rules is not None:
87
+ result["rules"] = [
88
+ {
89
+ "toolName": rule.tool_name,
90
+ "ruleContent": rule.rule_content,
91
+ }
92
+ for rule in self.rules
93
+ ]
94
+ if self.behavior is not None:
95
+ result["behavior"] = self.behavior
96
+
97
+ elif self.type == "setMode":
98
+ # Mode variant requires mode
99
+ if self.mode is not None:
100
+ result["mode"] = self.mode
101
+
102
+ elif self.type in ["addDirectories", "removeDirectories"]:
103
+ # Directory variants require directories
104
+ if self.directories is not None:
105
+ result["directories"] = self.directories
106
+
107
+ return result
108
+
73
109
 
74
110
  # Tool callback types
75
111
  @dataclass
@@ -90,7 +90,7 @@ class TestToolPermissionCallbacks:
90
90
  # Check response was sent
91
91
  assert len(transport.written_messages) == 1
92
92
  response = transport.written_messages[0]
93
- assert '"allow": true' in response
93
+ assert '"behavior": "allow"' in response
94
94
 
95
95
  @pytest.mark.asyncio
96
96
  async def test_permission_callback_deny(self):
@@ -125,8 +125,8 @@ class TestToolPermissionCallbacks:
125
125
  # Check response
126
126
  assert len(transport.written_messages) == 1
127
127
  response = transport.written_messages[0]
128
- assert '"allow": false' in response
129
- assert '"reason": "Security policy violation"' in response
128
+ assert '"behavior": "deny"' in response
129
+ assert '"message": "Security policy violation"' in response
130
130
 
131
131
  @pytest.mark.asyncio
132
132
  async def test_permission_callback_input_modification(self):
@@ -164,7 +164,7 @@ class TestToolPermissionCallbacks:
164
164
  # Check response includes modified input
165
165
  assert len(transport.written_messages) == 1
166
166
  response = transport.written_messages[0]
167
- assert '"allow": true' in response
167
+ assert '"behavior": "allow"' in response
168
168
  assert '"safe_mode": true' in response
169
169
 
170
170
  @pytest.mark.asyncio
@@ -106,7 +106,7 @@ class TestSubprocessCLITransport:
106
106
  options=ClaudeAgentOptions(
107
107
  allowed_tools=["Read", "Write"],
108
108
  disallowed_tools=["Bash"],
109
- model="claude-3-5-sonnet",
109
+ model="claude-sonnet-4-5",
110
110
  permission_mode="acceptEdits",
111
111
  max_turns=5,
112
112
  ),
@@ -119,7 +119,7 @@ class TestSubprocessCLITransport:
119
119
  assert "--disallowedTools" in cmd
120
120
  assert "Bash" in cmd
121
121
  assert "--model" in cmd
122
- assert "claude-3-5-sonnet" in cmd
122
+ assert "claude-sonnet-4-5" in cmd
123
123
  assert "--permission-mode" in cmd
124
124
  assert "acceptEdits" in cmd
125
125
  assert "--max-turns" in cmd
@@ -163,6 +163,16 @@ class TestSubprocessCLITransport:
163
163
 
164
164
  async def _test():
165
165
  with patch("anyio.open_process") as mock_exec:
166
+ # Mock version check process
167
+ mock_version_process = MagicMock()
168
+ mock_version_process.stdout = MagicMock()
169
+ mock_version_process.stdout.receive = AsyncMock(
170
+ return_value=b"2.0.0 (Claude Code)"
171
+ )
172
+ mock_version_process.terminate = MagicMock()
173
+ mock_version_process.wait = AsyncMock()
174
+
175
+ # Mock main process
166
176
  mock_process = MagicMock()
167
177
  mock_process.returncode = None
168
178
  mock_process.terminate = MagicMock()
@@ -175,7 +185,8 @@ class TestSubprocessCLITransport:
175
185
  mock_stdin.aclose = AsyncMock()
176
186
  mock_process.stdin = mock_stdin
177
187
 
178
- mock_exec.return_value = mock_process
188
+ # Return version process first, then main process
189
+ mock_exec.side_effect = [mock_version_process, mock_process]
179
190
 
180
191
  transport = SubprocessCLITransport(
181
192
  prompt="test",
@@ -363,13 +374,25 @@ class TestSubprocessCLITransport:
363
374
  with patch(
364
375
  "anyio.open_process", new_callable=AsyncMock
365
376
  ) as mock_open_process:
377
+ # Mock version check process
378
+ mock_version_process = MagicMock()
379
+ mock_version_process.stdout = MagicMock()
380
+ mock_version_process.stdout.receive = AsyncMock(
381
+ return_value=b"2.0.0 (Claude Code)"
382
+ )
383
+ mock_version_process.terminate = MagicMock()
384
+ mock_version_process.wait = AsyncMock()
385
+
386
+ # Mock main process
366
387
  mock_process = MagicMock()
367
388
  mock_process.stdout = MagicMock()
368
389
  mock_stdin = MagicMock()
369
390
  mock_stdin.aclose = AsyncMock() # Add async aclose method
370
391
  mock_process.stdin = mock_stdin
371
392
  mock_process.returncode = None
372
- mock_open_process.return_value = mock_process
393
+
394
+ # Return version process first, then main process
395
+ mock_open_process.side_effect = [mock_version_process, mock_process]
373
396
 
374
397
  transport = SubprocessCLITransport(
375
398
  prompt="test",
@@ -379,11 +402,13 @@ class TestSubprocessCLITransport:
379
402
 
380
403
  await transport.connect()
381
404
 
382
- # Verify open_process was called with correct env vars
383
- mock_open_process.assert_called_once()
384
- call_kwargs = mock_open_process.call_args.kwargs
385
- assert "env" in call_kwargs
386
- env_passed = call_kwargs["env"]
405
+ # Verify open_process was called twice (version check + main process)
406
+ assert mock_open_process.call_count == 2
407
+
408
+ # Check the second call (main process) for env vars
409
+ second_call_kwargs = mock_open_process.call_args_list[1].kwargs
410
+ assert "env" in second_call_kwargs
411
+ env_passed = second_call_kwargs["env"]
387
412
 
388
413
  # Check that custom env var was passed
389
414
  assert env_passed["MY_TEST_VAR"] == test_value
@@ -410,13 +435,25 @@ class TestSubprocessCLITransport:
410
435
  with patch(
411
436
  "anyio.open_process", new_callable=AsyncMock
412
437
  ) as mock_open_process:
438
+ # Mock version check process
439
+ mock_version_process = MagicMock()
440
+ mock_version_process.stdout = MagicMock()
441
+ mock_version_process.stdout.receive = AsyncMock(
442
+ return_value=b"2.0.0 (Claude Code)"
443
+ )
444
+ mock_version_process.terminate = MagicMock()
445
+ mock_version_process.wait = AsyncMock()
446
+
447
+ # Mock main process
413
448
  mock_process = MagicMock()
414
449
  mock_process.stdout = MagicMock()
415
450
  mock_stdin = MagicMock()
416
451
  mock_stdin.aclose = AsyncMock() # Add async aclose method
417
452
  mock_process.stdin = mock_stdin
418
453
  mock_process.returncode = None
419
- mock_open_process.return_value = mock_process
454
+
455
+ # Return version process first, then main process
456
+ mock_open_process.side_effect = [mock_version_process, mock_process]
420
457
 
421
458
  transport = SubprocessCLITransport(
422
459
  prompt="test",
@@ -426,11 +463,13 @@ class TestSubprocessCLITransport:
426
463
 
427
464
  await transport.connect()
428
465
 
429
- # Verify open_process was called with correct user
430
- mock_open_process.assert_called_once()
431
- call_kwargs = mock_open_process.call_args.kwargs
432
- assert "user" in call_kwargs
433
- user_passed = call_kwargs["user"]
466
+ # Verify open_process was called twice (version check + main process)
467
+ assert mock_open_process.call_count == 2
468
+
469
+ # Check the second call (main process) for user
470
+ second_call_kwargs = mock_open_process.call_args_list[1].kwargs
471
+ assert "user" in second_call_kwargs
472
+ user_passed = second_call_kwargs["user"]
434
473
 
435
474
  # Check that user was passed
436
475
  assert user_passed == "claude"
@@ -145,7 +145,7 @@ class TestOptions:
145
145
  def test_claude_code_options_with_model_specification(self):
146
146
  """Test Options with model specification."""
147
147
  options = ClaudeAgentOptions(
148
- model="claude-3-5-sonnet-20241022", permission_prompt_tool_name="CustomTool"
148
+ model="claude-sonnet-4-5", permission_prompt_tool_name="CustomTool"
149
149
  )
150
- assert options.model == "claude-3-5-sonnet-20241022"
150
+ assert options.model == "claude-sonnet-4-5"
151
151
  assert options.permission_prompt_tool_name == "CustomTool"