diff --git a/examples/tools_option.py b/examples/tools_option.py new file mode 100644 index 00000000..204676f9 --- /dev/null +++ b/examples/tools_option.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +"""Example demonstrating the tools option and verifying tools in system message.""" + +import anyio + +from claude_agent_sdk import ( + AssistantMessage, + ClaudeAgentOptions, + ResultMessage, + SystemMessage, + TextBlock, + query, +) + + +async def tools_array_example(): + """Example with tools as array of specific tool names.""" + print("=== Tools Array Example ===") + print("Setting tools=['Read', 'Glob', 'Grep']") + print() + + options = ClaudeAgentOptions( + tools=["Read", "Glob", "Grep"], + max_turns=1, + ) + + async for message in query( + prompt="What tools do you have available? Just list them briefly.", + options=options, + ): + if isinstance(message, SystemMessage) and message.subtype == "init": + tools = message.data.get("tools", []) + print(f"Tools from system message: {tools}") + print() + elif isinstance(message, AssistantMessage): + for block in message.content: + if isinstance(block, TextBlock): + print(f"Claude: {block.text}") + elif isinstance(message, ResultMessage): + if message.total_cost_usd: + print(f"\nCost: ${message.total_cost_usd:.4f}") + print() + + +async def tools_empty_array_example(): + """Example with tools as empty array (disables all built-in tools).""" + print("=== Tools Empty Array Example ===") + print("Setting tools=[] (disables all built-in tools)") + print() + + options = ClaudeAgentOptions( + tools=[], + max_turns=1, + ) + + async for message in query( + prompt="What tools do you have available? Just list them briefly.", + options=options, + ): + if isinstance(message, SystemMessage) and message.subtype == "init": + tools = message.data.get("tools", []) + print(f"Tools from system message: {tools}") + print() + elif isinstance(message, AssistantMessage): + for block in message.content: + if isinstance(block, TextBlock): + print(f"Claude: {block.text}") + elif isinstance(message, ResultMessage): + if message.total_cost_usd: + print(f"\nCost: ${message.total_cost_usd:.4f}") + print() + + +async def tools_preset_example(): + """Example with tools preset (all default Claude Code tools).""" + print("=== Tools Preset Example ===") + print("Setting tools={'type': 'preset', 'preset': 'claude_code'}") + print() + + options = ClaudeAgentOptions( + tools={"type": "preset", "preset": "claude_code"}, + max_turns=1, + ) + + async for message in query( + prompt="What tools do you have available? Just list them briefly.", + options=options, + ): + if isinstance(message, SystemMessage) and message.subtype == "init": + tools = message.data.get("tools", []) + print(f"Tools from system message ({len(tools)} tools): {tools[:5]}...") + print() + elif isinstance(message, AssistantMessage): + for block in message.content: + if isinstance(block, TextBlock): + print(f"Claude: {block.text}") + elif isinstance(message, ResultMessage): + if message.total_cost_usd: + print(f"\nCost: ${message.total_cost_usd:.4f}") + print() + + +async def main(): + """Run all examples.""" + await tools_array_example() + await tools_empty_array_example() + await tools_preset_example() + + +if __name__ == "__main__": + anyio.run(main) diff --git a/src/claude_agent_sdk/_internal/transport/subprocess_cli.py b/src/claude_agent_sdk/_internal/transport/subprocess_cli.py index 73c1b298..c6f8649f 100644 --- a/src/claude_agent_sdk/_internal/transport/subprocess_cli.py +++ b/src/claude_agent_sdk/_internal/transport/subprocess_cli.py @@ -185,6 +185,18 @@ def _build_command(self) -> list[str]: ["--append-system-prompt", self._options.system_prompt["append"]] ) + # Handle tools option (base set of tools) + if self._options.tools is not None: + tools = self._options.tools + if isinstance(tools, list): + if len(tools) == 0: + cmd.extend(["--tools", ""]) + else: + cmd.extend(["--tools", ",".join(tools)]) + else: + # Preset object - 'claude_code' preset maps to 'default' + cmd.extend(["--tools", "default"]) + if self._options.allowed_tools: cmd.extend(["--allowedTools", ",".join(self._options.allowed_tools)]) diff --git a/src/claude_agent_sdk/types.py b/src/claude_agent_sdk/types.py index f37fd3c9..c8b0528c 100644 --- a/src/claude_agent_sdk/types.py +++ b/src/claude_agent_sdk/types.py @@ -26,6 +26,13 @@ class SystemPromptPreset(TypedDict): append: NotRequired[str] +class ToolsPreset(TypedDict): + """Tools preset configuration.""" + + type: Literal["preset"] + preset: Literal["claude_code"] + + @dataclass class AgentDefinition: """Agent definition configuration.""" @@ -603,6 +610,7 @@ class StreamEvent: class ClaudeAgentOptions: """Query options for Claude SDK.""" + tools: list[str] | ToolsPreset | None = None allowed_tools: list[str] = field(default_factory=list) system_prompt: str | SystemPromptPreset | None = None mcp_servers: dict[str, McpServerConfig] | str | Path = field(default_factory=dict) diff --git a/tests/test_transport.py b/tests/test_transport.py index b834671e..c634fc2e 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -647,3 +647,49 @@ def test_sandbox_network_config(self): assert network["allowLocalBinding"] is True assert network["httpProxyPort"] == 8080 assert network["socksProxyPort"] == 8081 + + def test_build_command_with_tools_array(self): + """Test building CLI command with tools as array of tool names.""" + transport = SubprocessCLITransport( + prompt="test", + options=make_options(tools=["Read", "Edit", "Bash"]), + ) + + cmd = transport._build_command() + assert "--tools" in cmd + tools_idx = cmd.index("--tools") + assert cmd[tools_idx + 1] == "Read,Edit,Bash" + + def test_build_command_with_tools_empty_array(self): + """Test building CLI command with tools as empty array (disables all tools).""" + transport = SubprocessCLITransport( + prompt="test", + options=make_options(tools=[]), + ) + + cmd = transport._build_command() + assert "--tools" in cmd + tools_idx = cmd.index("--tools") + assert cmd[tools_idx + 1] == "" + + def test_build_command_with_tools_preset(self): + """Test building CLI command with tools preset.""" + transport = SubprocessCLITransport( + prompt="test", + options=make_options(tools={"type": "preset", "preset": "claude_code"}), + ) + + cmd = transport._build_command() + assert "--tools" in cmd + tools_idx = cmd.index("--tools") + assert cmd[tools_idx + 1] == "default" + + def test_build_command_without_tools(self): + """Test building CLI command without tools option (default None).""" + transport = SubprocessCLITransport( + prompt="test", + options=make_options(), + ) + + cmd = transport._build_command() + assert "--tools" not in cmd