claude-agent-sdk 0.16.6 → 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.6'
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,1529 +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., E2B sandbox, remote SSH, WebSocket):
315
-
316
- ```ruby
317
- # Custom transport must implement the Transport interface:
318
- # connect, write, read_messages, end_input, close, ready?
319
- class E2BSandboxTransport < ClaudeAgentSDK::Transport
320
- def initialize(options, sandbox:)
321
- @options = options
322
- @sandbox = sandbox
323
- end
324
-
325
- def connect
326
- @sandbox.connect
327
- end
328
-
329
- def write(data)
330
- @sandbox.stdin_write(data)
331
- end
332
-
333
- def read_messages(&block)
334
- @sandbox.stdout_read_lines { |line| yield JSON.parse(line, symbolize_names: true) }
335
- end
336
-
337
- def end_input
338
- @sandbox.close_stdin
339
- end
340
-
341
- def close
342
- @sandbox.disconnect
343
- end
344
-
345
- def ready?
346
- @sandbox.connected?
347
- end
348
- end
349
-
350
- # Use it with Client — all connect orchestration (option transforms,
351
- # MCP extraction, hook conversion, Query lifecycle) is handled for you
352
- Async do
353
- client = ClaudeAgentSDK::Client.new(
354
- options: options,
355
- transport_class: E2BSandboxTransport,
356
- transport_args: { sandbox: my_sandbox }
357
- )
358
- client.connect
359
- client.query("Hello from the sandbox!")
360
- client.receive_response { |msg| puts msg }
361
- client.disconnect
362
- end.wait
363
- ```
170
+ Advanced features (`interrupt`, mid-session model/permission switching, MCP status, custom transports for E2B/SSH/etc.) → see [docs/client.md](docs/client.md).
364
171
 
365
172
  ## Custom Tools (SDK MCP Servers)
366
173
 
367
- A **custom tool** is a Ruby proc/lambda that you can offer to Claude, for Claude to invoke as needed.
368
-
369
- 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.
370
-
371
- **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.
372
-
373
- ### 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.
374
175
 
