Skip to content
Open
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
1 change: 1 addition & 0 deletions src/claude_agent_sdk/_internal/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ async def _handle_control_request(self, request: SDKControlRequest) -> None:
],
tool_use_id=permission_request.get("tool_use_id"),
agent_id=permission_request.get("agent_id"),
agent_type=permission_request.get("agent_type"),
blocked_path=permission_request.get("blocked_path"),
decision_reason=permission_request.get("decision_reason"),
title=permission_request.get("title"),
Expand Down
5 changes: 5 additions & 0 deletions src/claude_agent_sdk/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ class ToolPermissionContext:
field-ordering compatibility, so callers do not need to handle ``None``."""
agent_id: str | None = None
"""If running within the context of a sub-agent, the sub-agent's ID."""
agent_type: str | None = None
"""Agent type name (e.g. "general-purpose", "code-reviewer"). Present
inside a sub-agent (alongside agent_id), or on the main thread of a
session started with --agent (without agent_id)."""
blocked_path: str | None = None
"""The file path that triggered the permission request, if applicable.
For example, when a Bash command tries to access a path outside allowed directories."""
Expand Down Expand Up @@ -2109,6 +2113,7 @@ class SDKControlPermissionRequest(TypedDict):
description: NotRequired[str]
tool_use_id: str
agent_id: NotRequired[str]
agent_type: NotRequired[str]


class SDKControlInitializeRequest(TypedDict):
Expand Down
77 changes: 77 additions & 0 deletions tests/test_tool_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,83 @@ async def capture_callback(
assert received_context.tool_use_id == "toolu_01XYZ789"
assert received_context.agent_id is None

@pytest.mark.anyio
async def test_permission_callback_receives_agent_type(self):
"""Test that agent_type is passed through to the context, alongside agent_id."""
received_context = None

async def capture_callback(
tool_name: str, input_data: dict, context: ToolPermissionContext
) -> PermissionResultAllow:
nonlocal received_context
received_context = context
return PermissionResultAllow()

transport = MockTransport()
query = Query(
transport=transport,
is_streaming_mode=True,
can_use_tool=capture_callback,
hooks=None,
)

request = {
"type": "control_request",
"request_id": "test-agenttype",
"request": {
"subtype": "can_use_tool",
"tool_name": "TestTool",
"input": {},
"permission_suggestions": [],
"tool_use_id": "toolu_01AGENTTYPE",
"agent_id": "agent-456",
"agent_type": "code-reviewer",
},
}

await query._handle_control_request(request)

assert received_context is not None
assert received_context.agent_id == "agent-456"
assert received_context.agent_type == "code-reviewer"

@pytest.mark.anyio
async def test_permission_callback_missing_agent_type(self):
"""Test that agent_type defaults to None when not sent."""
received_context = None

async def capture_callback(
tool_name: str, input_data: dict, context: ToolPermissionContext
) -> PermissionResultAllow:
nonlocal received_context
received_context = context
return PermissionResultAllow()

transport = MockTransport()
query = Query(
transport=transport,
is_streaming_mode=True,
can_use_tool=capture_callback,
hooks=None,
)

request = {
"type": "control_request",
"request_id": "test-noagenttype",
"request": {
"subtype": "can_use_tool",
"tool_name": "TestTool",
"input": {},
"permission_suggestions": [],
"tool_use_id": "toolu_01XYZ789",
},
}

await query._handle_control_request(request)

assert received_context is not None
assert received_context.agent_type is None

@pytest.mark.anyio
async def test_permission_callback_receives_decision_reason(self):
"""Test that decision_reason and permission-display fields are forwarded
Expand Down