claude-agent-sdk 0.1.2__tar.gz → 0.1.4__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.
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/PKG-INFO +11 -1
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/README.md +10 -0
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/pyproject.toml +1 -1
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/src/claude_agent_sdk/__init__.py +27 -2
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/src/claude_agent_sdk/_internal/client.py +2 -1
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/src/claude_agent_sdk/_internal/query.py +22 -1
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/src/claude_agent_sdk/_internal/transport/subprocess_cli.py +7 -5
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/src/claude_agent_sdk/_version.py +1 -1
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/src/claude_agent_sdk/types.py +121 -14
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/tests/test_sdk_mcp_integration.py +72 -0
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/tests/test_subprocess_buffering.py +17 -23
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/tests/test_tool_callbacks.py +74 -8
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/tests/test_transport.py +29 -44
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/.gitignore +0 -0
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/LICENSE +0 -0
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/src/claude_agent_sdk/_errors.py +0 -0
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/src/claude_agent_sdk/_internal/__init__.py +0 -0
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/src/claude_agent_sdk/_internal/message_parser.py +0 -0
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/src/claude_agent_sdk/_internal/transport/__init__.py +0 -0
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/src/claude_agent_sdk/client.py +0 -0
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/src/claude_agent_sdk/py.typed +0 -0
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/src/claude_agent_sdk/query.py +0 -0
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/tests/conftest.py +0 -0
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/tests/test_changelog.py +0 -0
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/tests/test_client.py +0 -0
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/tests/test_errors.py +0 -0
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/tests/test_integration.py +0 -0
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/tests/test_message_parser.py +0 -0
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/tests/test_streaming_client.py +0 -0
- {claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/tests/test_types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-agent-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
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
|
|
@@ -313,6 +313,16 @@ If you're upgrading from the Claude Code SDK (versions < 0.1.0), please see the
|
|
|
313
313
|
- Settings isolation and explicit control
|
|
314
314
|
- New programmatic subagents and session forking features
|
|
315
315
|
|
|
316
|
+
## Development
|
|
317
|
+
|
|
318
|
+
If you're contributing to this project, run the initial setup script to install git hooks:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
./scripts/initial-setup.sh
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
This installs a pre-push hook that runs lint checks before pushing, matching the CI workflow. To skip the hook temporarily, use `git push --no-verify`.
|
|
325
|
+
|
|
316
326
|
## License
|
|
317
327
|
|
|
318
328
|
MIT
|
|
@@ -280,6 +280,16 @@ If you're upgrading from the Claude Code SDK (versions < 0.1.0), please see the
|
|
|
280
280
|
- Settings isolation and explicit control
|
|
281
281
|
- New programmatic subagents and session forking features
|
|
282
282
|
|
|
283
|
+
## Development
|
|
284
|
+
|
|
285
|
+
If you're contributing to this project, run the initial setup script to install git hooks:
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
./scripts/initial-setup.sh
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
This installs a pre-push hook that runs lint checks before pushing, matching the CI workflow. To skip the hook temporarily, use `git push --no-verify`.
|
|
292
|
+
|
|
283
293
|
## License
|
|
284
294
|
|
|
285
295
|
MIT
|
|
@@ -18,11 +18,13 @@ from .query import query
|
|
|
18
18
|
from .types import (
|
|
19
19
|
AgentDefinition,
|
|
20
20
|
AssistantMessage,
|
|
21
|
+
BaseHookInput,
|
|
21
22
|
CanUseTool,
|
|
22
23
|
ClaudeAgentOptions,
|
|
23
24
|
ContentBlock,
|
|
24
25
|
HookCallback,
|
|
25
26
|
HookContext,
|
|
27
|
+
HookInput,
|
|
26
28
|
HookJSONOutput,
|
|
27
29
|
HookMatcher,
|
|
28
30
|
McpSdkServerConfig,
|
|
@@ -33,8 +35,13 @@ from .types import (
|
|
|
33
35
|
PermissionResultAllow,
|
|
34
36
|
PermissionResultDeny,
|
|
35
37
|
PermissionUpdate,
|
|
38
|
+
PostToolUseHookInput,
|
|
39
|
+
PreCompactHookInput,
|
|
40
|
+
PreToolUseHookInput,
|
|
36
41
|
ResultMessage,
|
|
37
42
|
SettingSource,
|
|
43
|
+
StopHookInput,
|
|
44
|
+
SubagentStopHookInput,
|
|
38
45
|
SystemMessage,
|
|
39
46
|
TextBlock,
|
|
40
47
|
ThinkingBlock,
|
|
@@ -42,6 +49,7 @@ from .types import (
|
|
|
42
49
|
ToolResultBlock,
|
|
43
50
|
ToolUseBlock,
|
|
44
51
|
UserMessage,
|
|
52
|
+
UserPromptSubmitHookInput,
|
|
45
53
|
)
|
|
46
54
|
|
|
47
55
|
# MCP Server Support
|
|
@@ -195,7 +203,7 @@ def create_sdk_mcp_server(
|
|
|
195
203
|
- ClaudeAgentOptions: Configuration for using servers with query()
|
|
196
204
|
"""
|
|
197
205
|
from mcp.server import Server
|
|
198
|
-
from mcp.types import TextContent, Tool
|
|
206
|
+
from mcp.types import ImageContent, TextContent, Tool
|
|
199
207
|
|
|
200
208
|
# Create MCP server instance
|
|
201
209
|
server = Server(name, version=version)
|
|
@@ -265,11 +273,19 @@ def create_sdk_mcp_server(
|
|
|
265
273
|
# Convert result to MCP format
|
|
266
274
|
# The decorator expects us to return the content, not a CallToolResult
|
|
267
275
|
# It will wrap our return value in CallToolResult
|
|
268
|
-
content = []
|
|
276
|
+
content: list[TextContent | ImageContent] = []
|
|
269
277
|
if "content" in result:
|
|
270
278
|
for item in result["content"]:
|
|
271
279
|
if item.get("type") == "text":
|
|
272
280
|
content.append(TextContent(type="text", text=item["text"]))
|
|
281
|
+
if item.get("type") == "image":
|
|
282
|
+
content.append(
|
|
283
|
+
ImageContent(
|
|
284
|
+
type="image",
|
|
285
|
+
data=item["data"],
|
|
286
|
+
mimeType=item["mimeType"],
|
|
287
|
+
)
|
|
288
|
+
)
|
|
273
289
|
|
|
274
290
|
# Return just the content list - the decorator wraps it
|
|
275
291
|
return content
|
|
@@ -307,8 +323,17 @@ __all__ = [
|
|
|
307
323
|
"PermissionResultAllow",
|
|
308
324
|
"PermissionResultDeny",
|
|
309
325
|
"PermissionUpdate",
|
|
326
|
+
# Hook support
|
|
310
327
|
"HookCallback",
|
|
311
328
|
"HookContext",
|
|
329
|
+
"HookInput",
|
|
330
|
+
"BaseHookInput",
|
|
331
|
+
"PreToolUseHookInput",
|
|
332
|
+
"PostToolUseHookInput",
|
|
333
|
+
"UserPromptSubmitHookInput",
|
|
334
|
+
"StopHookInput",
|
|
335
|
+
"SubagentStopHookInput",
|
|
336
|
+
"PreCompactHookInput",
|
|
312
337
|
"HookJSONOutput",
|
|
313
338
|
"HookMatcher",
|
|
314
339
|
# Agent support
|
|
@@ -31,6 +31,25 @@ if TYPE_CHECKING:
|
|
|
31
31
|
logger = logging.getLogger(__name__)
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
def _convert_hook_output_for_cli(hook_output: dict[str, Any]) -> dict[str, Any]:
|
|
35
|
+
"""Convert Python-safe field names to CLI-expected field names.
|
|
36
|
+
|
|
37
|
+
The Python SDK uses `async_` and `continue_` to avoid keyword conflicts,
|
|
38
|
+
but the CLI expects `async` and `continue`. This function performs the
|
|
39
|
+
necessary conversion.
|
|
40
|
+
"""
|
|
41
|
+
converted = {}
|
|
42
|
+
for key, value in hook_output.items():
|
|
43
|
+
# Convert Python-safe names to JavaScript names
|
|
44
|
+
if key == "async_":
|
|
45
|
+
converted["async"] = value
|
|
46
|
+
elif key == "continue_":
|
|
47
|
+
converted["continue"] = value
|
|
48
|
+
else:
|
|
49
|
+
converted[key] = value
|
|
50
|
+
return converted
|
|
51
|
+
|
|
52
|
+
|
|
34
53
|
class Query:
|
|
35
54
|
"""Handles bidirectional control protocol on top of Transport.
|
|
36
55
|
|
|
@@ -244,11 +263,13 @@ class Query:
|
|
|
244
263
|
if not callback:
|
|
245
264
|
raise Exception(f"No hook callback found for ID: {callback_id}")
|
|
246
265
|
|
|
247
|
-
|
|
266
|
+
hook_output = await callback(
|
|
248
267
|
request_data.get("input"),
|
|
249
268
|
request_data.get("tool_use_id"),
|
|
250
269
|
{"signal": None}, # TODO: Add abort signal support
|
|
251
270
|
)
|
|
271
|
+
# Convert Python-safe field names (async_, continue_) to CLI-expected names (async, continue)
|
|
272
|
+
response_data = _convert_hook_output_for_cli(hook_output)
|
|
252
273
|
|
|
253
274
|
elif subtype == "mcp_message":
|
|
254
275
|
# Handle SDK MCP request
|
|
@@ -37,12 +37,13 @@ class SubprocessCLITransport(Transport):
|
|
|
37
37
|
self,
|
|
38
38
|
prompt: str | AsyncIterable[dict[str, Any]],
|
|
39
39
|
options: ClaudeAgentOptions,
|
|
40
|
-
cli_path: str | Path | None = None,
|
|
41
40
|
):
|
|
42
41
|
self._prompt = prompt
|
|
43
42
|
self._is_streaming = not isinstance(prompt, str)
|
|
44
43
|
self._options = options
|
|
45
|
-
self._cli_path =
|
|
44
|
+
self._cli_path = (
|
|
45
|
+
str(options.cli_path) if options.cli_path is not None else self._find_cli()
|
|
46
|
+
)
|
|
46
47
|
self._cwd = str(options.cwd) if options.cwd else None
|
|
47
48
|
self._process: Process | None = None
|
|
48
49
|
self._stdout_stream: TextReceiveStream | None = None
|
|
@@ -79,8 +80,8 @@ class SubprocessCLITransport(Transport):
|
|
|
79
80
|
" npm install -g @anthropic-ai/claude-code\n"
|
|
80
81
|
"\nIf already installed locally, try:\n"
|
|
81
82
|
' export PATH="$HOME/node_modules/.bin:$PATH"\n'
|
|
82
|
-
"\nOr
|
|
83
|
-
"
|
|
83
|
+
"\nOr provide the path via ClaudeAgentOptions:\n"
|
|
84
|
+
" ClaudeAgentOptions(cli_path='/path/to/claude')"
|
|
84
85
|
)
|
|
85
86
|
|
|
86
87
|
def _build_command(self) -> list[str]:
|
|
@@ -205,7 +206,8 @@ class SubprocessCLITransport(Transport):
|
|
|
205
206
|
if self._process:
|
|
206
207
|
return
|
|
207
208
|
|
|
208
|
-
|
|
209
|
+
if not os.environ.get("CLAUDE_AGENT_SDK_SKIP_VERSION_CHECK"):
|
|
210
|
+
await self._check_claude_version()
|
|
209
211
|
|
|
210
212
|
cmd = self._build_command()
|
|
211
213
|
try:
|
|
@@ -157,6 +157,73 @@ HookEvent = (
|
|
|
157
157
|
)
|
|
158
158
|
|
|
159
159
|
|
|
160
|
+
# Hook input types - strongly typed for each hook event
|
|
161
|
+
class BaseHookInput(TypedDict):
|
|
162
|
+
"""Base hook input fields present across many hook events."""
|
|
163
|
+
|
|
164
|
+
session_id: str
|
|
165
|
+
transcript_path: str
|
|
166
|
+
cwd: str
|
|
167
|
+
permission_mode: NotRequired[str]
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class PreToolUseHookInput(BaseHookInput):
|
|
171
|
+
"""Input data for PreToolUse hook events."""
|
|
172
|
+
|
|
173
|
+
hook_event_name: Literal["PreToolUse"]
|
|
174
|
+
tool_name: str
|
|
175
|
+
tool_input: dict[str, Any]
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class PostToolUseHookInput(BaseHookInput):
|
|
179
|
+
"""Input data for PostToolUse hook events."""
|
|
180
|
+
|
|
181
|
+
hook_event_name: Literal["PostToolUse"]
|
|
182
|
+
tool_name: str
|
|
183
|
+
tool_input: dict[str, Any]
|
|
184
|
+
tool_response: Any
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class UserPromptSubmitHookInput(BaseHookInput):
|
|
188
|
+
"""Input data for UserPromptSubmit hook events."""
|
|
189
|
+
|
|
190
|
+
hook_event_name: Literal["UserPromptSubmit"]
|
|
191
|
+
prompt: str
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class StopHookInput(BaseHookInput):
|
|
195
|
+
"""Input data for Stop hook events."""
|
|
196
|
+
|
|
197
|
+
hook_event_name: Literal["Stop"]
|
|
198
|
+
stop_hook_active: bool
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class SubagentStopHookInput(BaseHookInput):
|
|
202
|
+
"""Input data for SubagentStop hook events."""
|
|
203
|
+
|
|
204
|
+
hook_event_name: Literal["SubagentStop"]
|
|
205
|
+
stop_hook_active: bool
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class PreCompactHookInput(BaseHookInput):
|
|
209
|
+
"""Input data for PreCompact hook events."""
|
|
210
|
+
|
|
211
|
+
hook_event_name: Literal["PreCompact"]
|
|
212
|
+
trigger: Literal["manual", "auto"]
|
|
213
|
+
custom_instructions: str | None
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# Union type for all hook inputs
|
|
217
|
+
HookInput = (
|
|
218
|
+
PreToolUseHookInput
|
|
219
|
+
| PostToolUseHookInput
|
|
220
|
+
| UserPromptSubmitHookInput
|
|
221
|
+
| StopHookInput
|
|
222
|
+
| SubagentStopHookInput
|
|
223
|
+
| PreCompactHookInput
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
160
227
|
# Hook-specific output types
|
|
161
228
|
class PreToolUseHookSpecificOutput(TypedDict):
|
|
162
229
|
"""Hook-specific output for PreToolUse events."""
|
|
@@ -198,18 +265,56 @@ HookSpecificOutput = (
|
|
|
198
265
|
|
|
199
266
|
# See https://docs.anthropic.com/en/docs/claude-code/hooks#advanced%3A-json-output
|
|
200
267
|
# for documentation of the output types.
|
|
268
|
+
#
|
|
269
|
+
# IMPORTANT: The Python SDK uses `async_` and `continue_` (with underscores) to avoid
|
|
270
|
+
# Python keyword conflicts. These fields are automatically converted to `async` and
|
|
271
|
+
# `continue` when sent to the CLI. You should use the underscore versions in your
|
|
272
|
+
# Python code.
|
|
201
273
|
class AsyncHookJSONOutput(TypedDict):
|
|
202
|
-
"""Async hook output that defers hook execution.
|
|
274
|
+
"""Async hook output that defers hook execution.
|
|
203
275
|
|
|
204
|
-
|
|
276
|
+
Fields:
|
|
277
|
+
async_: Set to True to defer hook execution. Note: This is converted to
|
|
278
|
+
"async" when sent to the CLI - use "async_" in your Python code.
|
|
279
|
+
asyncTimeout: Optional timeout in milliseconds for the async operation.
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
async_: Literal[
|
|
283
|
+
True
|
|
284
|
+
] # Using async_ to avoid Python keyword (converted to "async" for CLI)
|
|
205
285
|
asyncTimeout: NotRequired[int]
|
|
206
286
|
|
|
207
287
|
|
|
208
288
|
class SyncHookJSONOutput(TypedDict):
|
|
209
|
-
"""Synchronous hook output with control and decision fields.
|
|
289
|
+
"""Synchronous hook output with control and decision fields.
|
|
290
|
+
|
|
291
|
+
This defines the structure for hook callbacks to control execution and provide
|
|
292
|
+
feedback to Claude.
|
|
293
|
+
|
|
294
|
+
Common Control Fields:
|
|
295
|
+
continue_: Whether Claude should proceed after hook execution (default: True).
|
|
296
|
+
Note: This is converted to "continue" when sent to the CLI.
|
|
297
|
+
suppressOutput: Hide stdout from transcript mode (default: False).
|
|
298
|
+
stopReason: Message shown when continue is False.
|
|
299
|
+
|
|
300
|
+
Decision Fields:
|
|
301
|
+
decision: Set to "block" to indicate blocking behavior.
|
|
302
|
+
systemMessage: Warning message displayed to the user.
|
|
303
|
+
reason: Feedback message for Claude about the decision.
|
|
304
|
+
|
|
305
|
+
Hook-Specific Output:
|
|
306
|
+
hookSpecificOutput: Event-specific controls (e.g., permissionDecision for
|
|
307
|
+
PreToolUse, additionalContext for PostToolUse).
|
|
308
|
+
|
|
309
|
+
Note: The CLI documentation shows field names without underscores ("async", "continue"),
|
|
310
|
+
but Python code should use the underscore versions ("async_", "continue_") as they
|
|
311
|
+
are automatically converted.
|
|
312
|
+
"""
|
|
210
313
|
|
|
211
314
|
# Common control fields
|
|
212
|
-
continue_: NotRequired[
|
|
315
|
+
continue_: NotRequired[
|
|
316
|
+
bool
|
|
317
|
+
] # Using continue_ to avoid Python keyword (converted to "continue" for CLI)
|
|
213
318
|
suppressOutput: NotRequired[bool]
|
|
214
319
|
stopReason: NotRequired[str]
|
|
215
320
|
|
|
@@ -227,21 +332,22 @@ class SyncHookJSONOutput(TypedDict):
|
|
|
227
332
|
HookJSONOutput = AsyncHookJSONOutput | SyncHookJSONOutput
|
|
228
333
|
|
|
229
334
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
"""Context information for hook callbacks."""
|
|
335
|
+
class HookContext(TypedDict):
|
|
336
|
+
"""Context information for hook callbacks.
|
|
233
337
|
|
|
234
|
-
|
|
338
|
+
Fields:
|
|
339
|
+
signal: Reserved for future abort signal support. Currently always None.
|
|
340
|
+
"""
|
|
341
|
+
|
|
342
|
+
signal: Any | None # Future: abort signal support
|
|
235
343
|
|
|
236
344
|
|
|
237
345
|
HookCallback = Callable[
|
|
238
346
|
# HookCallback input parameters:
|
|
239
|
-
# - input
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
|
|
243
|
-
# - context
|
|
244
|
-
[dict[str, Any], str | None, HookContext],
|
|
347
|
+
# - input: Strongly-typed hook input with discriminated unions based on hook_event_name
|
|
348
|
+
# - tool_use_id: Optional tool use identifier
|
|
349
|
+
# - context: Hook context with abort signal support (currently placeholder)
|
|
350
|
+
[HookInput, str | None, HookContext],
|
|
245
351
|
Awaitable[HookJSONOutput],
|
|
246
352
|
]
|
|
247
353
|
|
|
@@ -406,6 +512,7 @@ class ClaudeAgentOptions:
|
|
|
406
512
|
model: str | None = None
|
|
407
513
|
permission_prompt_tool_name: str | None = None
|
|
408
514
|
cwd: str | Path | None = None
|
|
515
|
+
cli_path: str | Path | None = None
|
|
409
516
|
settings: str | None = None
|
|
410
517
|
add_dirs: list[str | Path] = field(default_factory=list)
|
|
411
518
|
env: dict[str, str] = field(default_factory=dict)
|
|
@@ -4,9 +4,11 @@ This test file verifies that SDK MCP servers work correctly through the full sta
|
|
|
4
4
|
matching the TypeScript SDK test/sdk.test.ts pattern.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import base64
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
9
10
|
import pytest
|
|
11
|
+
from mcp.types import CallToolRequest, CallToolRequestParams
|
|
10
12
|
|
|
11
13
|
from claude_agent_sdk import (
|
|
12
14
|
ClaudeAgentOptions,
|
|
@@ -191,3 +193,73 @@ async def test_server_creation():
|
|
|
191
193
|
|
|
192
194
|
# When no tools are provided, the handlers are not registered
|
|
193
195
|
assert ListToolsRequest not in instance.request_handlers
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@pytest.mark.asyncio
|
|
199
|
+
async def test_image_content_support():
|
|
200
|
+
"""Test that tools can return image content with base64 data."""
|
|
201
|
+
|
|
202
|
+
# Create sample base64 image data (a simple 1x1 pixel PNG)
|
|
203
|
+
png_data = base64.b64encode(
|
|
204
|
+
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01"
|
|
205
|
+
b"\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\tpHYs\x00\x00\x0b\x13"
|
|
206
|
+
b"\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x0cIDATx\x9cc```"
|
|
207
|
+
b"\x00\x00\x00\x04\x00\x01]U!\x1c\x00\x00\x00\x00IEND\xaeB`\x82"
|
|
208
|
+
).decode("utf-8")
|
|
209
|
+
|
|
210
|
+
# Track tool executions
|
|
211
|
+
tool_executions: list[dict[str, Any]] = []
|
|
212
|
+
|
|
213
|
+
# Create a tool that returns both text and image content
|
|
214
|
+
@tool(
|
|
215
|
+
"generate_chart", "Generates a chart and returns it as an image", {"title": str}
|
|
216
|
+
)
|
|
217
|
+
async def generate_chart(args: dict[str, Any]) -> dict[str, Any]:
|
|
218
|
+
tool_executions.append({"name": "generate_chart", "args": args})
|
|
219
|
+
return {
|
|
220
|
+
"content": [
|
|
221
|
+
{"type": "text", "text": f"Generated chart: {args['title']}"},
|
|
222
|
+
{
|
|
223
|
+
"type": "image",
|
|
224
|
+
"data": png_data,
|
|
225
|
+
"mimeType": "image/png",
|
|
226
|
+
},
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
server_config = create_sdk_mcp_server(
|
|
231
|
+
name="image-test-server", version="1.0.0", tools=[generate_chart]
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Get the server instance
|
|
235
|
+
server = server_config["instance"]
|
|
236
|
+
|
|
237
|
+
call_handler = server.request_handlers[CallToolRequest]
|
|
238
|
+
|
|
239
|
+
# Call the chart generation tool
|
|
240
|
+
chart_request = CallToolRequest(
|
|
241
|
+
method="tools/call",
|
|
242
|
+
params=CallToolRequestParams(
|
|
243
|
+
name="generate_chart", arguments={"title": "Sales Report"}
|
|
244
|
+
),
|
|
245
|
+
)
|
|
246
|
+
result = await call_handler(chart_request)
|
|
247
|
+
|
|
248
|
+
# Verify the result contains both text and image content
|
|
249
|
+
assert len(result.root.content) == 2
|
|
250
|
+
|
|
251
|
+
# Check text content
|
|
252
|
+
text_content = result.root.content[0]
|
|
253
|
+
assert text_content.type == "text"
|
|
254
|
+
assert text_content.text == "Generated chart: Sales Report"
|
|
255
|
+
|
|
256
|
+
# Check image content
|
|
257
|
+
image_content = result.root.content[1]
|
|
258
|
+
assert image_content.type == "image"
|
|
259
|
+
assert image_content.data == png_data
|
|
260
|
+
assert image_content.mimeType == "image/png"
|
|
261
|
+
|
|
262
|
+
# Verify the tool was executed correctly
|
|
263
|
+
assert len(tool_executions) == 1
|
|
264
|
+
assert tool_executions[0]["name"] == "generate_chart"
|
|
265
|
+
assert tool_executions[0]["args"]["title"] == "Sales Report"
|
|
@@ -15,6 +15,15 @@ from claude_agent_sdk._internal.transport.subprocess_cli import (
|
|
|
15
15
|
)
|
|
16
16
|
from claude_agent_sdk.types import ClaudeAgentOptions
|
|
17
17
|
|
|
18
|
+
DEFAULT_CLI_PATH = "/usr/bin/claude"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def make_options(**kwargs: object) -> ClaudeAgentOptions:
|
|
22
|
+
"""Construct ClaudeAgentOptions with a default CLI path for tests."""
|
|
23
|
+
|
|
24
|
+
cli_path = kwargs.pop("cli_path", DEFAULT_CLI_PATH)
|
|
25
|
+
return ClaudeAgentOptions(cli_path=cli_path, **kwargs)
|
|
26
|
+
|
|
18
27
|
|
|
19
28
|
class MockTextReceiveStream:
|
|
20
29
|
"""Mock TextReceiveStream for testing."""
|
|
@@ -50,9 +59,7 @@ class TestSubprocessBuffering:
|
|
|
50
59
|
|
|
51
60
|
buffered_line = json.dumps(json_obj1) + "\n" + json.dumps(json_obj2)
|
|
52
61
|
|
|
53
|
-
transport = SubprocessCLITransport(
|
|
54
|
-
prompt="test", options=ClaudeAgentOptions(), cli_path="/usr/bin/claude"
|
|
55
|
-
)
|
|
62
|
+
transport = SubprocessCLITransport(prompt="test", options=make_options())
|
|
56
63
|
|
|
57
64
|
mock_process = MagicMock()
|
|
58
65
|
mock_process.returncode = None
|
|
@@ -85,9 +92,7 @@ class TestSubprocessBuffering:
|
|
|
85
92
|
|
|
86
93
|
buffered_line = json.dumps(json_obj1) + "\n" + json.dumps(json_obj2)
|
|
87
94
|
|
|
88
|
-
transport = SubprocessCLITransport(
|
|
89
|
-
prompt="test", options=ClaudeAgentOptions(), cli_path="/usr/bin/claude"
|
|
90
|
-
)
|
|
95
|
+
transport = SubprocessCLITransport(prompt="test", options=make_options())
|
|
91
96
|
|
|
92
97
|
mock_process = MagicMock()
|
|
93
98
|
mock_process.returncode = None
|
|
@@ -115,9 +120,7 @@ class TestSubprocessBuffering:
|
|
|
115
120
|
|
|
116
121
|
buffered_line = json.dumps(json_obj1) + "\n\n\n" + json.dumps(json_obj2)
|
|
117
122
|
|
|
118
|
-
transport = SubprocessCLITransport(
|
|
119
|
-
prompt="test", options=ClaudeAgentOptions(), cli_path="/usr/bin/claude"
|
|
120
|
-
)
|
|
123
|
+
transport = SubprocessCLITransport(prompt="test", options=make_options())
|
|
121
124
|
|
|
122
125
|
mock_process = MagicMock()
|
|
123
126
|
mock_process.returncode = None
|
|
@@ -161,9 +164,7 @@ class TestSubprocessBuffering:
|
|
|
161
164
|
part2 = complete_json[100:250]
|
|
162
165
|
part3 = complete_json[250:]
|
|
163
166
|
|
|
164
|
-
transport = SubprocessCLITransport(
|
|
165
|
-
prompt="test", options=ClaudeAgentOptions(), cli_path="/usr/bin/claude"
|
|
166
|
-
)
|
|
167
|
+
transport = SubprocessCLITransport(prompt="test", options=make_options())
|
|
167
168
|
|
|
168
169
|
mock_process = MagicMock()
|
|
169
170
|
mock_process.returncode = None
|
|
@@ -209,9 +210,7 @@ class TestSubprocessBuffering:
|
|
|
209
210
|
for i in range(0, len(complete_json), chunk_size)
|
|
210
211
|
]
|
|
211
212
|
|
|
212
|
-
transport = SubprocessCLITransport(
|
|
213
|
-
prompt="test", options=ClaudeAgentOptions(), cli_path="/usr/bin/claude"
|
|
214
|
-
)
|
|
213
|
+
transport = SubprocessCLITransport(prompt="test", options=make_options())
|
|
215
214
|
|
|
216
215
|
mock_process = MagicMock()
|
|
217
216
|
mock_process.returncode = None
|
|
@@ -239,9 +238,7 @@ class TestSubprocessBuffering:
|
|
|
239
238
|
async def _test() -> None:
|
|
240
239
|
huge_incomplete = '{"data": "' + "x" * (_DEFAULT_MAX_BUFFER_SIZE + 1000)
|
|
241
240
|
|
|
242
|
-
transport = SubprocessCLITransport(
|
|
243
|
-
prompt="test", options=ClaudeAgentOptions(), cli_path="/usr/bin/claude"
|
|
244
|
-
)
|
|
241
|
+
transport = SubprocessCLITransport(prompt="test", options=make_options())
|
|
245
242
|
|
|
246
243
|
mock_process = MagicMock()
|
|
247
244
|
mock_process.returncode = None
|
|
@@ -269,8 +266,7 @@ class TestSubprocessBuffering:
|
|
|
269
266
|
|
|
270
267
|
transport = SubprocessCLITransport(
|
|
271
268
|
prompt="test",
|
|
272
|
-
options=
|
|
273
|
-
cli_path="/usr/bin/claude",
|
|
269
|
+
options=make_options(max_buffer_size=custom_limit),
|
|
274
270
|
)
|
|
275
271
|
|
|
276
272
|
mock_process = MagicMock()
|
|
@@ -309,9 +305,7 @@ class TestSubprocessBuffering:
|
|
|
309
305
|
large_json[3000:] + "\n" + msg3,
|
|
310
306
|
]
|
|
311
307
|
|
|
312
|
-
transport = SubprocessCLITransport(
|
|
313
|
-
prompt="test", options=ClaudeAgentOptions(), cli_path="/usr/bin/claude"
|
|
314
|
-
)
|
|
308
|
+
transport = SubprocessCLITransport(prompt="test", options=make_options())
|
|
315
309
|
|
|
316
310
|
mock_process = MagicMock()
|
|
317
311
|
mock_process.returncode = None
|
|
@@ -7,6 +7,7 @@ import pytest
|
|
|
7
7
|
from claude_agent_sdk import (
|
|
8
8
|
ClaudeAgentOptions,
|
|
9
9
|
HookContext,
|
|
10
|
+
HookInput,
|
|
10
11
|
HookJSONOutput,
|
|
11
12
|
HookMatcher,
|
|
12
13
|
PermissionResultAllow,
|
|
@@ -216,7 +217,7 @@ class TestHookCallbacks:
|
|
|
216
217
|
hook_calls = []
|
|
217
218
|
|
|
218
219
|
async def test_hook(
|
|
219
|
-
input_data:
|
|
220
|
+
input_data: HookInput, tool_use_id: str | None, context: HookContext
|
|
220
221
|
) -> dict:
|
|
221
222
|
hook_calls.append({"input": input_data, "tool_use_id": tool_use_id})
|
|
222
223
|
return {"processed": True}
|
|
@@ -266,7 +267,7 @@ class TestHookCallbacks:
|
|
|
266
267
|
|
|
267
268
|
# Test all SyncHookJSONOutput fields together
|
|
268
269
|
async def comprehensive_hook(
|
|
269
|
-
input_data:
|
|
270
|
+
input_data: HookInput, tool_use_id: str | None, context: HookContext
|
|
270
271
|
) -> HookJSONOutput:
|
|
271
272
|
return {
|
|
272
273
|
# Control fields
|
|
@@ -322,8 +323,11 @@ class TestHookCallbacks:
|
|
|
322
323
|
# The hook result is nested at response.response
|
|
323
324
|
result = response_data["response"]["response"]
|
|
324
325
|
|
|
325
|
-
# Verify control fields are present
|
|
326
|
-
assert result.get("
|
|
326
|
+
# Verify control fields are present and converted to CLI format
|
|
327
|
+
assert result.get("continue") is True, (
|
|
328
|
+
"continue_ should be converted to continue"
|
|
329
|
+
)
|
|
330
|
+
assert "continue_" not in result, "continue_ should not appear in CLI output"
|
|
327
331
|
assert result.get("suppressOutput") is False
|
|
328
332
|
assert result.get("stopReason") == "Test stop reason"
|
|
329
333
|
|
|
@@ -346,7 +350,7 @@ class TestHookCallbacks:
|
|
|
346
350
|
"""Test AsyncHookJSONOutput type with proper async fields."""
|
|
347
351
|
|
|
348
352
|
async def async_hook(
|
|
349
|
-
input_data:
|
|
353
|
+
input_data: HookInput, tool_use_id: str | None, context: HookContext
|
|
350
354
|
) -> HookJSONOutput:
|
|
351
355
|
# Test that async hooks properly use async_ and asyncTimeout fields
|
|
352
356
|
return {
|
|
@@ -386,10 +390,72 @@ class TestHookCallbacks:
|
|
|
386
390
|
# The hook result is nested at response.response
|
|
387
391
|
result = response_data["response"]["response"]
|
|
388
392
|
|
|
389
|
-
# The SDK should
|
|
390
|
-
assert result.get("
|
|
393
|
+
# The SDK should convert async_ to "async" for CLI compatibility
|
|
394
|
+
assert result.get("async") is True, "async_ should be converted to async"
|
|
395
|
+
assert "async_" not in result, "async_ should not appear in CLI output"
|
|
391
396
|
assert result.get("asyncTimeout") == 5000
|
|
392
397
|
|
|
398
|
+
@pytest.mark.asyncio
|
|
399
|
+
async def test_field_name_conversion(self):
|
|
400
|
+
"""Test that Python-safe field names (async_, continue_) are converted to CLI format (async, continue)."""
|
|
401
|
+
|
|
402
|
+
async def conversion_test_hook(
|
|
403
|
+
input_data: HookInput, tool_use_id: str | None, context: HookContext
|
|
404
|
+
) -> HookJSONOutput:
|
|
405
|
+
# Return both async_ and continue_ to test conversion
|
|
406
|
+
return {
|
|
407
|
+
"async_": True,
|
|
408
|
+
"asyncTimeout": 10000,
|
|
409
|
+
"continue_": False,
|
|
410
|
+
"stopReason": "Testing field conversion",
|
|
411
|
+
"systemMessage": "Fields should be converted",
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
transport = MockTransport()
|
|
415
|
+
hooks = {"PreToolUse": [{"matcher": None, "hooks": [conversion_test_hook]}]}
|
|
416
|
+
|
|
417
|
+
query = Query(
|
|
418
|
+
transport=transport, is_streaming_mode=True, can_use_tool=None, hooks=hooks
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
callback_id = "test_conversion"
|
|
422
|
+
query.hook_callbacks[callback_id] = conversion_test_hook
|
|
423
|
+
|
|
424
|
+
request = {
|
|
425
|
+
"type": "control_request",
|
|
426
|
+
"request_id": "test-conversion",
|
|
427
|
+
"request": {
|
|
428
|
+
"subtype": "hook_callback",
|
|
429
|
+
"callback_id": callback_id,
|
|
430
|
+
"input": {"test": "data"},
|
|
431
|
+
"tool_use_id": None,
|
|
432
|
+
},
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
await query._handle_control_request(request)
|
|
436
|
+
|
|
437
|
+
# Check response has converted field names
|
|
438
|
+
assert len(transport.written_messages) > 0
|
|
439
|
+
last_response = transport.written_messages[-1]
|
|
440
|
+
|
|
441
|
+
response_data = json.loads(last_response)
|
|
442
|
+
result = response_data["response"]["response"]
|
|
443
|
+
|
|
444
|
+
# Verify async_ was converted to async
|
|
445
|
+
assert result.get("async") is True, "async_ should be converted to async"
|
|
446
|
+
assert "async_" not in result, "async_ should not appear in output"
|
|
447
|
+
|
|
448
|
+
# Verify continue_ was converted to continue
|
|
449
|
+
assert result.get("continue") is False, (
|
|
450
|
+
"continue_ should be converted to continue"
|
|
451
|
+
)
|
|
452
|
+
assert "continue_" not in result, "continue_ should not appear in output"
|
|
453
|
+
|
|
454
|
+
# Verify other fields are unchanged
|
|
455
|
+
assert result.get("asyncTimeout") == 10000
|
|
456
|
+
assert result.get("stopReason") == "Testing field conversion"
|
|
457
|
+
assert result.get("systemMessage") == "Fields should be converted"
|
|
458
|
+
|
|
393
459
|
|
|
394
460
|
class TestClaudeAgentOptionsIntegration:
|
|
395
461
|
"""Test that callbacks work through ClaudeAgentOptions."""
|
|
@@ -403,7 +469,7 @@ class TestClaudeAgentOptionsIntegration:
|
|
|
403
469
|
return PermissionResultAllow()
|
|
404
470
|
|
|
405
471
|
async def my_hook(
|
|
406
|
-
input_data:
|
|
472
|
+
input_data: HookInput, tool_use_id: str | None, context: HookContext
|
|
407
473
|
) -> dict:
|
|
408
474
|
return {}
|
|
409
475
|
|
|
@@ -10,6 +10,15 @@ import pytest
|
|
|
10
10
|
from claude_agent_sdk._internal.transport.subprocess_cli import SubprocessCLITransport
|
|
11
11
|
from claude_agent_sdk.types import ClaudeAgentOptions
|
|
12
12
|
|
|
13
|
+
DEFAULT_CLI_PATH = "/usr/bin/claude"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def make_options(**kwargs: object) -> ClaudeAgentOptions:
|
|
17
|
+
"""Construct options using the standard CLI path unless overridden."""
|
|
18
|
+
|
|
19
|
+
cli_path = kwargs.pop("cli_path", DEFAULT_CLI_PATH)
|
|
20
|
+
return ClaudeAgentOptions(cli_path=cli_path, **kwargs)
|
|
21
|
+
|
|
13
22
|
|
|
14
23
|
class TestSubprocessCLITransport:
|
|
15
24
|
"""Test subprocess transport implementation."""
|
|
@@ -29,9 +38,7 @@ class TestSubprocessCLITransport:
|
|
|
29
38
|
|
|
30
39
|
def test_build_command_basic(self):
|
|
31
40
|
"""Test building basic CLI command."""
|
|
32
|
-
transport = SubprocessCLITransport(
|
|
33
|
-
prompt="Hello", options=ClaudeAgentOptions(), cli_path="/usr/bin/claude"
|
|
34
|
-
)
|
|
41
|
+
transport = SubprocessCLITransport(prompt="Hello", options=make_options())
|
|
35
42
|
|
|
36
43
|
cmd = transport._build_command()
|
|
37
44
|
assert cmd[0] == "/usr/bin/claude"
|
|
@@ -47,8 +54,7 @@ class TestSubprocessCLITransport:
|
|
|
47
54
|
path = Path("/usr/bin/claude")
|
|
48
55
|
transport = SubprocessCLITransport(
|
|
49
56
|
prompt="Hello",
|
|
50
|
-
options=ClaudeAgentOptions(),
|
|
51
|
-
cli_path=path,
|
|
57
|
+
options=ClaudeAgentOptions(cli_path=path),
|
|
52
58
|
)
|
|
53
59
|
|
|
54
60
|
# Path object is converted to string, compare with str(path)
|
|
@@ -58,10 +64,9 @@ class TestSubprocessCLITransport:
|
|
|
58
64
|
"""Test building CLI command with system prompt as string."""
|
|
59
65
|
transport = SubprocessCLITransport(
|
|
60
66
|
prompt="test",
|
|
61
|
-
options=
|
|
67
|
+
options=make_options(
|
|
62
68
|
system_prompt="Be helpful",
|
|
63
69
|
),
|
|
64
|
-
cli_path="/usr/bin/claude",
|
|
65
70
|
)
|
|
66
71
|
|
|
67
72
|
cmd = transport._build_command()
|
|
@@ -72,10 +77,9 @@ class TestSubprocessCLITransport:
|
|
|
72
77
|
"""Test building CLI command with system prompt preset."""
|
|
73
78
|
transport = SubprocessCLITransport(
|
|
74
79
|
prompt="test",
|
|
75
|
-
options=
|
|
80
|
+
options=make_options(
|
|
76
81
|
system_prompt={"type": "preset", "preset": "claude_code"},
|
|
77
82
|
),
|
|
78
|
-
cli_path="/usr/bin/claude",
|
|
79
83
|
)
|
|
80
84
|
|
|
81
85
|
cmd = transport._build_command()
|
|
@@ -86,14 +90,13 @@ class TestSubprocessCLITransport:
|
|
|
86
90
|
"""Test building CLI command with system prompt preset and append."""
|
|
87
91
|
transport = SubprocessCLITransport(
|
|
88
92
|
prompt="test",
|
|
89
|
-
options=
|
|
93
|
+
options=make_options(
|
|
90
94
|
system_prompt={
|
|
91
95
|
"type": "preset",
|
|
92
96
|
"preset": "claude_code",
|
|
93
97
|
"append": "Be concise.",
|
|
94
98
|
},
|
|
95
99
|
),
|
|
96
|
-
cli_path="/usr/bin/claude",
|
|
97
100
|
)
|
|
98
101
|
|
|
99
102
|
cmd = transport._build_command()
|
|
@@ -105,14 +108,13 @@ class TestSubprocessCLITransport:
|
|
|
105
108
|
"""Test building CLI command with options."""
|
|
106
109
|
transport = SubprocessCLITransport(
|
|
107
110
|
prompt="test",
|
|
108
|
-
options=
|
|
111
|
+
options=make_options(
|
|
109
112
|
allowed_tools=["Read", "Write"],
|
|
110
113
|
disallowed_tools=["Bash"],
|
|
111
114
|
model="claude-sonnet-4-5",
|
|
112
115
|
permission_mode="acceptEdits",
|
|
113
116
|
max_turns=5,
|
|
114
117
|
),
|
|
115
|
-
cli_path="/usr/bin/claude",
|
|
116
118
|
)
|
|
117
119
|
|
|
118
120
|
cmd = transport._build_command()
|
|
@@ -135,8 +137,7 @@ class TestSubprocessCLITransport:
|
|
|
135
137
|
dir2 = Path("/path/to/dir2")
|
|
136
138
|
transport = SubprocessCLITransport(
|
|
137
139
|
prompt="test",
|
|
138
|
-
options=
|
|
139
|
-
cli_path="/usr/bin/claude",
|
|
140
|
+
options=make_options(add_dirs=[dir1, dir2]),
|
|
140
141
|
)
|
|
141
142
|
|
|
142
143
|
cmd = transport._build_command()
|
|
@@ -155,10 +156,7 @@ class TestSubprocessCLITransport:
|
|
|
155
156
|
"""Test session continuation options."""
|
|
156
157
|
transport = SubprocessCLITransport(
|
|
157
158
|
prompt="Continue from before",
|
|
158
|
-
options=
|
|
159
|
-
continue_conversation=True, resume="session-123"
|
|
160
|
-
),
|
|
161
|
-
cli_path="/usr/bin/claude",
|
|
159
|
+
options=make_options(continue_conversation=True, resume="session-123"),
|
|
162
160
|
)
|
|
163
161
|
|
|
164
162
|
cmd = transport._build_command()
|
|
@@ -198,8 +196,7 @@ class TestSubprocessCLITransport:
|
|
|
198
196
|
|
|
199
197
|
transport = SubprocessCLITransport(
|
|
200
198
|
prompt="test",
|
|
201
|
-
options=
|
|
202
|
-
cli_path="/usr/bin/claude",
|
|
199
|
+
options=make_options(),
|
|
203
200
|
)
|
|
204
201
|
|
|
205
202
|
await transport.connect()
|
|
@@ -215,9 +212,7 @@ class TestSubprocessCLITransport:
|
|
|
215
212
|
"""Test reading messages from CLI output."""
|
|
216
213
|
# This test is simplified to just test the transport creation
|
|
217
214
|
# The full async stream handling is tested in integration tests
|
|
218
|
-
transport = SubprocessCLITransport(
|
|
219
|
-
prompt="test", options=ClaudeAgentOptions(), cli_path="/usr/bin/claude"
|
|
220
|
-
)
|
|
215
|
+
transport = SubprocessCLITransport(prompt="test", options=make_options())
|
|
221
216
|
|
|
222
217
|
# The transport now just provides raw message reading via read_messages()
|
|
223
218
|
# So we just verify the transport can be created and basic structure is correct
|
|
@@ -231,8 +226,7 @@ class TestSubprocessCLITransport:
|
|
|
231
226
|
async def _test():
|
|
232
227
|
transport = SubprocessCLITransport(
|
|
233
228
|
prompt="test",
|
|
234
|
-
options=
|
|
235
|
-
cli_path="/usr/bin/claude",
|
|
229
|
+
options=make_options(cwd="/this/directory/does/not/exist"),
|
|
236
230
|
)
|
|
237
231
|
|
|
238
232
|
with pytest.raises(CLIConnectionError) as exc_info:
|
|
@@ -246,8 +240,7 @@ class TestSubprocessCLITransport:
|
|
|
246
240
|
"""Test building CLI command with settings as file path."""
|
|
247
241
|
transport = SubprocessCLITransport(
|
|
248
242
|
prompt="test",
|
|
249
|
-
options=
|
|
250
|
-
cli_path="/usr/bin/claude",
|
|
243
|
+
options=make_options(settings="/path/to/settings.json"),
|
|
251
244
|
)
|
|
252
245
|
|
|
253
246
|
cmd = transport._build_command()
|
|
@@ -259,8 +252,7 @@ class TestSubprocessCLITransport:
|
|
|
259
252
|
settings_json = '{"permissions": {"allow": ["Bash(ls:*)"]}}'
|
|
260
253
|
transport = SubprocessCLITransport(
|
|
261
254
|
prompt="test",
|
|
262
|
-
options=
|
|
263
|
-
cli_path="/usr/bin/claude",
|
|
255
|
+
options=make_options(settings=settings_json),
|
|
264
256
|
)
|
|
265
257
|
|
|
266
258
|
cmd = transport._build_command()
|
|
@@ -271,14 +263,13 @@ class TestSubprocessCLITransport:
|
|
|
271
263
|
"""Test building CLI command with extra_args for future flags."""
|
|
272
264
|
transport = SubprocessCLITransport(
|
|
273
265
|
prompt="test",
|
|
274
|
-
options=
|
|
266
|
+
options=make_options(
|
|
275
267
|
extra_args={
|
|
276
268
|
"new-flag": "value",
|
|
277
269
|
"boolean-flag": None,
|
|
278
270
|
"another-option": "test-value",
|
|
279
271
|
}
|
|
280
272
|
),
|
|
281
|
-
cli_path="/usr/bin/claude",
|
|
282
273
|
)
|
|
283
274
|
|
|
284
275
|
cmd = transport._build_command()
|
|
@@ -309,8 +300,7 @@ class TestSubprocessCLITransport:
|
|
|
309
300
|
|
|
310
301
|
transport = SubprocessCLITransport(
|
|
311
302
|
prompt="test",
|
|
312
|
-
options=
|
|
313
|
-
cli_path="/usr/bin/claude",
|
|
303
|
+
options=make_options(mcp_servers=mcp_servers),
|
|
314
304
|
)
|
|
315
305
|
|
|
316
306
|
cmd = transport._build_command()
|
|
@@ -333,8 +323,7 @@ class TestSubprocessCLITransport:
|
|
|
333
323
|
string_path = "/path/to/mcp-config.json"
|
|
334
324
|
transport = SubprocessCLITransport(
|
|
335
325
|
prompt="test",
|
|
336
|
-
options=
|
|
337
|
-
cli_path="/usr/bin/claude",
|
|
326
|
+
options=make_options(mcp_servers=string_path),
|
|
338
327
|
)
|
|
339
328
|
|
|
340
329
|
cmd = transport._build_command()
|
|
@@ -346,8 +335,7 @@ class TestSubprocessCLITransport:
|
|
|
346
335
|
path_obj = Path("/path/to/mcp-config.json")
|
|
347
336
|
transport = SubprocessCLITransport(
|
|
348
337
|
prompt="test",
|
|
349
|
-
options=
|
|
350
|
-
cli_path="/usr/bin/claude",
|
|
338
|
+
options=make_options(mcp_servers=path_obj),
|
|
351
339
|
)
|
|
352
340
|
|
|
353
341
|
cmd = transport._build_command()
|
|
@@ -361,8 +349,7 @@ class TestSubprocessCLITransport:
|
|
|
361
349
|
json_config = '{"mcpServers": {"server": {"type": "stdio", "command": "test"}}}'
|
|
362
350
|
transport = SubprocessCLITransport(
|
|
363
351
|
prompt="test",
|
|
364
|
-
options=
|
|
365
|
-
cli_path="/usr/bin/claude",
|
|
352
|
+
options=make_options(mcp_servers=json_config),
|
|
366
353
|
)
|
|
367
354
|
|
|
368
355
|
cmd = transport._build_command()
|
|
@@ -379,7 +366,7 @@ class TestSubprocessCLITransport:
|
|
|
379
366
|
"MY_TEST_VAR": test_value,
|
|
380
367
|
}
|
|
381
368
|
|
|
382
|
-
options =
|
|
369
|
+
options = make_options(env=custom_env)
|
|
383
370
|
|
|
384
371
|
# Mock the subprocess to capture the env argument
|
|
385
372
|
with patch(
|
|
@@ -408,7 +395,6 @@ class TestSubprocessCLITransport:
|
|
|
408
395
|
transport = SubprocessCLITransport(
|
|
409
396
|
prompt="test",
|
|
410
397
|
options=options,
|
|
411
|
-
cli_path="/usr/bin/claude",
|
|
412
398
|
)
|
|
413
399
|
|
|
414
400
|
await transport.connect()
|
|
@@ -440,7 +426,7 @@ class TestSubprocessCLITransport:
|
|
|
440
426
|
|
|
441
427
|
async def _test():
|
|
442
428
|
custom_user = "claude"
|
|
443
|
-
options =
|
|
429
|
+
options = make_options(user=custom_user)
|
|
444
430
|
|
|
445
431
|
# Mock the subprocess to capture the env argument
|
|
446
432
|
with patch(
|
|
@@ -469,7 +455,6 @@ class TestSubprocessCLITransport:
|
|
|
469
455
|
transport = SubprocessCLITransport(
|
|
470
456
|
prompt="test",
|
|
471
457
|
options=options,
|
|
472
|
-
cli_path="/usr/bin/claude",
|
|
473
458
|
)
|
|
474
459
|
|
|
475
460
|
await transport.connect()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/src/claude_agent_sdk/_internal/__init__.py
RENAMED
|
File without changes
|
{claude_agent_sdk-0.1.2 → claude_agent_sdk-0.1.4}/src/claude_agent_sdk/_internal/message_parser.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|