this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

sync

+550 -110
-110
claudeio/PERMISSIONS.md
··· 1 - # Claude OCaml SDK Permission System 2 - 3 - The Claude OCaml SDK provides a flexible permission system for controlling tool access when Claude attempts to use various tools like Read, Write, Bash, etc. 4 - 5 - ## Overview 6 - 7 - The permission system allows you to: 8 - - Control which tools Claude can use 9 - - Implement custom permission callbacks 10 - - Discover what permissions an operation needs 11 - - Grant or deny permissions dynamically 12 - 13 - ## Permission Modes 14 - 15 - Claude supports several permission modes: 16 - - `Default` - Standard permission checks 17 - - `Accept_edits` - Automatically accept file edits 18 - - `Plan` - Planning mode with restricted execution 19 - - `Bypass_permissions` - Skip all permission checks 20 - 21 - ## Using Permission Callbacks 22 - 23 - ### Basic Callback 24 - 25 - ```ocaml 26 - let my_permission_callback ~tool_name ~input ~context = 27 - match tool_name with 28 - | "Read" -> 29 - (* Always allow reading *) 30 - Claude.Permissions.Result.allow () 31 - | "Write" | "Edit" -> 32 - (* Deny write operations *) 33 - Claude.Permissions.Result.deny 34 - ~message:"Write operations are not allowed" 35 - ~interrupt:false 36 - | _ -> 37 - (* Allow everything else *) 38 - Claude.Permissions.Result.allow () 39 - 40 - let options = Claude.Options.create 41 - ~permission_callback:my_permission_callback 42 - () in 43 - ``` 44 - 45 - ### Interactive Callback 46 - 47 - See `test/simulated_permissions.ml` for an example that prompts the user interactively. 48 - 49 - ### Discovery Mode 50 - 51 - The discovery callback helps you understand what permissions an operation needs: 52 - 53 - ```ocaml 54 - let client = Claude.Client.discover_permissions client in 55 - (* Run operations *) 56 - let discovered = Claude.Client.get_discovered_permissions client in 57 - List.iter (fun rule -> 58 - Printf.printf "Tool needed: %s\n" 59 - (Claude.Permissions.Rule.tool_name rule) 60 - ) discovered 61 - ``` 62 - 63 - ## Permission Results 64 - 65 - Callbacks return permission results that can: 66 - - Allow the operation (optionally with modified input) 67 - - Deny the operation (with a message and optional interrupt) 68 - 69 - ```ocaml 70 - (* Allow with modified input *) 71 - Claude.Permissions.Result.allow 72 - ~updated_input:(Ezjsonm.dict ["sanitized", Ezjsonm.string "data"]) 73 - () 74 - 75 - (* Deny and stop execution *) 76 - Claude.Permissions.Result.deny 77 - ~message:"Security policy violation" 78 - ~interrupt:true 79 - ``` 80 - 81 - ## Tool Restrictions 82 - 83 - You can also restrict tools at the options level: 84 - 85 - ```ocaml 86 - let options = Claude.Options.create 87 - ~allowed_tools:["Read"; "Grep"] (* Only these tools *) 88 - ~disallowed_tools:["Bash"; "Write"] (* Block these *) 89 - () 90 - ``` 91 - 92 - ## Examples 93 - 94 - The `test/` directory contains several examples: 95 - - `simulated_permissions.ml` - Interactive permission demo 96 - - `permission_demo.ml` - Attempts to trigger real permission requests 97 - - `discovery_demo.ml` - Shows permission discovery mode 98 - 99 - ## Important Notes 100 - 101 - 1. **Control Requests**: The permission callback is only invoked when Claude's CLI sends control requests. This typically happens when running with restricted permissions or specific security policies. 102 - 103 - 2. **Default Behavior**: In default mode, Claude may have broad permissions already, so callbacks might not be triggered for every tool use. 104 - 105 - 3. **Testing**: For testing permission callbacks, you can: 106 - - Use the simulated demo to test callback logic 107 - - Run Claude with restricted permissions via CLI flags 108 - - Use discovery mode to understand permission needs 109 - 110 - 4. **Security**: Permission callbacks are a security feature - always validate inputs and be cautious about what operations you allow.
+253
claudeio/TODO.md
··· 1 + # TODO: Missing Features from Python SDK 2 + 3 + ## 1. Hook Support 4 + 5 + ### Overview 6 + Hooks allow users to intercept and modify Claude's behavior at specific points during execution. The Python SDK supports several hook events that are not yet implemented in the OCaml library. 7 + 8 + ### Required Components 9 + 10 + #### Hook Events 11 + ```ocaml 12 + type hook_event = 13 + | Pre_tool_use (* Before a tool is invoked *) 14 + | Post_tool_use (* After a tool completes *) 15 + | User_prompt_submit (* When user submits a prompt *) 16 + | Stop (* When stopping execution *) 17 + | Subagent_stop (* When a subagent stops *) 18 + | Pre_compact (* Before context compaction *) 19 + ``` 20 + 21 + #### Hook Context 22 + ```ocaml 23 + module Hook_context : sig 24 + type t = { 25 + signal : [ `Abort ] option; (* Future: abort signal support *) 26 + } 27 + end 28 + ``` 29 + 30 + #### Hook Output 31 + ```ocaml 32 + module Hook_output : sig 33 + type t = { 34 + decision : [ `Block | `Continue ] option; 35 + system_message : string option; 36 + hook_specific_output : Ezjsonm.value option; 37 + } 38 + end 39 + ``` 40 + 41 + #### Hook Callback 42 + ```ocaml 43 + type hook_callback = 44 + input:Ezjsonm.value -> 45 + tool_use_id:string option -> 46 + context:Hook_context.t -> 47 + Hook_output.t Eio.Promise.t 48 + ``` 49 + 50 + #### Hook Matcher 51 + ```ocaml 52 + module Hook_matcher : sig 53 + type t = { 54 + matcher : string option; (* e.g., "Bash" or "Write|MultiEdit|Edit" *) 55 + hooks : hook_callback list; 56 + } 57 + end 58 + ``` 59 + 60 + ### Implementation Plan 61 + 62 + 1. **Add hook types to a new `lib/hooks.mli` module** 63 + 2. **Integrate hooks into `Options.t`**: 64 + - Add `hooks : (hook_event * Hook_matcher.t list) list` field 65 + 3. **Update `Client` module to handle hook callbacks**: 66 + - Intercept tool use events 67 + - Call registered hooks before/after operations 68 + - Handle hook responses (block, modify, continue) 69 + 4. **Update SDK control protocol** to support hook registration via `SDKControlInitializeRequest` 70 + 71 + ### Usage Example 72 + ```ocaml 73 + let pre_tool_hook ~input ~tool_use_id:_ ~context:_ = 74 + match Ezjsonm.find input ["name"] |> Ezjsonm.get_string with 75 + | "Bash" -> 76 + Eio.Promise.resolve Hook_output.{ 77 + decision = Some `Block; 78 + system_message = Some "Bash commands blocked by hook"; 79 + hook_specific_output = None; 80 + } 81 + | _ -> 82 + Eio.Promise.resolve Hook_output.{ 83 + decision = Some `Continue; 84 + system_message = None; 85 + hook_specific_output = None; 86 + } 87 + 88 + let options = Options.create 89 + ~hooks:[ 90 + Pre_tool_use, [{ 91 + matcher = Some "Bash"; 92 + hooks = [pre_tool_hook] 93 + }] 94 + ] 95 + () 96 + ``` 97 + 98 + ## 2. MCP (Model Context Protocol) Server Support 99 + 100 + ### Overview 101 + MCP servers allow Claude to interact with external services and tools. The Python SDK supports multiple MCP server configurations. 102 + 103 + ### Required Components 104 + 105 + #### MCP Server Types 106 + ```ocaml 107 + module Mcp_server : sig 108 + type stdio_config = { 109 + command : string; 110 + args : string list option; 111 + env : (string * string) list option; 112 + } 113 + 114 + type sse_config = { 115 + url : string; 116 + headers : (string * string) list option; 117 + } 118 + 119 + type http_config = { 120 + url : string; 121 + headers : (string * string) list option; 122 + } 123 + 124 + type sdk_config = { 125 + name : string; 126 + (* In OCaml, we'd need to define an MCP server interface *) 127 + instance : mcp_server; 128 + } 129 + 130 + and mcp_server = < 131 + (* MCP server methods would go here *) 132 + > 133 + 134 + type config = 135 + | Stdio of stdio_config 136 + | SSE of sse_config 137 + | HTTP of http_config 138 + | SDK of sdk_config 139 + end 140 + ``` 141 + 142 + ### Implementation Plan 143 + 144 + 1. **Create `lib/mcp.mli` module** with server configuration types 145 + 2. **Add MCP support to `Options.t`**: 146 + - Add `mcp_servers : (string * Mcp_server.config) list` field 147 + 3. **Create MCP transport layer**: 148 + - Stdio: Use Eio.Process for subprocess communication 149 + - SSE: Use Cohttp and event stream parsing 150 + - HTTP: Use Cohttp for REST API calls 151 + - SDK: Direct OCaml object interface 152 + 4. **Update SDK control protocol** to handle `SDKControlMcpMessageRequest` 153 + 5. **Implement MCP message routing** in Client module 154 + 155 + ### Usage Example 156 + ```ocaml 157 + let stdio_server = Mcp_server.Stdio { 158 + command = "calculator-server"; 159 + args = Some ["--mode", "advanced"]; 160 + env = None; 161 + } 162 + 163 + let http_server = Mcp_server.HTTP { 164 + url = "https://api.example.com/mcp"; 165 + headers = Some [("Authorization", "Bearer token")]; 166 + } 167 + 168 + let options = Options.create 169 + ~mcp_servers:[ 170 + "calculator", stdio_server; 171 + "api", http_server; 172 + ] 173 + () 174 + ``` 175 + 176 + ### MCP Message Flow 177 + 178 + 1. Claude requests tool use from MCP server 179 + 2. Client sends `mcp_message` control request 180 + 3. SDK routes message to appropriate MCP server 181 + 4. MCP server responds with result 182 + 5. Client forwards result back to Claude 183 + 184 + ## 3. Integration with SDK Control Protocol 185 + 186 + Both hooks and MCP will require updates to the SDK control protocol: 187 + 188 + ### Control Request Types 189 + ```ocaml 190 + module Sdk_control : sig 191 + type interrupt_request = { 192 + subtype : [`Interrupt]; 193 + } 194 + 195 + type permission_request = { 196 + subtype : [`Can_use_tool]; 197 + tool_name : string; 198 + input : Ezjsonm.value; 199 + permission_suggestions : Permissions.Update.t list option; 200 + blocked_path : string option; 201 + } 202 + 203 + type initialize_request = { 204 + subtype : [`Initialize]; 205 + hooks : (hook_event * Ezjsonm.value) list option; 206 + } 207 + 208 + type set_permission_mode_request = { 209 + subtype : [`Set_permission_mode]; 210 + mode : Permissions.Mode.t; 211 + } 212 + 213 + type hook_callback_request = { 214 + subtype : [`Hook_callback]; 215 + callback_id : string; 216 + input : Ezjsonm.value; 217 + tool_use_id : string option; 218 + } 219 + 220 + type mcp_message_request = { 221 + subtype : [`Mcp_message]; 222 + server_name : string; 223 + message : Ezjsonm.value; 224 + } 225 + 226 + type request = 227 + | Interrupt of interrupt_request 228 + | Permission of permission_request 229 + | Initialize of initialize_request 230 + | Set_permission_mode of set_permission_mode_request 231 + | Hook_callback of hook_callback_request 232 + | Mcp_message of mcp_message_request 233 + end 234 + ``` 235 + 236 + ## Implementation Priority 237 + 238 + 1. **Phase 1**: Implement typed SDK control protocol (prerequisite for both) 239 + 2. **Phase 2**: Implement hook support (simpler, self-contained) 240 + 3. **Phase 3**: Implement MCP server support (requires external dependencies) 241 + 242 + ## Testing Strategy 243 + 244 + ### Hooks 245 + - Unit tests for hook registration and matching 246 + - Integration tests with mock tool invocations 247 + - Test hook blocking, modification, and pass-through scenarios 248 + 249 + ### MCP 250 + - Unit tests for configuration parsing 251 + - Mock MCP server for integration testing 252 + - Test different transport types (stdio, HTTP, SSE) 253 + - Test message routing and error handling
+112
claudeio/test/TEST.md
··· 1 + # Claude Library Architecture Summary 2 + 3 + This document summarizes the architecture of the OCaml Eio Claude library located in `../lib`. 4 + 5 + ## Overview 6 + 7 + The Claude library is a high-quality OCaml Eio wrapper around the Claude Code CLI that provides structured JSON streaming communication with Claude. It follows a clean layered architecture with strong typing and comprehensive error handling. 8 + 9 + ## Core Architecture 10 + 11 + The library is organized into several focused modules that work together to provide a complete Claude integration: 12 + 13 + ### 1. Transport Layer (`Transport`) 14 + - **Purpose**: Low-level CLI process management and communication 15 + - **Key Functions**: 16 + - Spawns and manages the `claude` CLI process using Eio's process manager 17 + - Handles bidirectional JSON streaming via stdin/stdout 18 + - Provides `send`/`receive_line` primitives with proper resource cleanup 19 + - **Integration**: Forms the foundation for all Claude communication 20 + 21 + ### 2. Message Protocol Layer 22 + 23 + #### Content Blocks (`Content_block`) 24 + - **Purpose**: Defines the building blocks of Claude messages 25 + - **Types**: Text, Tool_use, Tool_result, Thinking blocks 26 + - **Key Features**: Each block type has specialized accessors and JSON serialization 27 + - **Integration**: Used by messages to represent diverse content types 28 + 29 + #### Messages (`Message`) 30 + - **Purpose**: Structured message types for Claude communication 31 + - **Types**: User, Assistant, System, Result messages 32 + - **Key Features**: 33 + - User messages support both simple strings and complex content blocks 34 + - Assistant messages include model info and mixed content 35 + - System messages handle session control 36 + - Result messages provide conversation metadata and usage stats 37 + - **Integration**: Primary data structures exchanged between client and Claude 38 + 39 + #### Control Messages (`Control`) 40 + - **Purpose**: Session management and control flow 41 + - **Key Features**: Request IDs, subtypes, and arbitrary JSON data payload 42 + - **Integration**: Used for session initialization, cancellation, and other operational commands 43 + 44 + ### 3. Permission System (`Permissions`) 45 + - **Purpose**: Fine-grained control over Claude's tool usage 46 + - **Components**: 47 + - **Modes**: Default, Accept_edits, Plan, Bypass_permissions 48 + - **Rules**: Tool-specific permission specifications 49 + - **Callbacks**: Custom permission logic with context and suggestions 50 + - **Results**: Allow/Deny decisions with optional modifications 51 + - **Integration**: Consulted by client before allowing tool invocations 52 + 53 + ### 4. Configuration (`Options`) 54 + - **Purpose**: Session configuration and behavior control 55 + - **Features**: 56 + - Tool allow/disallow lists 57 + - System prompt customization (replace or append) 58 + - Model selection and thinking token limits 59 + - Working directory and environment variables 60 + - **Integration**: Passed to transport layer and used throughout the session 61 + - **Pattern**: Builder pattern with `with_*` functions for immutable updates 62 + 63 + ### 5. Client Interface (`Client`) 64 + - **Purpose**: High-level API for Claude interactions 65 + - **Key Functions**: 66 + - Session creation and management 67 + - Message sending (`query`, `send_message`, `send_user_message`) 68 + - Response streaming (`receive`, `receive_all`) 69 + - Permission discovery and callback management 70 + - **Integration**: Orchestrates all other modules to provide the main user API 71 + 72 + ### 6. Main Module (`Claude`) 73 + - **Purpose**: Public API facade with comprehensive documentation 74 + - **Features**: 75 + - Re-exports all sub-modules 76 + - Extensive usage examples and architectural documentation 77 + - Logging configuration guidance 78 + - **Integration**: Single entry point for library users 79 + 80 + ## Data Flow 81 + 82 + 1. **Configuration**: Options are created with desired settings 83 + 2. **Transport**: Client creates transport layer with CLI process 84 + 3. **Message Exchange**: 85 + - User messages are sent via JSON streaming 86 + - Claude responses are received as streaming JSON 87 + - Messages are parsed into strongly-typed structures 88 + 4. **Permission Checking**: Tool usage is filtered through permission system 89 + 5. **Content Processing**: Response content blocks are extracted and processed 90 + 6. **Session Management**: Control messages handle session lifecycle 91 + 92 + ## Key Design Principles 93 + 94 + - **Eio Integration**: Native use of Eio's concurrency primitives (Switch, Process.mgr) 95 + - **Type Safety**: Comprehensive typing with specific error exceptions 96 + - **Streaming**: Efficient processing via `Message.t Seq.t` sequences 97 + - **Modularity**: Clear separation of concerns with minimal inter-dependencies 98 + - **Documentation**: Extensive interface documentation with usage examples 99 + - **Error Handling**: Specific exception types for different failure modes 100 + - **Logging**: Structured logging with per-module sources using the Logs library 101 + 102 + ## Usage Patterns 103 + 104 + The library supports both simple text queries and complex multi-turn conversations: 105 + 106 + - **Simple Queries**: `Client.query` with text input 107 + - **Tool Control**: Permission callbacks and allow/disallow lists 108 + - **Streaming**: Process responses as they arrive via sequences 109 + - **Session Management**: Full control over Claude's execution environment 110 + - **Custom Prompts**: System prompt replacement and augmentation 111 + 112 + The architecture enables fine-grained control over Claude's capabilities while maintaining ease of use for common scenarios.
+185
claudeio/test/permission_demo.py
··· 1 + #!/usr/bin/env python3 2 + # /// script 3 + # requires-python = ">=3.9" 4 + # dependencies = [ 5 + # "claude-code-sdk", 6 + # ] 7 + # /// 8 + """ 9 + Permission demo for Claude Code SDK Python. 10 + Demonstrates how the permission callback system works. 11 + """ 12 + 13 + import asyncio 14 + import sys 15 + import logging 16 + from typing import Any, Dict 17 + 18 + from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions 19 + from claude_code_sdk.types import ( 20 + PermissionResultAllow, 21 + PermissionResultDeny, 22 + ToolPermissionContext, 23 + ) 24 + 25 + # Set up logging 26 + logging.basicConfig( 27 + level=logging.INFO, 28 + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' 29 + ) 30 + logger = logging.getLogger(__name__) 31 + 32 + # Track granted permissions 33 + granted_permissions = set() 34 + 35 + 36 + async def interactive_permission_callback( 37 + tool_name: str, 38 + tool_input: Dict[str, Any], 39 + context: ToolPermissionContext 40 + ) -> PermissionResultAllow | PermissionResultDeny: 41 + """Interactive permission callback that asks user for permission.""" 42 + 43 + logger.info(f"🔔 Permission callback invoked for tool: {tool_name}") 44 + print(f"\n🔐 PERMISSION REQUEST 🔐") 45 + print(f"Tool: {tool_name}") 46 + 47 + # Log the full input for debugging 48 + logger.info(f"Full input: {tool_input}") 49 + 50 + # Show input details 51 + try: 52 + if tool_name == "Read": 53 + file_path = tool_input.get("file_path", "") 54 + print(f"File: {file_path}") 55 + elif tool_name == "Bash": 56 + command = tool_input.get("command", "") 57 + print(f"Command: {command}") 58 + elif tool_name in ["Write", "Edit"]: 59 + file_path = tool_input.get("file_path", "") 60 + print(f"File: {file_path}") 61 + elif tool_name == "Glob": 62 + pattern = tool_input.get("pattern", "") 63 + path = tool_input.get("path", "(current directory)") 64 + print(f"Pattern: {pattern}") 65 + print(f"Path: {path}") 66 + elif tool_name == "Grep": 67 + pattern = tool_input.get("pattern", "") 68 + path = tool_input.get("path", "(current directory)") 69 + print(f"Pattern: {pattern}") 70 + print(f"Path: {path}") 71 + else: 72 + print(f"Input: {tool_input}") 73 + except Exception as e: 74 + logger.info(f"Failed to parse input details: {e}") 75 + 76 + # Check if already granted 77 + if tool_name in granted_permissions: 78 + print("→ Auto-approved (previously granted)") 79 + logger.info(f"Returning allow result for {tool_name}") 80 + return PermissionResultAllow() 81 + 82 + # Ask user 83 + response = input("Allow? [y/N/always]: ").lower().strip() 84 + 85 + if response in ["y", "yes"]: 86 + print("→ Allowed (this time only)") 87 + logger.info(f"User approved {tool_name} for this request only") 88 + return PermissionResultAllow() 89 + elif response in ["a", "always"]: 90 + granted_permissions.add(tool_name) 91 + print(f"✅ Permission granted for: {tool_name}") 92 + logger.info(f"User granted permanent permission for {tool_name}") 93 + return PermissionResultAllow() 94 + else: 95 + print(f"❌ Permission denied for: {tool_name}") 96 + logger.info(f"User denied permission for {tool_name}") 97 + return PermissionResultDeny( 98 + message=f"User denied access to {tool_name}", 99 + interrupt=False 100 + ) 101 + 102 + 103 + async def run_demo(): 104 + """Run the permission demo.""" 105 + print("🚀 Starting Permission Demo") 106 + print("==================================") 107 + print("This demo starts with NO permissions.") 108 + print("Claude will request permissions as needed.\n") 109 + 110 + # Create options with custom permission callback 111 + # Test WITHOUT allowed_tools to see if permission requests come through 112 + options = ClaudeCodeOptions( 113 + model="sonnet", 114 + # allowed_tools=["Read", "Write", "Bash", "Edit", "Glob", "Grep"], 115 + can_use_tool=interactive_permission_callback, 116 + ) 117 + 118 + async with ClaudeSDKClient(options=options) as client: 119 + # First prompt - Claude will need to request Read permission 120 + print("\n📤 Sending first prompt (reading from ../lib)...") 121 + messages = [] 122 + await client.query( 123 + "Please read and analyze the source files in the ../lib directory. " 124 + "Focus on the main OCaml modules and their purpose. " 125 + "What is the overall architecture of this Claude library?" 126 + ) 127 + 128 + async for msg in client.receive_response(): 129 + messages.append(msg) 130 + if hasattr(msg, 'content'): 131 + if isinstance(msg.content, str): 132 + print(f"\n📝 Claude says:\n{msg.content}") 133 + elif isinstance(msg.content, list): 134 + for block in msg.content: 135 + if hasattr(block, 'text'): 136 + print(f"\n📝 Claude says:\n{block.text}") 137 + 138 + # Show current permissions 139 + print("\n📋 Current permission status:") 140 + if granted_permissions: 141 + print(f"Currently granted permissions: {', '.join(granted_permissions)}") 142 + else: 143 + print("No permissions granted yet") 144 + 145 + # Second prompt - will need Write permission 146 + print("\n📤 Sending second prompt (writing TEST.md)...") 147 + await client.query( 148 + "Now write a summary of what you learned about the Claude library " 149 + "architecture to a file called TEST.md in the current directory. " 150 + "Include the main modules, their purposes, and how they work together." 151 + ) 152 + 153 + async for msg in client.receive_response(): 154 + if hasattr(msg, 'content'): 155 + if isinstance(msg.content, str): 156 + print(f"\n📝 Claude says:\n{msg.content}") 157 + elif isinstance(msg.content, list): 158 + for block in msg.content: 159 + if hasattr(block, 'text'): 160 + print(f"\n📝 Claude says:\n{block.text}") 161 + 162 + # Show final permissions 163 + print("\n📋 Final permission status:") 164 + if granted_permissions: 165 + print(f"Currently granted permissions: {', '.join(granted_permissions)}") 166 + else: 167 + print("No permissions granted yet") 168 + 169 + print("\n==================================") 170 + print("✨ Demo complete!") 171 + 172 + 173 + async def main(): 174 + """Main entry point.""" 175 + try: 176 + await run_demo() 177 + except KeyboardInterrupt: 178 + print("\n\nDemo interrupted by user.") 179 + except Exception as e: 180 + logger.error(f"Error in demo: {e}", exc_info=True) 181 + sys.exit(1) 182 + 183 + 184 + if __name__ == "__main__": 185 + asyncio.run(main())