7fac3e3bb6
- Introduced a detailed reference for implementing hooks in Claude Code, covering configuration, project-specific scripts, plugin hooks, and various hook events. - Explained the input modes available in the Claude Agent SDK, emphasizing the benefits of streaming input mode and providing implementation examples for both streaming and single message input. - Highlighted security considerations and best practices for writing hooks, along with debugging tips and execution details.
296 lines
8.5 KiB
Markdown
296 lines
8.5 KiB
Markdown
# Streaming Input
|
|
|
|
> Understanding the two input modes for Claude Agent SDK and when to use each
|
|
|
|
## Overview
|
|
|
|
The Claude Agent SDK supports two distinct input modes for interacting with agents:
|
|
|
|
* **Streaming Input Mode** (Default & Recommended) - A persistent, interactive session
|
|
* **Single Message Input** - One-shot queries that use session state and resuming
|
|
|
|
This guide explains the differences, benefits, and use cases for each mode to help you choose the right approach for your application.
|
|
|
|
## Streaming Input Mode (Recommended)
|
|
|
|
Streaming input mode is the **preferred** way to use the Claude Agent SDK. It provides full access to the agent's capabilities and enables rich, interactive experiences.
|
|
|
|
It allows the agent to operate as a long lived process that takes in user input, handles interruptions, surfaces permission requests, and handles session management.
|
|
|
|
### How It Works
|
|
|
|
```mermaid theme={null}
|
|
%%{init: {"theme": "base", "themeVariables": {"edgeLabelBackground": "#F0F0EB", "lineColor": "#91918D", "primaryColor": "#F0F0EB", "primaryTextColor": "#191919", "primaryBorderColor": "#D9D8D5", "secondaryColor": "#F5E6D8", "tertiaryColor": "#CC785C", "noteBkgColor": "#FAF0E6", "noteBorderColor": "#91918D"}, "sequence": {"actorMargin": 50, "width": 150, "height": 65, "boxMargin": 10, "boxTextMargin": 5, "noteMargin": 10, "messageMargin": 35}}}%%
|
|
sequenceDiagram
|
|
participant App as Your Application
|
|
participant Agent as Claude Agent
|
|
participant Tools as Tools/Hooks
|
|
participant FS as Environment/<br/>File System
|
|
|
|
App->>Agent: Initialize with AsyncGenerator
|
|
activate Agent
|
|
|
|
App->>Agent: Yield Message 1
|
|
Agent->>Tools: Execute tools
|
|
Tools->>FS: Read files
|
|
FS-->>Tools: File contents
|
|
Tools->>FS: Write/Edit files
|
|
FS-->>Tools: Success/Error
|
|
Agent-->>App: Stream partial response
|
|
Agent-->>App: Stream more content...
|
|
Agent->>App: Complete Message 1
|
|
|
|
App->>Agent: Yield Message 2 + Image
|
|
Agent->>Tools: Process image & execute
|
|
Tools->>FS: Access filesystem
|
|
FS-->>Tools: Operation results
|
|
Agent-->>App: Stream response 2
|
|
|
|
App->>Agent: Queue Message 3
|
|
App->>Agent: Interrupt/Cancel
|
|
Agent->>App: Handle interruption
|
|
|
|
Note over App,Agent: Session stays alive
|
|
Note over Tools,FS: Persistent file system<br/>state maintained
|
|
|
|
deactivate Agent
|
|
```
|
|
|
|
### Benefits
|
|
|
|
<CardGroup cols={2}>
|
|
<Card title="Image Uploads" icon="image">
|
|
Attach images directly to messages for visual analysis and understanding
|
|
</Card>
|
|
|
|
<Card title="Queued Messages" icon="layer-group">
|
|
Send multiple messages that process sequentially, with ability to interrupt
|
|
</Card>
|
|
|
|
<Card title="Tool Integration" icon="wrench">
|
|
Full access to all tools and custom MCP servers during the session
|
|
</Card>
|
|
|
|
<Card title="Hooks Support" icon="link">
|
|
Use lifecycle hooks to customize behavior at various points
|
|
</Card>
|
|
|
|
<Card title="Real-time Feedback" icon="bolt">
|
|
See responses as they're generated, not just final results
|
|
</Card>
|
|
|
|
<Card title="Context Persistence" icon="database">
|
|
Maintain conversation context across multiple turns naturally
|
|
</Card>
|
|
</CardGroup>
|
|
|
|
### Implementation Example
|
|
|
|
<CodeGroup>
|
|
```typescript TypeScript theme={null}
|
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
import { readFileSync } from "fs";
|
|
|
|
async function* generateMessages() {
|
|
// First message
|
|
yield {
|
|
type: "user" as const,
|
|
message: {
|
|
role: "user" as const,
|
|
content: "Analyze this codebase for security issues"
|
|
}
|
|
};
|
|
|
|
// Wait for conditions or user input
|
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
|
|
// Follow-up with image
|
|
yield {
|
|
type: "user" as const,
|
|
message: {
|
|
role: "user" as const,
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: "Review this architecture diagram"
|
|
},
|
|
{
|
|
type: "image",
|
|
source: {
|
|
type: "base64",
|
|
media_type: "image/png",
|
|
data: readFileSync("diagram.png", "base64")
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
}
|
|
|
|
// Process streaming responses
|
|
for await (const message of query({
|
|
prompt: generateMessages(),
|
|
options: {
|
|
maxTurns: 10,
|
|
allowedTools: ["Read", "Grep"]
|
|
}
|
|
})) {
|
|
if (message.type === "result") {
|
|
console.log(message.result);
|
|
}
|
|
}
|
|
```
|
|
|
|
```python Python theme={null}
|
|
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, AssistantMessage, TextBlock
|
|
import asyncio
|
|
import base64
|
|
|
|
async def streaming_analysis():
|
|
async def message_generator():
|
|
# First message
|
|
yield {
|
|
"type": "user",
|
|
"message": {
|
|
"role": "user",
|
|
"content": "Analyze this codebase for security issues"
|
|
}
|
|
}
|
|
|
|
# Wait for conditions
|
|
await asyncio.sleep(2)
|
|
|
|
# Follow-up with image
|
|
with open("diagram.png", "rb") as f:
|
|
image_data = base64.b64encode(f.read()).decode()
|
|
|
|
yield {
|
|
"type": "user",
|
|
"message": {
|
|
"role": "user",
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": "Review this architecture diagram"
|
|
},
|
|
{
|
|
"type": "image",
|
|
"source": {
|
|
"type": "base64",
|
|
"media_type": "image/png",
|
|
"data": image_data
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
# Use ClaudeSDKClient for streaming input
|
|
options = ClaudeAgentOptions(
|
|
max_turns=10,
|
|
allowed_tools=["Read", "Grep"]
|
|
)
|
|
|
|
async with ClaudeSDKClient(options) as client:
|
|
# Send streaming input
|
|
await client.query(message_generator())
|
|
|
|
# Process responses
|
|
async for message in client.receive_response():
|
|
if isinstance(message, AssistantMessage):
|
|
for block in message.content:
|
|
if isinstance(block, TextBlock):
|
|
print(block.text)
|
|
|
|
asyncio.run(streaming_analysis())
|
|
```
|
|
</CodeGroup>
|
|
|
|
## Single Message Input
|
|
|
|
Single message input is simpler but more limited.
|
|
|
|
### When to Use Single Message Input
|
|
|
|
Use single message input when:
|
|
|
|
* You need a one-shot response
|
|
* You do not need image attachments, hooks, etc.
|
|
* You need to operate in a stateless environment, such as a lambda function
|
|
|
|
### Limitations
|
|
|
|
<Warning>
|
|
Single message input mode does **not** support:
|
|
|
|
* Direct image attachments in messages
|
|
* Dynamic message queueing
|
|
* Real-time interruption
|
|
* Hook integration
|
|
* Natural multi-turn conversations
|
|
</Warning>
|
|
|
|
### Implementation Example
|
|
|
|
<CodeGroup>
|
|
```typescript TypeScript theme={null}
|
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
|
|
// Simple one-shot query
|
|
for await (const message of query({
|
|
prompt: "Explain the authentication flow",
|
|
options: {
|
|
maxTurns: 1,
|
|
allowedTools: ["Read", "Grep"]
|
|
}
|
|
})) {
|
|
if (message.type === "result") {
|
|
console.log(message.result);
|
|
}
|
|
}
|
|
|
|
// Continue conversation with session management
|
|
for await (const message of query({
|
|
prompt: "Now explain the authorization process",
|
|
options: {
|
|
continue: true,
|
|
maxTurns: 1
|
|
}
|
|
})) {
|
|
if (message.type === "result") {
|
|
console.log(message.result);
|
|
}
|
|
}
|
|
```
|
|
|
|
```python Python theme={null}
|
|
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
|
|
import asyncio
|
|
|
|
async def single_message_example():
|
|
# Simple one-shot query using query() function
|
|
async for message in query(
|
|
prompt="Explain the authentication flow",
|
|
options=ClaudeAgentOptions(
|
|
max_turns=1,
|
|
allowed_tools=["Read", "Grep"]
|
|
)
|
|
):
|
|
if isinstance(message, ResultMessage):
|
|
print(message.result)
|
|
|
|
# Continue conversation with session management
|
|
async for message in query(
|
|
prompt="Now explain the authorization process",
|
|
options=ClaudeAgentOptions(
|
|
continue_conversation=True,
|
|
max_turns=1
|
|
)
|
|
):
|
|
if isinstance(message, ResultMessage):
|
|
print(message.result)
|
|
|
|
asyncio.run(single_message_example())
|
|
```
|
|
</CodeGroup>
|