claude-agent-sdk 0.1.4__tar.gz → 0.1.6__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.4 → claude_agent_sdk-0.1.6}/.gitignore +1 -0
  2. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/PKG-INFO +1 -1
  3. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/pyproject.toml +1 -1
  4. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/src/claude_agent_sdk/__init__.py +3 -0
  5. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/src/claude_agent_sdk/_internal/transport/subprocess_cli.py +64 -2
  6. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/src/claude_agent_sdk/_version.py +1 -1
  7. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/src/claude_agent_sdk/types.py +15 -0
  8. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/tests/test_integration.py +70 -0
  9. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/tests/test_transport.py +13 -0
  10. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/LICENSE +0 -0
  11. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/README.md +0 -0
  12. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/src/claude_agent_sdk/_errors.py +0 -0
  13. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/src/claude_agent_sdk/_internal/__init__.py +0 -0
  14. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/src/claude_agent_sdk/_internal/client.py +0 -0
  15. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/src/claude_agent_sdk/_internal/message_parser.py +0 -0
  16. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/src/claude_agent_sdk/_internal/query.py +0 -0
  17. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/src/claude_agent_sdk/_internal/transport/__init__.py +0 -0
  18. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/src/claude_agent_sdk/client.py +0 -0
  19. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/src/claude_agent_sdk/py.typed +0 -0
  20. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/src/claude_agent_sdk/query.py +0 -0
  21. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/tests/conftest.py +0 -0
  22. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/tests/test_changelog.py +0 -0
  23. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/tests/test_client.py +0 -0
  24. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/tests/test_errors.py +0 -0
  25. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/tests/test_message_parser.py +0 -0
  26. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/tests/test_sdk_mcp_integration.py +0 -0
  27. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/tests/test_streaming_client.py +0 -0
  28. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/tests/test_subprocess_buffering.py +0 -0
  29. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/tests/test_tool_callbacks.py +0 -0
  30. {claude_agent_sdk-0.1.4 → claude_agent_sdk-0.1.6}/tests/test_types.py +0 -0
@@ -26,6 +26,7 @@ venv/
26
26
  ENV/
27
27
  env/
28
28
  .venv
29
+ uv.lock
29
30
 
30
31
  # IDEs
31
32
  .vscode/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-agent-sdk
3
- Version: 0.1.4
3
+ Version: 0.1.6
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "claude-agent-sdk"
7
- version = "0.1.4"
7
+ version = "0.1.6"
8
8
  description = "Python SDK for Claude Code"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -39,6 +39,7 @@ from .types import (
39
39
  PreCompactHookInput,
40
40
  PreToolUseHookInput,
41
41
  ResultMessage,
42
+ SdkPluginConfig,
42
43
  SettingSource,
43
44
  StopHookInput,
44
45
  SubagentStopHookInput,
@@ -339,6 +340,8 @@ __all__ = [
339
340
  # Agent support
340
341
  "AgentDefinition",
341
342
  "SettingSource",
343
+ # Plugin support
344
+ "SdkPluginConfig",
342
345
  # MCP Server Support
343
346
  "create_sdk_mcp_server",
344
347
  "tool",
@@ -3,9 +3,11 @@
3
3
  import json
4
4
  import logging
5
5
  import os
6
+ import platform
6
7
  import re
7
8
  import shutil
8
9
  import sys
10
+ import tempfile
9
11
  from collections.abc import AsyncIterable, AsyncIterator
10
12
  from contextlib import suppress
11
13
  from dataclasses import asdict
@@ -29,6 +31,11 @@ logger = logging.getLogger(__name__)
29
31
  _DEFAULT_MAX_BUFFER_SIZE = 1024 * 1024 # 1MB buffer limit
30
32
  MINIMUM_CLAUDE_CODE_VERSION = "2.0.0"
31
33
 
34
+ # Platform-specific command line length limits
35
+ # Windows cmd.exe has a limit of 8191 characters, use 8000 for safety
36
+ # Other platforms have much higher limits
37
+ _CMD_LENGTH_LIMIT = 8000 if platform.system() == "Windows" else 100000
38
+
32
39
 
33
40
  class SubprocessCLITransport(Transport):
34
41
  """Subprocess transport using Claude Code CLI."""
@@ -57,6 +64,7 @@ class SubprocessCLITransport(Transport):
57
64
  if options.max_buffer_size is not None
58
65
  else _DEFAULT_MAX_BUFFER_SIZE
59
66
  )
67
+ self._temp_files: list[str] = [] # Track temporary files for cleanup
60
68
 
61
69
  def _find_cli(self) -> str:
62
70
  """Find Claude Code CLI binary."""
@@ -89,7 +97,7 @@ class SubprocessCLITransport(Transport):
89
97
  cmd = [self._cli_path, "--output-format", "stream-json", "--verbose"]
90
98
 
91
99
  if self._options.system_prompt is None:
92
- pass
100
+ cmd.extend(["--system-prompt", ""])
93
101
  elif isinstance(self._options.system_prompt, str):
94
102
  cmd.extend(["--system-prompt", self._options.system_prompt])
95
103
  else:
@@ -107,6 +115,9 @@ class SubprocessCLITransport(Transport):
107
115
  if self._options.max_turns:
108
116
  cmd.extend(["--max-turns", str(self._options.max_turns)])
109
117
 
118
+ if self._options.max_budget_usd is not None:
119
+ cmd.extend(["--max-budget-usd", str(self._options.max_budget_usd)])
120
+
110
121
  if self._options.disallowed_tools:
111
122
  cmd.extend(["--disallowedTools", ",".join(self._options.disallowed_tools)])
112
123
 
@@ -173,7 +184,8 @@ class SubprocessCLITransport(Transport):
173
184
  name: {k: v for k, v in asdict(agent_def).items() if v is not None}
174
185
  for name, agent_def in self._options.agents.items()
175
186
  }
