claude-agent-sdk 0.16.7 → 0.16.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -6,12 +6,9 @@
6
6
 
7
7
  An **unofficial, community-maintained** Ruby SDK for the [Claude Code](https://docs.claude.com/en/docs/claude-code-overview) agent runtime. Not affiliated with or supported by Anthropic.
8
8
 
9
- ### Official SDKs
9
+ Official SDKs: [TypeScript](https://github.com/anthropics/claude-agent-sdk-typescript) · [Python](https://github.com/anthropics/claude-agent-sdk-python).
10
10
 
11
- - **TypeScript** (official): [anthropics/claude-agent-sdk-typescript](https://github.com/anthropics/claude-agent-sdk-typescript)
12
- - **Python** (official): [anthropics/claude-agent-sdk-python](https://github.com/anthropics/claude-agent-sdk-python)
13
-
14
- ### Why a Ruby SDK?
11
+ ## Why a Ruby SDK?
15
12
 
16
13
  Ruby powers a massive ecosystem — Rails, Sidekiq, Kamal, countless production web apps — but has no official Claude Agent SDK. This gem fills that gap so Ruby and Rails developers can build AI agents, automate coding workflows, and integrate Claude into existing applications without switching languages or shelling out to Python/Node.
17
14
 
@@ -39,16 +36,14 @@ All three SDKs share the same underlying mechanism: they spawn the `claude` CLI
39
36
  | Custom transport (pluggable I/O) | — | — | ✅ |
40
37
  | Rails integration | — | — | ✅ |
41
38
 
42
- **Where Ruby goes further:** Built-in OpenTelemetry observer with Langfuse flow diagram support — no third-party instrumentation library needed. Custom transport support lets you swap the subprocess for any I/O layer (e.g., connect to a remote Claude Code instance over SSH or a container). Rails integration provides a `configure` block for initializers with thread-safe observer factories, and plays well with ActionCable for real-time streaming. Full typed coverage for all 24 CLI message types and all 27 hook events — some of which the Python SDK hasn't typed yet.
39
+ **Where Ruby goes further:** Built-in OpenTelemetry observer with Langfuse flow diagram support — no third-party instrumentation library needed. Custom transport support lets you swap the subprocess for any I/O layer (e.g., connect to a remote Claude Code instance over SSH or a container). Rails integration provides a `configure` block for initializers with thread-safe observer factories, and plays well with ActionCable for real-time streaming. Full typed coverage for all 24 CLI message types and all 27 hook events.
43
40
 
44
41
  **What's missing:** The Ruby gem does not bundle the `claude` CLI binary (`npm install -g @anthropic-ai/claude-code`).
45
42
 
46
43
  <details>
47
44
  <summary><strong>Implementation differences from the official SDKs</strong></summary>
48
45
 
49
- #### Async model
50
-
51
- TypeScript uses native `async`/`await`. Python uses `async`/`await` with `anyio`. Ruby uses the [`async`](https://github.com/socketry/async) gem with fibers — no `await` keyword needed, blocking calls yield automatically inside an `Async` block.
46
+ TypeScript uses native `async`/`await`. Python uses `async`/`await` with `anyio`. Ruby uses the [`async`](https://github.com/socketry/async) gem with fibers — no `await` keyword needed; blocking calls yield automatically inside an `Async` block:
52
47
 
53
48
  ```ruby
54
49
  Async do
@@ -60,68 +55,23 @@ Async do
60
55
  end.wait
61
56
  ```
62
57
 
63
- #### Types
64
-
65
- TypeScript has Zod schemas with inferred types. Python uses `dataclass` with type annotations. Ruby uses plain classes with `attr_accessor` and keyword args — no runtime type checking, but the same structure and field names.
66
-
67
- #### Subprocess transport
68
-
69
- All three SDKs spawn `claude` CLI as a subprocess with stream-JSON over stdin/stdout. TypeScript uses Node `child_process`, Python uses `anyio.open_process`, Ruby uses `Open3.popen3`. The wire protocol is identical.
58
+ Types use plain Ruby classes with `attr_accessor` and keyword args — no runtime type checking, but the same structure and field names as the TS Zod schemas / Python dataclasses. Subprocess transport uses `Open3.popen3`; wire protocol is identical.
70
59
 
71
60
  </details>
72
61
 
73
- ## Table of Contents
74
-
75
- - [Installation](#installation)
76
- - [Quick Start](#quick-start)
77
- - [Basic Usage: query()](#basic-usage-query)
78
- - [Client](#client)
79
- - [Custom Transport](#custom-transport)
80
- - [Custom Tools (SDK MCP Servers)](#custom-tools-sdk-mcp-servers)
81
- - [Hooks](#hooks)
82
- - [Permission Callbacks](#permission-callbacks)
83
- - [Structured Output](#structured-output)
84
- - [Thinking Configuration](#thinking-configuration)
85
- - [Budget Control](#budget-control)
86
- - [Fallback Model](#fallback-model)
87
- - [Beta Features](#beta-features)
88
- - [Tools Configuration](#tools-configuration)
89
- - [Sandbox Settings](#sandbox-settings)
90
- - [Bare Mode](#bare-mode)
91
- - [File Checkpointing & Rewind](#file-checkpointing--rewind)
92
- - [Session Browsing](#session-browsing)
93
- - [Session Mutations](#session-mutations)
94
- - [Observability (OpenTelemetry / Langfuse)](#observability-opentelemetry--langfuse)
95
- - [Rails Integration](#rails-integration)
96
- - [Types](#types)
97
- - [Error Handling](#error-handling)
98
- - [Examples](#examples)
99
- - [Development](#development)
100
- - [License](#license)
101
-
102
62
  ## Installation
103
63
 
104
64
  Add this line to your application's Gemfile:
105
65
 
106
66
  ```ruby
107
- # Recommended: Use the latest from GitHub for newest features
67
+ # Recommended: use the latest from GitHub for newest features
108
68
  gem 'claude-agent-sdk', github: 'ya-luotao/claude-agent-sdk-ruby'
109
69
 
110
70
  # Or use a stable version from RubyGems
111
- gem 'claude-agent-sdk', '~> 0.16.7'
112
- ```
113
-
114
- And then execute:
115
-
116
- ```bash
117
- bundle install
71
+ gem 'claude-agent-sdk', '~> 0.16.8'
118
72
  ```
119
73
 
120
- Or install directly from RubyGems:
121
-
122
- ```bash
123
- gem install claude-agent-sdk
124
- ```
74
+ Then `bundle install`, or install directly: `gem install claude-agent-sdk`.
125
75
 
126
76
  **Prerequisites:**
127
77
  - Ruby 3.2+
@@ -133,14 +83,11 @@ gem install claude-agent-sdk
133
83
  If you're using [Claude Code](https://claude.ai/claude-code), this repo is a Claude Code plugin marketplace. Add it once, then install the skill:
134
84
 
135
85
  ```bash
136
- # Add the marketplace
137
86
  /plugin marketplace add ya-luotao/claude-agent-sdk-ruby
138
-
139
- # Install the plugin
140
87
  /plugin install claude-agent-ruby@claude-agent-sdk-ruby
141
88
  ```
142
89
 
143
- This skill teaches your AI coding assistant about the SDK's APIs, patterns, and best practices, making it easier to get help writing code that uses this SDK.
90
+ This skill teaches your AI coding assistant about the SDK's APIs, patterns, and best practices.
144
91
 
145
92
  ## Quick Start
146
93
 
@@ -152,7 +99,7 @@ ClaudeAgentSDK.query(prompt: "What is 2 + 2?") do |message|
152
99
  end
153
100
  ```
154
101
 
155
- ## Basic Usage: query()
102
+ ## Basic Usage: `query()`
156
103
 
157
104
  `query()` is a function for querying Claude Code. It yields response messages to a block.
158
105
 
@@ -175,72 +122,33 @@ ClaudeAgentSDK.query(prompt: "Tell me a joke", options: options) do |message|
175
122
  end
176
123
  ```
177
124
 
178
- ### Using Tools
125
+ **Using tools:**
179
126
 
180
127
  ```ruby
181
128
  options = ClaudeAgentSDK::ClaudeAgentOptions.new(
182
129
  allowed_tools: ['Read', 'Write', 'Bash'],
183
- permission_mode: 'acceptEdits' # auto-accept file edits
184
- )
185
-
186
- ClaudeAgentSDK.query(
187
- prompt: "Create a hello.rb file",
188
- options: options
189
- ) do |message|
190
- # Process tool use and results
191
- end
192
- ```
193
-
194
- ### Working Directory
195
-
196
- ```ruby
197
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
130
+ permission_mode: 'acceptEdits',
198
131
  cwd: "/path/to/project"
199
132
  )
200
- ```
201
-
202
- ### Streaming Input
203
133
 
204
- The `query()` function supports streaming input, allowing you to send multiple messages dynamically instead of a single prompt string.
205
-
206
- ```ruby
207
- require 'claude_agent_sdk'
208
-
209
- # Create a stream of messages
210
- messages = ['Hello!', 'What is 2+2?', 'Thanks!']
211
- stream = ClaudeAgentSDK::Streaming.from_array(messages)
212
-
213
- # Query with streaming input
214
- ClaudeAgentSDK.query(prompt: stream) do |message|
215
- puts message if message.is_a?(ClaudeAgentSDK::AssistantMessage)
216
- end
134
+ ClaudeAgentSDK.query(prompt: "Create a hello.rb file", options: options) { |message| }
217
135
  ```
218
136
 
219
- You can also create custom streaming enumerators:
137
+ **Streaming input** send multiple messages dynamically instead of a single prompt string:
220
138
 
221
139
  ```ruby
222
- # Dynamic message generation
223
- stream = Enumerator.new do |yielder|
224
- yielder << ClaudeAgentSDK::Streaming.user_message("First message")
225
- # Do some processing...
226
- yielder << ClaudeAgentSDK::Streaming.user_message("Second message")
227
- yielder << ClaudeAgentSDK::Streaming.user_message("Third message")
228
- end
140
+ stream = ClaudeAgentSDK::Streaming.from_array(['Hello!', 'What is 2+2?', 'Thanks!'])
229
141
 
230
142
  ClaudeAgentSDK.query(prompt: stream) do |message|
231
- # Process responses
143
+ puts message if message.is_a?(ClaudeAgentSDK::AssistantMessage)
232
144
  end
233
145
  ```
234
146
 
235
- For a complete example, see [examples/streaming_input_example.rb](examples/streaming_input_example.rb).
236
-
237
- ## Client
238
-
239
- `ClaudeAgentSDK::Client` supports bidirectional, interactive conversations with Claude Code. Unlike `query()`, `Client` enables **custom tools**, **hooks**, and **permission callbacks**, all of which can be defined as Ruby procs/lambdas.
147
+ See [examples/streaming_input_example.rb](examples/streaming_input_example.rb) and [examples/quick_start.rb](examples/quick_start.rb).
240
148
 
241
- **The Client class automatically uses streaming mode** for bidirectional communication, allowing you to send multiple queries dynamically during a single session without closing the connection.
149
+ ## `Client` Bidirectional Sessions
242
150
 
243
- ### Basic Client Usage
151
+ `Client` supports interactive conversations with hooks, permission callbacks, and custom tools. It uses streaming mode automatically.
244
152
 
245
153
  ```ruby
246
154
  require 'claude_agent_sdk'
@@ -250,1602 +158,122 @@ Async do
250
158
  client = ClaudeAgentSDK::Client.new
251
159
 
252
160
  begin
253
- # Connect automatically uses streaming mode for bidirectional communication
254
161
  client.connect
255
-
256
- # Send a query
257
162
  client.query("What is the capital of France?")
258
-
259
- # Receive the response
260
- client.receive_response do |msg|
261
- case msg
262
- when ClaudeAgentSDK::AssistantMessage
263
- puts msg.text
264
- when ClaudeAgentSDK::ResultMessage
265
- puts "Cost: $#{msg.total_cost_usd}" if msg.total_cost_usd
266
- end
267
- end
268
-
163
+ client.receive_response { |msg| puts msg }
269
164
  ensure
270
165
  client.disconnect
271
166
  end
272
167
  end.wait
273
168
  ```
274
169
 
275
- ### Advanced Client Features
276
-
277
- ```ruby
278
- Async do
279
- client = ClaudeAgentSDK::Client.new
280
- client.connect
281
-
282
- # Send interrupt signal
283
- client.interrupt
284
-
285
- # Change permission mode during conversation
286
- client.set_permission_mode('acceptEdits')
287
-
288
- # Change AI model during conversation
289
- client.set_model('claude-sonnet-4-5')
290
-
291
- # Get MCP server connection status
292
- status = client.get_mcp_status
293
- puts "MCP status: #{status}"
294
-
295
- # Get server initialization info
296
- info = client.get_server_info
297
- puts "Available commands: #{info}"
298
-
299
- # Reconnect a failed MCP server
300
- client.reconnect_mcp_server('my-server')
301
-
302
- # Enable or disable an MCP server
303
- client.toggle_mcp_server('my-server', false)
304
-
305
- # Stop a running background task
306
- client.stop_task('task_abc123')
307
-
308
- client.disconnect
309
- end.wait
310
- ```
311
-
312
- ### Custom Transport
313
-
314
- By default, `Client` uses `SubprocessCLITransport` to spawn the Claude Code CLI locally. You can provide a custom transport class to connect via other channels (e.g., remote SSH, WebSocket, or a sandbox VM).
315
-
316
- A transport must implement six methods:
317
-
318
- | Method | Purpose |
319
- |---|---|
320
- | `connect` | Establish the connection / spawn the remote CLI |
321
- | `write(data)` | Send raw JSON-line bytes to stdin |
322
- | `read_messages { \|hash\| ... }` | Yield parsed JSON messages from stdout; block until the stream closes |
323
- | `end_input` | Signal EOF on stdin |
324
- | `close` | Terminate and clean up |
325
- | `ready?` | Report whether the transport can accept I/O |
326
-
327
- Then plug it into `Client` via `transport_class:` / `transport_args:`. All connect orchestration (option transforms, MCP extraction, hook conversion, Query lifecycle) is handled for you.
328
-
329
- ```ruby
330
- client = ClaudeAgentSDK::Client.new(
331
- options: options,
332
- transport_class: MyTransport,
333
- transport_args: { foo: 'bar' } # forwarded to MyTransport.new(options, **transport_args)
334
- )
335
- ```
336
-
337
- #### Reference: running `claude` inside an E2B sandbox
338
-
339
- [`examples/e2b_transport_example.rb`](examples/e2b_transport_example.rb) is a working transport that runs the Claude Code CLI inside an [E2B](https://e2b.dev) Firecracker microVM instead of on your host. The wire protocol stays identical — only the I/O layer changes:
340
-
341
- ```
342
- ClaudeAgentSDK::Client (host)
343
- │ JSON-lines
344
-
345
- E2BCliTransport (host)
346
- │ send_stdin / commands.run(background:) / CommandHandle#each
347
-
348
- E2B envd RPC (HTTP/2)
349
-
350
-
351
- /usr/local/bin/claude (in-VM subprocess)
352
- ```
353
-
354
- The example reuses the SDK's `CommandBuilder` to produce the exact same argv that `SubprocessCLITransport` would build (including SDK MCP server `:instance` field stripping), shell-escapes it for E2B's `/bin/bash -l -c` execution path, and streams stdout/stderr back through `CommandHandle#each`.
355
-
356
- Sketch (full file is ~250 lines):
357
-
358
- ```ruby
359
- require 'claude_agent_sdk'
360
- require 'e2b'
361
-
362
- class E2BCliTransport < ClaudeAgentSDK::Transport
363
- def initialize(options, sandbox:, cli_path: '/usr/local/bin/claude')
364
- @options, @sandbox, @cli_path = options, sandbox, cli_path
365
- end
366
-
367
- def connect
368
- argv = ClaudeAgentSDK::CommandBuilder.new(@cli_path, @options).build
369
- cmd = argv.map { |a| Shellwords.shellescape(a.to_s) }.join(' ')
370
- @handle = @sandbox.commands.run(cmd, background: true, stdin: true,
371
- cwd: @options.cwd&.to_s, envs: build_env)
372
- @pid = @handle.pid
373
- @ready = true
374
- end
375
-
376
- def write(data) = @sandbox.commands.send_stdin(@pid, data)
377
- def end_input = @sandbox.commands.close_stdin(@pid)
378
- def close = @handle&.kill
379
- def ready? = @ready
380
-
381
- def read_messages(&block)
382
- buf = +''
383
- @handle.each do |stdout, stderr, _pty|
384
- next if stderr && !stderr.empty?
385
- stdout.each_line do |line|
386
- buf << line.strip
387
- begin
388
- yield JSON.parse(buf, symbolize_names: true)
389
- buf.clear
390
- rescue JSON::ParserError
391
- # JSON line split across reads — keep buffering
392
- end
393
- end
394
- end
395
- @handle.wait # raises E2B::CommandExitError on non-zero exit
396
- end
397
- end
398
-
399
- # Use it
400
- sandbox = E2B::Sandbox.create(template: 'base', timeout: 600)
401
- Async do
402
- client = ClaudeAgentSDK::Client.new(
403
- options: options,
404
- transport_class: E2BCliTransport,
405
- transport_args: { sandbox: sandbox }
406
- )
407
- client.connect
408
- client.query('Hello from the sandbox!')
409
- client.receive_response { |msg| puts msg }
410
- client.disconnect
411
- ensure
412
- sandbox.kill
413
- end.wait
414
- ```
415
-
416
- **Why use a remote transport?** Untrusted code execution, multi-tenant agent runs that can't share a host, environments without local Node.js, or simply isolating filesystem/network blast radius. The Firecracker VM gives you a fresh `/home/user` per session and is killable without touching the host.
417
-
418
- **Production hardening** (intentionally omitted from the example for clarity): inactivity watchdog, keepalive heartbeat, stream reconnect on transient SSL/EOF errors, host env-var blocklist, MCP server filtering for sandbox compatibility. See the example file's header comments for what to add and why.
170
+ Advanced features (`interrupt`, mid-session model/permission switching, MCP status, custom transports for E2B/SSH/etc.) → see [docs/client.md](docs/client.md).
419
171
 
420
172
  ## Custom Tools (SDK MCP Servers)
421
173
 
422
- A **custom tool** is a Ruby proc/lambda that you can offer to Claude, for Claude to invoke as needed.
423
-
424
- Custom tools are implemented as in-process MCP servers that run directly within your Ruby application, eliminating the need for separate processes that regular MCP servers require.
425
-
426
- **Implementation**: This SDK uses the [official Ruby MCP SDK](https://github.com/modelcontextprotocol/ruby-sdk) (`mcp` gem) internally, providing full protocol compliance while offering a simpler block-based API for tool definition.
427
-
428
- ### Creating a Simple Tool
174
+ Define tools as Ruby procs/lambdas that run in-process no subprocess, no IPC, direct access to your app state.
429
175
 
430
176
  ```ruby
431
- require 'claude_agent_sdk'
432
- require 'async'
433
-
434
- # Define a tool using create_tool (with optional annotations)
435
- greet_tool = ClaudeAgentSDK.create_tool(
436
- 'greet', 'Greet a user', { name: :string },
437
- annotations: { title: 'Greeter', readOnlyHint: true }
438
- ) do |args|
177
+ greet = ClaudeAgentSDK.create_tool('greet', 'Greet a user', { name: :string }) do |args|
439
178
  { content: [{ type: 'text', text: "Hello, #{args[:name]}!" }] }
440
179
  end
441
180
 
442
- # Create an SDK MCP server
443
- server = ClaudeAgentSDK.create_sdk_mcp_server(
444
- name: 'my-tools',
445
- version: '1.0.0',
446
- tools: [greet_tool]
447
- )
181
+ server = ClaudeAgentSDK.create_sdk_mcp_server(name: 'my-tools', tools: [greet])
448
182
 
449
- # Use it with Claude
450
183
  options = ClaudeAgentSDK::ClaudeAgentOptions.new(
451
184
  mcp_servers: { tools: server },
452
185
  allowed_tools: ['mcp__tools__greet']
453
186
  )
454
-
455
- Async do
456
- client = ClaudeAgentSDK::Client.new(options: options)
457
- client.connect
458
-
459
- client.query("Greet Alice")
460
- client.receive_response { |msg| puts msg }
461
-
462
- client.disconnect
463
- end.wait
464
- ```
465
-
466
- ### Pre-built JSON Schemas
467
-
468
- If your schemas come from another library (e.g., [RubyLLM](https://github.com/crmne/ruby_llm)) that deep-stringifies keys, the SDK handles them transparently — both symbol-keyed and string-keyed schemas are accepted and normalized:
469
-
470
- ```ruby
471
- # Symbol keys (standard Ruby)
472
- tool = ClaudeAgentSDK.create_tool('save', 'Save a fact', {
473
- type: 'object',
474
- properties: { fact: { type: 'string' } },
475
- required: ['fact']
476
- }) { |args| { content: [{ type: 'text', text: "Saved: #{args[:fact]}" }] } }
477
-
478
- # String keys (e.g., from RubyLLM or JSON.parse)
479
- tool = ClaudeAgentSDK.create_tool('save', 'Save a fact', {
480
- 'type' => 'object',
481
- 'properties' => { 'fact' => { 'type' => 'string' } },
482
- 'required' => ['fact']
483
- }) { |args| { content: [{ type: 'text', text: "Saved: #{args[:fact]}" }] } }
484
- ```
485
-
486
- ### Benefits Over External MCP Servers
487
-
488
- - **No subprocess management** - Runs in the same process as your application
489
- - **Better performance** - No IPC overhead for tool calls
490
- - **Simpler deployment** - Single Ruby process instead of multiple
491
- - **Easier debugging** - All code runs in the same process
492
- - **Direct access** - Tools can directly access your application's state
493
-
494
- ### Calculator Example
495
-
496
- ```ruby
497
- # Define calculator tools
498
- add_tool = ClaudeAgentSDK.create_tool('add', 'Add two numbers', { a: :number, b: :number }) do |args|
499
- result = args[:a] + args[:b]
500
- { content: [{ type: 'text', text: "#{args[:a]} + #{args[:b]} = #{result}" }] }
501
- end
502
-
503
- divide_tool = ClaudeAgentSDK.create_tool('divide', 'Divide numbers', { a: :number, b: :number }) do |args|
504
- if args[:b] == 0
505
- { content: [{ type: 'text', text: 'Error: Division by zero' }], is_error: true }
506
- else
507
- result = args[:a] / args[:b]
508
- { content: [{ type: 'text', text: "Result: #{result}" }] }
509
- end
510
- end
511
-
512
- # Create server
513
- calculator = ClaudeAgentSDK.create_sdk_mcp_server(
514
- name: 'calculator',
515
- tools: [add_tool, divide_tool]
516
- )
517
-
518
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
519
- mcp_servers: { calc: calculator },
520
- allowed_tools: ['mcp__calc__add', 'mcp__calc__divide']
521
- )
522
- ```
523
-
524
- ### Mixed Server Support
525
-
526
- You can use both SDK and external MCP servers together:
527
-
528
- ```ruby
529
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
530
- mcp_servers: {
531
- internal: sdk_server, # In-process SDK server
532
- external: { # External subprocess server
533
- type: 'stdio',
534
- command: 'external-server'
535
- }
536
- }
537
- )
538
- ```
539
-
540
- ### MCP Resources and Prompts
541
-
542
- SDK MCP servers can also expose **resources** (data sources) and **prompts** (reusable templates):
543
-
544
- ```ruby
545
- # Create a resource (data source Claude can read)
546
- config_resource = ClaudeAgentSDK.create_resource(
547
- uri: 'config://app/settings',
548
- name: 'Application Settings',
549
- description: 'Current app configuration',
550
- mime_type: 'application/json'
551
- ) do
552
- config_data = { app_name: 'MyApp', version: '1.0.0' }
553
- {
554
- contents: [{
555
- uri: 'config://app/settings',
556
- mimeType: 'application/json',
557
- text: JSON.pretty_generate(config_data)
558
- }]
559
- }
560
- end
561
-
562
- # Create a prompt template
563
- review_prompt = ClaudeAgentSDK.create_prompt(
564
- name: 'code_review',
565
- description: 'Review code for best practices',
566
- arguments: [
567
- { name: 'code', description: 'Code to review', required: true }
568
- ]
569
- ) do |args|
570
- {
571
- messages: [{
572
- role: 'user',
573
- content: {
574
- type: 'text',
575
- text: "Review this code: #{args[:code]}"
576
- }
577
- }]
578
- }
579
- end
580
-
581
- # Create server with tools, resources, and prompts
582
- server = ClaudeAgentSDK.create_sdk_mcp_server(
583
- name: 'dev-tools',
584
- tools: [my_tool],
585
- resources: [config_resource],
586
- prompts: [review_prompt]
587
- )
588
- ```
589
-
590
- For complete examples, see [examples/mcp_calculator.rb](examples/mcp_calculator.rb) and [examples/mcp_resources_prompts_example.rb](examples/mcp_resources_prompts_example.rb).
591
-
592
- ## Hooks
593
-
594
- A **hook** is a Ruby proc/lambda that the Claude Code *application* (*not* Claude) invokes at specific points of the Claude agent loop. Hooks can provide deterministic processing and automated feedback for Claude. Read more in [Claude Code Hooks Reference](https://docs.anthropic.com/en/docs/claude-code/hooks).
595
-
596
- ### Supported Events
597
-
598
- All hook input objects include common fields like `session_id`, `transcript_path`, `cwd`, and `permission_mode`.
599
-
600
- - `PreToolUse` → `PreToolUseHookInput` (`tool_name`, `tool_input`, `tool_use_id`)
601
- - `PostToolUse` → `PostToolUseHookInput` (`tool_name`, `tool_input`, `tool_response`, `tool_use_id`)
602
- - `PostToolUseFailure` → `PostToolUseFailureHookInput` (`tool_name`, `tool_input`, `tool_use_id`, `error`, `is_interrupt`)
603
- - `UserPromptSubmit` → `UserPromptSubmitHookInput` (`prompt`)
604
- - `Stop` → `StopHookInput` (`stop_hook_active`)
605
- - `SubagentStop` → `SubagentStopHookInput` (`stop_hook_active`, `agent_id`, `agent_transcript_path`, `agent_type`)
606
- - `PreCompact` → `PreCompactHookInput` (`trigger`, `custom_instructions`)
607
- - `Notification` → `NotificationHookInput` (`message`, `title`, `notification_type`)
608
- - `SubagentStart` → `SubagentStartHookInput` (`agent_id`, `agent_type`)
609
- - `PermissionRequest` → `PermissionRequestHookInput` (`tool_name`, `tool_input`, `permission_suggestions`)
610
-
611
- ### Example
612
-
613
- ```ruby
614
- require 'claude_agent_sdk'
615
- require 'async'
616
-
617
- Async do
618
- # Define a hook that blocks dangerous bash commands
619
- bash_hook = lambda do |input, _tool_use_id, _context|
620
- # Hook inputs are typed objects (e.g., PreToolUseHookInput) with Ruby-style accessors
621
- return {} unless input.respond_to?(:tool_name) && input.tool_name == 'Bash'
622
-
623
- tool_input = input.tool_input || {}
624
- command = tool_input[:command] || tool_input['command'] || ''
625
- block_patterns = ['rm -rf', 'foo.sh']
626
-
627
- block_patterns.each do |pattern|
628
- if command.include?(pattern)
629
- return {
630
- hookSpecificOutput: {
631
- hookEventName: 'PreToolUse',
632
- permissionDecision: 'deny',
633
- permissionDecisionReason: "Command contains forbidden pattern: #{pattern}"
634
- }
635
- }
636
- end
637
- end
638
-
639
- {} # Allow if no patterns match
640
- end
641
-
642
- # Create options with hook
643
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
644
- allowed_tools: ['Bash'],
645
- hooks: {
646
- 'PreToolUse' => [
647
- ClaudeAgentSDK::HookMatcher.new(
648
- matcher: 'Bash',
649
- hooks: [bash_hook]
650
- )
651
- ]
652
- }
653
- )
654
-
655
- client = ClaudeAgentSDK::Client.new(options: options)
656
- client.connect
657
-
658
- # Test: Command with forbidden pattern (will be blocked)
659
- client.query("Run the bash command: ./foo.sh --help")
660
- client.receive_response { |msg| puts msg }
661
-
662
- client.disconnect
663
- end.wait
664
- ```
665
-
666
- For more examples, see [examples/hooks_example.rb](examples/hooks_example.rb).
667
-
668
- ## Permission Callbacks
669
-
670
- A **permission callback** is a Ruby proc/lambda that allows you to programmatically control tool execution. This gives you fine-grained control over what tools Claude can use and with what inputs.
671
-
672
- ### Example
673
-
674
- ```ruby
675
- require 'claude_agent_sdk'
676
- require 'async'
677
-
678
- Async do
679
- # Define a permission callback
680
- permission_callback = lambda do |tool_name, input, context|
681
- # Allow Read operations
682
- if tool_name == 'Read'
683
- return ClaudeAgentSDK::PermissionResultAllow.new
684
- end
685
-
686
- # Block Write to sensitive files
687
- if tool_name == 'Write'
688
- file_path = input[:file_path] || input['file_path']
689
- if file_path && file_path.include?('/etc/')
690
- return ClaudeAgentSDK::PermissionResultDeny.new(
691
- message: 'Cannot write to sensitive system files',
692
- interrupt: false
693
- )
694
- end
695
- return ClaudeAgentSDK::PermissionResultAllow.new
696
- end
697
-
698
- # Default: allow
699
- ClaudeAgentSDK::PermissionResultAllow.new
700
- end
701
-
702
- # Create options with permission callback
703
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
704
- allowed_tools: ['Read', 'Write', 'Bash'],
705
- can_use_tool: permission_callback
706
- )
707
-
708
- client = ClaudeAgentSDK::Client.new(options: options)
709
- client.connect
710
-
711
- # This will be allowed
712
- client.query("Create a file called test.txt with content 'Hello'")
713
- client.receive_response { |msg| puts msg }
714
-
715
- # This will be blocked
716
- client.query("Write to /etc/passwd")
717
- client.receive_response { |msg| puts msg }
718
-
719
- client.disconnect
720
- end.wait
721
- ```
722
-
723
- For more examples, see [examples/permission_callback_example.rb](examples/permission_callback_example.rb).
724
-
725
- ## Structured Output
726
-
727
- Use `output_format` to get validated JSON responses matching a schema. The Claude CLI returns structured output via a `StructuredOutput` tool use block.
728
-
729
- ```ruby
730
- require 'claude_agent_sdk'
731
- require 'json'
732
-
733
- # Define a JSON schema
734
- schema = {
735
- type: 'object',
736
- properties: {
737
- name: { type: 'string' },
738
- age: { type: 'integer' },
739
- skills: { type: 'array', items: { type: 'string' } }
740
- },
741
- required: %w[name age skills]
742
- }
743
-
744
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
745
- output_format: { type: 'json_schema', schema: schema },
746
- max_turns: 3
747
- )
748
-
749
- structured_data = nil
750
-
751
- ClaudeAgentSDK.query(
752
- prompt: "Create a profile for a software engineer",
753
- options: options
754
- ) do |message|
755
- if message.is_a?(ClaudeAgentSDK::AssistantMessage)
756
- message.content.each do |block|
757
- # Structured output comes via StructuredOutput tool use
758
- if block.is_a?(ClaudeAgentSDK::ToolUseBlock) && block.name == 'StructuredOutput'
759
- structured_data = block.input
760
- end
761
- end
762
- end
763
- end
764
-
765
- if structured_data
766
- puts "Name: #{structured_data[:name]}"
767
- puts "Age: #{structured_data[:age]}"
768
- puts "Skills: #{structured_data[:skills].join(', ')}"
769
- end
770
- ```
771
-
772
- For complete examples, see [examples/structured_output_example.rb](examples/structured_output_example.rb).
773
-
774
- ## Thinking Configuration
775
-
776
- Control extended thinking behavior with typed configuration objects. The `thinking` option takes precedence over the deprecated `max_thinking_tokens`.
777
-
778
- ```ruby
779
- # Adaptive thinking — CLI dynamically adjusts budget based on task complexity
780
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
781
- thinking: ClaudeAgentSDK::ThinkingConfigAdaptive.new
782
- )
783
-
784
- # Enabled thinking with explicit token budget
785
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
786
- thinking: ClaudeAgentSDK::ThinkingConfigEnabled.new(budget_tokens: 50_000)
787
- )
788
-
789
- # Explicitly disabled thinking
790
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
791
- thinking: ClaudeAgentSDK::ThinkingConfigDisabled.new
792
- )
793
- ```
794
-
795
- Use the `effort` option to control the model's effort level:
796
-
797
- ```ruby
798
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
799
- effort: 'xhigh' # see ClaudeAgentSDK::EFFORT_LEVELS
800
- )
801
187
  ```
802
188
 
803
- Valid levels live in `ClaudeAgentSDK::EFFORT_LEVELS` (`low`, `medium`, `high`, `xhigh`, `max`). The set of *supported* levels is model-dependent — `xhigh` is available on Opus 4.7 and the CLI falls back to the highest supported level at or below the one you set (e.g. `xhigh` → `high` on Opus 4.6). When `effort` is `nil`, the CLI picks a model-native default (Opus 4.7 → `xhigh`).
804
-
805
- > **Note:** When `system_prompt` is `nil` (the default), the SDK passes `--system-prompt ""` to the CLI, which suppresses the default Claude Code system prompt. To use the default system prompt, use a `SystemPromptPreset`.
189
+ Resources, prompts, mixed (SDK + external) servers, RubyLLM schema compatibility see [docs/mcp-servers.md](docs/mcp-servers.md).
806
190
 
807
- ### Cross-User Prompt Caching
191
+ ## Hooks & Permission Callbacks
808
192
 
809
- When running a multi-user fleet with shared preset prompts, enable `exclude_dynamic_sections` to make the system prompt byte-identical across users for prompt-caching hits:
193
+ **Hooks** let the Claude Code application invoke your Ruby code at all 27 lifecycle points (`PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `Stop`, `PreCompact`, etc.) with typed input objects. **Permission callbacks** give you programmatic control over tool execution.
810
194
 
811
195
  ```ruby
812
196
  options = ClaudeAgentSDK::ClaudeAgentOptions.new(
813
- system_prompt: ClaudeAgentSDK::SystemPromptPreset.new(
814
- preset: 'claude_code',
815
- append: '...your shared domain instructions...',
816
- exclude_dynamic_sections: true
817
- )
197
+ hooks: { 'PreToolUse' => [ClaudeAgentSDK::HookMatcher.new(matcher: 'Bash', hooks: [my_hook])] },
198
+ can_use_tool: my_permission_callback
818
199
  )
819
200
  ```
820
201
 
821
- When set, the CLI strips per-user dynamic sections (working directory, auto-memory, git status) from the system prompt and re-injects them into the first user message instead. Older CLIs silently ignore this option.
202
+ Full event list, typed inputs, and worked examples in [docs/hooks-and-permissions.md](docs/hooks-and-permissions.md).
822
203
 
823
- ## Budget Control
204
+ ## Advanced Topics
824
205
 
825
- Use `max_budget_usd` to set a spending cap for your queries:
206
+ | Topic | Reference |
207
+ |-------|-----------|
208
+ | Structured output, thinking config, budget, fallback model, beta features, sandbox, bare mode, file checkpointing | [docs/configuration.md](docs/configuration.md) |
209
+ | Session listing, reading, renaming, tagging, deleting, forking, resume-at-message | [docs/sessions.md](docs/sessions.md) |
210
+ | OpenTelemetry tracing, Langfuse setup, custom observers | [docs/observability.md](docs/observability.md) |
211
+ | Rails integration (fiber safety, ActionCable, sessions, jobs, HTTP MCP, observability initializer) | [docs/rails.md](docs/rails.md) |
212
+ | Message, content block, and configuration type reference | [docs/types.md](docs/types.md) |
213
+ | Error handling, exception hierarchy, timeout configuration | [docs/errors.md](docs/errors.md) |
826
214
 
827
- ```ruby
828
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
829
- max_budget_usd: 0.10, # Cap at $0.10
830
- max_turns: 3
831
- )
215
+ ## Examples
832
216
 
833
- ClaudeAgentSDK.query(prompt: "Explain recursion", options: options) do |message|
834
- if message.is_a?(ClaudeAgentSDK::ResultMessage)
835
- puts "Cost: $#{message.total_cost_usd}"
836
- end
837
- end
838
- ```
217
+ ### Core
839
218
 
840
- For complete examples, see [examples/budget_control_example.rb](examples/budget_control_example.rb).
219
+ | Example | Description |
220
+ |---------|-------------|
221
+ | [quick_start.rb](examples/quick_start.rb) | Basic `query()` usage with options |
222
+ | [client_example.rb](examples/client_example.rb) | Interactive Client usage |
223
+ | [message_types_example.rb](examples/message_types_example.rb) | Handling all 24 SDK message types |
224
+ | [streaming_input_example.rb](examples/streaming_input_example.rb) | Streaming input for multi-turn conversations |
225
+ | [session_resumption_example.rb](examples/session_resumption_example.rb) | Multi-turn conversations with session persistence |
226
+ | [structured_output_example.rb](examples/structured_output_example.rb) | JSON schema structured output |
227
+ | [error_handling_example.rb](examples/error_handling_example.rb) | Error handling with `AssistantMessage.error` |
228
+ | [bare_mode_example.rb](examples/bare_mode_example.rb) | Minimal startup with `bare: true` |
229
+ | [sandbox_example.rb](examples/sandbox_example.rb) | Full sandbox settings (network, filesystem, violations) |
230
+
231
+ ### MCP Servers
841
232
 
842
- ## Fallback Model
233
+ | Example | Description |
234
+ |---------|-------------|
235
+ | [mcp_calculator.rb](examples/mcp_calculator.rb) | Custom tools with SDK MCP servers |
236
+ | [mcp_resources_prompts_example.rb](examples/mcp_resources_prompts_example.rb) | MCP resources and prompts |
237
+ | [http_mcp_server_example.rb](examples/http_mcp_server_example.rb) | HTTP/SSE MCP server configuration |
843
238
 
844
- Use `fallback_model` to specify a backup model if the primary is unavailable:
239
+ ### Hooks & Permissions
845
240
 
846
- ```ruby
847
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
848
- model: 'claude-sonnet-4-20250514',
849
- fallback_model: 'claude-3-5-haiku-20241022'
850
- )
241
+ | Example | Description |
242
+ |---------|-------------|
243
+ | [hooks_example.rb](examples/hooks_example.rb) | Using hooks to control tool execution |
244
+ | [advanced_hooks_example.rb](examples/advanced_hooks_example.rb) | Typed hook inputs/outputs |
245
+ | [lifecycle_hooks_example.rb](examples/lifecycle_hooks_example.rb) | All 27 hook events |
246
+ | [permission_callback_example.rb](examples/permission_callback_example.rb) | Dynamic tool permission control |
851
247
 
852
- ClaudeAgentSDK.query(prompt: "Hello", options: options) do |message|
853
- if message.is_a?(ClaudeAgentSDK::AssistantMessage)
854
- puts "Model used: #{message.model}"
855
- end
856
- end
857
- ```
248
+ ### Advanced
858
249
 
859
- For complete examples, see [examples/fallback_model_example.rb](examples/fallback_model_example.rb).
250
+ | Example | Description |
251
+ |---------|-------------|
252
+ | [budget_control_example.rb](examples/budget_control_example.rb) | Budget control with `max_budget_usd` |
253
+ | [fallback_model_example.rb](examples/fallback_model_example.rb) | Fallback model configuration |
254
+ | [extended_thinking_example.rb](examples/extended_thinking_example.rb) | Extended thinking |
255
+ | [e2b_transport_example.rb](examples/e2b_transport_example.rb) | Custom transport running CLI in an E2B microVM |
860
256
 
861
- ## Beta Features
257
+ ### Observability & Rails
862
258
 
863
- Enable experimental features using the `betas` option:
259
+ | Example | Description |
260
+ |---------|-------------|
261
+ | [otel_langfuse_example.rb](examples/otel_langfuse_example.rb) | OpenTelemetry tracing with Langfuse backend |
262
+ | [rails_actioncable_example.rb](examples/rails_actioncable_example.rb) | ActionCable streaming to frontend |
263
+ | [rails_background_job_example.rb](examples/rails_background_job_example.rb) | Background jobs with session resumption |
864
264
 
865
- ```ruby
866
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
867
- betas: ['context-1m-2025-08-07'] # Extended context window
868
- )
265
+ ## Available Tools
869
266
 
870
- ClaudeAgentSDK.query(prompt: "Analyze this large document...", options: options) do |message|
871
- puts message
872
- end
873
- ```
267
+ See the [Claude Code documentation](https://docs.anthropic.com/en/docs/claude-code/settings#tools-available-to-claude) for a complete list of available tools.
874
268
 
875
- Available beta features are listed in the `SDK_BETAS` constant.
269
+ ## Development
876
270
 
877
- ## Tools Configuration
271
+ After checking out the repo, run `bundle install` to install dependencies. Then `bundle exec rspec` to run the tests.
878
272
 
879
- Configure base tools separately from allowed tools:
880
-
881
- ```ruby
882
- # Using an array of tool names
883
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
884
- tools: ['Read', 'Edit', 'Bash'] # Base tools available
885
- )
886
-
887
- # Using a preset
888
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
889
- tools: ClaudeAgentSDK::ToolsPreset.new(preset: 'claude_code')
890
- )
891
-
892
- # Appending to allowed tools
893
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
894
- append_allowed_tools: ['Write', 'Bash']
895
- )
896
- ```
897
-
898
- ## Sandbox Settings
899
-
900
- Configure [sandbox-runtime](https://github.com/anthropic-experimental/sandbox-runtime) restrictions (network policy, filesystem access) via the CLI's `--sandbox` flag. The CLI handles OS-level process isolation using `srt`.
901
-
902
- ```ruby
903
- sandbox = ClaudeAgentSDK::SandboxSettings.new(
904
- enabled: true,
905
- auto_allow_bash_if_sandboxed: true,
906
- network: ClaudeAgentSDK::SandboxNetworkConfig.new(
907
- allow_local_binding: true
908
- )
909
- )
910
-
911
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
912
- sandbox: sandbox,
913
- permission_mode: 'acceptEdits'
914
- )
915
-
916
- ClaudeAgentSDK.query(prompt: "Run some commands", options: options) do |message|
917
- puts message
918
- end
919
- ```
920
-
921
- ## Bare Mode
922
-
923
- Bare mode (`--bare`) is a minimal startup mode that skips hooks, LSP, plugin sync, attribution, auto-memory, background prefetches, keychain reads, and CLAUDE.md auto-discovery. It sets `CLAUDE_CODE_SIMPLE=1` internally. This is useful for scripted/programmatic usage where you want fast startup and full control over what's loaded.
924
-
925
- ```ruby
926
- # Sugar option
927
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
928
- bare: true,
929
- system_prompt: 'You are a code reviewer.',
930
- permission_mode: 'bypassPermissions'
931
- )
932
-
933
- ClaudeAgentSDK.query(prompt: "Review this function", options: options) do |message|
934
- # ...
935
- end
936
- ```
937
-
938
- In bare mode, explicitly provide any context you need:
939
-
940
- ```ruby
941
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
942
- bare: true,
943
- system_prompt: 'You are a helpful assistant.',
944
- add_dirs: ['/path/to/project'], # CLAUDE.md directories (auto-discovery is off)
945
- setting_sources: ['project'], # load .claude/settings.json
946
- allowed_tools: ['Read', 'Grep', 'Glob'],
947
- permission_mode: 'bypassPermissions'
948
- )
949
- ```
950
-
951
- **What bare mode skips:** hooks, LSP, plugin sync, attribution, auto-memory, background prefetches, keychain reads, CLAUDE.md auto-discovery, teammate snapshots, release notes.
952
-
953
- **What still works:** skills (via `/skill-name`), explicit `--add-dir` CLAUDE.md, `--settings`, `--mcp-config`, `--agents`, `--plugin-dir`, API key from `ANTHROPIC_API_KEY` env var.
954
-
955
- ## File Checkpointing & Rewind
956
-
957
- Enable file checkpointing to revert file changes to a previous state:
958
-
959
- ```ruby
960
- require 'async'
961
-
962
- Async do
963
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
964
- enable_file_checkpointing: true,
965
- permission_mode: 'acceptEdits'
966
- )
967
-
968
- client = ClaudeAgentSDK::Client.new(options: options)
969
- client.connect
970
-
971
- # Track user message UUIDs for potential rewind
972
- user_message_uuids = []
973
-
974
- # First query - create a file
975
- client.query("Create a test.rb file with some code")
976
- client.receive_response do |message|
977
- # Process all message types as needed
978
- case message
979
- when ClaudeAgentSDK::UserMessage
980
- # Capture UUID for rewind capability
981
- user_message_uuids << message.uuid if message.uuid
982
- when ClaudeAgentSDK::AssistantMessage
983
- puts message.text
984
- when ClaudeAgentSDK::ResultMessage
985
- puts "Query completed (cost: $#{message.total_cost_usd})"
986
- end
987
- end
988
-
989
- # Second query - modify the file
990
- client.query("Modify the test.rb file to add error handling")
991
- client.receive_response do |message|
992
- user_message_uuids << message.uuid if message.is_a?(ClaudeAgentSDK::UserMessage) && message.uuid
993
- end
994
-
995
- # Rewind to the first checkpoint (undoes the second query's changes)
996
- if user_message_uuids.first
997
- puts "Rewinding to checkpoint: #{user_message_uuids.first}"
998
- client.rewind_files(user_message_uuids.first)
999
- end
1000
-
1001
- client.disconnect
1002
- end.wait
1003
- ```
1004
-
1005
- > **Note:** The `uuid` field on `UserMessage` is populated by the CLI and represents checkpoint identifiers. Rewinding to a UUID restores file state to what it was at that point in the conversation.
1006
-
1007
- ## Session Browsing
1008
-
1009
- Browse and inspect previous Claude Code sessions directly from Ruby — no CLI subprocess required.
1010
-
1011
- ### Listing Sessions
1012
-
1013
- ```ruby
1014
- # List all sessions (sorted by most recent first)
1015
- sessions = ClaudeAgentSDK.list_sessions
1016
- sessions.each do |session|
1017
- puts "#{session.session_id}: #{session.summary} (#{session.git_branch})"
1018
- end
1019
-
1020
- # List sessions for a specific directory
1021
- sessions = ClaudeAgentSDK.list_sessions(directory: '/path/to/project', limit: 10)
1022
-
1023
- # Paginate with offset
1024
- page2 = ClaudeAgentSDK.list_sessions(directory: '.', limit: 10, offset: 10)
1025
-
1026
- # Include git worktree sessions
1027
- sessions = ClaudeAgentSDK.list_sessions(directory: '.', include_worktrees: true)
1028
- ```
1029
-
1030
- Each `SDKSessionInfo` includes:
1031
- - `session_id`, `summary`, `last_modified`, `file_size`
1032
- - `custom_title`, `first_prompt`, `git_branch`, `cwd`
1033
-
1034
- ### Reading Session Messages
1035
-
1036
- ```ruby
1037
- # Get the full conversation from a session
1038
- messages = ClaudeAgentSDK.get_session_messages(session_id: 'abc-123-...')
1039
- messages.each do |msg|
1040
- puts "[#{msg.type}] #{msg.message}"
1041
- end
1042
-
1043
- # Paginate through messages
1044
- page = ClaudeAgentSDK.get_session_messages(session_id: 'abc-123-...', offset: 10, limit: 20)
1045
- ```
1046
-
1047
- Each `SessionMessage` includes `type` (`"user"` or `"assistant"`), `uuid`, `session_id`, and `message` (raw API dict).
1048
-
1049
- > **Note:** Session browsing reads `~/.claude/projects/` JSONL files directly. It respects the `CLAUDE_CONFIG_DIR` environment variable and automatically detects git worktrees.
1050
-
1051
- ## Session Mutations
1052
-
1053
- Rename or tag sessions programmatically — no CLI subprocess required.
1054
-
1055
- ### Renaming a Session
1056
-
1057
- ```ruby
1058
- # Rename a session (appends a custom-title JSONL entry)
1059
- ClaudeAgentSDK.rename_session(
1060
- session_id: '550e8400-e29b-41d4-a716-446655440000',
1061
- title: 'My refactoring session',
1062
- directory: '/path/to/project' # optional
1063
- )
1064
- ```
1065
-
1066
- ### Tagging a Session
1067
-
1068
- ```ruby
1069
- # Tag a session (Unicode-sanitized before storing)
1070
- ClaudeAgentSDK.tag_session(
1071
- session_id: '550e8400-e29b-41d4-a716-446655440000',
1072
- tag: 'experiment'
1073
- )
1074
-
1075
- # Clear a tag
1076
- ClaudeAgentSDK.tag_session(
1077
- session_id: '550e8400-e29b-41d4-a716-446655440000',
1078
- tag: nil
1079
- )
1080
- ```
1081
-
1082
- ### Deleting a Session
1083
-
1084
- ```ruby
1085
- # Hard-delete a session (removes the JSONL file permanently)
1086
- ClaudeAgentSDK.delete_session(
1087
- session_id: '550e8400-e29b-41d4-a716-446655440000',
1088
- directory: '/path/to/project' # optional
1089
- )
1090
- ```
1091
-
1092
- ### Forking a Session
1093
-
1094
- ```ruby
1095
- # Fork a session into a new branch with fresh UUIDs
1096
- result = ClaudeAgentSDK.fork_session(
1097
- session_id: '550e8400-e29b-41d4-a716-446655440000',
1098
- title: 'Experiment branch' # optional, auto-generated if omitted
1099
- )
1100
- puts result.session_id # UUID of the new forked session
1101
-
1102
- # Fork up to a specific message (partial fork)
1103
- result = ClaudeAgentSDK.fork_session(
1104
- session_id: '550e8400-e29b-41d4-a716-446655440000',
1105
- up_to_message_id: 'message-uuid-here'
1106
- )
1107
- ```
1108
-
1109
- > **Note:** Session mutations use append-only JSONL writes with `O_WRONLY | O_APPEND` (no `O_CREAT`) for TOCTOU safety. They are safe to call while the session is open in a CLI process. `fork_session` uses `O_CREAT | O_EXCL` to prevent race conditions.
1110
-
1111
- ### Resuming at a Specific Message
1112
-
1113
- `resume_session_at` truncates the resumed conversation to messages up to **and including** the assistant message with the given UUID — useful for rewriting history from a known point or branching exploration without forking the session file. The flag rides on top of `resume`, so the original session ID is preserved; only the in-memory history loaded for the new turn is shortened.
1114
-
1115
- ```ruby
1116
- ClaudeAgentSDK.query(
1117
- prompt: 'Try a different approach',
1118
- options: ClaudeAgentSDK::ClaudeAgentOptions.new(
1119
- resume: '550e8400-e29b-41d4-a716-446655440000',
1120
- resume_session_at: 'assistant-message-uuid-from-history'
1121
- )
1122
- ) do |message|
1123
- # ...
1124
- end
1125
- ```
1126
-
1127
- `resume_session_at` requires `resume`; the SDK raises `ArgumentError` from `CommandBuilder` when this constraint is violated, matching the underlying CLI's validation but surfacing it synchronously in the caller's stack.
1128
-
1129
- ## Observability (OpenTelemetry / Langfuse)
1130
-
1131
- The SDK includes a built-in **observer interface** and an **OpenTelemetry observer** for tracing agent sessions. Traces are emitted using standard `gen_ai.*` semantic conventions, compatible with Langfuse, Jaeger, Datadog, and any OTel backend.
1132
-
1133
- ### How It Works
1134
-
1135
- Register observers via `ClaudeAgentOptions`. The SDK calls `on_message` for every parsed message in both `query()` and `Client`, and `on_close` when the session ends. Observer errors are silently rescued so they never crash your application.
1136
-
1137
- ```
1138
- claude_agent.session (root span — one per query/session)
1139
- ├── claude_agent.generation (per AssistantMessage, with model + token usage)
1140
- ├── claude_agent.tool.Bash (per tool call, open on ToolUseBlock, close on ToolResultBlock)
1141
- ├── claude_agent.tool.Read
1142
- ├── claude_agent.generation
1143
- └── ...
1144
- ```
1145
-
1146
- ### Setup with Langfuse
1147
-
1148
- **1. Install the OTel gems** (not bundled with the SDK — you choose your exporter):
1149
-
1150
- ```bash
1151
- gem install opentelemetry-sdk opentelemetry-exporter-otlp
1152
- ```
1153
-
1154
- Or add to your Gemfile:
1155
-
1156
- ```ruby
1157
- gem 'opentelemetry-sdk', '~> 1.4'
1158
- gem 'opentelemetry-exporter-otlp', '~> 0.28'
1159
- ```
1160
-
1161
- **2. Configure the OTel SDK** to export to your Langfuse instance:
1162
-
1163
- ```ruby
1164
- require 'base64'
1165
- require 'opentelemetry/sdk'
1166
- require 'opentelemetry/exporter/otlp'
1167
-
1168
- # Langfuse authenticates via Basic Auth over OTLP
1169
- public_key = ENV['LANGFUSE_PUBLIC_KEY']
1170
- secret_key = ENV['LANGFUSE_SECRET_KEY']
1171
- auth = Base64.strict_encode64("#{public_key}:#{secret_key}")
1172
-
1173
- # Self-hosted or cloud: https://cloud.langfuse.com (EU) / https://us.cloud.langfuse.com (US)
1174
- langfuse_host = ENV.fetch('LANGFUSE_HOST', 'https://cloud.langfuse.com')
1175
-
1176
- OpenTelemetry::SDK.configure do |c|
1177
- c.service_name = 'my-agent-app'
1178
- c.add_span_processor(
1179
- OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
1180
- OpenTelemetry::Exporter::OTLP::Exporter.new(
1181
- endpoint: "#{langfuse_host}/api/public/otel/v1/traces",
1182
- headers: {
1183
- 'Authorization' => "Basic #{auth}",
1184
- 'x-langfuse-ingestion-version' => '4'
1185
- }
1186
- )
1187
- )
1188
- )
1189
- end
1190
- ```
1191
-
1192
- **3. Create the observer and run a query:**
1193
-
1194
- ```ruby
1195
- require 'claude_agent_sdk'
1196
- require 'claude_agent_sdk/instrumentation'
1197
-
1198
- observer = ClaudeAgentSDK::Instrumentation::OTelObserver.new(
1199
- 'langfuse.session.id' => 'my-session-123', # optional: group traces by session
1200
- 'user.id' => 'user-42' # optional: tag with user ID
1201
- )
1202
-
1203
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
1204
- observers: [observer],
1205
- allowed_tools: ['Bash', 'Read'],
1206
- permission_mode: 'bypassPermissions'
1207
- )
1208
-
1209
- ClaudeAgentSDK.query(prompt: "List files in /tmp", options: options) do |msg|
1210
- puts msg.text if msg.is_a?(ClaudeAgentSDK::AssistantMessage)
1211
- end
1212
-
1213
- # For long-running apps, flush before exit:
1214
- # OpenTelemetry.tracer_provider.shutdown
1215
- ```
1216
-
1217
- ### Span Attributes
1218
-
1219
- The OTel observer sets attributes using both `gen_ai.*` (OTel GenAI) and OpenInference conventions for maximum backend compatibility:
1220
-
1221
- | Span | Type | Key Attributes |
1222
- |------|------|----------------|
1223
- | `claude_agent.session` | `agent` | `gen_ai.system`, `gen_ai.request.model`, `session.id`, `input.value`, `output.value`, `gen_ai.usage.cost`, `llm.cost.total` |
1224
- | `claude_agent.generation` | `generation` | `gen_ai.response.model`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, `output.value` |
1225
- | `claude_agent.tool.*` | `tool` | `tool.name`, `input.value`, `output.value` |
1226
-
1227
- Events (`api_retry`, `rate_limit`, `tool_progress`) are recorded on the root span.
1228
-
1229
- The `langfuse.observation.type` attribute is set on each span (`agent`/`generation`/`tool`) to enable Langfuse's **trace flow diagram** (DAG graph visualization).
1230
-
1231
- ### Custom Observers
1232
-
1233
- Implement the `Observer` module to build your own instrumentation:
1234
-
1235
- ```ruby
1236
- class MyObserver
1237
- include ClaudeAgentSDK::Observer
1238
-
1239
- def on_message(message)
1240
- case message
1241
- when ClaudeAgentSDK::ResultMessage
1242
- puts "Cost: $#{message.total_cost_usd}, Tokens: #{message.usage}"
1243
- end
1244
- end
1245
-
1246
- def on_close
1247
- puts "Session ended"
1248
- end
1249
- end
1250
-
1251
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(observers: [MyObserver.new])
1252
- ```
1253
-
1254
- For a complete multi-tool example, see [examples/otel_langfuse_example.rb](examples/otel_langfuse_example.rb).
1255
-
1256
- ## Rails Integration
1257
-
1258
- The SDK integrates well with Rails applications. Here are common patterns:
1259
-
1260
- ### Thread-keyed libraries are safe inside SDK callbacks
1261
-
1262
- The SDK depends on [`async`](https://github.com/socketry/async), which installs
1263
- a Fiber scheduler that multiplexes fibers onto a single OS thread and
1264
- intercepts IO so blocking calls yield to siblings. Most mature Ruby libraries
1265
- are thread-safe but not fiber-safe — they key state (checked-out DB
1266
- connections, per-thread caches, request stores) on `Thread.current`. When the
1267
- scheduler interleaves two fibers on one thread, those fibers share the same
1268
- state slot, and interleaved IO on a shared connection silently corrupts wire
1269
- protocols. This affects every DB driver keyed by thread (`pg`, `mysql2`,
1270
- `sqlite3`), ActiveRecord's connection pool, and HTTP/cache clients pooled per
1271
- thread.
1272
-
1273
- You do **not** need to think about this. The SDK hops to a plain thread at
1274
- every user-callback boundary — message blocks given to `query` / `Client`, SDK
1275
- MCP tool handlers, hooks, permission callbacks, and observer methods — so
1276
- your code runs with no Fiber scheduler active and inherits the ordinary
1277
- thread-keyed assumptions every Rails / Sidekiq / Kamal app already makes:
1278
-
1279
- ```ruby
1280
- tool = ClaudeAgentSDK.create_tool('lookup_user', 'Look up a user', { id: Integer }) do |args|
1281
- user = User.find(args[:id]) # just works
1282
- { content: [{ type: 'text', text: user.name }] }
1283
- end
1284
-
1285
- ClaudeAgentSDK.query(prompt: '...') do |message|
1286
- Message.create!(role: 'assistant', body: message.to_s) # just works
1287
- end
1288
- ```
1289
-
1290
- The trade-off: because callbacks run on a plain thread rather than inside
1291
- an `Async::Task`, fiber-specific primitives aren't available to them —
1292
- `Async::Task.current` will raise "No async task available". If a callback
1293
- wants cooperative concurrency it should open its own `Async { }` block. In
1294
- practice, callbacks typically do some Ruby work, call external services, and
1295
- return — so this rarely matters. If you wrap your own call site in an outer
1296
- `Async { }` block, the scheduler is visible to your code again; you've opted
1297
- in, and whatever fiber-safety rules your app uses apply there.
1298
-
1299
- ### ActionCable Streaming
1300
-
1301
- Stream Claude responses to the frontend in real-time:
1302
-
1303
- ```ruby
1304
- # app/jobs/chat_agent_job.rb
1305
- class ChatAgentJob < ApplicationJob
1306
- queue_as :claude_agents
1307
-
1308
- def perform(chat_id, message_content)
1309
- Async do
1310
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
1311
- system_prompt: { type: 'preset', preset: 'claude_code' },
1312
- permission_mode: 'bypassPermissions'
1313
- )
1314
-
1315
- client = ClaudeAgentSDK::Client.new(options: options)
1316
-
1317
- begin
1318
- client.connect
1319
- client.query(message_content)
1320
-
1321
- client.receive_response do |message|
1322
- case message
1323
- when ClaudeAgentSDK::AssistantMessage
1324
- ChatChannel.broadcast_to(chat_id, { type: 'chunk', content: message.text })
1325
-
1326
- when ClaudeAgentSDK::ResultMessage
1327
- ChatChannel.broadcast_to(chat_id, {
1328
- type: 'complete',
1329
- content: message.result,
1330
- cost: message.total_cost_usd
1331
- })
1332
- end
1333
- end
1334
- ensure
1335
- client.disconnect
1336
- end
1337
- end.wait
1338
- end
1339
- end
1340
- ```
1341
-
1342
- ### Session Resumption
1343
-
1344
- Persist Claude sessions for multi-turn conversations:
1345
-
1346
- ```ruby
1347
- # app/models/chat_session.rb
1348
- class ChatSession < ApplicationRecord
1349
- # Columns: id, claude_session_id, user_id, created_at, updated_at
1350
-
1351
- def send_message(content)
1352
- options = build_options
1353
- client = ClaudeAgentSDK::Client.new(options: options)
1354
-
1355
- Async do
1356
- client.connect
1357
- client.query(content, session_id: claude_session_id ? nil : generate_session_id)
1358
-
1359
- client.receive_response do |message|
1360
- if message.is_a?(ClaudeAgentSDK::ResultMessage)
1361
- # Save session ID for next message
1362
- update!(claude_session_id: message.session_id)
1363
- end
1364
- end
1365
- ensure
1366
- client.disconnect
1367
- end.wait
1368
- end
1369
-
1370
- private
1371
-
1372
- def build_options
1373
- opts = {
1374
- permission_mode: 'bypassPermissions',
1375
- setting_sources: []
1376
- }
1377
- opts[:resume] = claude_session_id if claude_session_id.present?
1378
- ClaudeAgentSDK::ClaudeAgentOptions.new(**opts)
1379
- end
1380
-
1381
- def generate_session_id
1382
- "chat_#{id}_#{Time.current.to_i}"
1383
- end
1384
- end
1385
- ```
1386
-
1387
- ### Background Jobs with Error Handling
1388
-
1389
- ```ruby
1390
- class ClaudeAgentJob < ApplicationJob
1391
- queue_as :claude_agents
1392
- retry_on ClaudeAgentSDK::ProcessError, wait: :polynomially_longer, attempts: 3
1393
-
1394
- def perform(task_id)
1395
- task = Task.find(task_id)
1396
-
1397
- Async do
1398
- execute_agent(task)
1399
- end.wait
1400
-
1401
- rescue ClaudeAgentSDK::CLINotFoundError => e
1402
- task.update!(status: 'failed', error: 'Claude CLI not installed')
1403
- raise
1404
- end
1405
-
1406
- private
1407
-
1408
- def execute_agent(task)
1409
- # ... agent execution
1410
- end
1411
- end
1412
- ```
1413
-
1414
- ### HTTP MCP Servers
1415
-
1416
- Connect to remote tool services:
1417
-
1418
- ```ruby
1419
- mcp_servers = {
1420
- 'api_tools' => ClaudeAgentSDK::McpHttpServerConfig.new(
1421
- url: ENV['MCP_SERVER_URL'],
1422
- headers: { 'Authorization' => "Bearer #{ENV['MCP_TOKEN']}" }
1423
- ).to_h
1424
- }
1425
-
1426
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
1427
- mcp_servers: mcp_servers,
1428
- permission_mode: 'bypassPermissions'
1429
- )
1430
- ```
1431
-
1432
- ### Observability in Rails
1433
-
1434
- Add OpenTelemetry tracing to your Rails app with a single initializer:
1435
-
1436
- ```ruby
1437
- # config/initializers/opentelemetry.rb
1438
- require 'base64'
1439
- require 'opentelemetry/sdk'
1440
- require 'opentelemetry/exporter/otlp'
1441
-
1442
- if ENV['LANGFUSE_PUBLIC_KEY'].present?
1443
- auth = Base64.strict_encode64("#{ENV['LANGFUSE_PUBLIC_KEY']}:#{ENV['LANGFUSE_SECRET_KEY']}")
1444
- langfuse_host = ENV.fetch('LANGFUSE_HOST', 'https://cloud.langfuse.com')
1445
-
1446
- OpenTelemetry::SDK.configure do |c|
1447
- c.service_name = Rails.application.class.module_parent_name.underscore
1448
- c.add_span_processor(
1449
- OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
1450
- OpenTelemetry::Exporter::OTLP::Exporter.new(
1451
- endpoint: "#{langfuse_host}/api/public/otel/v1/traces",
1452
- headers: {
1453
- 'Authorization' => "Basic #{auth}",
1454
- 'x-langfuse-ingestion-version' => '4'
1455
- }
1456
- )
1457
- )
1458
- )
1459
- end
1460
- end
1461
- ```
1462
-
1463
- ```ruby
1464
- # config/initializers/claude_agent_sdk.rb
1465
- require 'claude_agent_sdk/instrumentation'
1466
-
1467
- ClaudeAgentSDK.configure do |config|
1468
- config.default_options = {
1469
- permission_mode: 'bypassPermissions',
1470
- observers: ENV['LANGFUSE_PUBLIC_KEY'].present? ? [
1471
- # Use a lambda so each query gets a fresh observer instance (thread-safe).
1472
- # A single shared instance would have its span state clobbered by concurrent requests.
1473
- -> { ClaudeAgentSDK::Instrumentation::OTelObserver.new }
1474
- ] : []
1475
- }
1476
- end
1477
- ```
1478
-
1479
- Then every `ClaudeAgentSDK.query` and `Client` session automatically gets traced — no per-call wiring needed. The lambda factory ensures each request gets its own observer with isolated span state, safe for concurrent Puma/Sidekiq workers.
1480
-
1481
- For complete examples, see:
1482
- - [examples/rails_actioncable_example.rb](examples/rails_actioncable_example.rb)
1483
- - [examples/rails_background_job_example.rb](examples/rails_background_job_example.rb)
1484
- - [examples/session_resumption_example.rb](examples/session_resumption_example.rb)
1485
- - [examples/http_mcp_server_example.rb](examples/http_mcp_server_example.rb)
1486
-
1487
- ## Types
1488
-
1489
- See [lib/claude_agent_sdk/types.rb](lib/claude_agent_sdk/types.rb) for complete type definitions.
1490
-
1491
- ### Message Types
1492
-
1493
- ```ruby
1494
- # Union type of all possible messages
1495
- Message = UserMessage | AssistantMessage | SystemMessage | ResultMessage
1496
- ```
1497
-
1498
- #### UserMessage
1499
-
1500
- User input message.
1501
-
1502
- ```ruby
1503
- class UserMessage
1504
- attr_accessor :content, # String | Array<ContentBlock>
1505
- :uuid, # String | nil - Unique ID for rewind support
1506
- :parent_tool_use_id, # String | nil
1507
- :tool_use_result # Hash | nil - Tool result data when message is a tool response
1508
- end
1509
- ```
1510
-
1511
- #### AssistantMessage
1512
-
1513
- Assistant response message with content blocks.
1514
-
1515
- ```ruby
1516
- class AssistantMessage
1517
- attr_accessor :content, # Array<ContentBlock>
1518
- :model, # String
1519
- :parent_tool_use_id,# String | nil
1520
- :error, # String | nil ('authentication_failed', 'billing_error', 'rate_limit', 'invalid_request', 'server_error', 'unknown')
1521
- :usage # Hash | nil - Token usage info from the API response
1522
- end
1523
- ```
1524
-
1525
- #### SystemMessage
1526
-
1527
- System message with metadata. Task lifecycle events are typed subclasses.
1528
-
1529
- ```ruby
1530
- class SystemMessage
1531
- attr_accessor :subtype, # String ('init', 'task_started', 'task_progress', 'task_notification', etc.)
1532
- :data # Hash
1533
- end
1534
-
1535
- # Typed subclasses (all inherit from SystemMessage, so is_a?(SystemMessage) still works)
1536
- class TaskStartedMessage < SystemMessage
1537
- attr_accessor :task_id, :description, :uuid, :session_id, :tool_use_id, :task_type
1538
- end
1539
-
1540
- class TaskProgressMessage < SystemMessage
1541
- attr_accessor :task_id, :description, :usage, :uuid, :session_id, :tool_use_id, :last_tool_name
1542
- end
1543
-
1544
- class TaskNotificationMessage < SystemMessage
1545
- attr_accessor :task_id, :status, :output_file, :summary, :uuid, :session_id, :tool_use_id, :usage
1546
- end
1547
- ```
1548
-
1549
- #### ResultMessage
1550
-
1551
- Final result message with cost and usage information.
1552
-
1553
- ```ruby
1554
- class ResultMessage
1555
- attr_accessor :subtype, # String
1556
- :duration_ms, # Integer
1557
- :duration_api_ms, # Integer
1558
- :is_error, # Boolean
1559
- :num_turns, # Integer
1560
- :session_id, # String
1561
- :stop_reason, # String | nil ('end_turn', 'max_tokens', 'stop_sequence')
1562
- :total_cost_usd, # Float | nil
1563
- :usage, # Hash | nil
1564
- :result, # String | nil (final text result)
1565
- :structured_output # Hash | nil (when using output_format)
1566
- end
1567
- ```
1568
-
1569
- ### Content Block Types
1570
-
1571
- ```ruby
1572
- # Union type of all content blocks
1573
- ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock | UnknownBlock
1574
- ```
1575
-
1576
- #### TextBlock
1577
-
1578
- Text content block.
1579
-
1580
- ```ruby
1581
- class TextBlock
1582
- attr_accessor :text # String
1583
- end
1584
- ```
1585
-
1586
- #### ThinkingBlock
1587
-
1588
- Thinking content block (for models with extended thinking capability).
1589
-
1590
- ```ruby
1591
- class ThinkingBlock
1592
- attr_accessor :thinking, # String
1593
- :signature # String
1594
- end
1595
- ```
1596
-
1597
- #### ToolUseBlock
1598
-
1599
- Tool use request block.
1600
-
1601
- ```ruby
1602
- class ToolUseBlock
1603
- attr_accessor :id, # String
1604
- :name, # String
1605
- :input # Hash
1606
- end
1607
- ```
1608
-
1609
- #### ToolResultBlock
1610
-
1611
- Tool execution result block.
1612
-
1613
- ```ruby
1614
- class ToolResultBlock
1615
- attr_accessor :tool_use_id, # String
1616
- :content, # String | Array<Hash> | nil
1617
- :is_error # Boolean | nil
1618
- end
1619
- ```
1620
-
1621
- #### UnknownBlock
1622
-
1623
- Generic content block for types the SDK doesn't explicitly handle (e.g., `document` for PDFs, `image` for inline images). Preserves the raw data for forward compatibility with newer CLI versions.
1624
-
1625
- ```ruby
1626
- class UnknownBlock
1627
- attr_accessor :type, # String — the original block type (e.g., "document")
1628
- :data # Hash — the full raw block hash
1629
- end
1630
- ```
1631
-
1632
- ### Error Types
1633
-
1634
- ```ruby
1635
- # Base exception class for all SDK errors
1636
- class ClaudeSDKError < StandardError; end
1637
-
1638
- # Raised when connection to Claude Code fails
1639
- class CLIConnectionError < ClaudeSDKError; end
1640
-
1641
- # Raised when the control protocol does not respond in time
1642
- class ControlRequestTimeoutError < CLIConnectionError; end
1643
-
1644
- # Raised when Claude Code CLI is not found
1645
- class CLINotFoundError < CLIConnectionError
1646
- # @param message [String] Error message (default: "Claude Code not found")
1647
- # @param cli_path [String, nil] Optional path to the CLI that was not found
1648
- end
1649
-
1650
- # Raised when the Claude Code process fails
1651
- class ProcessError < ClaudeSDKError
1652
- attr_reader :exit_code, # Integer | nil
1653
- :stderr # String | nil
1654
- end
1655
-
1656
- # Raised when JSON parsing fails
1657
- class CLIJSONDecodeError < ClaudeSDKError
1658
- attr_reader :line, # String - The line that failed to parse
1659
- :original_error # Exception - The original JSON decode exception
1660
- end
1661
-
1662
- # Raised when message parsing fails
1663
- class MessageParseError < ClaudeSDKError
1664
- attr_reader :data # Hash | nil
1665
- end
1666
- ```
1667
-
1668
- ### Configuration Types
1669
-
1670
- | Type | Description |
1671
- |------|-------------|
1672
- | `Configuration` | Global defaults via `ClaudeAgentSDK.configure` block |
1673
- | `ClaudeAgentOptions` | Main configuration for queries and clients |
1674
- | `HookMatcher` | Hook configuration with matcher pattern and timeout |
1675
- | `PermissionResultAllow` | Permission callback result to allow tool use |
1676
- | `PermissionResultDeny` | Permission callback result to deny tool use |
1677
- | `AgentDefinition` | Agent definition with description, prompt, tools, model, skills, memory, mcp_servers |
1678
- | `ThinkingConfigAdaptive` | Adaptive thinking mode (CLI dynamically adjusts budget) |
1679
- | `ThinkingConfigEnabled` | Enabled thinking with explicit `budget_tokens` |
1680
- | `ThinkingConfigDisabled` | Disabled thinking |
1681
- | `SdkMcpTool` | SDK MCP tool definition with name, description, input_schema, handler, annotations |
1682
- | `McpStdioServerConfig` | MCP server config for stdio transport |
1683
- | `McpSSEServerConfig` | MCP server config for SSE transport |
1684
- | `McpHttpServerConfig` | MCP server config for HTTP transport |
1685
- | `SdkPluginConfig` | SDK plugin configuration |
1686
- | `McpServerStatus` | Status of a single MCP server connection (with `.parse`) |
1687
- | `McpStatusResponse` | Response from `get_mcp_status` containing all server statuses (with `.parse`) |
1688
- | `McpServerInfo` | MCP server name and version |
1689
- | `McpToolInfo` | MCP tool name, description, and annotations |
1690
- | `McpToolAnnotations` | MCP tool annotation hints (`read_only`, `destructive`, `open_world`) |
1691
- | `TaskUsage` | Typed usage data (`total_tokens`, `tool_uses`, `duration_ms`) with `from_hash` factory |
1692
- | `SDKSessionInfo` | Session metadata from `list_sessions` |
1693
- | `SessionMessage` | Single message from `get_session_messages` |
1694
- | `SandboxSettings` | Sandbox settings for isolated command execution |
1695
- | `SandboxNetworkConfig` | Network configuration for sandbox |
1696
- | `SandboxIgnoreViolations` | Configure which sandbox violations to ignore |
1697
- | `SystemPromptPreset` | System prompt preset configuration |
1698
- | `ToolsPreset` | Tools preset configuration for base tools selection |
1699
-
1700
- ### Constants
1701
-
1702
- | Constant | Description |
1703
- |----------|-------------|
1704
- | `SDK_BETAS` | Available beta features (e.g., `"context-1m-2025-08-07"`) |
1705
- | `PERMISSION_MODES` | Available permission modes |
1706
- | `SETTING_SOURCES` | Available setting sources |
1707
- | `HOOK_EVENTS` | Available hook events |
1708
- | `ASSISTANT_MESSAGE_ERRORS` | Possible error types in AssistantMessage |
1709
- | `TASK_NOTIFICATION_STATUSES` | Task lifecycle notification statuses (`completed`, `failed`, `stopped`) |
1710
- | `MCP_SERVER_CONNECTION_STATUSES` | MCP server connection states (`connected`, `failed`, `needs-auth`, `pending`, `disabled`) |
1711
-
1712
- ## Error Handling
1713
-
1714
- ### AssistantMessage Errors
1715
-
1716
- `AssistantMessage` includes an `error` field for API-level errors:
1717
-
1718
- ```ruby
1719
- ClaudeAgentSDK.query(prompt: "Hello") do |message|
1720
- if message.is_a?(ClaudeAgentSDK::AssistantMessage) && message.error
1721
- case message.error
1722
- when 'rate_limit'
1723
- puts "Rate limited - retry after delay"
1724
- when 'authentication_failed'
1725
- puts "Check your API key"
1726
- when 'billing_error'
1727
- puts "Check your billing status"
1728
- when 'invalid_request'
1729
- puts "Invalid request format"
1730
- when 'server_error'
1731
- puts "Server error - retry later"
1732
- end
1733
- end
1734
- end
1735
- ```
1736
-
1737
- For complete examples, see [examples/error_handling_example.rb](examples/error_handling_example.rb).
1738
-
1739
- ### Exception Handling
1740
-
1741
- ```ruby
1742
- require 'claude_agent_sdk'
1743
-
1744
- begin
1745
- ClaudeAgentSDK.query(prompt: "Hello") do |message|
1746
- puts message
1747
- end
1748
- rescue ClaudeAgentSDK::ControlRequestTimeoutError
1749
- puts "Control protocol timed out — consider increasing the timeout"
1750
- rescue ClaudeAgentSDK::CLINotFoundError
1751
- puts "Please install Claude Code"
1752
- rescue ClaudeAgentSDK::ProcessError => e
1753
- puts "Process failed with exit code: #{e.exit_code}"
1754
- rescue ClaudeAgentSDK::CLIJSONDecodeError => e
1755
- puts "Failed to parse response: #{e}"
1756
- end
1757
- ```
1758
-
1759
- #### Configuring Timeout
1760
-
1761
- The control request timeout defaults to **1200 seconds** (20 minutes) to accommodate long-running agent sessions. Override it via environment variable:
1762
-
1763
- ```bash
1764
- # Set a custom timeout (in seconds)
1765
- export CLAUDE_AGENT_SDK_CONTROL_REQUEST_TIMEOUT_SECONDS=300 # 5 minutes
1766
- ```
1767
-
1768
- ### Error Types
1769
-
1770
- | Error | Description |
1771
- |-------|-------------|
1772
- | `ClaudeSDKError` | Base error for all SDK errors |
1773
- | `CLIConnectionError` | Connection issues |
1774
- | `ControlRequestTimeoutError` | Control protocol timeout (configurable via env var) |
1775
- | `CLINotFoundError` | Claude Code not installed |
1776
- | `ProcessError` | Process failed (includes `exit_code` and `stderr`) |
1777
- | `CLIJSONDecodeError` | JSON parsing issues |
1778
- | `MessageParseError` | Message parsing issues |
1779
-
1780
- See [lib/claude_agent_sdk/errors.rb](lib/claude_agent_sdk/errors.rb) for all error types.
1781
-
1782
- ## Available Tools
1783
-
1784
- See the [Claude Code documentation](https://docs.anthropic.com/en/docs/claude-code/settings#tools-available-to-claude) for a complete list of available tools.
1785
-
1786
- ## Examples
1787
-
1788
- ### Core Examples
1789
-
1790
- | Example | Description |
1791
- |---------|-------------|
1792
- | [examples/quick_start.rb](examples/quick_start.rb) | Basic `query()` usage with options |
1793
- | [examples/client_example.rb](examples/client_example.rb) | Interactive Client usage |
1794
- | [examples/message_types_example.rb](examples/message_types_example.rb) | Handling all 24 SDK message types |
1795
- | [examples/streaming_input_example.rb](examples/streaming_input_example.rb) | Streaming input for multi-turn conversations |
1796
- | [examples/session_resumption_example.rb](examples/session_resumption_example.rb) | Multi-turn conversations with session persistence |
1797
- | [examples/structured_output_example.rb](examples/structured_output_example.rb) | JSON schema structured output |
1798
- | [examples/error_handling_example.rb](examples/error_handling_example.rb) | Error handling with `AssistantMessage.error` |
1799
- | [examples/bare_mode_example.rb](examples/bare_mode_example.rb) | Minimal startup with `bare: true` |
1800
- | [examples/sandbox_example.rb](examples/sandbox_example.rb) | Full sandbox settings (network, filesystem, violations) |
1801
-
1802
- ### MCP Server Examples
1803
-
1804
- | Example | Description |
1805
- |---------|-------------|
1806
- | [examples/mcp_calculator.rb](examples/mcp_calculator.rb) | Custom tools with SDK MCP servers |
1807
- | [examples/mcp_resources_prompts_example.rb](examples/mcp_resources_prompts_example.rb) | MCP resources and prompts |
1808
- | [examples/http_mcp_server_example.rb](examples/http_mcp_server_example.rb) | HTTP/SSE MCP server configuration |
1809
-
1810
- ### Hooks & Permissions
1811
-
1812
- | Example | Description |
1813
- |---------|-------------|
1814
- | [examples/hooks_example.rb](examples/hooks_example.rb) | Using hooks to control tool execution |
1815
- | [examples/advanced_hooks_example.rb](examples/advanced_hooks_example.rb) | Typed hook inputs/outputs (PreToolUse, PostToolUse) |
1816
- | [examples/lifecycle_hooks_example.rb](examples/lifecycle_hooks_example.rb) | All 27 hook events (SessionStart, Stop, PostCompact, etc.) |
1817
- | [examples/permission_callback_example.rb](examples/permission_callback_example.rb) | Dynamic tool permission control |
1818
-
1819
- ### Advanced Features
1820
-
1821
- | Example | Description |
1822
- |---------|-------------|
1823
- | [examples/budget_control_example.rb](examples/budget_control_example.rb) | Budget control with `max_budget_usd` |
1824
- | [examples/fallback_model_example.rb](examples/fallback_model_example.rb) | Fallback model configuration |
1825
- | [examples/extended_thinking_example.rb](examples/extended_thinking_example.rb) | Extended thinking (API parity) |
1826
-
1827
- ### Observability
1828
-
1829
- | Example | Description |
1830
- |---------|-------------|
1831
- | [examples/otel_langfuse_example.rb](examples/otel_langfuse_example.rb) | OpenTelemetry tracing with Langfuse backend |
1832
-
1833
- ### Rails Integration
1834
-
1835
- | Example | Description |
1836
- |---------|-------------|
1837
- | [examples/rails_actioncable_example.rb](examples/rails_actioncable_example.rb) | ActionCable streaming to frontend |
1838
- | [examples/rails_background_job_example.rb](examples/rails_background_job_example.rb) | Background jobs with session resumption |
1839
-
1840
- ## Development
1841
-
1842
- After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rspec` to run the tests.
1843
-
1844
- ## License
273
+ ## License
1845
274
 
1846
275
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
1847
276
 
1848
-
1849
277
  ## Star History
1850
278
 
1851
279
  <a href="https://www.star-history.com/?repos=ya-luotao%2Fclaude-agent-sdk-ruby&type=date&legend=top-left">