375
176
  ```ruby
376
- require 'claude_agent_sdk'
377
- require 'async'
378
-
379
- # Define a tool using create_tool (with optional annotations)
380
- greet_tool = ClaudeAgentSDK.create_tool(
381
- 'greet', 'Greet a user', { name: :string },
382
- annotations: { title: 'Greeter', readOnlyHint: true }
383
- ) do |args|
177
+ greet = ClaudeAgentSDK.create_tool('greet', 'Greet a user', { name: :string }) do |args|
384
178
  { content: [{ type: 'text', text: "Hello, #{args[:name]}!" }] }
385
179
  end
386
180
 
387
- # Create an SDK MCP server
388
- server = ClaudeAgentSDK.create_sdk_mcp_server(
389
- name: 'my-tools',
390
- version: '1.0.0',
391
- tools: [greet_tool]
392
- )
181
+ server = ClaudeAgentSDK.create_sdk_mcp_server(name: 'my-tools', tools: [greet])
393
182
 
394
- # Use it with Claude
395
183
  options = ClaudeAgentSDK::ClaudeAgentOptions.new(
396
184
  mcp_servers: { tools: server },
397
185
  allowed_tools: ['mcp__tools__greet']
398
186
  )
399
-
400
- Async do
401
- client = ClaudeAgentSDK::Client.new(options: options)
402
- client.connect
403
-
404
- client.query("Greet Alice")
405
- client.receive_response { |msg| puts msg }
406
-
407
- client.disconnect
408
- end.wait
409
- ```
410
-
411
- ### Pre-built JSON Schemas
412
-
413
- 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:
414
-
415
- ```ruby
416
- # Symbol keys (standard Ruby)
417
- tool = ClaudeAgentSDK.create_tool('save', 'Save a fact', {
418
- type: 'object',
419
- properties: { fact: { type: 'string' } },
420
- required: ['fact']
421
- }) { |args| { content: [{ type: 'text', text: "Saved: #{args[:fact]}" }] } }
422
-
423
- # String keys (e.g., from RubyLLM or JSON.parse)
424
- tool = ClaudeAgentSDK.create_tool('save', 'Save a fact', {
425
- 'type' => 'object',
426
- 'properties' => { 'fact' => { 'type' => 'string' } },
427
- 'required' => ['fact']
428
- }) { |args| { content: [{ type: 'text', text: "Saved: #{args[:fact]}" }] } }
429
- ```
430
-
431
- ### Benefits Over External MCP Servers
432
-
433
- - **No subprocess management** - Runs in the same process as your application
434
- - **Better performance** - No IPC overhead for tool calls
435
- - **Simpler deployment** - Single Ruby process instead of multiple
436
- - **Easier debugging** - All code runs in the same process
437
- - **Direct access** - Tools can directly access your application's state
438
-
439
- ### Calculator Example
440
-
441
- ```ruby
442
- # Define calculator tools
443
- add_tool = ClaudeAgentSDK.create_tool('add', 'Add two numbers', { a: :number, b: :number }) do |args|
444
- result = args[:a] + args[:b]
445
- { content: [{ type: 'text', text: "#{args[:a]} + #{args[:b]} = #{result}" }] }
446
- end
447
-
448
- divide_tool = ClaudeAgentSDK.create_tool('divide', 'Divide numbers', { a: :number, b: :number }) do |args|
449
- if args[:b] == 0
450
- { content: [{ type: 'text', text: 'Error: Division by zero' }], is_error: true }
451
- else
452
- result = args[:a] / args[:b]
453
- { content: [{ type: 'text', text: "Result: #{result}" }] }
454
- end
455
- end
456
-
457
- # Create server
458
- calculator = ClaudeAgentSDK.create_sdk_mcp_server(
459
- name: 'calculator',
460
- tools: [add_tool, divide_tool]
461
- )
462
-
463
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
464
- mcp_servers: { calc: calculator },
465
- allowed_tools: ['mcp__calc__add', 'mcp__calc__divide']
466
- )
467
- ```
468
-
469
- ### Mixed Server Support
470
-
471
- You can use both SDK and external MCP servers together:
472
-
473
- ```ruby
474
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
475
- mcp_servers: {
476
- internal: sdk_server, # In-process SDK server
477
- external: { # External subprocess server
478
- type: 'stdio',
479
- command: 'external-server'
480
- }
481
- }
482
- )
483
- ```
484
-
485
- ### MCP Resources and Prompts
486
-
487
- SDK MCP servers can also expose **resources** (data sources) and **prompts** (reusable templates):
488
-
489
- ```ruby
490
- # Create a resource (data source Claude can read)
491
- config_resource = ClaudeAgentSDK.create_resource(
492
- uri: 'config://app/settings',
493
- name: 'Application Settings',
494
- description: 'Current app configuration',
495
- mime_type: 'application/json'
496
- ) do
497
- config_data = { app_name: 'MyApp', version: '1.0.0' }
498
- {
499
- contents: [{
500
- uri: 'config://app/settings',
501
- mimeType: 'application/json',
502
- text: JSON.pretty_generate(config_data)
503
- }]
504
- }
505
- end
506
-
507
- # Create a prompt template
508
- review_prompt = ClaudeAgentSDK.create_prompt(
509
- name: 'code_review',
510
- description: 'Review code for best practices',
511
- arguments: [
512
- { name: 'code', description: 'Code to review', required: true }
513
- ]
514
- ) do |args|
515
- {
516
- messages: [{
517
- role: 'user',
518
- content: {
519
- type: 'text',
520
- text: "Review this code: #{args[:code]}"
521
- }
522
- }]
523
- }
524
- end
525
-
526
- # Create server with tools, resources, and prompts
527
- server = ClaudeAgentSDK.create_sdk_mcp_server(
528
- name: 'dev-tools',
529
- tools: [my_tool],
530
- resources: [config_resource],
531
- prompts: [review_prompt]
532
- )
533
- ```
534
-
535
- 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).
536
-
537
- ## Hooks
538
-
539
- 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).
540
-
541
- ### Supported Events
542
-
543
- All hook input objects include common fields like `session_id`, `transcript_path`, `cwd`, and `permission_mode`.
544
-
545
- - `PreToolUse` → `PreToolUseHookInput` (`tool_name`, `tool_input`, `tool_use_id`)
546
- - `PostToolUse` → `PostToolUseHookInput` (`tool_name`, `tool_input`, `tool_response`, `tool_use_id`)
547
- - `PostToolUseFailure` → `PostToolUseFailureHookInput` (`tool_name`, `tool_input`, `tool_use_id`, `error`, `is_interrupt`)
548
- - `UserPromptSubmit` → `UserPromptSubmitHookInput` (`prompt`)
549
- - `Stop` → `StopHookInput` (`stop_hook_active`)
550
- - `SubagentStop` → `SubagentStopHookInput` (`stop_hook_active`, `agent_id`, `agent_transcript_path`, `agent_type`)
551
- - `PreCompact` → `PreCompactHookInput` (`trigger`, `custom_instructions`)
552
- - `Notification` → `NotificationHookInput` (`message`, `title`, `notification_type`)
553
- - `SubagentStart` → `SubagentStartHookInput` (`agent_id`, `agent_type`)
554
- - `PermissionRequest` → `PermissionRequestHookInput` (`tool_name`, `tool_input`, `permission_suggestions`)
555
-
556
- ### Example
557
-
558
- ```ruby
559
- require 'claude_agent_sdk'
560
- require 'async'
561
-
562
- Async do
563
- # Define a hook that blocks dangerous bash commands
564
- bash_hook = lambda do |input, _tool_use_id, _context|
565
- # Hook inputs are typed objects (e.g., PreToolUseHookInput) with Ruby-style accessors
566
- return {} unless input.respond_to?(:tool_name) && input.tool_name == 'Bash'
567
-
568
- tool_input = input.tool_input || {}
569
- command = tool_input[:command] || tool_input['command'] || ''
570
- block_patterns = ['rm -rf', 'foo.sh']
571
-
572
- block_patterns.each do |pattern|
573
- if command.include?(pattern)
574
- return {
575
- hookSpecificOutput: {
576
- hookEventName: 'PreToolUse',
577
- permissionDecision: 'deny',
578
- permissionDecisionReason: "Command contains forbidden pattern: #{pattern}"
579
- }
580
- }
581
- end
582
- end
583
-
584
- {} # Allow if no patterns match
585
- end
586
-
587
- # Create options with hook
588
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
589
- allowed_tools: ['Bash'],
590
- hooks: {
591
- 'PreToolUse' => [
592
- ClaudeAgentSDK::HookMatcher.new(
593
- matcher: 'Bash',
594
- hooks: [bash_hook]
595
- )
596
- ]
597
- }
598
- )
599
-
600
- client = ClaudeAgentSDK::Client.new(options: options)
601
- client.connect
602
-
603
- # Test: Command with forbidden pattern (will be blocked)
604
- client.query("Run the bash command: ./foo.sh --help")
605
- client.receive_response { |msg| puts msg }
606
-
607
- client.disconnect
608
- end.wait
609
- ```
610
-
611
- For more examples, see [examples/hooks_example.rb](examples/hooks_example.rb).
612
-
613
- ## Permission Callbacks
614
-
615
- 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.
616
-
617
- ### Example
618
-
619
- ```ruby
620
- require 'claude_agent_sdk'
621
- require 'async'
622
-
623
- Async do
624
- # Define a permission callback
625
- permission_callback = lambda do |tool_name, input, context|
626
- # Allow Read operations
627
- if tool_name == 'Read'
628
- return ClaudeAgentSDK::PermissionResultAllow.new
629
- end
630
-
631
- # Block Write to sensitive files
632
- if tool_name == 'Write'
633
- file_path = input[:file_path] || input['file_path']
634
- if file_path && file_path.include?('/etc/')
635
- return ClaudeAgentSDK::PermissionResultDeny.new(
636
- message: 'Cannot write to sensitive system files',
637
- interrupt: false
638
- )
639
- end
640
- return ClaudeAgentSDK::PermissionResultAllow.new
641
- end
642
-
643
- # Default: allow
644
- ClaudeAgentSDK::PermissionResultAllow.new
645
- end
646
-
647
- # Create options with permission callback
648
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
649
- allowed_tools: ['Read', 'Write', 'Bash'],
650
- can_use_tool: permission_callback
651
- )
652
-
653
- client = ClaudeAgentSDK::Client.new(options: options)
654
- client.connect
655
-
656
- # This will be allowed
657
- client.query("Create a file called test.txt with content 'Hello'")
658
- client.receive_response { |msg| puts msg }
659
-
660
- # This will be blocked
661
- client.query("Write to /etc/passwd")
662
- client.receive_response { |msg| puts msg }
663
-
664
- client.disconnect
665
- end.wait
666
- ```
667
-
668
- For more examples, see [examples/permission_callback_example.rb](examples/permission_callback_example.rb).
669
-
670
- ## Structured Output
671
-
672
- Use `output_format` to get validated JSON responses matching a schema. The Claude CLI returns structured output via a `StructuredOutput` tool use block.
673
-
674
- ```ruby
675
- require 'claude_agent_sdk'
676
- require 'json'
677
-
678
- # Define a JSON schema
679
- schema = {
680
- type: 'object',
681
- properties: {
682
- name: { type: 'string' },
683
- age: { type: 'integer' },
684
- skills: { type: 'array', items: { type: 'string' } }
685
- },
686
- required: %w[name age skills]
687
- }
688
-
689
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
690
- output_format: { type: 'json_schema', schema: schema },
691
- max_turns: 3
692
- )
693
-
694
- structured_data = nil
695
-
696
- ClaudeAgentSDK.query(
697
- prompt: "Create a profile for a software engineer",
698
- options: options
699
- ) do |message|
700
- if message.is_a?(ClaudeAgentSDK::AssistantMessage)
701
- message.content.each do |block|
702
- # Structured output comes via StructuredOutput tool use
703
- if block.is_a?(ClaudeAgentSDK::ToolUseBlock) && block.name == 'StructuredOutput'
704
- structured_data = block.input
705
- end
706
- end
707
- end
708
- end
709
-
710
- if structured_data
711
- puts "Name: #{structured_data[:name]}"
712
- puts "Age: #{structured_data[:age]}"
713
- puts "Skills: #{structured_data[:skills].join(', ')}"
714
- end
715
- ```
716
-
717
- For complete examples, see [examples/structured_output_example.rb](examples/structured_output_example.rb).
718
-
719
- ## Thinking Configuration
720
-
721
- Control extended thinking behavior with typed configuration objects. The `thinking` option takes precedence over the deprecated `max_thinking_tokens`.
722
-
723
- ```ruby
724
- # Adaptive thinking — CLI dynamically adjusts budget based on task complexity
725
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
726
- thinking: ClaudeAgentSDK::ThinkingConfigAdaptive.new
727
- )
728
-
729
- # Enabled thinking with explicit token budget
730
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
731
- thinking: ClaudeAgentSDK::ThinkingConfigEnabled.new(budget_tokens: 50_000)
732
- )
733
-
734
- # Explicitly disabled thinking
735
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
736
- thinking: ClaudeAgentSDK::ThinkingConfigDisabled.new
737
- )
738
- ```
739
-
740
- Use the `effort` option to control the model's effort level:
741
-
742
- ```ruby
743
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
744
- effort: 'xhigh' # see ClaudeAgentSDK::EFFORT_LEVELS
745
- )
746
187
  ```