176
- cmd.extend(["--agents", json.dumps(agents_dict)])
187
+ agents_json = json.dumps(agents_dict)
188
+ cmd.extend(["--agents", agents_json])
177
189
 
178
190
  sources_value = (
179
191
  ",".join(self._options.setting_sources)
@@ -182,6 +194,14 @@ class SubprocessCLITransport(Transport):
182
194
  )
183
195
  cmd.extend(["--setting-sources", sources_value])
184
196
 
197
+ # Add plugin directories
198
+ if self._options.plugins:
199
+ for plugin in self._options.plugins:
200
+ if plugin["type"] == "local":
201
+ cmd.extend(["--plugin-dir", plugin["path"]])
202
+ else:
203
+ raise ValueError(f"Unsupported plugin type: {plugin['type']}")
204
+
185
205
  # Add extra args for future CLI flags
186
206
  for flag, value in self._options.extra_args.items():
187
207
  if value is None:
@@ -199,6 +219,42 @@ class SubprocessCLITransport(Transport):
199
219
  # String mode: use --print with the prompt
200
220
  cmd.extend(["--print", "--", str(self._prompt)])
201
221
 
222
+ if self._options.max_thinking_tokens is not None:
223
+ cmd.extend(
224
+ ["--max-thinking-tokens", str(self._options.max_thinking_tokens)]
225
+ )
226
+
227
+ # Check if command line is too long (Windows limitation)
228
+ cmd_str = " ".join(cmd)
229
+ if len(cmd_str) > _CMD_LENGTH_LIMIT and self._options.agents:
230
+ # Command is too long - use temp file for agents
231
+ # Find the --agents argument and replace its value with @filepath
232
+ try:
233
+ agents_idx = cmd.index("--agents")
234
+ agents_json_value = cmd[agents_idx + 1]
235
+
236
+ # Create a temporary file
237
+ # ruff: noqa: SIM115
238
+ temp_file = tempfile.NamedTemporaryFile(
239
+ mode="w", suffix=".json", delete=False, encoding="utf-8"
240
+ )
241
+ temp_file.write(agents_json_value)
242
+ temp_file.close()
243
+
244
+ # Track for cleanup
245
+ self._temp_files.append(temp_file.name)
246
+
247
+ # Replace agents JSON with @filepath reference
248
+ cmd[agents_idx + 1] = f"@{temp_file.name}"
249
+
250
+ logger.info(
251
+ f"Command line length ({len(cmd_str)}) exceeds limit ({_CMD_LENGTH_LIMIT}). "
252
+ f"Using temp file for --agents: {temp_file.name}"
253
+ )
254
+ except (ValueError, IndexError) as e:
255
+ # This shouldn't happen, but log it just in case
256
+ logger.warning(f"Failed to optimize command line length: {e}")
257
+
202
258
  return cmd
203
259
 
204
260
  async def connect(self) -> None:
@@ -309,6 +365,12 @@ class SubprocessCLITransport(Transport):
309
365
  """Close the transport and clean up resources."""
310
366
  self._ready = False
311
367
 
368
+ # Clean up temporary files first (before early return)
369
+ for temp_file in self._temp_files:
370
+ with suppress(Exception):
371
+ Path(temp_file).unlink(missing_ok=True)
372
+ self._temp_files.clear()
373
+
312
374
  if not self._process:
313
375
  return
314
376
 
@@ -1,3 +1,3 @@
1
1
  """Version information for claude-agent-sdk."""
2
2
 
3
- __version__ = "0.1.4"
3
+ __version__ = "0.1.6"
@@ -406,6 +406,16 @@ McpServerConfig = (
406
406
  )
407
407
 
408
408
 
409
+ class SdkPluginConfig(TypedDict):
410
+ """SDK plugin configuration.
411
+
412
+ Currently only local plugins are supported via the 'local' type.
413
+ """
414
+
415
+ type: Literal["local"]
416
+ path: str
417
+
418
+
409
419
  # Content block types
410
420
  @dataclass
411
421
  class TextBlock:
