or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

agent-definitions.mdagents.mdclient.mdconfiguration-options.mdcontent-blocks.mdcore-query-interface.mdcustom-tools.mderror-handling.mderrors.mdhook-system.mdhooks.mdindex.mdmcp-config.mdmcp-server-configuration.mdmessages-and-content.mdmessages.mdoptions.mdpermission-control.mdpermissions.mdquery.mdtransport.md
COMPLETION_SUMMARY.md

client.mddocs/

0

# Interactive Client

1

2

The ClaudeSDKClient class provides full control over conversation flow with support for bidirectional communication, streaming, interrupts, and dynamic message sending. It's ideal for chat interfaces, interactive debugging, and multi-turn conversations.

3

4

## Capabilities

5

6

### ClaudeSDKClient Class

7

8

Client for bidirectional, interactive conversations with Claude Code. Maintains conversation state and allows sending messages at any time based on responses.

9

10

```python { .api }

11

class ClaudeSDKClient:

12

"""

13

Client for bidirectional, interactive conversations with Claude Code.

14

15

This client provides full control over the conversation flow with support

16

for streaming, interrupts, and dynamic message sending. For simple one-shot

17

queries, consider using the query() function instead.

18

19

Key features:

20

- Bidirectional: Send and receive messages at any time

21

- Stateful: Maintains conversation context across messages

22

- Interactive: Send follow-ups based on responses

23

- Control flow: Support for interrupts and session management

24

25

When to use ClaudeSDKClient:

26

- Building chat interfaces or conversational UIs

27

- Interactive debugging or exploration sessions

28

- Multi-turn conversations with context

29

- When you need to react to Claude's responses

30

- Real-time applications with user input

31

- When you need interrupt capabilities

32

33

When to use query() instead:

34

- Simple one-off questions

35

- Batch processing of prompts

36

- Fire-and-forget automation scripts

37

- When all inputs are known upfront

38

- Stateless operations

39

40

Caveat: As of v0.0.20, you cannot use a ClaudeSDKClient instance across

41

different async runtime contexts (e.g., different trio nurseries or asyncio

42

task groups). The client internally maintains a persistent anyio task group

43

for reading messages that remains active from connect() until disconnect().

44

This means you must complete all operations with the client within the same

45

async context where it was connected.

46

"""

47

48

def __init__(

49

self,

50

options: ClaudeAgentOptions | None = None,

51

transport: Transport | None = None,

52

):

53

"""

54

Initialize Claude SDK client.

55

56

Args:

57

options: Optional configuration (ClaudeAgentOptions instance)

58

transport: Optional custom transport implementation

59

"""

60

61

async def connect(

62

self, prompt: str | AsyncIterable[dict[str, Any]] | None = None

63

) -> None:

64

"""

65

Connect to Claude with a prompt or message stream.

66

67

Args:

68

prompt: Optional initial prompt (string or AsyncIterable of message dicts)

69

If None, connects without sending initial messages (for interactive use)

70

"""

71

72

async def receive_messages(self) -> AsyncIterator[Message]:

73

"""

74

Receive all messages from Claude.

75

76

Yields:

77

Message objects (UserMessage, AssistantMessage, SystemMessage,

78

ResultMessage, or StreamEvent)

79

"""

80

81

async def query(

82

self, prompt: str | AsyncIterable[dict[str, Any]], session_id: str = "default"

83

) -> None:

84

"""

85

Send a new request in streaming mode.

86

87

Args:

88

prompt: Either a string message or an async iterable of message dictionaries

89

session_id: Session identifier for the conversation (default: "default")

90

"""

91

92

async def interrupt(self) -> None:

93

"""

94

Send interrupt signal (only works with streaming mode).

95

96

Interrupts the current Claude operation, stopping generation and

97

tool execution.

98

"""

99

100

async def set_permission_mode(self, mode: str) -> None:

101

"""

102

Change permission mode during conversation (only works with streaming mode).

103

104

Args:

105

mode: The permission mode to set. Valid options:

106

- 'default': CLI prompts for dangerous tools

107

- 'acceptEdits': Auto-accept file edits

108

- 'bypassPermissions': Allow all tools (use with caution)

109

"""

110

111

async def set_model(self, model: str | None = None) -> None:

112

"""

113

Change the AI model during conversation (only works with streaming mode).

114

115

Args:

116

model: The model to use, or None to use default. Examples:

117

- 'claude-sonnet-4-5'

118

- 'claude-opus-4-1-20250805'

119

- 'claude-opus-4-20250514'

120

"""

121

122

async def get_server_info(self) -> dict[str, Any] | None:

123

"""

124

Get server initialization info including available commands and output styles.

125

126

Returns initialization information from the Claude Code server including:

127

- Available commands (slash commands, system commands, etc.)

128

- Current and available output styles

129

- Server capabilities

130

131

Returns:

132

Dictionary with server info, or None if not in streaming mode

133

"""

134

135

async def receive_response(self) -> AsyncIterator[Message]:

136

"""

137

Receive messages from Claude until and including a ResultMessage.

138

139

This async iterator yields all messages in sequence and automatically terminates

140

after yielding a ResultMessage (which indicates the response is complete).

141

It's a convenience method over receive_messages() for single-response workflows.

142

143

Stopping Behavior:

144

- Yields each message as it's received

145

- Terminates immediately after yielding a ResultMessage

146

- The ResultMessage IS included in the yielded messages

147

- If no ResultMessage is received, the iterator continues indefinitely

148

149

Yields:

150

Message objects (each message received until ResultMessage)

151

152

Note:

153

To collect all messages: messages = [msg async for msg in client.receive_response()]

154

The final message in the list will always be a ResultMessage.

155

"""

156

157

async def disconnect(self) -> None:

158

"""

159

Disconnect from Claude.

160

161

Closes the connection and cleans up resources.

162

"""

163

164

async def __aenter__(self) -> "ClaudeSDKClient":

165

"""

166

Enter async context - automatically connects with empty stream for interactive use.

167

168

Returns:

169

Self (the ClaudeSDKClient instance)

170

"""

171

172

async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> bool:

173

"""

174

Exit async context - always disconnects.

175

176

Returns:

177

False (does not suppress exceptions)

178

"""

179

```

