Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions examples/tools_option.py
Original file line number Diff line number Diff line change
@@ -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)
12 changes: 12 additions & 0 deletions src/claude_agent_sdk/_internal/transport/subprocess_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)])

Expand Down
8 changes: 8 additions & 0 deletions src/claude_agent_sdk/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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)
Expand Down
46 changes: 46 additions & 0 deletions tests/test_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -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