747
188
 
748
- 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`).
749
-
750
- > **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).
751
190
 
752
- ### Cross-User Prompt Caching
191
+ ## Hooks & Permission Callbacks
753
192
 
754
- 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.
755
194
 
756
195
  ```ruby
757
196
  options = ClaudeAgentSDK::ClaudeAgentOptions.new(
758
- system_prompt: ClaudeAgentSDK::SystemPromptPreset.new(
759
- preset: 'claude_code',
760
- append: '...your shared domain instructions...',
761
- exclude_dynamic_sections: true
762
- )
197
+ hooks: { 'PreToolUse' => [ClaudeAgentSDK::HookMatcher.new(matcher: 'Bash', hooks: [my_hook])] },
198
+ can_use_tool: my_permission_callback
763
199
  )
764
200
  ```
765
201
 
766
- 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).
767
203
 
768
- ## Budget Control
769
-
770
- Use `max_budget_usd` to set a spending cap for your queries:
771
-
772
- ```ruby
773
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
774
- max_budget_usd: 0.10, # Cap at $0.10
775
- max_turns: 3
776
- )
204
+ ## Advanced Topics
777
205
 
778
- ClaudeAgentSDK.query(prompt: "Explain recursion", options: options) do |message|
779
- if message.is_a?(ClaudeAgentSDK::ResultMessage)
780
- puts "Cost: $#{message.total_cost_usd}"
781
- end
782
- end
783
- ```
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) |
784
214
 
785
- For complete examples, see [examples/budget_control_example.rb](examples/budget_control_example.rb).
215
+ ## Examples
786
216
 
787
- ## Fallback Model
217
+ ### Core
788
218
 
789
- Use `fallback_model` to specify a backup model if the primary is unavailable:
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
790
232
 
791
- ```ruby
792
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
793
- model: 'claude-sonnet-4-20250514',
794
- fallback_model: 'claude-3-5-haiku-20241022'
795
- )
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 |
796
238
 
797
- ClaudeAgentSDK.query(prompt: "Hello", options: options) do |message|
798
- if message.is_a?(ClaudeAgentSDK::AssistantMessage)
799
- puts "Model used: #{message.model}"
800
- end
801
- end
802
- ```
239
+ ### Hooks & Permissions
803
240
 
804
- For complete examples, see [examples/fallback_model_example.rb](examples/fallback_model_example.rb).
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 |
805
247
 
806
- ## Beta Features
248
+ ### Advanced
807
249
 
808
- Enable experimental features using the `betas` option:
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 |
809
256
 
810
- ```ruby
811
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
812
- betas: ['context-1m-2025-08-07'] # Extended context window
813
- )
257
+ ### Observability & Rails
814
258
 
815
- ClaudeAgentSDK.query(prompt: "Analyze this large document...", options: options) do |message|
816
- puts message
817
- end
818
- ```
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 |
819
264
 
820
- Available beta features are listed in the `SDK_BETAS` constant.
265
+ ## Available Tools
821
266
 
822
- ## Tools Configuration
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.
823
268
 
824
- Configure base tools separately from allowed tools:
269
+ ## Development
825
270
 
826
- ```ruby
827
- # Using an array of tool names
828
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
829
- tools: ['Read', 'Edit', 'Bash'] # Base tools available
830
- )
271
+ After checking out the repo, run `bundle install` to install dependencies. Then `bundle exec rspec` to run the tests.
831
272
 