180

181

## Usage Examples

182

183

### Basic Interactive Session

184

185

```python

186

import anyio

187

from claude_agent_sdk import ClaudeSDKClient, AssistantMessage, TextBlock

188

189

async def main():

190

async with ClaudeSDKClient() as client:

191

await client.query("What is the capital of France?")

192

193

async for msg in client.receive_response():

194

if isinstance(msg, AssistantMessage):

195

for block in msg.content:

196

if isinstance(block, TextBlock):

197

print(f"Claude: {block.text}")

198

199

anyio.run(main)

200

```

201

202

### Multi-Turn Conversation

203

204

```python

205

import anyio

206

from claude_agent_sdk import ClaudeSDKClient, AssistantMessage, TextBlock, ResultMessage

207

208

async def main():

209

async with ClaudeSDKClient() as client:

210

# First question

211

await client.query("What is Python?")

212

async for msg in client.receive_response():

213

if isinstance(msg, AssistantMessage):

214

for block in msg.content:

215

if isinstance(block, TextBlock):

216

print(f"Claude: {block.text}")

217

218

# Follow-up question

219

await client.query("Can you show me a simple Python example?")

220

async for msg in client.receive_response():

221

if isinstance(msg, AssistantMessage):

222

for block in msg.content:

223

if isinstance(block, TextBlock):

224

print(f"Claude: {block.text}")

225

226

anyio.run(main)

227

```

228

229

### Using with Options

230

231

```python

232

import anyio

233

from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions

234

235

async def main():

236

options = ClaudeAgentOptions(

237

system_prompt="You are a helpful Python expert",

238

allowed_tools=["Read", "Bash"],

239

permission_mode='default',

240

cwd="/home/user/project"

241

)

242

243

async with ClaudeSDKClient(options=options) as client:

244

await client.query("Analyze the Python files in this directory")

245

246

async for msg in client.receive_response():

247

print(msg)

248

249

anyio.run(main)

250

```

251

252

### Interrupting Execution

253

254

```python

255

import anyio

256

from claude_agent_sdk import ClaudeSDKClient, AssistantMessage

257

258

async def main():

259

async with ClaudeSDKClient() as client:

260

await client.query("Generate 1000 lines of code")

261

262

# Interrupt after a short delay

263

async def interrupt_after_delay():

264

await anyio.sleep(2)

265

await client.interrupt()

266

267

async with anyio.create_task_group() as tg:

268

tg.start_soon(interrupt_after_delay)

269

270

async for msg in client.receive_messages():

271

if isinstance(msg, AssistantMessage):

272

print("Received message before interrupt")

273

274

anyio.run(main)

275

```

276

277

### Dynamic Permission Mode

278

279

```python

280

import anyio

281

from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions

282

283

async def main():

284

options = ClaudeAgentOptions(

285

allowed_tools=["Read", "Write", "Edit"],

286

permission_mode='default' # Start with default (ask for permissions)

287

)

288

289

async with ClaudeSDKClient(options=options) as client:

290

# Review mode - ask for permissions

291

await client.query("Help me analyze this codebase")

292

async for msg in client.receive_response():

293

print(msg)

294

295

# Implementation mode - auto-accept edits

296

await client.set_permission_mode('acceptEdits')

297

await client.query("Now implement the fix we discussed")

298

async for msg in client.receive_response():

299

print(msg)

300

301

anyio.run(main)

302

```

