From 8f12409e6284e3ded44b782549e2b7e556a69c78 Mon Sep 17 00:00:00 2001 From: Husni Adil Makmur Date: Wed, 18 Mar 2026 05:29:35 +0700 Subject: [PATCH] feat(types): add PostCompact hook event support Add PostCompact hook event type, input, and specific output types to match Claude Code CLI v2.1.76+. PostCompact fires after compact operations complete, providing trigger type and compact summary. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/claude_agent_sdk/__init__.py | 4 +++ src/claude_agent_sdk/types.py | 18 ++++++++++ tests/test_tool_callbacks.py | 57 ++++++++++++++++++++++++++++++++ tests/test_types.py | 39 ++++++++++++++++++++++ 4 files changed, 118 insertions(+) diff --git a/src/claude_agent_sdk/__init__.py b/src/claude_agent_sdk/__init__.py index 4e0d91cf..c95645a4 100644 --- a/src/claude_agent_sdk/__init__.py +++ b/src/claude_agent_sdk/__init__.py @@ -70,6 +70,8 @@ PermissionResultAllow, PermissionResultDeny, PermissionUpdate, + PostCompactHookInput, + PostCompactHookSpecificOutput, PostToolUseFailureHookInput, PostToolUseFailureHookSpecificOutput, PostToolUseHookInput, @@ -539,6 +541,8 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> Any: "StopHookInput", "SubagentStopHookInput", "PreCompactHookInput", + "PostCompactHookInput", + "PostCompactHookSpecificOutput", "NotificationHookInput", "SubagentStartHookInput", "PermissionRequestHookInput", diff --git a/src/claude_agent_sdk/types.py b/src/claude_agent_sdk/types.py index c26c859c..2fec62d8 100644 --- a/src/claude_agent_sdk/types.py +++ b/src/claude_agent_sdk/types.py @@ -206,6 +206,7 @@ class PermissionResultDeny: | Literal["Stop"] | Literal["SubagentStop"] | Literal["PreCompact"] + | Literal["PostCompact"] | Literal["Notification"] | Literal["SubagentStart"] | Literal["PermissionRequest"] @@ -309,6 +310,14 @@ class PreCompactHookInput(BaseHookInput): custom_instructions: str | None +class PostCompactHookInput(BaseHookInput): + """Input data for PostCompact hook events.""" + + hook_event_name: Literal["PostCompact"] + trigger: Literal["manual", "auto"] + compact_summary: str + + class NotificationHookInput(BaseHookInput): """Input data for Notification hook events.""" @@ -344,6 +353,7 @@ class PermissionRequestHookInput(BaseHookInput, _SubagentContextMixin): | StopHookInput | SubagentStopHookInput | PreCompactHookInput + | PostCompactHookInput | NotificationHookInput | SubagentStartHookInput | PermissionRequestHookInput @@ -390,6 +400,13 @@ class SessionStartHookSpecificOutput(TypedDict): additionalContext: NotRequired[str] +class PostCompactHookSpecificOutput(TypedDict): + """Hook-specific output for PostCompact events.""" + + hookEventName: Literal["PostCompact"] + additionalContext: NotRequired[str] + + class NotificationHookSpecificOutput(TypedDict): """Hook-specific output for Notification events.""" @@ -417,6 +434,7 @@ class PermissionRequestHookSpecificOutput(TypedDict): | PostToolUseFailureHookSpecificOutput | UserPromptSubmitHookSpecificOutput | SessionStartHookSpecificOutput + | PostCompactHookSpecificOutput | NotificationHookSpecificOutput | SubagentStartHookSpecificOutput | PermissionRequestHookSpecificOutput diff --git a/tests/test_tool_callbacks.py b/tests/test_tool_callbacks.py index 749054b5..9678502b 100644 --- a/tests/test_tool_callbacks.py +++ b/tests/test_tool_callbacks.py @@ -821,6 +821,63 @@ async def pre_tool_hook( ) assert result["hookSpecificOutput"]["permissionDecision"] == "allow" + @pytest.mark.asyncio + async def test_post_compact_hook_callback(self): + """Test that a PostCompact hook callback receives correct input and returns output.""" + hook_calls: list[dict[str, Any]] = [] + + async def post_compact_hook( + input_data: HookInput, tool_use_id: str | None, context: HookContext + ) -> HookJSONOutput: + hook_calls.append({"input": input_data, "tool_use_id": tool_use_id}) + return { + "hookSpecificOutput": { + "hookEventName": "PostCompact", + "additionalContext": "Compaction logged successfully", + } + } + + transport = MockTransport() + query = Query( + transport=transport, is_streaming_mode=True, can_use_tool=None, hooks={} + ) + + callback_id = "test_post_compact_hook" + query.hook_callbacks[callback_id] = post_compact_hook + + request = { + "type": "control_request", + "request_id": "test-post-compact", + "request": { + "subtype": "hook_callback", + "callback_id": callback_id, + "input": { + "session_id": "sess-1", + "transcript_path": "/tmp/t", + "cwd": "/home", + "hook_event_name": "PostCompact", + "trigger": "auto", + "compact_summary": "Conversation summary...", + }, + "tool_use_id": None, + }, + } + + await query._handle_control_request(request) + + assert len(hook_calls) == 1 + assert hook_calls[0]["input"]["hook_event_name"] == "PostCompact" + assert hook_calls[0]["input"]["trigger"] == "auto" + assert hook_calls[0]["input"]["compact_summary"] == "Conversation summary..." + + response_data = json.loads(transport.written_messages[-1]) + result = response_data["response"]["response"] + assert result["hookSpecificOutput"]["hookEventName"] == "PostCompact" + assert ( + result["hookSpecificOutput"]["additionalContext"] + == "Compaction logged successfully" + ) + class TestHookInitializeRegistration: """Test that new hook events can be registered through the initialize flow.""" diff --git a/tests/test_types.py b/tests/test_types.py index 2047514c..5a2e0d1c 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -7,6 +7,8 @@ NotificationHookSpecificOutput, PermissionRequestHookInput, PermissionRequestHookSpecificOutput, + PostCompactHookInput, + PostCompactHookSpecificOutput, ResultMessage, SubagentStartHookInput, SubagentStartHookSpecificOutput, @@ -299,6 +301,34 @@ def test_permission_request_hook_input_with_suggestions(self): } assert len(hook_input["permission_suggestions"]) == 1 + def test_post_compact_hook_input(self): + """Test PostCompactHookInput construction.""" + hook_input: PostCompactHookInput = { + "session_id": "sess-1", + "transcript_path": "/tmp/transcript.jsonl", + "cwd": "/home/user/project", + "hook_event_name": "PostCompact", + "trigger": "auto", + "compact_summary": "Summary of the compacted conversation...", + } + assert hook_input["hook_event_name"] == "PostCompact" + assert hook_input["trigger"] == "auto" + assert ( + hook_input["compact_summary"] == "Summary of the compacted conversation..." + ) + + def test_post_compact_hook_input_manual_trigger(self): + """Test PostCompactHookInput with manual trigger.""" + hook_input: PostCompactHookInput = { + "session_id": "sess-2", + "transcript_path": "/tmp/transcript.jsonl", + "cwd": "/home/user/project", + "hook_event_name": "PostCompact", + "trigger": "manual", + "compact_summary": "User-initiated compaction summary", + } + assert hook_input["trigger"] == "manual" + class TestHookSpecificOutputTypes: """Test hook-specific output type definitions.""" @@ -345,6 +375,15 @@ def test_post_tool_use_output_has_updated_mcp_tool_output(self): } assert output["updatedMCPToolOutput"] == {"result": "modified"} + def test_post_compact_hook_specific_output(self): + """Test PostCompactHookSpecificOutput construction.""" + output: PostCompactHookSpecificOutput = { + "hookEventName": "PostCompact", + "additionalContext": "Compaction complete, summary logged", + } + assert output["hookEventName"] == "PostCompact" + assert output["additionalContext"] == "Compaction complete, summary logged" + class TestMcpServerStatusTypes: """Test MCP server status type definitions."""