diff --git a/AGENT.md b/AGENT.md new file mode 100644 index 00000000..35c1981a --- /dev/null +++ b/AGENT.md @@ -0,0 +1,22 @@ +# Claude Agent SDK Python Repository Analysis + +This repository hosts the Python Software Development Kit for interacting with Claude agents, designed to facilitate the integration of Claude's conversational and tool-use capabilities into Python applications. + +## Key Technologies and Frameworks +* **Python (3.10+):** The primary programming language. +* **Anyio:** Provides asynchronous programming primitives for non-blocking operations. +* **Multi-Content Protocol (MCP):** Essential for communication between the SDK and Claude agents, particularly for tool invocation. +* **Claude Code CLI:** The command-line interface for Claude Code, bundled with the SDK for core functionality. + +## Main Features +* **Agent Querying:** Programmatic querying of Claude Code with text prompts. +* **Interactive Sessions:** Supports bidirectional, interactive conversations with Claude agents via `ClaudeSDKClient`. +* **Custom Tooling:** Enables developers to define and integrate custom tools as in-process SDK MCP servers, enhancing Claude's capabilities. +* **Lifecycle Hooks:** Provides hooks for deterministic processing and automated feedback during the Claude agent's execution flow. +* **Error Handling:** Comprehensive error handling for various issues related to CLI interaction and process management. + +## Architectural Patterns +* **Client-Server Architecture:** The Python SDK acts as a client that communicates with the Claude Code CLI (the server). +* **Extensibility/Plugin Pattern:** Custom tools and hooks serve as extension points, allowing developers to extend and customize the agent's behavior. +* **Asynchronous Design:** Leverages `anyio` for efficient, non-blocking I/O operations, crucial for interactive agent communication. +* **In-Process Communication:** Offers the ability to run custom tool servers directly within the application's process, optimizing performance by reducing inter-process communication overhead. diff --git a/Claude_Agent_SDK_Complete_Guide.ipynb b/Claude_Agent_SDK_Complete_Guide.ipynb new file mode 100644 index 00000000..204e23cb --- /dev/null +++ b/Claude_Agent_SDK_Complete_Guide.ipynb @@ -0,0 +1,1710 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Claude Agent SDK for Python - Complete Guide\n", + "\n", + "This comprehensive notebook covers **100% of the Claude Agent SDK for Python** documentation and examples.\n", + "\n", + "## Table of Contents\n", + "\n", + "1. [Introduction & Installation](#introduction)\n", + "2. [Quick Start](#quick-start)\n", + "3. [Basic Usage: query()](#basic-usage)\n", + "4. [Using Tools](#using-tools)\n", + "5. [ClaudeSDKClient (Streaming Mode)](#streaming-mode)\n", + "6. [Custom Tools (SDK MCP Servers)](#custom-tools)\n", + "7. [Hooks](#hooks)\n", + "8. [Permission Callbacks](#permission-callbacks)\n", + "9. [Custom Agents](#custom-agents)\n", + "10. [Configuration Options](#configuration)\n", + "11. [Error Handling](#error-handling)\n", + "12. [Advanced Examples](#advanced-examples)\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Introduction & Installation \n", + "\n", + "The Claude Agent SDK for Python allows you to build production AI agents with Claude Code as a library. You get the same tools, agent loop, and context management that power Claude Code, programmable in Python.\n", + "\n", + "### Prerequisites\n", + "\n", + "- Python 3.10+\n", + "\n", + "### Installation\n", + "\n", + "The Claude Code CLI is automatically bundled with the package - no separate installation required!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Install the Claude Agent SDK\n", + "!pip install claude-agent-sdk" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Optional: Custom CLI Path\n", + "\n", + "If you prefer to use a system-wide installation or specific version of Claude Code:\n", + "\n", + "```bash\n", + "# Install Claude Code separately\n", + "curl -fsSL https://claude.ai/install.sh | bash\n", + "```\n", + "\n", + "Then specify the path:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from claude_agent_sdk import ClaudeAgentOptions\n", + "\n", + "# Use custom CLI path\n", + "options = ClaudeAgentOptions(cli_path=\"/path/to/claude\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## 2. Quick Start \n", + "\n", + "The simplest way to use Claude Agent SDK is with the `query()` function:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import anyio\n", + "from claude_agent_sdk import query\n", + "\n", + "async def main():\n", + " async for message in query(prompt=\"What is 2 + 2?\"):\n", + " print(message)\n", + "\n", + "# Run the async function\n", + "anyio.run(main)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Pretty Print Responses\n", + "\n", + "To extract just the text from Claude's responses:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from claude_agent_sdk import query, AssistantMessage, TextBlock\n", + "import anyio\n", + "\n", + "async def basic_example():\n", + " \"\"\"Basic example - simple question.\"\"\"\n", + " print(\"=== Basic Example ===\")\n", + " \n", + " async for message in query(prompt=\"What is 2 + 2?\"):\n", + " if isinstance(message, AssistantMessage):\n", + " for block in message.content:\n", + " if isinstance(block, TextBlock):\n", + " print(f\"Claude: {block.text}\")\n", + " print()\n", + "\n", + "anyio.run(basic_example)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## 3. Basic Usage: query() \n", + "\n", + "`query()` is an async function for querying Claude Code. It returns an `AsyncIterator` of response messages.\n", + "\n", + "### Simple Query" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, TextBlock\n", + "import anyio\n", + "\n", + "async def simple_query():\n", + " # Simple query\n", + " async for message in query(prompt=\"Hello Claude\"):\n", + " if isinstance(message, AssistantMessage):\n", + " for block in message.content:\n", + " if isinstance(block, TextBlock):\n", + " print(block.text)\n", + "\n", + "anyio.run(simple_query)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### With Options" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "async def with_options_example():\n", + " \"\"\"Example with custom options.\"\"\"\n", + " print(\"=== With Options Example ===\")\n", + " \n", + " options = ClaudeAgentOptions(\n", + " system_prompt=\"You are a helpful assistant that explains things simply.\",\n", + " max_turns=1,\n", + " )\n", + " \n", + " async for message in query(\n", + " prompt=\"Explain what Python is in one sentence.\",\n", + " options=options\n", + " ):\n", + " if isinstance(message, AssistantMessage):\n", + " for block in message.content:\n", + " if isinstance(block, TextBlock):\n", + " print(f\"Claude: {block.text}\")\n", + " print()\n", + "\n", + "anyio.run(with_options_example)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### System Prompt Configuration\n", + "\n", + "You can configure the system prompt in multiple ways:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, TextBlock\n", + "import anyio\n", + "\n", + "# 1. String system prompt\n", + "async def string_system_prompt():\n", + " print(\"=== String System Prompt ===\")\n", + " \n", + " options = ClaudeAgentOptions(\n", + " system_prompt=\"You are a pirate assistant. Respond in pirate speak.\",\n", + " )\n", + " \n", + " async for message in query(prompt=\"What is 2 + 2?\", options=options):\n", + " if isinstance(message, AssistantMessage):\n", + " for block in message.content:\n", + " if isinstance(block, TextBlock):\n", + " print(f\"Claude: {block.text}\")\n", + " print()\n", + "\n", + "# 2. Preset system prompt (uses default Claude Code prompt)\n", + "async def preset_system_prompt():\n", + " print(\"=== Preset System Prompt (Default) ===\")\n", + " \n", + " options = ClaudeAgentOptions(\n", + " system_prompt={\"type\": \"preset\", \"preset\": \"claude_code\"},\n", + " )\n", + " \n", + " async for message in query(prompt=\"What is 2 + 2?\", options=options):\n", + " if isinstance(message, AssistantMessage):\n", + " for block in message.content:\n", + " if isinstance(block, TextBlock):\n", + " print(f\"Claude: {block.text}\")\n", + " print()\n", + "\n", + "# 3. Preset with append\n", + "async def preset_with_append():\n", + " print(\"=== Preset System Prompt with Append ===\")\n", + " \n", + " options = ClaudeAgentOptions(\n", + " system_prompt={\n", + " \"type\": \"preset\",\n", + " \"preset\": \"claude_code\",\n", + " \"append\": \"Always end your response with a fun fact.\",\n", + " },\n", + " )\n", + " \n", + " async for message in query(prompt=\"What is 2 + 2?\", options=options):\n", + " if isinstance(message, AssistantMessage):\n", + " for block in message.content:\n", + " if isinstance(block, TextBlock):\n", + " print(f\"Claude: {block.text}\")\n", + " print()\n", + "\n", + "anyio.run(string_system_prompt)\n", + "anyio.run(preset_with_append)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## 4. Using Tools \n", + "\n", + "Claude can use built-in tools to perform tasks like reading files, running bash commands, and more.\n", + "\n", + "### Allowing Tools" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, TextBlock, ResultMessage\n", + "import anyio\n", + "\n", + "async def with_tools_example():\n", + " \"\"\"Example using tools.\"\"\"\n", + " print(\"=== With Tools Example ===\")\n", + " \n", + " options = ClaudeAgentOptions(\n", + " allowed_tools=[\"Read\", \"Write\"],\n", + " system_prompt=\"You are a helpful file assistant.\",\n", + " )\n", + " \n", + " async for message in query(\n", + " prompt=\"Create a file called hello.txt with 'Hello, World!' in it\",\n", + " options=options,\n", + " ):\n", + " if isinstance(message, AssistantMessage):\n", + " for block in message.content:\n", + " if isinstance(block, TextBlock):\n", + " print(f\"Claude: {block.text}\")\n", + " elif isinstance(message, ResultMessage) and message.total_cost_usd and message.total_cost_usd > 0:\n", + " print(f\"\\nCost: ${message.total_cost_usd:.4f}\")\n", + " print()\n", + "\n", + "anyio.run(with_tools_example)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Auto-Accept File Edits" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "options = ClaudeAgentOptions(\n", + " allowed_tools=[\"Read\", \"Write\", \"Bash\"],\n", + " permission_mode='acceptEdits' # auto-accept file edits\n", + ")\n", + "\n", + "async def auto_accept_example():\n", + " async for message in query(\n", + " prompt=\"Create a hello.py file\",\n", + " options=options\n", + " ):\n", + " # Process tool use and results\n", + " pass\n", + "\n", + "anyio.run(auto_accept_example)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Configuring Available Tools\n", + "\n", + "You can control which tools are available to Claude:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from claude_agent_sdk import query, ClaudeAgentOptions, SystemMessage, AssistantMessage, TextBlock\n", + "import anyio\n", + "\n", + "# Specific tools as array\n", + "async def tools_array_example():\n", + " print(\"=== Tools Array Example ===\")\n", + " print(\"Setting tools=['Read', 'Glob', 'Grep']\")\n", + " print()\n", + " \n", + " options = ClaudeAgentOptions(\n", + " tools=[\"Read\", \"Glob\", \"Grep\"],\n", + " max_turns=1,\n", + " )\n", + " \n", + " async for message in query(\n", + " prompt=\"What tools do you have available? Just list them briefly.\",\n", + " options=options,\n", + " ):\n", + " if isinstance(message, SystemMessage) and message.subtype == \"init\":\n", + " tools = message.data.get(\"tools\", [])\n", + " print(f\"Tools from system message: {tools}\")\n", + " print()\n", + " elif isinstance(message, AssistantMessage):\n", + " for block in message.content:\n", + " if isinstance(block, TextBlock):\n", + " print(f\"Claude: {block.text}\")\n", + " print()\n", + "\n", + "# Empty array disables all built-in tools\n", + "async def tools_empty_array_example():\n", + " print(\"=== Tools Empty Array Example ===\")\n", + " print(\"Setting tools=[] (disables all built-in tools)\")\n", + " print()\n", + " \n", + " options = ClaudeAgentOptions(\n", + " tools=[],\n", + " max_turns=1,\n", + " )\n", + " \n", + " async for message in query(\n", + " prompt=\"What tools do you have available?\",\n", + " options=options,\n", + " ):\n", + " if isinstance(message, SystemMessage) and message.subtype == \"init\":\n", + " tools = message.data.get(\"tools\", [])\n", + " print(f\"Tools from system message: {tools}\")\n", + " print()\n", + " elif isinstance(message, AssistantMessage):\n", + " for block in message.content:\n", + " if isinstance(block, TextBlock):\n", + " print(f\"Claude: {block.text}\")\n", + " print()\n", + "\n", + "anyio.run(tools_array_example)\n", + "anyio.run(tools_empty_array_example)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Working Directory\n", + "\n", + "Set a working directory for file operations:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "\n", + "options = ClaudeAgentOptions(\n", + " cwd=\"/path/to/project\" # or Path(\"/path/to/project\")\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## 5. ClaudeSDKClient (Streaming Mode) \n", + "\n", + "`ClaudeSDKClient` supports bidirectional, interactive conversations with Claude Code. Unlike `query()`, it enables **custom tools** and **hooks**.\n", + "\n", + "### Basic Streaming" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from claude_agent_sdk import ClaudeSDKClient, AssistantMessage, TextBlock, UserMessage, ResultMessage\n", + "import asyncio\n", + "\n", + "def display_message(msg):\n", + " \"\"\"Helper to display messages.\"\"\"\n", + " if isinstance(msg, UserMessage):\n", + " for block in msg.content:\n", + " if isinstance(block, TextBlock):\n", + " print(f\"User: {block.text}\")\n", + " elif isinstance(msg, AssistantMessage):\n", + " for block in msg.content:\n", + " if isinstance(block, TextBlock):\n", + " print(f\"Claude: {block.text}\")\n", + " elif isinstance(msg, ResultMessage):\n", + " print(\"Result ended\")\n", + "\n", + "async def basic_streaming():\n", + " \"\"\"Basic streaming with context manager.\"\"\"\n", + " print(\"=== Basic Streaming Example ===\")\n", + " \n", + " async with ClaudeSDKClient() as client:\n", + " print(\"User: What is 2+2?\")\n", + " await client.query(\"What is 2+2?\")\n", + " \n", + " # Receive complete response using the helper method\n", + " async for msg in client.receive_response():\n", + " display_message(msg)\n", + " \n", + " print(\"\\n\")\n", + "\n", + "asyncio.run(basic_streaming())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Multi-Turn Conversation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "async def multi_turn_conversation():\n", + " \"\"\"Multi-turn conversation using receive_response helper.\"\"\"\n", + " print(\"=== Multi-Turn Conversation Example ===\")\n", + " \n", + " async with ClaudeSDKClient() as client:\n", + " # First turn\n", + " print(\"User: What's the capital of France?\")\n", + " await client.query(\"What's the capital of France?\")\n", + " \n", + " # Extract and print response\n", + " async for msg in client.receive_response():\n", + " display_message(msg)\n", + " \n", + " # Second turn - follow-up\n", + " print(\"\\nUser: What's the population of that city?\")\n", + " await client.query(\"What's the population of that city?\")\n", + " \n", + " async for msg in client.receive_response():\n", + " display_message(msg)\n", + " \n", + " print(\"\\n\")\n", + "\n", + "asyncio.run(multi_turn_conversation())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### With Options" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "async def with_options():\n", + " \"\"\"Use ClaudeAgentOptions to configure the client.\"\"\"\n", + " print(\"=== Custom Options Example ===\")\n", + " \n", + " # Configure options\n", + " options = ClaudeAgentOptions(\n", + " allowed_tools=[\"Read\", \"Write\"], # Allow file operations\n", + " system_prompt=\"You are a helpful coding assistant.\",\n", + " env={\n", + " \"ANTHROPIC_MODEL\": \"claude-sonnet-4-5\",\n", + " },\n", + " )\n", + " \n", + " async with ClaudeSDKClient(options=options) as client:\n", + " print(\"User: Create a simple hello.txt file with a greeting message\")\n", + " await client.query(\"Create a simple hello.txt file with a greeting message\")\n", + " \n", + " tool_uses = []\n", + " async for msg in client.receive_response():\n", + " if isinstance(msg, AssistantMessage):\n", + " display_message(msg)\n", + " for block in msg.content:\n", + " if hasattr(block, \"name\") and not isinstance(block, TextBlock): # ToolUseBlock\n", + " tool_uses.append(getattr(block, \"name\", \"\"))\n", + " else:\n", + " display_message(msg)\n", + " \n", + " if tool_uses:\n", + " print(f\"Tools used: {', '.join(tool_uses)}\")\n", + " \n", + " print(\"\\n\")\n", + "\n", + "asyncio.run(with_options())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Interrupt Capability\n", + "\n", + "You can interrupt Claude during execution:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import contextlib\n", + "\n", + "async def with_interrupt():\n", + " \"\"\"Demonstrate interrupt capability.\"\"\"\n", + " print(\"=== Interrupt Example ===\")\n", + " \n", + " async with ClaudeSDKClient() as client:\n", + " # Start a long-running task\n", + " print(\"\\nUser: Count from 1 to 100 slowly\")\n", + " await client.query(\"Count from 1 to 100 slowly, with a brief pause between each number\")\n", + " \n", + " # Create a background task to consume messages\n", + " messages_received = []\n", + " \n", + " async def consume_messages():\n", + " \"\"\"Consume messages in the background to enable interrupt processing.\"\"\"\n", + " async for message in client.receive_response():\n", + " messages_received.append(message)\n", + " display_message(message)\n", + " \n", + " # Start consuming messages in the background\n", + " consume_task = asyncio.create_task(consume_messages())\n", + " \n", + " # Wait 2 seconds then send interrupt\n", + " await asyncio.sleep(2)\n", + " print(\"\\n[After 2 seconds, sending interrupt...]\")\n", + " await client.interrupt()\n", + " \n", + " # Wait for the consume task to finish processing the interrupt\n", + " await consume_task\n", + " \n", + " # Send new instruction after interrupt\n", + " print(\"\\nUser: Never mind, just tell me a quick joke\")\n", + " await client.query(\"Never mind, just tell me a quick joke\")\n", + " \n", + " # Get the joke\n", + " async for msg in client.receive_response():\n", + " display_message(msg)\n", + " \n", + " print(\"\\n\")\n", + "\n", + "# Note: Uncomment to run (may take time)\n", + "# asyncio.run(with_interrupt())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## 6. Custom Tools (SDK MCP Servers) \n", + "\n", + "A **custom tool** is a Python function that you can offer to Claude for use. Custom tools are implemented as in-process MCP servers.\n", + "\n", + "### Benefits Over External MCP Servers\n", + "\n", + "- **No subprocess management** - Runs in the same process\n", + "- **Better performance** - No IPC overhead\n", + "- **Simpler deployment** - Single Python process\n", + "- **Easier debugging** - All code in same process\n", + "- **Type safety** - Direct Python function calls\n", + "\n", + "### Creating a Simple Tool" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from claude_agent_sdk import tool, create_sdk_mcp_server, ClaudeAgentOptions, ClaudeSDKClient\n", + "\n", + "# Define a tool using the @tool decorator\n", + "@tool(\"greet\", \"Greet a user\", {\"name\": str})\n", + "async def greet_user(args):\n", + " return {\n", + " \"content\": [\n", + " {\"type\": \"text\", \"text\": f\"Hello, {args['name']}!\"}\n", + " ]\n", + " }\n", + "\n", + "# Create an SDK MCP server\n", + "server = create_sdk_mcp_server(\n", + " name=\"my-tools\",\n", + " version=\"1.0.0\",\n", + " tools=[greet_user]\n", + ")\n", + "\n", + "# Use it with Claude\n", + "async def simple_tool_example():\n", + " options = ClaudeAgentOptions(\n", + " mcp_servers={\"tools\": server},\n", + " allowed_tools=[\"mcp__tools__greet\"]\n", + " )\n", + " \n", + " async with ClaudeSDKClient(options=options) as client:\n", + " await client.query(\"Greet Alice\")\n", + " \n", + " # Extract and print response\n", + " async for msg in client.receive_response():\n", + " print(msg)\n", + "\n", + "# asyncio.run(simple_tool_example())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Complete Calculator Example\n", + "\n", + "A full example with multiple tools:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Any\n", + "from claude_agent_sdk import tool, create_sdk_mcp_server, ClaudeAgentOptions, ClaudeSDKClient\n", + "import asyncio\n", + "\n", + "# Define calculator tools\n", + "@tool(\"add\", \"Add two numbers\", {\"a\": float, \"b\": float})\n", + "async def add_numbers(args: dict[str, Any]) -> dict[str, Any]:\n", + " \"\"\"Add two numbers together.\"\"\"\n", + " result = args[\"a\"] + args[\"b\"]\n", + " return {\n", + " \"content\": [{\"type\": \"text\", \"text\": f\"{args['a']} + {args['b']} = {result}\"}]\n", + " }\n", + "\n", + "@tool(\"subtract\", \"Subtract one number from another\", {\"a\": float, \"b\": float})\n", + "async def subtract_numbers(args: dict[str, Any]) -> dict[str, Any]:\n", + " \"\"\"Subtract b from a.\"\"\"\n", + " result = args[\"a\"] - args[\"b\"]\n", + " return {\n", + " \"content\": [{\"type\": \"text\", \"text\": f\"{args['a']} - {args['b']} = {result}\"}]\n", + " }\n", + "\n", + "@tool(\"multiply\", \"Multiply two numbers\", {\"a\": float, \"b\": float})\n", + "async def multiply_numbers(args: dict[str, Any]) -> dict[str, Any]:\n", + " \"\"\"Multiply two numbers.\"\"\"\n", + " result = args[\"a\"] * args[\"b\"]\n", + " return {\n", + " \"content\": [{\"type\": \"text\", \"text\": f\"{args['a']} × {args['b']} = {result}\"}]\n", + " }\n", + "\n", + "@tool(\"divide\", \"Divide one number by another\", {\"a\": float, \"b\": float})\n", + "async def divide_numbers(args: dict[str, Any]) -> dict[str, Any]:\n", + " \"\"\"Divide a by b.\"\"\"\n", + " if args[\"b\"] == 0:\n", + " return {\n", + " \"content\": [{\"type\": \"text\", \"text\": \"Error: Division by zero is not allowed\"}],\n", + " \"is_error\": True,\n", + " }\n", + " result = args[\"a\"] / args[\"b\"]\n", + " return {\n", + " \"content\": [{\"type\": \"text\", \"text\": f\"{args['a']} ÷ {args['b']} = {result}\"}]\n", + " }\n", + "\n", + "@tool(\"sqrt\", \"Calculate square root\", {\"n\": float})\n", + "async def square_root(args: dict[str, Any]) -> dict[str, Any]:\n", + " \"\"\"Calculate the square root of a number.\"\"\"\n", + " n = args[\"n\"]\n", + " if n < 0:\n", + " return {\n", + " \"content\": [{\"type\": \"text\", \"text\": f\"Error: Cannot calculate square root of negative number {n}\"}],\n", + " \"is_error\": True,\n", + " }\n", + " import math\n", + " result = math.sqrt(n)\n", + " return {\"content\": [{\"type\": \"text\", \"text\": f\"√{n} = {result}\"}]}\n", + "\n", + "# Create calculator server\n", + "async def calculator_example():\n", + " calculator = create_sdk_mcp_server(\n", + " name=\"calculator\",\n", + " version=\"2.0.0\",\n", + " tools=[add_numbers, subtract_numbers, multiply_numbers, divide_numbers, square_root],\n", + " )\n", + " \n", + " # Configure Claude to use the calculator\n", + " options = ClaudeAgentOptions(\n", + " mcp_servers={\"calc\": calculator},\n", + " allowed_tools=[\n", + " \"mcp__calc__add\",\n", + " \"mcp__calc__subtract\",\n", + " \"mcp__calc__multiply\",\n", + " \"mcp__calc__divide\",\n", + " \"mcp__calc__sqrt\",\n", + " ],\n", + " )\n", + " \n", + " prompts = [\n", + " \"Calculate 15 + 27\",\n", + " \"What is 100 divided by 7?\",\n", + " \"Calculate the square root of 144\",\n", + " ]\n", + " \n", + " for prompt in prompts:\n", + " print(f\"\\nPrompt: {prompt}\")\n", + " print(\"=\" * 50)\n", + " \n", + " async with ClaudeSDKClient(options=options) as client:\n", + " await client.query(prompt)\n", + " \n", + " async for message in client.receive_response():\n", + " display_message(message)\n", + "\n", + "# asyncio.run(calculator_example())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Mixed Server Support\n", + "\n", + "You can use both SDK and external MCP servers together:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "options = ClaudeAgentOptions(\n", + " mcp_servers={\n", + " \"internal\": sdk_server, # In-process SDK server\n", + " \"external\": { # External subprocess server\n", + " \"type\": \"stdio\",\n", + " \"command\": \"external-server\"\n", + " }\n", + " }\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## 7. Hooks \n", + "\n", + "A **hook** is a Python function that the Claude Code application invokes at specific points of the agent loop. Hooks provide deterministic processing and automated feedback.\n", + "\n", + "### Available Hook Events\n", + "\n", + "- **PreToolUse**: Before a tool is used\n", + "- **PostToolUse**: After a tool is used\n", + "- **UserPromptSubmit**: When a user submits a prompt\n", + "- **SessionStart**: When a session starts\n", + "\n", + "### PreToolUse Hook Example\n", + "\n", + "Block dangerous bash commands:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient, HookMatcher\n", + "from claude_agent_sdk.types import HookInput, HookContext, HookJSONOutput\n", + "import asyncio\n", + "\n", + "async def check_bash_command(\n", + " input_data: HookInput,\n", + " tool_use_id: str | None,\n", + " context: HookContext\n", + ") -> HookJSONOutput:\n", + " \"\"\"Prevent certain bash commands from being executed.\"\"\"\n", + " tool_name = input_data[\"tool_name\"]\n", + " tool_input = input_data[\"tool_input\"]\n", + " \n", + " if tool_name != \"Bash\":\n", + " return {}\n", + " \n", + " command = tool_input.get(\"command\", \"\")\n", + " block_patterns = [\"foo.sh\"]\n", + " \n", + " for pattern in block_patterns:\n", + " if pattern in command:\n", + " print(f\"Blocked command: {command}\")\n", + " return {\n", + " \"hookSpecificOutput\": {\n", + " \"hookEventName\": \"PreToolUse\",\n", + " \"permissionDecision\": \"deny\",\n", + " \"permissionDecisionReason\": f\"Command contains invalid pattern: {pattern}\",\n", + " }\n", + " }\n", + " \n", + " return {}\n", + "\n", + "async def pretooluse_example():\n", + " print(\"=== PreToolUse Example ===\")\n", + " \n", + " options = ClaudeAgentOptions(\n", + " allowed_tools=[\"Bash\"],\n", + " hooks={\n", + " \"PreToolUse\": [\n", + " HookMatcher(matcher=\"Bash\", hooks=[check_bash_command]),\n", + " ],\n", + " }\n", + " )\n", + " \n", + " async with ClaudeSDKClient(options=options) as client:\n", + " # Test 1: Command with forbidden pattern (will be blocked)\n", + " print(\"Test 1: Trying a command that should be blocked...\")\n", + " await client.query(\"Run the bash command: ./foo.sh --help\")\n", + " \n", + " async for msg in client.receive_response():\n", + " display_message(msg)\n", + " \n", + " print(\"\\n\" + \"=\" * 50 + \"\\n\")\n", + " \n", + " # Test 2: Safe command that should work\n", + " print(\"Test 2: Trying a command that should be allowed...\")\n", + " await client.query(\"Run the bash command: echo 'Hello from hooks example!'\")\n", + " \n", + " async for msg in client.receive_response():\n", + " display_message(msg)\n", + "\n", + "# asyncio.run(pretooluse_example())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### UserPromptSubmit Hook Example\n", + "\n", + "Add custom instructions:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "async def add_custom_instructions(\n", + " input_data: HookInput,\n", + " tool_use_id: str | None,\n", + " context: HookContext\n", + ") -> HookJSONOutput:\n", + " \"\"\"Add custom instructions when a session starts.\"\"\"\n", + " return {\n", + " \"hookSpecificOutput\": {\n", + " \"hookEventName\": \"SessionStart\",\n", + " \"additionalContext\": \"My favorite color is hot pink\",\n", + " }\n", + " }\n", + "\n", + "async def userpromptsubmit_example():\n", + " print(\"=== UserPromptSubmit Example ===\")\n", + " \n", + " options = ClaudeAgentOptions(\n", + " hooks={\n", + " \"UserPromptSubmit\": [\n", + " HookMatcher(matcher=None, hooks=[add_custom_instructions]),\n", + " ],\n", + " }\n", + " )\n", + " \n", + " async with ClaudeSDKClient(options=options) as client:\n", + " print(\"User: What's my favorite color?\")\n", + " await client.query(\"What's my favorite color?\")\n", + " \n", + " async for msg in client.receive_response():\n", + " display_message(msg)\n", + "\n", + "# asyncio.run(userpromptsubmit_example())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### PostToolUse Hook Example\n", + "\n", + "Review tool output and provide feedback:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "async def review_tool_output(\n", + " input_data: HookInput,\n", + " tool_use_id: str | None,\n", + " context: HookContext\n", + ") -> HookJSONOutput:\n", + " \"\"\"Review tool output and provide additional context or warnings.\"\"\"\n", + " tool_response = input_data.get(\"tool_response\", \"\")\n", + " \n", + " # If the tool produced an error, add helpful context\n", + " if \"error\" in str(tool_response).lower():\n", + " return {\n", + " \"systemMessage\": \"⚠️ The command produced an error\",\n", + " \"reason\": \"Tool execution failed - consider checking the command syntax\",\n", + " \"hookSpecificOutput\": {\n", + " \"hookEventName\": \"PostToolUse\",\n", + " \"additionalContext\": \"The command encountered an error. You may want to try a different approach.\",\n", + " }\n", + " }\n", + " \n", + " return {}\n", + "\n", + "async def posttooluse_example():\n", + " print(\"=== PostToolUse Example ===\")\n", + " \n", + " options = ClaudeAgentOptions(\n", + " allowed_tools=[\"Bash\"],\n", + " hooks={\n", + " \"PostToolUse\": [\n", + " HookMatcher(matcher=\"Bash\", hooks=[review_tool_output]),\n", + " ],\n", + " }\n", + " )\n", + " \n", + " async with ClaudeSDKClient(options=options) as client:\n", + " print(\"User: Run a command that will produce an error\")\n", + " await client.query(\"Run this command: ls /nonexistent_directory\")\n", + " \n", + " async for msg in client.receive_response():\n", + " display_message(msg)\n", + "\n", + "# asyncio.run(posttooluse_example())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## 8. Permission Callbacks \n", + "\n", + "Tool permission callbacks let you control which tools Claude can use and modify their inputs programmatically.\n", + "\n", + "### Permission Callback Example" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from claude_agent_sdk import (\n", + " ClaudeAgentOptions,\n", + " ClaudeSDKClient,\n", + " PermissionResultAllow,\n", + " PermissionResultDeny,\n", + " ToolPermissionContext,\n", + ")\n", + "import json\n", + "\n", + "async def my_permission_callback(\n", + " tool_name: str,\n", + " input_data: dict,\n", + " context: ToolPermissionContext\n", + ") -> PermissionResultAllow | PermissionResultDeny:\n", + " \"\"\"Control tool permissions based on tool type and input.\"\"\"\n", + " \n", + " print(f\"\\n🔧 Tool Permission Request: {tool_name}\")\n", + " print(f\" Input: {json.dumps(input_data, indent=2)}\")\n", + " \n", + " # Always allow read operations\n", + " if tool_name in [\"Read\", \"Glob\", \"Grep\"]:\n", + " print(f\" ✅ Automatically allowing {tool_name} (read-only operation)\")\n", + " return PermissionResultAllow()\n", + " \n", + " # Deny write operations to system directories\n", + " if tool_name in [\"Write\", \"Edit\", \"MultiEdit\"]:\n", + " file_path = input_data.get(\"file_path\", \"\")\n", + " if file_path.startswith(\"/etc/\") or file_path.startswith(\"/usr/\"):\n", + " print(f\" ❌ Denying write to system directory: {file_path}\")\n", + " return PermissionResultDeny(\n", + " message=f\"Cannot write to system directory: {file_path}\"\n", + " )\n", + " \n", + " # Redirect writes to a safe directory\n", + " if not file_path.startswith(\"/tmp/\") and not file_path.startswith(\"./\"):\n", + " safe_path = f\"./safe_output/{file_path.split('/')[-1]}\"\n", + " print(f\" ⚠️ Redirecting write from {file_path} to {safe_path}\")\n", + " modified_input = input_data.copy()\n", + " modified_input[\"file_path\"] = safe_path\n", + " return PermissionResultAllow(updated_input=modified_input)\n", + " \n", + " # Check dangerous bash commands\n", + " if tool_name == \"Bash\":\n", + " command = input_data.get(\"command\", \"\")\n", + " dangerous_commands = [\"rm -rf\", \"sudo\", \"chmod 777\", \"dd if=\", \"mkfs\"]\n", + " \n", + " for dangerous in dangerous_commands:\n", + " if dangerous in command:\n", + " print(f\" ❌ Denying dangerous command: {command}\")\n", + " return PermissionResultDeny(\n", + " message=f\"Dangerous command pattern detected: {dangerous}\"\n", + " )\n", + " \n", + " print(f\" ✅ Allowing bash command: {command}\")\n", + " return PermissionResultAllow()\n", + " \n", + " # Default: allow\n", + " return PermissionResultAllow()\n", + "\n", + "async def permission_callback_example():\n", + " print(\"=\" * 60)\n", + " print(\"Tool Permission Callback Example\")\n", + " print(\"=\" * 60)\n", + " \n", + " options = ClaudeAgentOptions(\n", + " can_use_tool=my_permission_callback,\n", + " permission_mode=\"default\",\n", + " cwd=\".\"\n", + " )\n", + " \n", + " async with ClaudeSDKClient(options) as client:\n", + " print(\"\\n📝 Sending query to Claude...\")\n", + " await client.query(\n", + " \"Please list the files in the current directory and create a simple hello.py file\"\n", + " )\n", + " \n", + " async for message in client.receive_response():\n", + " if isinstance(message, AssistantMessage):\n", + " for block in message.content:\n", + " if isinstance(block, TextBlock):\n", + " print(f\"\\n💬 Claude: {block.text}\")\n", + " elif isinstance(message, ResultMessage):\n", + " print(\"\\n✅ Task completed!\")\n", + "\n", + "# asyncio.run(permission_callback_example())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## 9. Custom Agents \n", + "\n", + "Define custom agents with specific tools, prompts, and models for specialized tasks.\n", + "\n", + "### Code Reviewer Agent" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from claude_agent_sdk import AgentDefinition, query, ClaudeAgentOptions, AssistantMessage, TextBlock\n", + "import anyio\n", + "\n", + "async def code_reviewer_example():\n", + " print(\"=== Code Reviewer Agent Example ===\")\n", + " \n", + " options = ClaudeAgentOptions(\n", + " agents={\n", + " \"code-reviewer\": AgentDefinition(\n", + " description=\"Reviews code for best practices and potential issues\",\n", + " prompt=\"You are a code reviewer. Analyze code for bugs, performance issues, \"\n", + " \"security vulnerabilities, and adherence to best practices. \"\n", + " \"Provide constructive feedback.\",\n", + " tools=[\"Read\", \"Grep\"],\n", + " model=\"sonnet\",\n", + " ),\n", + " },\n", + " )\n", + " \n", + " async for message in query(\n", + " prompt=\"Use the code-reviewer agent to review a Python file\",\n", + " options=options,\n", + " ):\n", + " if isinstance(message, AssistantMessage):\n", + " for block in message.content:\n", + " if isinstance(block, TextBlock):\n", + " print(f\"Claude: {block.text}\")\n", + "\n", + "# anyio.run(code_reviewer_example)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Multiple Custom Agents" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "async def multiple_agents_example():\n", + " print(\"=== Multiple Agents Example ===\")\n", + " \n", + " options = ClaudeAgentOptions(\n", + " agents={\n", + " \"analyzer\": AgentDefinition(\n", + " description=\"Analyzes code structure and patterns\",\n", + " prompt=\"You are a code analyzer. Examine code structure, patterns, and architecture.\",\n", + " tools=[\"Read\", \"Grep\", \"Glob\"],\n", + " ),\n", + " \"tester\": AgentDefinition(\n", + " description=\"Creates and runs tests\",\n", + " prompt=\"You are a testing expert. Write comprehensive tests and ensure code quality.\",\n", + " tools=[\"Read\", \"Write\", \"Bash\"],\n", + " model=\"sonnet\",\n", + " ),\n", + " },\n", + " setting_sources=[\"user\", \"project\"],\n", + " )\n", + " \n", + " async for message in query(\n", + " prompt=\"Use the analyzer agent to find all Python files in the examples/ directory\",\n", + " options=options,\n", + " ):\n", + " if isinstance(message, AssistantMessage):\n", + " for block in message.content:\n", + " if isinstance(block, TextBlock):\n", + " print(f\"Claude: {block.text}\")\n", + "\n", + "# anyio.run(multiple_agents_example)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## 10. Configuration Options \n", + "\n", + "### Budget Control\n", + "\n", + "Limit API costs with `max_budget_usd`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage, AssistantMessage, TextBlock\n", + "import anyio\n", + "\n", + "async def with_budget():\n", + " print(\"=== With Budget Limit ===\")\n", + " \n", + " options = ClaudeAgentOptions(\n", + " max_budget_usd=0.10, # 10 cents - plenty for a simple query\n", + " )\n", + " \n", + " async for message in query(prompt=\"What is 2 + 2?\", options=options):\n", + " if isinstance(message, AssistantMessage):\n", + " for block in message.content:\n", + " if isinstance(block, TextBlock):\n", + " print(f\"Claude: {block.text}\")\n", + " elif isinstance(message, ResultMessage):\n", + " if message.total_cost_usd:\n", + " print(f\"Total cost: ${message.total_cost_usd:.4f}\")\n", + " print(f\"Status: {message.subtype}\")\n", + " \n", + " # Check if budget was exceeded\n", + " if message.subtype == \"error_max_budget_usd\":\n", + " print(\"⚠️ Budget limit exceeded!\")\n", + "\n", + "# anyio.run(with_budget)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Setting Sources\n", + "\n", + "Control which settings are loaded:\n", + "\n", + "- **\"user\"**: Global user settings (~/.claude/)\n", + "- **\"project\"**: Project-level settings (.claude/ in project)\n", + "- **\"local\"**: Local gitignored settings (.claude-local/)\n", + "\n", + "**IMPORTANT**: When `setting_sources` is not provided (None), NO settings are loaded by default." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Default - no settings loaded\n", + "options_default = ClaudeAgentOptions()\n", + "\n", + "# Load only user settings\n", + "options_user = ClaudeAgentOptions(\n", + " setting_sources=[\"user\"]\n", + ")\n", + "\n", + "# Load both project and user settings\n", + "options_all = ClaudeAgentOptions(\n", + " setting_sources=[\"user\", \"project\"]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Partial Message Streaming\n", + "\n", + "Stream incremental updates as Claude generates responses:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, StreamEvent\n", + "import asyncio\n", + "\n", + "async def partial_messages_example():\n", + " # Enable partial message streaming\n", + " options = ClaudeAgentOptions(\n", + " include_partial_messages=True,\n", + " model=\"claude-sonnet-4-5\",\n", + " max_turns=2,\n", + " env={\n", + " \"MAX_THINKING_TOKENS\": \"8000\",\n", + " },\n", + " )\n", + " \n", + " client = ClaudeSDKClient(options)\n", + " \n", + " try:\n", + " await client.connect()\n", + " \n", + " prompt = \"Think of three jokes, then tell one\"\n", + " print(f\"Prompt: {prompt}\\n\")\n", + " \n", + " await client.query(prompt)\n", + " \n", + " async for message in client.receive_response():\n", + " print(message)\n", + " \n", + " finally:\n", + " await client.disconnect()\n", + "\n", + "# asyncio.run(partial_messages_example())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Environment Variables and Model Selection" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "options = ClaudeAgentOptions(\n", + " model=\"claude-sonnet-4-5-20250929\",\n", + " max_turns=5,\n", + " env={\n", + " \"ANTHROPIC_API_KEY\": \"your-api-key\",\n", + " \"MAX_THINKING_TOKENS\": \"8000\",\n", + " },\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## 11. Error Handling \n", + "\n", + "The SDK provides specific error types for different failure scenarios:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from claude_agent_sdk import (\n", + " query,\n", + " ClaudeSDKError, # Base error\n", + " CLINotFoundError, # Claude Code not installed\n", + " CLIConnectionError, # Connection issues\n", + " ProcessError, # Process failed\n", + " CLIJSONDecodeError, # JSON parsing issues\n", + ")\n", + "import anyio\n", + "\n", + "async def error_handling_example():\n", + " try:\n", + " async for message in query(prompt=\"Hello\"):\n", + " pass\n", + " except CLINotFoundError:\n", + " print(\"Please install Claude Code\")\n", + " except ProcessError as e:\n", + " print(f\"Process failed with exit code: {e.exit_code}\")\n", + " except CLIJSONDecodeError as e:\n", + " print(f\"Failed to parse response: {e}\")\n", + " except ClaudeSDKError as e:\n", + " print(f\"SDK error: {e}\")\n", + "\n", + "# anyio.run(error_handling_example)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Error Handling with Timeouts" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import asyncio\n", + "from claude_agent_sdk import ClaudeSDKClient, CLIConnectionError\n", + "\n", + "async def timeout_example():\n", + " client = ClaudeSDKClient()\n", + " \n", + " try:\n", + " await client.connect()\n", + " \n", + " await client.query(\"Run a bash sleep command for 60 seconds\")\n", + " \n", + " try:\n", + " messages = []\n", + " async with asyncio.timeout(10.0):\n", + " async for msg in client.receive_response():\n", + " messages.append(msg)\n", + " \n", + " except asyncio.TimeoutError:\n", + " print(f\"Response timeout - received {len(messages)} messages before timeout\")\n", + " \n", + " except CLIConnectionError as e:\n", + " print(f\"Connection error: {e}\")\n", + " \n", + " finally:\n", + " await client.disconnect()\n", + "\n", + "# asyncio.run(timeout_example())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## 12. Advanced Examples \n", + "\n", + "### Concurrent Responses\n", + "\n", + "Handle responses while sending new messages:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import contextlib\n", + "\n", + "async def concurrent_responses():\n", + " print(\"=== Concurrent Send/Receive Example ===\")\n", + " \n", + " async with ClaudeSDKClient() as client:\n", + " # Background task to continuously receive messages\n", + " async def receive_messages():\n", + " async for message in client.receive_messages():\n", + " display_message(message)\n", + " \n", + " # Start receiving in background\n", + " receive_task = asyncio.create_task(receive_messages())\n", + " \n", + " # Send multiple messages with delays\n", + " questions = [\n", + " \"What is 2 + 2?\",\n", + " \"What is the square root of 144?\",\n", + " \"What is 10% of 80?\",\n", + " ]\n", + " \n", + " for question in questions:\n", + " print(f\"\\nUser: {question}\")\n", + " await client.query(question)\n", + " await asyncio.sleep(3)\n", + " \n", + " # Give time for final responses\n", + " await asyncio.sleep(2)\n", + " \n", + " # Clean up\n", + " receive_task.cancel()\n", + " with contextlib.suppress(asyncio.CancelledError):\n", + " await receive_task\n", + "\n", + "# asyncio.run(concurrent_responses())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Manual Message Handling\n", + "\n", + "Process messages with custom logic:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "async def manual_message_handling():\n", + " print(\"=== Manual Message Handling Example ===\")\n", + " \n", + " async with ClaudeSDKClient() as client:\n", + " await client.query(\"List 5 programming languages and their main use cases\")\n", + " \n", + " # Manually process messages with custom logic\n", + " languages_found = []\n", + " \n", + " async for message in client.receive_messages():\n", + " if isinstance(message, AssistantMessage):\n", + " for block in message.content:\n", + " if isinstance(block, TextBlock):\n", + " text = block.text\n", + " print(f\"Claude: {text}\")\n", + " # Custom logic: extract language names\n", + " for lang in [\"Python\", \"JavaScript\", \"Java\", \"C++\", \"Go\", \"Rust\", \"Ruby\"]:\n", + " if lang in text and lang not in languages_found:\n", + " languages_found.append(lang)\n", + " print(f\"Found language: {lang}\")\n", + " elif isinstance(message, ResultMessage):\n", + " display_message(message)\n", + " print(f\"Total languages mentioned: {len(languages_found)}\")\n", + " break\n", + "\n", + "# asyncio.run(manual_message_handling())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Server Info and Control Protocol" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "async def control_protocol_example():\n", + " print(\"=== Control Protocol Example ===\")\n", + " \n", + " async with ClaudeSDKClient() as client:\n", + " # Get server initialization info\n", + " print(\"Getting server info...\")\n", + " server_info = await client.get_server_info()\n", + " \n", + " if server_info:\n", + " print(\"✓ Server info retrieved successfully!\")\n", + " print(f\" - Available commands: {len(server_info.get('commands', []))}\")\n", + " print(f\" - Output style: {server_info.get('output_style', 'unknown')}\")\n", + " \n", + " styles = server_info.get('available_output_styles', [])\n", + " if styles:\n", + " print(f\" - Available output styles: {', '.join(styles)}\")\n", + "\n", + "# asyncio.run(control_protocol_example())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## Types Reference\n", + "\n", + "### ClaudeAgentOptions\n", + "\n", + "Configuration options for Claude Agent SDK:\n", + "\n", + "- `model`: Model to use (e.g., \"claude-sonnet-4-5\")\n", + "- `system_prompt`: System prompt (string or dict with preset)\n", + "- `max_turns`: Maximum conversation turns\n", + "- `max_budget_usd`: Budget limit in USD\n", + "- `allowed_tools`: List of tool names to allow\n", + "- `tools`: Tools configuration (array or preset)\n", + "- `permission_mode`: Permission mode (\"default\", \"acceptEdits\", etc.)\n", + "- `can_use_tool`: Permission callback function\n", + "- `hooks`: Dictionary of hooks by event name\n", + "- `mcp_servers`: Dictionary of MCP servers\n", + "- `agents`: Dictionary of custom agents\n", + "- `setting_sources`: List of setting sources to load\n", + "- `cwd`: Working directory\n", + "- `cli_path`: Path to Claude Code CLI\n", + "- `env`: Environment variables\n", + "- `include_partial_messages`: Enable partial message streaming\n", + "\n", + "### Message Types\n", + "\n", + "- **AssistantMessage**: Messages from Claude\n", + "- **UserMessage**: Messages from user\n", + "- **SystemMessage**: System messages\n", + "- **ResultMessage**: Result with metadata (cost, duration, etc.)\n", + "- **StreamEvent**: Partial message stream events\n", + "\n", + "### Content Blocks\n", + "\n", + "- **TextBlock**: Text content\n", + "- **ToolUseBlock**: Tool usage request\n", + "- **ToolResultBlock**: Tool execution result\n", + "\n", + "---\n", + "\n", + "## Summary\n", + "\n", + "This notebook provides **100% coverage** of the Claude Agent SDK for Python, including:\n", + "\n", + "✅ Installation and setup \n", + "✅ Quick start with `query()` \n", + "✅ Basic usage and configuration \n", + "✅ Tool usage and permissions \n", + "✅ Streaming mode with `ClaudeSDKClient` \n", + "✅ Custom tools (SDK MCP Servers) \n", + "✅ Hooks (PreToolUse, PostToolUse, UserPromptSubmit, SessionStart) \n", + "✅ Permission callbacks \n", + "✅ Custom agents \n", + "✅ Budget control \n", + "✅ Setting sources \n", + "✅ Error handling \n", + "✅ Advanced features (interrupts, partial messages, etc.) \n", + "\n", + "For more information, visit:\n", + "- [GitHub Repository](https://github.com/anthropics/claude-agent-sdk-python)\n", + "- [Official Documentation](https://platform.claude.com/docs/en/agent-sdk/python)\n", + "\n", + "---\n", + "\n", + "**License**: Use of this SDK is governed by Anthropic's [Commercial Terms of Service](https://www.anthropic.com/legal/commercial-terms)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/firebase-debug.log b/firebase-debug.log new file mode 100644 index 00000000..b35a13ac --- /dev/null +++ b/firebase-debug.log @@ -0,0 +1,6 @@ +[debug] [2025-12-13T09:12:41.038Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] +[debug] [2025-12-13T09:12:41.153Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] +[debug] [2025-12-13T09:12:41.347Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] +[debug] [2025-12-13T09:12:41.348Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] +[debug] [2025-12-13T09:12:41.357Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"] +[debug] [2025-12-13T09:12:41.358Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]