@@ -508,6 +518,7 @@ class ClaudeAgentOptions:
508
518
  continue_conversation: bool = False
509
519
  resume: str | None = None
510
520
  max_turns: int | None = None
521
+ max_budget_usd: float | None = None
511
522
  disallowed_tools: list[str] = field(default_factory=list)
512
523
  model: str | None = None
513
524
  permission_prompt_tool_name: str | None = None
@@ -542,6 +553,10 @@ class ClaudeAgentOptions:
542
553
  agents: dict[str, AgentDefinition] | None = None
543
554
  # Setting sources to load (user, project, local)
544
555
  setting_sources: list[SettingSource] | None = None
556
+ # Plugin configurations for custom plugins
557
+ plugins: list[SdkPluginConfig] = field(default_factory=list)
558
+ # Max tokens for thinking blocks
559
+ max_thinking_tokens: int | None = None
545
560
 
546
561
 
547
562
  # SDK Control Protocol
@@ -212,3 +212,73 @@ class TestIntegration:
212
212
  assert call_kwargs["options"].continue_conversation is True
213
213
 
214
214
  anyio.run(_test)
215
+
216
+ def test_max_budget_usd_option(self):
217
+ """Test query with max_budget_usd option."""
218
+
219
+ async def _test():
220
+ with patch(
221
+ "claude_agent_sdk._internal.client.SubprocessCLITransport"
222
+ ) as mock_transport_class:
223
+ mock_transport = AsyncMock()
224
+ mock_transport_class.return_value = mock_transport
225
+
226
+ # Mock the message stream that exceeds budget
227
+ async def mock_receive():
228
+ yield {
229
+ "type": "assistant",
230
+ "message": {
231
+ "role": "assistant",
232
+ "content": [
233
+ {"type": "text", "text": "Starting to read..."}
234
+ ],
235
+ "model": "claude-opus-4-1-20250805",
236
+ },
237
+ }
238
+ yield {
239
+ "type": "result",
240
+ "subtype": "error_max_budget_usd",
241
+ "duration_ms": 500,
242
+ "duration_api_ms": 400,
243
+ "is_error": False,
244
+ "num_turns": 1,
245
+ "session_id": "test-session-budget",
246
+ "total_cost_usd": 0.0002,
247
+ "usage": {
248
+ "input_tokens": 100,
249
+ "output_tokens": 50,
250
+ },
251
+ }
252
+
253
+ mock_transport.read_messages = mock_receive
254
+ mock_transport.connect = AsyncMock()
255
+ mock_transport.close = AsyncMock()
256
+ mock_transport.end_input = AsyncMock()
257
+ mock_transport.write = AsyncMock()
258
+ mock_transport.is_ready = Mock(return_value=True)
259
+
260
+ # Run query with very small budget
261
+ messages = []
262
+ async for msg in query(
263
+ prompt="Read the readme",
264
+ options=ClaudeAgentOptions(max_budget_usd=0.0001),
265
+ ):
266
+ messages.append(msg)
267
+
268
+ # Verify results
269
+ assert len(messages) == 2
270
+
271
+ # Check result message
272
+ assert isinstance(messages[1], ResultMessage)
273
+ assert messages[1].subtype == "error_max_budget_usd"
274
+ assert messages[1].is_error is False
275
+ assert messages[1].total_cost_usd == 0.0002
276
+ assert messages[1].total_cost_usd is not None
277
+ assert messages[1].total_cost_usd > 0
278
+
279
+ # Verify transport was created with max_budget_usd option
280
+ mock_transport_class.assert_called_once()
281
+ call_kwargs = mock_transport_class.call_args.kwargs
282
+ assert call_kwargs["options"].max_budget_usd == 0.0001
283
+
284
+ anyio.run(_test)
@@ -46,6 +46,8 @@ class TestSubprocessCLITransport:
46
46
  assert "stream-json" in cmd
47
47
  assert "--print" in cmd
48
48
  assert "Hello" in cmd
49
+ assert "--system-prompt" in cmd
50
+ assert cmd[cmd.index("--system-prompt") + 1] == ""
49
51
 
50
52
  def test_cli_path_accepts_pathlib_path(self):
51
53
  """Test that cli_path accepts pathlib.Path objects."""
@@ -129,6 +131,17 @@ class TestSubprocessCLITransport:
129
131
  assert "--max-turns" in cmd
130
132
  assert "5" in cmd
131
133
 
134
+ def test_build_command_with_max_thinking_tokens(self):
135
+ """Test building CLI command with max_thinking_tokens option."""
136
+ transport = SubprocessCLITransport(
137
+ prompt="test",
138
+ options=make_options(max_thinking_tokens=5000),
139
+ )
140
+
141
+ cmd = transport._build_command()
142
+ assert "--max-thinking-tokens" in cmd
143
+ assert "5000" in cmd
144
+
132
145
  def test_build_command_with_add_dirs(self):
133
146
  """Test building CLI command with add_dirs option."""
134
147
  from pathlib import Path