303

304

### Switching Models

305

306

```python

307

import anyio

308

from claude_agent_sdk import ClaudeSDKClient

309

310

async def main():

311

async with ClaudeSDKClient() as client:

312

# Start with default model

313

await client.query("Explain quantum computing briefly")

314

async for msg in client.receive_response():

315

print(msg)

316

317

# Switch to a specific model for detailed implementation

318

await client.set_model('claude-sonnet-4-5')

319

await client.query("Now implement a quantum circuit simulator")

320

async for msg in client.receive_response():

321

print(msg)

322

323

anyio.run(main)

324

```

325

326

### Getting Server Info

327

328

```python

329

import anyio

330

from claude_agent_sdk import ClaudeSDKClient

331

332

async def main():

333

async with ClaudeSDKClient() as client:

334

info = await client.get_server_info()

335

if info:

336

print(f"Available commands: {len(info.get('commands', []))}")

337

print(f"Output style: {info.get('output_style', 'default')}")

338

print(f"Server capabilities: {info.get('capabilities', {})}")

339

340

anyio.run(main)

341

```

342

343

### Manual Connection Management

344

345

```python

346

import anyio

347

from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions

348

349

async def main():

350

options = ClaudeAgentOptions(

351

allowed_tools=["Read", "Write"]

352

)

353

354

client = ClaudeSDKClient(options=options)

355

356

try:

357

# Manual connect

358

await client.connect()

359

360

await client.query("Hello Claude")

361

async for msg in client.receive_response():

362

print(msg)

363

364

finally:

365

# Manual disconnect

366

await client.disconnect()

367

368

anyio.run(main)

369

```

370

371

### Collecting All Messages

372

373

```python

374

import anyio

375

from claude_agent_sdk import ClaudeSDKClient, ResultMessage

376

377

async def main():

378

async with ClaudeSDKClient() as client:

379

await client.query("What is 2 + 2?")

380

381

# Collect all messages into a list

382

messages = [msg async for msg in client.receive_response()]

383

384

# Last message is always ResultMessage

385

result = messages[-1]

386

if isinstance(result, ResultMessage):

387

print(f"Total cost: ${result.total_cost_usd:.4f}")

388

print(f"Number of turns: {result.num_turns}")

389

print(f"Duration: {result.duration_ms}ms")

390

391

anyio.run(main)

392

```

393

394

### Streaming with AsyncIterable Prompts

395

396

```python

397

import anyio

398

from claude_agent_sdk import ClaudeSDKClient

399

400

async def main():

401

async def message_stream():

402

yield {"type": "user", "message": {"role": "user", "content": "First question"}, "session_id": "1"}

403

await anyio.sleep(1)

404

yield {"type": "user", "message": {"role": "user", "content": "Follow up"}, "session_id": "1"}

405

406

async with ClaudeSDKClient() as client:

407

await client.query(message_stream())

408

409

async for msg in client.receive_messages():

410

print(msg)

411

412

anyio.run(main)

413

```

414

415

### Using Custom Transport

416

417

```python

418

import anyio

419

from claude_agent_sdk import ClaudeSDKClient, Transport

420

421

class MyCustomTransport(Transport):

422

# Implement custom transport logic

423

async def connect(self) -> None:

424

pass

425

426

async def write(self, data: str) -> None:

427

pass

428

429

def read_messages(self):

430

pass

431

432

async def close(self) -> None:

433

pass

434

435

def is_ready(self) -> bool:

436

return True

437

438

async def end_input(self) -> None:

439

pass

440

441

async def main():

442

transport = MyCustomTransport()

443

client = ClaudeSDKClient(transport=transport)

444

445

async with client:

446

await client.query("Hello")

447

async for msg in client.receive_response():

448

print(msg)

449

450

anyio.run(main)

451

```

452

453

### Error Handling

454

455

```python

456

import anyio

457

from claude_agent_sdk import (

458

ClaudeSDKClient,

459

ClaudeSDKError,

460

CLINotFoundError,

461

CLIConnectionError

462

)

463

464

async def main():

465

try:

466

async with ClaudeSDKClient() as client:

467

await client.query("Hello Claude")

468

async for msg in client.receive_response():

469

print(msg)

470

except CLINotFoundError:

471

print("Please install Claude Code")

472

except CLIConnectionError as e:

473

print(f"Connection error: {e}")

474

except ClaudeSDKError as e:

475

print(f"SDK error: {e}")

476

477

anyio.run(main)

478

```

479