832
- # Using a preset
833
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
834
- tools: ClaudeAgentSDK::ToolsPreset.new(preset: 'claude_code')
835
- )
836
-
837
- # Appending to allowed tools
838
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
839
- append_allowed_tools: ['Write', 'Bash']
840
- )
841
- ```
842
-
843
- ## Sandbox Settings
844
-
845
- 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`.
846
-
847
- ```ruby
848
- sandbox = ClaudeAgentSDK::SandboxSettings.new(
849
- enabled: true,
850
- auto_allow_bash_if_sandboxed: true,
851
- network: ClaudeAgentSDK::SandboxNetworkConfig.new(
852
- allow_local_binding: true
853
- )
854
- )
855
-
856
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
857
- sandbox: sandbox,
858
- permission_mode: 'acceptEdits'
859
- )
860
-
861
- ClaudeAgentSDK.query(prompt: "Run some commands", options: options) do |message|
862
- puts message
863
- end
864
- ```
865
-
866
- ## Bare Mode
867
-
868
- 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.
869
-
870
- ```ruby
871
- # Sugar option
872
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
873
- bare: true,
874
- system_prompt: 'You are a code reviewer.',
875
- permission_mode: 'bypassPermissions'
876
- )
877
-
878
- ClaudeAgentSDK.query(prompt: "Review this function", options: options) do |message|
879
- # ...
880
- end
881
- ```
882
-
883
- In bare mode, explicitly provide any context you need:
884
-
885
- ```ruby
886
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
887
- bare: true,
888
- system_prompt: 'You are a helpful assistant.',
889
- add_dirs: ['/path/to/project'], # CLAUDE.md directories (auto-discovery is off)
890
- setting_sources: ['project'], # load .claude/settings.json
891
- allowed_tools: ['Read', 'Grep', 'Glob'],
892
- permission_mode: 'bypassPermissions'
893
- )
894
- ```
895
-
896
- **What bare mode skips:** hooks, LSP, plugin sync, attribution, auto-memory, background prefetches, keychain reads, CLAUDE.md auto-discovery, teammate snapshots, release notes.
897
-
898
- **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.
899
-
900
- ## File Checkpointing & Rewind
901
-
902
- Enable file checkpointing to revert file changes to a previous state:
903
-
904
- ```ruby
905
- require 'async'
906
-
907
- Async do
908
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
909
- enable_file_checkpointing: true,
910
- permission_mode: 'acceptEdits'
911
- )
912
-
913
- client = ClaudeAgentSDK::Client.new(options: options)
914
- client.connect
915
-
916
- # Track user message UUIDs for potential rewind
917
- user_message_uuids = []
918
-
919
- # First query - create a file
920
- client.query("Create a test.rb file with some code")
921
- client.receive_response do |message|
922
- # Process all message types as needed
923
- case message
924
- when ClaudeAgentSDK::UserMessage
925
- # Capture UUID for rewind capability
926
- user_message_uuids << message.uuid if message.uuid
927
- when ClaudeAgentSDK::AssistantMessage
928
- puts message.text
929
- when ClaudeAgentSDK::ResultMessage
930
- puts "Query completed (cost: $#{message.total_cost_usd})"
931
- end
932
- end
933
-
934
- # Second query - modify the file
935
- client.query("Modify the test.rb file to add error handling")
936
- client.receive_response do |message|
937
- user_message_uuids << message.uuid if message.is_a?(ClaudeAgentSDK::UserMessage) && message.uuid
938
- end
939
-
940
- # Rewind to the first checkpoint (undoes the second query's changes)
941
- if user_message_uuids.first
942
- puts "Rewinding to checkpoint: #{user_message_uuids.first}"
943
- client.rewind_files(user_message_uuids.first)
944
- end
945
-
946
- client.disconnect
947
- end.wait
948
- ```
949
-
950
- > **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.
951
-
952
- ## Session Browsing
953
-
954
- Browse and inspect previous Claude Code sessions directly from Ruby — no CLI subprocess required.
955
-
956
- ### Listing Sessions
957
-
958
- ```ruby
959
- # List all sessions (sorted by most recent first)
960
- sessions = ClaudeAgentSDK.list_sessions
961
- sessions.each do |session|
962
- puts "#{session.session_id}: #{session.summary} (#{session.git_branch})"
963
- end
964
-
965
- # List sessions for a specific directory
966
- sessions = ClaudeAgentSDK.list_sessions(directory: '/path/to/project', limit: 10)
967
-
968
- # Paginate with offset
969
- page2 = ClaudeAgentSDK.list_sessions(directory: '.', limit: 10, offset: 10)
970
-
971
- # Include git worktree sessions
972
- sessions = ClaudeAgentSDK.list_sessions(directory: '.', include_worktrees: true)
973
- ```
974
-
975
- Each `SDKSessionInfo` includes:
976
- - `session_id`, `summary`, `last_modified`, `file_size`
977
- - `custom_title`, `first_prompt`, `git_branch`, `cwd`
978
-
979
- ### Reading Session Messages
980
-
981
- ```ruby
982
- # Get the full conversation from a session
983
- messages = ClaudeAgentSDK.get_session_messages(session_id: 'abc-123-...')
984
- messages.each do |msg|
985
- puts "[#{msg.type}] #{msg.message}"
986
- end
987
-
988
- # Paginate through messages
989
- page = ClaudeAgentSDK.get_session_messages(session_id: 'abc-123-...', offset: 10, limit: 20)
990
- ```
991
-
992
- Each `SessionMessage` includes `type` (`"user"` or `"assistant"`), `uuid`, `session_id`, and `message` (raw API dict).
993
-
994
- > **Note:** Session browsing reads `~/.claude/projects/` JSONL files directly. It respects the `CLAUDE_CONFIG_DIR` environment variable and automatically detects git worktrees.
995
-
996
- ## Session Mutations
997
-
998
- Rename or tag sessions programmatically — no CLI subprocess required.
999
-
1000
- ### Renaming a Session
1001
-
1002
- ```ruby
1003
- # Rename a session (appends a custom-title JSONL entry)
1004
- ClaudeAgentSDK.rename_session(
1005
- session_id: '550e8400-e29b-41d4-a716-446655440000',
1006
- title: 'My refactoring session',
1007
- directory: '/path/to/project' # optional
1008
- )
1009
- ```
1010
-
1011
- ### Tagging a Session
1012
-
1013
- ```ruby
1014
- # Tag a session (Unicode-sanitized before storing)
1015
- ClaudeAgentSDK.tag_session(
1016
- session_id: '550e8400-e29b-41d4-a716-446655440000',
1017
- tag: 'experiment'
1018
- )
1019
-
1020
- # Clear a tag
1021
- ClaudeAgentSDK.tag_session(
1022
- session_id: '550e8400-e29b-41d4-a716-446655440000',
1023
- tag: nil
1024
- )
1025
- ```
1026
-
1027
- ### Deleting a Session
1028
-
1029
- ```ruby
1030
- # Hard-delete a session (removes the JSONL file permanently)
1031
- ClaudeAgentSDK.delete_session(
1032
- session_id: '550e8400-e29b-41d4-a716-446655440000',
1033
- directory: '/path/to/project' # optional
1034
- )
1035
- ```
1036
-
1037
- ### Forking a Session
1038
-
1039
- ```ruby
1040
- # Fork a session into a new branch with fresh UUIDs
1041
- result = ClaudeAgentSDK.fork_session(
1042
- session_id: '550e8400-e29b-41d4-a716-446655440000',
1043
- title: 'Experiment branch' # optional, auto-generated if omitted
1044
- )
1045
- puts result.session_id # UUID of the new forked session
1046
-
1047
- # Fork up to a specific message (partial fork)
1048
- result = ClaudeAgentSDK.fork_session(
1049
- session_id: '550e8400-e29b-41d4-a716-446655440000',
1050
- up_to_message_id: 'message-uuid-here'
1051
- )
1052
- ```
1053
-
1054
- > **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.
1055
-
1056
- ## Observability (OpenTelemetry / Langfuse)
1057
-
1058
- 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.
1059
-
1060
- ### How It Works
1061
-
1062
- 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.
1063
-
1064
- ```
1065
- claude_agent.session (root span — one per query/session)
1066
- ├── claude_agent.generation (per AssistantMessage, with model + token usage)
1067
- ├── claude_agent.tool.Bash (per tool call, open on ToolUseBlock, close on ToolResultBlock)
1068
- ├── claude_agent.tool.Read
1069
- ├── claude_agent.generation
1070
- └── ...
1071
- ```
1072
-
1073
- ### Setup with Langfuse
1074
-
1075
- **1. Install the OTel gems** (not bundled with the SDK — you choose your exporter):
1076
-
1077
- ```bash
1078
- gem install opentelemetry-sdk opentelemetry-exporter-otlp
1079
- ```
1080
-
1081
- Or add to your Gemfile:
1082
-
1083
- ```ruby
1084
- gem 'opentelemetry-sdk', '~> 1.4'
1085
- gem 'opentelemetry-exporter-otlp', '~> 0.28'
1086
- ```
1087
-
1088
- **2. Configure the OTel SDK** to export to your Langfuse instance:
1089
-
1090
- ```ruby
1091
- require 'base64'
1092
- require 'opentelemetry/sdk'
1093
- require 'opentelemetry/exporter/otlp'
1094
-
1095
- # Langfuse authenticates via Basic Auth over OTLP
1096
- public_key = ENV['LANGFUSE_PUBLIC_KEY']
1097
- secret_key = ENV['LANGFUSE_SECRET_KEY']
1098
- auth = Base64.strict_encode64("#{public_key}:#{secret_key}")
1099
-
1100
- # Self-hosted or cloud: https://cloud.langfuse.com (EU) / https://us.cloud.langfuse.com (US)
1101
- langfuse_host = ENV.fetch('LANGFUSE_HOST', 'https://cloud.langfuse.com')
1102
-
1103
- OpenTelemetry::SDK.configure do |c|
1104
- c.service_name = 'my-agent-app'
1105
- c.add_span_processor(
1106
- OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
1107
- OpenTelemetry::Exporter::OTLP::Exporter.new(
1108
- endpoint: "#{langfuse_host}/api/public/otel/v1/traces",
1109
- headers: {
1110
- 'Authorization' => "Basic #{auth}",
1111
- 'x-langfuse-ingestion-version' => '4'
1112
- }
1113
- )
1114
- )
1115
- )
1116
- end
1117
- ```
1118
-
1119
- **3. Create the observer and run a query:**
1120
-
1121
- ```ruby
1122
- require 'claude_agent_sdk'
1123
- require 'claude_agent_sdk/instrumentation'
1124
-
1125
- observer = ClaudeAgentSDK::Instrumentation::OTelObserver.new(
1126
- 'langfuse.session.id' => 'my-session-123', # optional: group traces by session
1127
- 'user.id' => 'user-42' # optional: tag with user ID
1128
- )
1129
-
1130
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
1131
- observers: [observer],
1132
- allowed_tools: ['Bash', 'Read'],
1133
- permission_mode: 'bypassPermissions'
1134
- )
1135
-
1136
- ClaudeAgentSDK.query(prompt: "List files in /tmp", options: options) do |msg|
1137
- puts msg.text if msg.is_a?(ClaudeAgentSDK::AssistantMessage)
1138
- end
1139
-
1140
- # For long-running apps, flush before exit:
1141
- # OpenTelemetry.tracer_provider.shutdown
1142
- ```
1143
-
1144
- ### Span Attributes
1145
-
1146
- The OTel observer sets attributes using both `gen_ai.*` (OTel GenAI) and OpenInference conventions for maximum backend compatibility:
1147
-
1148
- | Span | Type | Key Attributes |
1149
- |------|------|----------------|
1150
- | `claude_agent.session` | `agent` | `gen_ai.system`, `gen_ai.request.model`, `session.id`, `input.value`, `output.value`, `gen_ai.usage.cost`, `llm.cost.total` |
1151
- | `claude_agent.generation` | `generation` | `gen_ai.response.model`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, `output.value` |
1152
- | `claude_agent.tool.*` | `tool` | `tool.name`, `input.value`, `output.value` |
1153
-
1154
- Events (`api_retry`, `rate_limit`, `tool_progress`) are recorded on the root span.
1155
-
1156
- The `langfuse.observation.type` attribute is set on each span (`agent`/`generation`/`tool`) to enable Langfuse's **trace flow diagram** (DAG graph visualization).
1157
-
1158
- ### Custom Observers
1159
-
1160
- Implement the `Observer` module to build your own instrumentation:
1161
-
1162
- ```ruby
1163
- class MyObserver
1164
- include ClaudeAgentSDK::Observer
1165
-
1166
- def on_message(message)
1167
- case message
1168
- when ClaudeAgentSDK::ResultMessage
1169
- puts "Cost: $#{message.total_cost_usd}, Tokens: #{message.usage}"
1170
- end
1171
- end
1172
-
1173
- def on_close
1174
- puts "Session ended"
1175
- end
1176
- end
1177
-
1178
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(observers: [MyObserver.new])
1179
- ```
1180
-
1181
- For a complete multi-tool example, see [examples/otel_langfuse_example.rb](examples/otel_langfuse_example.rb).
1182
-
1183
- ## Rails Integration
1184
-
1185
- The SDK integrates well with Rails applications. Here are common patterns:
1186
-
1187
- ### Thread-keyed libraries are safe inside SDK callbacks
1188
-
1189
- The SDK depends on [`async`](https://github.com/socketry/async), which installs
1190
- a Fiber scheduler that multiplexes fibers onto a single OS thread and
1191
- intercepts IO so blocking calls yield to siblings. Most mature Ruby libraries
1192
- are thread-safe but not fiber-safe — they key state (checked-out DB
1193
- connections, per-thread caches, request stores) on `Thread.current`. When the
1194
- scheduler interleaves two fibers on one thread, those fibers share the same
1195
- state slot, and interleaved IO on a shared connection silently corrupts wire
1196
- protocols. This affects every DB driver keyed by thread (`pg`, `mysql2`,
1197
- `sqlite3`), ActiveRecord's connection pool, and HTTP/cache clients pooled per
1198
- thread.
1199
-
1200
- You do **not** need to think about this. The SDK hops to a plain thread at
1201
- every user-callback boundary — message blocks given to `query` / `Client`, SDK
1202
- MCP tool handlers, hooks, permission callbacks, and observer methods — so
1203
- your code runs with no Fiber scheduler active and inherits the ordinary
1204
- thread-keyed assumptions every Rails / Sidekiq / Kamal app already makes:
1205
-
1206
- ```ruby
1207
- tool = ClaudeAgentSDK.create_tool('lookup_user', 'Look up a user', { id: Integer }) do |args|
1208
- user = User.find(args[:id]) # just works
1209
- { content: [{ type: 'text', text: user.name }] }
1210
- end
1211
-
1212
- ClaudeAgentSDK.query(prompt: '...') do |message|
1213
- Message.create!(role: 'assistant', body: message.to_s) # just works
1214
- end
1215
- ```
1216
-
1217
- The trade-off: because callbacks run on a plain thread rather than inside
1218
- an `Async::Task`, fiber-specific primitives aren't available to them —
1219
- `Async::Task.current` will raise "No async task available". If a callback
1220
- wants cooperative concurrency it should open its own `Async { }` block. In
1221
- practice, callbacks typically do some Ruby work, call external services, and
1222
- return — so this rarely matters. If you wrap your own call site in an outer
1223
- `Async { }` block, the scheduler is visible to your code again; you've opted
1224
- in, and whatever fiber-safety rules your app uses apply there.
1225
-
1226
- ### ActionCable Streaming
1227
-
1228
- Stream Claude responses to the frontend in real-time:
1229
-
1230
- ```ruby
1231
- # app/jobs/chat_agent_job.rb
1232
- class ChatAgentJob < ApplicationJob
1233
- queue_as :claude_agents
1234
-
1235
- def perform(chat_id, message_content)
1236
- Async do
1237
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
1238
- system_prompt: { type: 'preset', preset: 'claude_code' },
1239
- permission_mode: 'bypassPermissions'
1240
- )
1241
-
1242
- client = ClaudeAgentSDK::Client.new(options: options)
1243
-
1244
- begin
1245
- client.connect
1246
- client.query(message_content)
1247
-
1248
- client.receive_response do |message|
1249
- case message
1250
- when ClaudeAgentSDK::AssistantMessage
1251
- ChatChannel.broadcast_to(chat_id, { type: 'chunk', content: message.text })
1252
-
1253
- when ClaudeAgentSDK::ResultMessage
1254
- ChatChannel.broadcast_to(chat_id, {
1255
- type: 'complete',
1256
- content: message.result,
1257
- cost: message.total_cost_usd
1258
- })
1259
- end
1260
- end
1261
- ensure
1262
- client.disconnect
1263
- end
1264
- end.wait
1265
- end
1266
- end
1267
- ```
1268
-
1269
- ### Session Resumption
1270
-
1271
- Persist Claude sessions for multi-turn conversations:
1272
-
1273
- ```ruby
1274
- # app/models/chat_session.rb
1275
- class ChatSession < ApplicationRecord
1276
- # Columns: id, claude_session_id, user_id, created_at, updated_at
1277
-
1278
- def send_message(content)
1279
- options = build_options
1280
- client = ClaudeAgentSDK::Client.new(options: options)
1281
-
1282
- Async do
1283
- client.connect
1284
- client.query(content, session_id: claude_session_id ? nil : generate_session_id)
1285
-
1286
- client.receive_response do |message|
1287
- if message.is_a?(ClaudeAgentSDK::ResultMessage)
1288
- # Save session ID for next message
1289
- update!(claude_session_id: message.session_id)
1290
- end
1291
- end
1292
- ensure
1293
- client.disconnect
1294
- end.wait
1295
- end
1296
-
1297
- private
1298
-
1299
- def build_options
1300
- opts = {
1301
- permission_mode: 'bypassPermissions',
1302
- setting_sources: []
1303
- }
1304
- opts[:resume] = claude_session_id if claude_session_id.present?
1305
- ClaudeAgentSDK::ClaudeAgentOptions.new(**opts)
1306
- end
1307
-
1308
- def generate_session_id
1309
- "chat_#{id}_#{Time.current.to_i}"
1310
- end
1311
- end
1312
- ```
1313
-
1314
- ### Background Jobs with Error Handling
1315
-
1316
- ```ruby
1317
- class ClaudeAgentJob < ApplicationJob
1318
- queue_as :claude_agents
1319
- retry_on ClaudeAgentSDK::ProcessError, wait: :polynomially_longer, attempts: 3
1320
-
1321
- def perform(task_id)
1322
- task = Task.find(task_id)
1323
-
1324
- Async do
1325
- execute_agent(task)
1326
- end.wait
1327
-
1328
- rescue ClaudeAgentSDK::CLINotFoundError => e
1329
- task.update!(status: 'failed', error: 'Claude CLI not installed')
1330
- raise
1331
- end
1332
-
1333
- private
1334
-
1335
- def execute_agent(task)
1336
- # ... agent execution
1337
- end
1338
- end
1339
- ```
1340
-
1341
- ### HTTP MCP Servers
1342
-
1343
- Connect to remote tool services:
1344
-
1345
- ```ruby
1346
- mcp_servers = {
1347
- 'api_tools' => ClaudeAgentSDK::McpHttpServerConfig.new(
1348
- url: ENV['MCP_SERVER_URL'],
1349
- headers: { 'Authorization' => "Bearer #{ENV['MCP_TOKEN']}" }
1350
- ).to_h
1351
- }
1352
-
1353
- options = ClaudeAgentSDK::ClaudeAgentOptions.new(
1354
- mcp_servers: mcp_servers,
1355
- permission_mode: 'bypassPermissions'
1356
- )
1357
- ```
1358
-
1359
- ### Observability in Rails
1360
-
1361
- Add OpenTelemetry tracing to your Rails app with a single initializer:
1362
-
1363
- ```ruby
1364
- # config/initializers/opentelemetry.rb
1365
- require 'base64'
1366
- require 'opentelemetry/sdk'
1367
- require 'opentelemetry/exporter/otlp'
1368
-
1369
- if ENV['LANGFUSE_PUBLIC_KEY'].present?
1370
- auth = Base64.strict_encode64("#{ENV['LANGFUSE_PUBLIC_KEY']}:#{ENV['LANGFUSE_SECRET_KEY']}")
1371
- langfuse_host = ENV.fetch('LANGFUSE_HOST', 'https://cloud.langfuse.com')
1372
-
1373
- OpenTelemetry::SDK.configure do |c|
1374
- c.service_name = Rails.application.class.module_parent_name.underscore
1375
- c.add_span_processor(
1376
- OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
1377
- OpenTelemetry::Exporter::OTLP::Exporter.new(
1378
- endpoint: "#{langfuse_host}/api/public/otel/v1/traces",
1379
- headers: {
1380
- 'Authorization' => "Basic #{auth}",
1381
- 'x-langfuse-ingestion-version' => '4'
1382
- }
1383
- )
1384
- )
1385
- )
1386
- end
1387
- end
1388
- ```
1389
-
1390
- ```ruby
1391
- # config/initializers/claude_agent_sdk.rb
1392
- require 'claude_agent_sdk/instrumentation'
1393
-
1394
- ClaudeAgentSDK.configure do |config|
1395
- config.default_options = {
1396
- permission_mode: 'bypassPermissions',
1397
- observers: ENV['LANGFUSE_PUBLIC_KEY'].present? ? [
1398
- # Use a lambda so each query gets a fresh observer instance (thread-safe).
1399
- # A single shared instance would have its span state clobbered by concurrent requests.
1400
- -> { ClaudeAgentSDK::Instrumentation::OTelObserver.new }
1401
- ] : []
1402
- }
1403
- end
1404
- ```
1405
-
1406
- 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.
1407
-
1408
- For complete examples, see:
1409
- - [examples/rails_actioncable_example.rb](examples/rails_actioncable_example.rb)
1410
- - [examples/rails_background_job_example.rb](examples/rails_background_job_example.rb)
1411
- - [examples/session_resumption_example.rb](examples/session_resumption_example.rb)
1412
- - [examples/http_mcp_server_example.rb](examples/http_mcp_server_example.rb)
1413
-
1414
- ## Types
1415
-
1416
- See [lib/claude_agent_sdk/types.rb](lib/claude_agent_sdk/types.rb) for complete type definitions.
1417
-
1418
- ### Message Types
1419
-
1420
- ```ruby
1421
- # Union type of all possible messages
1422
- Message = UserMessage | AssistantMessage | SystemMessage | ResultMessage
1423
- ```
1424
-
1425
- #### UserMessage
1426
-
1427
- User input message.
1428
-
1429
- ```ruby
1430
- class UserMessage
1431
- attr_accessor :content, # String | Array<ContentBlock>
1432
- :uuid, # String | nil - Unique ID for rewind support
1433
- :parent_tool_use_id, # String | nil
1434
- :tool_use_result # Hash | nil - Tool result data when message is a tool response
1435
- end
1436
- ```
1437
-
1438
- #### AssistantMessage
1439
-
1440
- Assistant response message with content blocks.
1441
-
1442
- ```ruby
1443
- class AssistantMessage
1444
- attr_accessor :content, # Array<ContentBlock>
1445
- :model, # String
1446
- :parent_tool_use_id,# String | nil
1447
- :error, # String | nil ('authentication_failed', 'billing_error', 'rate_limit', 'invalid_request', 'server_error', 'unknown')
1448
- :usage # Hash | nil - Token usage info from the API response
1449
- end
1450
- ```
1451
-
1452
- #### SystemMessage
1453
-
1454
- System message with metadata. Task lifecycle events are typed subclasses.
1455
-
1456
- ```ruby
1457
- class SystemMessage
1458
- attr_accessor :subtype, # String ('init', 'task_started', 'task_progress', 'task_notification', etc.)
1459
- :data # Hash
1460
- end
1461
-
1462
- # Typed subclasses (all inherit from SystemMessage, so is_a?(SystemMessage) still works)
1463
- class TaskStartedMessage < SystemMessage
1464
- attr_accessor :task_id, :description, :uuid, :session_id, :tool_use_id, :task_type
1465
- end
1466
-
1467
- class TaskProgressMessage < SystemMessage
1468
- attr_accessor :task_id, :description, :usage, :uuid, :session_id, :tool_use_id, :last_tool_name
1469
- end
1470
-
1471
- class TaskNotificationMessage < SystemMessage
1472
- attr_accessor :task_id, :status, :output_file, :summary, :uuid, :session_id, :tool_use_id, :usage
1473
- end
1474
- ```
1475
-
1476
- #### ResultMessage
1477
-
1478
- Final result message with cost and usage information.
1479
-
1480
- ```ruby
1481
- class ResultMessage
1482
- attr_accessor :subtype, # String
1483
- :duration_ms, # Integer
1484
- :duration_api_ms, # Integer
1485
- :is_error, # Boolean
1486
- :num_turns, # Integer
1487
- :session_id, # String
1488
- :stop_reason, # String | nil ('end_turn', 'max_tokens', 'stop_sequence')
1489
- :total_cost_usd, # Float | nil
1490
- :usage, # Hash | nil
1491
- :result, # String | nil (final text result)
1492
- :structured_output # Hash | nil (when using output_format)
1493
- end
1494
- ```
1495
-
1496
- ### Content Block Types
1497
-
1498
- ```ruby
1499
- # Union type of all content blocks
1500
- ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock | UnknownBlock
1501
- ```
1502
-
1503
- #### TextBlock
1504
-
1505
- Text content block.
1506
-
1507
- ```ruby
1508
- class TextBlock
1509
- attr_accessor :text # String
1510
- end
1511
- ```
1512
-
1513
- #### ThinkingBlock
1514
-
1515
- Thinking content block (for models with extended thinking capability).
1516
-
1517
- ```ruby
1518
- class ThinkingBlock
1519
- attr_accessor :thinking, # String
1520
- :signature # String
1521
- end
1522
- ```
1523
-
1524
- #### ToolUseBlock
1525
-
1526
- Tool use request block.
1527
-
1528
- ```ruby
1529
- class ToolUseBlock
1530
- attr_accessor :id, # String
1531
- :name, # String
1532
- :input # Hash
1533
- end
1534
- ```
1535
-
1536
- #### ToolResultBlock
1537
-
1538
- Tool execution result block.
1539
-
1540
- ```ruby
1541
- class ToolResultBlock
1542
- attr_accessor :tool_use_id, # String
1543
- :content, # String | Array<Hash> | nil
1544
- :is_error # Boolean | nil
1545
- end
1546
- ```
1547
-
1548
- #### UnknownBlock
1549
-
1550
- 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.
1551
-
1552
- ```ruby
1553
- class UnknownBlock
1554
- attr_accessor :type, # String — the original block type (e.g., "document")
1555
- :data # Hash — the full raw block hash
1556
- end
1557
- ```
1558
-
1559
- ### Error Types
1560
-
1561
- ```ruby
1562
- # Base exception class for all SDK errors
1563
- class ClaudeSDKError < StandardError; end
1564
-
1565
- # Raised when connection to Claude Code fails
1566
- class CLIConnectionError < ClaudeSDKError; end
1567
-
1568
- # Raised when the control protocol does not respond in time
1569
- class ControlRequestTimeoutError < CLIConnectionError; end
1570
-
1571
- # Raised when Claude Code CLI is not found
1572
- class CLINotFoundError < CLIConnectionError
1573
- # @param message [String] Error message (default: "Claude Code not found")
1574
- # @param cli_path [String, nil] Optional path to the CLI that was not found
1575
- end
1576
-
1577
- # Raised when the Claude Code process fails
1578
- class ProcessError < ClaudeSDKError
1579
- attr_reader :exit_code, # Integer | nil
1580
- :stderr # String | nil
1581
- end
1582
-
1583
- # Raised when JSON parsing fails
1584
- class CLIJSONDecodeError < ClaudeSDKError
1585
- attr_reader :line, # String - The line that failed to parse
1586
- :original_error # Exception - The original JSON decode exception
1587
- end
1588
-
1589
- # Raised when message parsing fails
1590
- class MessageParseError < ClaudeSDKError
1591
- attr_reader :data # Hash | nil
1592
- end
1593
- ```
1594
-
1595
- ### Configuration Types
1596
-
1597
- | Type | Description |
1598
- |------|-------------|
1599
- | `Configuration` | Global defaults via `ClaudeAgentSDK.configure` block |
1600
- | `ClaudeAgentOptions` | Main configuration for queries and clients |
1601
- | `HookMatcher` | Hook configuration with matcher pattern and timeout |
1602
- | `PermissionResultAllow` | Permission callback result to allow tool use |
1603
- | `PermissionResultDeny` | Permission callback result to deny tool use |
1604
- | `AgentDefinition` | Agent definition with description, prompt, tools, model, skills, memory, mcp_servers |
1605
- | `ThinkingConfigAdaptive` | Adaptive thinking mode (CLI dynamically adjusts budget) |
1606
- | `ThinkingConfigEnabled` | Enabled thinking with explicit `budget_tokens` |
1607
- | `ThinkingConfigDisabled` | Disabled thinking |
1608
- | `SdkMcpTool` | SDK MCP tool definition with name, description, input_schema, handler, annotations |
1609
- | `McpStdioServerConfig` | MCP server config for stdio transport |
1610
- | `McpSSEServerConfig` | MCP server config for SSE transport |
1611
- | `McpHttpServerConfig` | MCP server config for HTTP transport |
1612
- | `SdkPluginConfig` | SDK plugin configuration |
1613
- | `McpServerStatus` | Status of a single MCP server connection (with `.parse`) |
1614
- | `McpStatusResponse` | Response from `get_mcp_status` containing all server statuses (with `.parse`) |
1615
- | `McpServerInfo` | MCP server name and version |
1616
- | `McpToolInfo` | MCP tool name, description, and annotations |
1617
- | `McpToolAnnotations` | MCP tool annotation hints (`read_only`, `destructive`, `open_world`) |
1618
- | `TaskUsage` | Typed usage data (`total_tokens`, `tool_uses`, `duration_ms`) with `from_hash` factory |
1619
- | `SDKSessionInfo` | Session metadata from `list_sessions` |
1620
- | `SessionMessage` | Single message from `get_session_messages` |
1621
- | `SandboxSettings` | Sandbox settings for isolated command execution |
1622
- | `SandboxNetworkConfig` | Network configuration for sandbox |
1623
- | `SandboxIgnoreViolations` | Configure which sandbox violations to ignore |
1624
- | `SystemPromptPreset` | System prompt preset configuration |
1625
- | `ToolsPreset` | Tools preset configuration for base tools selection |
1626
-
1627
- ### Constants
1628
-
1629
- | Constant | Description |
1630
- |----------|-------------|
1631
- | `SDK_BETAS` | Available beta features (e.g., `"context-1m-2025-08-07"`) |
1632
- | `PERMISSION_MODES` | Available permission modes |
1633
- | `SETTING_SOURCES` | Available setting sources |
1634
- | `HOOK_EVENTS` | Available hook events |
1635
- | `ASSISTANT_MESSAGE_ERRORS` | Possible error types in AssistantMessage |
1636
- | `TASK_NOTIFICATION_STATUSES` | Task lifecycle notification statuses (`completed`, `failed`, `stopped`) |
1637
- | `MCP_SERVER_CONNECTION_STATUSES` | MCP server connection states (`connected`, `failed`, `needs-auth`, `pending`, `disabled`) |
1638
-
1639
- ## Error Handling
1640
-
1641
- ### AssistantMessage Errors
1642
-
1643
- `AssistantMessage` includes an `error` field for API-level errors:
1644
-
1645
- ```ruby
1646
- ClaudeAgentSDK.query(prompt: "Hello") do |message|
1647
- if message.is_a?(ClaudeAgentSDK::AssistantMessage) && message.error
1648
- case message.error
1649
- when 'rate_limit'
1650
- puts "Rate limited - retry after delay"
1651
- when 'authentication_failed'
1652
- puts "Check your API key"
1653
- when 'billing_error'
1654
- puts "Check your billing status"
1655
- when 'invalid_request'
1656
- puts "Invalid request format"
1657
- when 'server_error'
1658
- puts "Server error - retry later"
1659
- end
1660
- end
1661
- end
1662
- ```
1663
-
1664
- For complete examples, see [examples/error_handling_example.rb](examples/error_handling_example.rb).
1665
-
1666
- ### Exception Handling
1667
-
1668
- ```ruby
1669
- require 'claude_agent_sdk'
1670
-
1671
- begin
1672
- ClaudeAgentSDK.query(prompt: "Hello") do |message|
1673
- puts message
1674
- end
1675
- rescue ClaudeAgentSDK::ControlRequestTimeoutError
1676
- puts "Control protocol timed out — consider increasing the timeout"
1677
- rescue ClaudeAgentSDK::CLINotFoundError
1678
- puts "Please install Claude Code"
1679
- rescue ClaudeAgentSDK::ProcessError => e
1680
- puts "Process failed with exit code: #{e.exit_code}"
1681
- rescue ClaudeAgentSDK::CLIJSONDecodeError => e
1682
- puts "Failed to parse response: #{e}"
1683
- end
1684
- ```
1685
-
1686
- #### Configuring Timeout
1687
-
1688
- The control request timeout defaults to **1200 seconds** (20 minutes) to accommodate long-running agent sessions. Override it via environment variable:
1689
-
1690
- ```bash
1691
- # Set a custom timeout (in seconds)
1692
- export CLAUDE_AGENT_SDK_CONTROL_REQUEST_TIMEOUT_SECONDS=300 # 5 minutes
1693
- ```
1694
-
1695
- ### Error Types
1696
-
1697
- | Error | Description |
1698
- |-------|-------------|
1699
- | `ClaudeSDKError` | Base error for all SDK errors |
1700
- | `CLIConnectionError` | Connection issues |
1701
- | `ControlRequestTimeoutError` | Control protocol timeout (configurable via env var) |
1702
- | `CLINotFoundError` | Claude Code not installed |
1703
- | `ProcessError` | Process failed (includes `exit_code` and `stderr`) |
1704
- | `CLIJSONDecodeError` | JSON parsing issues |
1705
- | `MessageParseError` | Message parsing issues |
1706
-
1707
- See [lib/claude_agent_sdk/errors.rb](lib/claude_agent_sdk/errors.rb) for all error types.
1708
-
1709
- ## Available Tools
1710
-
1711
- 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.
1712
-
1713
- ## Examples
1714
-
1715
- ### Core Examples
1716
-
1717
- | Example | Description |
1718
- |---------|-------------|
1719
- | [examples/quick_start.rb](examples/quick_start.rb) | Basic `query()` usage with options |
1720
- | [examples/client_example.rb](examples/client_example.rb) | Interactive Client usage |
1721
- | [examples/message_types_example.rb](examples/message_types_example.rb) | Handling all 24 SDK message types |
1722
- | [examples/streaming_input_example.rb](examples/streaming_input_example.rb) | Streaming input for multi-turn conversations |
1723
- | [examples/session_resumption_example.rb](examples/session_resumption_example.rb) | Multi-turn conversations with session persistence |
1724
- | [examples/structured_output_example.rb](examples/structured_output_example.rb) | JSON schema structured output |
1725
- | [examples/error_handling_example.rb](examples/error_handling_example.rb) | Error handling with `AssistantMessage.error` |
1726
- | [examples/bare_mode_example.rb](examples/bare_mode_example.rb) | Minimal startup with `bare: true` |
1727
- | [examples/sandbox_example.rb](examples/sandbox_example.rb) | Full sandbox settings (network, filesystem, violations) |
1728
-
1729
- ### MCP Server Examples
1730
-
1731
- | Example | Description |
1732
- |---------|-------------|
1733
- | [examples/mcp_calculator.rb](examples/mcp_calculator.rb) | Custom tools with SDK MCP servers |
1734
- | [examples/mcp_resources_prompts_example.rb](examples/mcp_resources_prompts_example.rb) | MCP resources and prompts |
1735
- | [examples/http_mcp_server_example.rb](examples/http_mcp_server_example.rb) | HTTP/SSE MCP server configuration |
1736
-
1737
- ### Hooks & Permissions
1738
-
1739
- | Example | Description |
1740
- |---------|-------------|
1741
- | [examples/hooks_example.rb](examples/hooks_example.rb) | Using hooks to control tool execution |
1742
- | [examples/advanced_hooks_example.rb](examples/advanced_hooks_example.rb) | Typed hook inputs/outputs (PreToolUse, PostToolUse) |
1743
- | [examples/lifecycle_hooks_example.rb](examples/lifecycle_hooks_example.rb) | All 27 hook events (SessionStart, Stop, PostCompact, etc.) |
1744
- | [examples/permission_callback_example.rb](examples/permission_callback_example.rb) | Dynamic tool permission control |
1745
-
1746
- ### Advanced Features
1747
-
1748
- | Example | Description |
1749
- |---------|-------------|
1750
- | [examples/budget_control_example.rb](examples/budget_control_example.rb) | Budget control with `max_budget_usd` |
1751
- | [examples/fallback_model_example.rb](examples/fallback_model_example.rb) | Fallback model configuration |
1752
- | [examples/extended_thinking_example.rb](examples/extended_thinking_example.rb) | Extended thinking (API parity) |
1753
-
1754
- ### Observability
1755
-
1756
- | Example | Description |
1757
- |---------|-------------|
1758
- | [examples/otel_langfuse_example.rb](examples/otel_langfuse_example.rb) | OpenTelemetry tracing with Langfuse backend |
1759
-
1760
- ### Rails Integration
1761
-
1762
- | Example | Description |
1763
- |---------|-------------|
1764
- | [examples/rails_actioncable_example.rb](examples/rails_actioncable_example.rb) | ActionCable streaming to frontend |
1765
- | [examples/rails_background_job_example.rb](examples/rails_background_job_example.rb) | Background jobs with session resumption |
1766
-
1767
- ## Development
1768
-
1769
- After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rspec` to run the tests.
1770
-
1771
- ## License
273
+ ## License
1772
274
 
1773
275
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
1774
276
 
1775
-
1776
277
  ## Star History
1777
278
 
1778
279
  <a href="https://www.star-history.com/?repos=ya-luotao%2Fclaude-agent-sdk-ruby&type=date&legend=top-left">