claude-agent-sdk 0.2.1 → 0.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db20a5dbe6ebc51b229f4de595489038a26a47d8109600131c45aa2bf27c6d2e
4
- data.tar.gz: 563e16e88a48ce331cd48c6ebce3302f0c57292ee6563ebaec7af02bed29433d
3
+ metadata.gz: b1d1549408835720ec2fefea719ca9ccb948b0010bae0bbeb2955ac5e461fd8c
4
+ data.tar.gz: 2b65f85b5ce0dec97e03395652ffeeadf6af4465a5346fc45d587ac20ac93642
5
5
  SHA512:
6
- metadata.gz: 288868f49db2968d92dc2250fe2ca91b7692431c92dc4fce41b04a35610858c146490da45178290877988a9210d67ebdf8257363f651e058a0bab534d24222fc
7
- data.tar.gz: 183224c382c68b916da22c20c64d2f4fd625861baee265611bae8f752c25effcc7de2159a61cc65822cd62f24344f85b485cd1e6005ceff0e88ea2c34be4c30c
6
+ metadata.gz: 1de77e6f190cc6d474232801b53db8abdf8c239832266b304f6f48399317ab31f8b9298f8ed8af554d142d878476ba81ad712832a117fd01f8abffb798d50be1
7
+ data.tar.gz: 9f9c6ad3bef6990c9e9efaea2f0b49c120b703508a74aaf152836032521b92143d07129f4930569a3bb5de97db1abac7f5d6e1868d9bcfb9bcf9a9fead5e1064
data/README.md CHANGED
@@ -1,19 +1,32 @@
1
1
  # Claude Agent SDK for Ruby
2
2
 
3
- > **⚠️ DISCLAIMER**: This is an **unofficial, community-maintained** Ruby SDK for Claude Agent. It is not officially supported or maintained by Anthropic. For official SDK support, please refer to the [Python SDK](https://docs.claude.com/en/api/agent-sdk/python).
3
+ > **Disclaimer**: This is an **unofficial, community-maintained** Ruby SDK for Claude Agent. It is not officially supported by Anthropic. For official SDK support, see the [Python SDK](https://docs.claude.com/en/api/agent-sdk/python).
4
4
  >
5
5
  > This implementation is based on the official Python SDK and aims to provide feature parity for Ruby developers. Use at your own risk.
6
6
 
7
- Ruby SDK for Claude Agent. See the [Claude Agent SDK documentation](https://docs.anthropic.com/en/docs/claude-code/sdk) for more information.
8
-
9
7
  [![Gem Version](https://badge.fury.io/rb/claude-agent-sdk.svg?icon=si%3Arubygems)](https://badge.fury.io/rb/claude-agent-sdk)
10
8
 
9
+ ## Table of Contents
10
+
11
+ - [Installation](#installation)
12
+ - [Quick Start](#quick-start)
13
+ - [Basic Usage: query()](#basic-usage-query)
14
+ - [Client](#client)
15
+ - [Custom Tools (SDK MCP Servers)](#custom-tools-sdk-mcp-servers)
16
+ - [Hooks](#hooks)
17
+ - [Permission Callbacks](#permission-callbacks)
18
+ - [Types](#types)
19
+ - [Error Handling](#error-handling)
20
+ - [Examples](#examples)
21
+ - [Development](#development)
22
+ - [License](#license)
23
+
11
24
  ## Installation
12
25
 
13
26
  Add this line to your application's Gemfile:
14
27
 
15
28
  ```ruby
16
- gem 'claude-agent-sdk', '~> 0.2.0'
29
+ gem 'claude-agent-sdk', '~> 0.2.1'
17
30
  ```
18
31
 
19
32
  And then execute:
@@ -45,7 +58,7 @@ end
45
58
 
46
59
  ## Basic Usage: query()
47
60
 
48
- `query()` is a function for querying Claude Code. It yields response messages to a block. See [lib/claude_agent_sdk.rb](lib/claude_agent_sdk.rb).
61
+ `query()` is a function for querying Claude Code. It yields response messages to a block.
49
62
 
50
63
  ```ruby
51
64
  require 'claude_agent_sdk'
@@ -133,11 +146,66 @@ For a complete example, see [examples/streaming_input_example.rb](examples/strea
133
146
 
134
147
  `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.
135
148
 
136
- **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. This matches the Python SDK's `ClaudeSDKClient` behavior.
149
+ **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.
150
+
151
+ ### Basic Client Usage
152
+
153
+ ```ruby
154
+ require 'claude_agent_sdk'
155
+ require 'async'
156
+
157
+ Async do
158
+ client = ClaudeAgentSDK::Client.new
159
+
160
+ begin
161
+ # Connect automatically uses streaming mode for bidirectional communication
162
+ client.connect
163
+
164
+ # Send a query
165
+ client.query("What is the capital of France?")
166
+
167
+ # Receive the response
168
+ client.receive_response do |msg|
169
+ if msg.is_a?(ClaudeAgentSDK::AssistantMessage)
170
+ msg.content.each do |block|
171
+ puts block.text if block.is_a?(ClaudeAgentSDK::TextBlock)
172
+ end
173
+ elsif msg.is_a?(ClaudeAgentSDK::ResultMessage)
174
+ puts "Cost: $#{msg.total_cost_usd}" if msg.total_cost_usd
175
+ end
176
+ end
177
+
178
+ ensure
179
+ client.disconnect
180
+ end
181
+ end.wait
182
+ ```
183
+
184
+ ### Advanced Client Features
185
+
186
+ ```ruby
187
+ Async do
188
+ client = ClaudeAgentSDK::Client.new
189
+ client.connect
137
190
 
138
- See [lib/claude_agent_sdk.rb](lib/claude_agent_sdk.rb) for implementation details.
191
+ # Send interrupt signal
192
+ client.interrupt
139
193
 
140
- ### Custom Tools (SDK MCP Servers)
194
+ # Change permission mode during conversation
195
+ client.set_permission_mode('acceptEdits')
196
+
197
+ # Change AI model during conversation
198
+ client.set_model('claude-sonnet-4-5')
199
+
200
+ # Get server initialization info
201
+ info = client.server_info
202
+ puts "Available commands: #{info}"
203
+
204
+ client.disconnect
205
+ end.wait
206
+ ```
207
+
208
+ ## Custom Tools (SDK MCP Servers)
141
209
 
142
210
  A **custom tool** is a Ruby proc/lambda that you can offer to Claude, for Claude to invoke as needed.
143
211
 
@@ -145,9 +213,7 @@ Custom tools are implemented as in-process MCP servers that run directly within
145
213
 
146
214
  **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.
147
215
 
148
- For a complete example, see [examples/mcp_calculator.rb](examples/mcp_calculator.rb).
149
-
150
- #### Creating a Simple Tool
216
+ ### Creating a Simple Tool
151
217
 
152
218
  ```ruby
153
219
  require 'claude_agent_sdk'
@@ -182,7 +248,7 @@ Async do
182
248
  end.wait
183
249
  ```
184
250
 
185
- #### Benefits Over External MCP Servers
251
+ ### Benefits Over External MCP Servers
186
252
 
187
253
  - **No subprocess management** - Runs in the same process as your application
188
254
  - **Better performance** - No IPC overhead for tool calls
@@ -190,7 +256,7 @@ end.wait
190
256
  - **Easier debugging** - All code runs in the same process
191
257
  - **Direct access** - Tools can directly access your application's state
192
258
 
193
- #### Calculator Example
259
+ ### Calculator Example
194
260
 
195
261
  ```ruby
196
262
  # Define calculator tools
@@ -220,7 +286,7 @@ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
220
286
  )
221
287
  ```
222
288
 
223
- #### Mixed Server Support
289
+ ### Mixed Server Support
224
290
 
225
291
  You can use both SDK and external MCP servers together:
226
292
 
@@ -236,7 +302,7 @@ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
236
302
  )
237
303
  ```
238
304
 
239
- #### MCP Resources and Prompts
305
+ ### MCP Resources and Prompts
240
306
 
241
307
  SDK MCP servers can also expose **resources** (data sources) and **prompts** (reusable templates):
242
308
 
@@ -286,48 +352,13 @@ server = ClaudeAgentSDK.create_sdk_mcp_server(
286
352
  )
287
353
  ```
288
354
 
289
- For complete examples, see [examples/mcp_resources_prompts_example.rb](examples/mcp_resources_prompts_example.rb).
355
+ 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).
290
356
 
291
- ### Basic Client Usage
292
-
293
- ```ruby
294
- require 'claude_agent_sdk'
295
- require 'async'
296
-
297
- Async do
298
- client = ClaudeAgentSDK::Client.new
299
-
300
- begin
301
- # Connect automatically uses streaming mode for bidirectional communication
302
- client.connect
303
-
304
- # Send a query
305
- client.query("What is the capital of France?")
306
-
307
- # Receive the response
308
- client.receive_response do |msg|
309
- if msg.is_a?(ClaudeAgentSDK::AssistantMessage)
310
- msg.content.each do |block|
311
- puts block.text if block.is_a?(ClaudeAgentSDK::TextBlock)
312
- end
313
- elsif msg.is_a?(ClaudeAgentSDK::ResultMessage)
314
- puts "Cost: $#{msg.total_cost_usd}" if msg.total_cost_usd
315
- end
316
- end
317
-
318
- ensure
319
- client.disconnect
320
- end
321
- end.wait
322
- ```
323
-
324
- ### Hooks
357
+ ## Hooks
325
358
 
326
359
  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).
327
360
 
328
- For more examples, see [examples/hooks_example.rb](examples/hooks_example.rb).
329
-
330
- #### Example
361
+ ### Example
331
362
 
332
363
  ```ruby
333
364
  require 'claude_agent_sdk'
@@ -383,13 +414,13 @@ Async do
383
414
  end.wait
384
415
  ```
385
416
 
386
- ### Permission Callbacks
417
+ For more examples, see [examples/hooks_example.rb](examples/hooks_example.rb).
387
418
 
388
- 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.
419
+ ## Permission Callbacks
389
420
 
390
- For more examples, see [examples/permission_callback_example.rb](examples/permission_callback_example.rb).
421
+ 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.
391
422
 
392
- #### Example
423
+ ### Example
393
424
 
394
425
  ```ruby
395
426
  require 'claude_agent_sdk'
@@ -440,38 +471,39 @@ Async do
440
471
  end.wait
441
472
  ```
442
473
 
443
- ### Advanced Client Features
444
-
445
- The Client class supports several advanced features:
474
+ For more examples, see [examples/permission_callback_example.rb](examples/permission_callback_example.rb).
446
475
 
447
- ```ruby
448
- Async do
449
- client = ClaudeAgentSDK::Client.new
450
- client.connect
476
+ ## Types
451
477
 
452
- # Send interrupt signal
453
- client.interrupt
478
+ See [lib/claude_agent_sdk/types.rb](lib/claude_agent_sdk/types.rb) for complete type definitions:
454
479
 
455
- # Change permission mode during conversation
456
- client.set_permission_mode('acceptEdits')
480
+ ### Message Types
457
481
 
458
- # Change AI model during conversation
459
- client.set_model('claude-sonnet-4-5')
482
+ | Type | Description |
483
+ |------|-------------|
484
+ | `AssistantMessage` | Response from Claude with content blocks |
485
+ | `UserMessage` | User input message |
486
+ | `SystemMessage` | System metadata message |
487
+ | `ResultMessage` | Final result with cost and usage information |
488
+ | `StreamEvent` | Partial message updates during streaming |
460
489
 
461
- # Get server initialization info
462
- info = client.server_info
463
- puts "Available commands: #{info}"
490
+ ### Content Blocks
464
491
 
465
- client.disconnect
466
- end.wait
467
- ```
492
+ | Type | Description |
493
+ |------|-------------|
494
+ | `TextBlock` | Text content with `text` attribute |
495
+ | `ThinkingBlock` | Claude's reasoning with `thinking` and `signature` |
496
+ | `ToolUseBlock` | Tool invocation with `id`, `name`, and `input` |
497
+ | `ToolResultBlock` | Tool execution result |
468
498
 
469
- ## Types
499
+ ### Configuration
470
500
 
471
- See [lib/claude_agent_sdk/types.rb](lib/claude_agent_sdk/types.rb) for complete type definitions:
472
- - `ClaudeAgentOptions` - Configuration options
473
- - `AssistantMessage`, `UserMessage`, `SystemMessage`, `ResultMessage` - Message types
474
- - `TextBlock`, `ToolUseBlock`, `ToolResultBlock` - Content blocks
501
+ | Type | Description |
502
+ |------|-------------|
503
+ | `ClaudeAgentOptions` | Main configuration for queries and clients |
504
+ | `HookMatcher` | Hook configuration with matcher pattern |
505
+ | `PermissionResultAllow` | Permission callback result to allow tool use |
506
+ | `PermissionResultDeny` | Permission callback result to deny tool use |
475
507
 
476
508
  ## Error Handling
477
509
 
@@ -491,13 +523,16 @@ rescue ClaudeAgentSDK::CLIJSONDecodeError => e
491
523
  end
492
524
  ```
493
525
 
494
- Error types:
495
- - `ClaudeSDKError` - Base error
496
- - `CLINotFoundError` - Claude Code not installed
497
- - `CLIConnectionError` - Connection issues
498
- - `ProcessError` - Process failed
499
- - `CLIJSONDecodeError` - JSON parsing issues
500
- - `MessageParseError` - Message parsing issues
526
+ ### Error Types
527
+
528
+ | Error | Description |
529
+ |-------|-------------|
530
+ | `ClaudeSDKError` | Base error for all SDK errors |
531
+ | `CLINotFoundError` | Claude Code not installed |
532
+ | `CLIConnectionError` | Connection issues |
533
+ | `ProcessError` | Process failed (includes `exit_code` and `stderr`) |
534
+ | `CLIJSONDecodeError` | JSON parsing issues |
535
+ | `MessageParseError` | Message parsing issues |
501
536
 
502
537
  See [lib/claude_agent_sdk/errors.rb](lib/claude_agent_sdk/errors.rb) for all error types.
503
538
 
@@ -507,15 +542,15 @@ See the [Claude Code documentation](https://docs.anthropic.com/en/docs/claude-co
507
542
 
508
543
  ## Examples
509
544
 
510
- See the following examples for complete working code:
511
-
512
- - [examples/quick_start.rb](examples/quick_start.rb) - Basic `query()` usage with options
513
- - [examples/client_example.rb](examples/client_example.rb) - Interactive Client usage
514
- - [examples/streaming_input_example.rb](examples/streaming_input_example.rb) - Streaming input for multi-turn conversations
515
- - [examples/mcp_calculator.rb](examples/mcp_calculator.rb) - Custom tools with SDK MCP servers
516
- - [examples/mcp_resources_prompts_example.rb](examples/mcp_resources_prompts_example.rb) - MCP resources and prompts
517
- - [examples/hooks_example.rb](examples/hooks_example.rb) - Using hooks to control tool execution
518
- - [examples/permission_callback_example.rb](examples/permission_callback_example.rb) - Dynamic tool permission control
545
+ | Example | Description |
546
+ |---------|-------------|
547
+ | [examples/quick_start.rb](examples/quick_start.rb) | Basic `query()` usage with options |
548
+ | [examples/client_example.rb](examples/client_example.rb) | Interactive Client usage |
549
+ | [examples/streaming_input_example.rb](examples/streaming_input_example.rb) | Streaming input for multi-turn conversations |
550
+ | [examples/mcp_calculator.rb](examples/mcp_calculator.rb) | Custom tools with SDK MCP servers |
551
+ | [examples/mcp_resources_prompts_example.rb](examples/mcp_resources_prompts_example.rb) | MCP resources and prompts |
552
+ | [examples/hooks_example.rb](examples/hooks_example.rb) | Using hooks to control tool execution |
553
+ | [examples/permission_callback_example.rb](examples/permission_callback_example.rb) | Dynamic tool permission control |
519
554
 
520
555
  ## Development
521
556
 
@@ -54,7 +54,8 @@ module ClaudeAgentSDK
54
54
  AssistantMessage.new(
55
55
  content: content_blocks,
56
56
  model: data.dig(:message, :model),
57
- parent_tool_use_id: data[:parent_tool_use_id]
57
+ parent_tool_use_id: data[:parent_tool_use_id],
58
+ error: data[:error] # authentication_failed, billing_error, rate_limit, invalid_request, server_error, unknown
58
59
  )
59
60
  end
60
61
 
@@ -75,7 +76,8 @@ module ClaudeAgentSDK
75
76
  session_id: data[:session_id],
76
77
  total_cost_usd: data[:total_cost_usd],
77
78
  usage: data[:usage],
78
- result: data[:result]
79
+ result: data[:result],
80
+ structured_output: data[:structured_output] # Structured output when output_format is specified
79
81
  )
80
82
  end
81
83
 
@@ -217,16 +217,73 @@ module ClaudeAgentSDK
217
217
  callback = @hook_callbacks[callback_id]
218
218
  raise "No hook callback found for ID: #{callback_id}" unless callback
219
219
 
220
+ # Parse input data into typed HookInput object
221
+ input_data = request_data[:input] || {}
222
+ hook_input = parse_hook_input(input_data)
223
+
224
+ # Create typed HookContext
225
+ context = HookContext.new(signal: nil)
226
+
220
227
  hook_output = callback.call(
221
- request_data[:input],
228
+ hook_input,
222
229
  request_data[:tool_use_id],
223
- { signal: nil }
230
+ context
224
231
  )
225
232
 
226
233
  # Convert Ruby-safe field names to CLI-expected names
227
234
  convert_hook_output_for_cli(hook_output)
228
235
  end
229
236
 
237
+ def parse_hook_input(input_data)
238
+ event_name = input_data[:hook_event_name] || input_data['hook_event_name']
239
+ base_args = {
240
+ session_id: input_data[:session_id],
241
+ transcript_path: input_data[:transcript_path],
242
+ cwd: input_data[:cwd],
243
+ permission_mode: input_data[:permission_mode]
244
+ }
245
+
246
+ case event_name
247
+ when 'PreToolUse'
248
+ PreToolUseHookInput.new(
249
+ tool_name: input_data[:tool_name],
250
+ tool_input: input_data[:tool_input],
251
+ **base_args
252
+ )
253
+ when 'PostToolUse'
254
+ PostToolUseHookInput.new(
255
+ tool_name: input_data[:tool_name],
256
+ tool_input: input_data[:tool_input],
257
+ tool_response: input_data[:tool_response],
258
+ **base_args
259
+ )
260
+ when 'UserPromptSubmit'
261
+ UserPromptSubmitHookInput.new(
262
+ prompt: input_data[:prompt],
263
+ **base_args
264
+ )
265
+ when 'Stop'
266
+ StopHookInput.new(
267
+ stop_hook_active: input_data[:stop_hook_active],
268
+ **base_args
269
+ )
270
+ when 'SubagentStop'
271
+ SubagentStopHookInput.new(
272
+ stop_hook_active: input_data[:stop_hook_active],
273
+ **base_args
274
+ )
275
+ when 'PreCompact'
276
+ PreCompactHookInput.new(
277
+ trigger: input_data[:trigger],
278
+ custom_instructions: input_data[:custom_instructions],
279
+ **base_args
280
+ )
281
+ else
282
+ # Return base input for unknown event types
283
+ BaseHookInput.new(**base_args)
284
+ end
285
+ end
286
+
230
287
  def handle_mcp_message(request_data)
231
288
  server_name = request_data[:server_name]
232
289
  mcp_message = request_data[:message]
@@ -238,6 +295,13 @@ module ClaudeAgentSDK
238
295
  end
239
296
 
240
297
  def convert_hook_output_for_cli(hook_output)
298
+ # Handle typed output objects
299
+ if hook_output.respond_to?(:to_h) && !hook_output.is_a?(Hash)
300
+ return hook_output.to_h
301
+ end
302
+
303
+ return {} unless hook_output.is_a?(Hash)
304
+
241
305
  # Convert Ruby hash with symbol keys to CLI format
242
306
  # Handle special keywords that might be Ruby-safe versions
243
307
  converted = {}
@@ -245,9 +309,21 @@ module ClaudeAgentSDK
245
309
  converted_key = case key
246
310
  when :async_, 'async_' then 'async'
247
311
  when :continue_, 'continue_' then 'continue'
248
- else key.to_s.gsub('_', '')
312
+ when :hook_specific_output then 'hookSpecificOutput'
313
+ when :suppress_output then 'suppressOutput'
314
+ when :stop_reason then 'stopReason'
315
+ when :system_message then 'systemMessage'
316
+ when :async_timeout then 'asyncTimeout'
317
+ else key.to_s
249
318
  end
250
- converted[converted_key] = value
319
+
320
+ # Recursively convert nested objects
321
+ converted_value = if value.respond_to?(:to_h) && !value.is_a?(Hash)
322
+ value.to_h
323
+ else
324
+ value
325
+ end
326
+ converted[converted_key] = converted_value
251
327
  end
252
328
  converted
253
329
  end
@@ -75,12 +75,33 @@ module ClaudeAgentSDK
75
75
  cmd.concat(['--max-turns', @options.max_turns.to_s]) if @options.max_turns
76
76
  cmd.concat(['--disallowedTools', @options.disallowed_tools.join(',')]) unless @options.disallowed_tools.empty?
77
77
  cmd.concat(['--model', @options.model]) if @options.model
78
+ cmd.concat(['--fallback-model', @options.fallback_model]) if @options.fallback_model
78
79
  cmd.concat(['--permission-prompt-tool', @options.permission_prompt_tool_name]) if @options.permission_prompt_tool_name
79
80
  cmd.concat(['--permission-mode', @options.permission_mode]) if @options.permission_mode
80
81
  cmd << '--continue' if @options.continue_conversation
81
82
  cmd.concat(['--resume', @options.resume]) if @options.resume
82
83
  cmd.concat(['--settings', @options.settings]) if @options.settings
83
84
 
85
+ # New options to match Python SDK
86
+ cmd.concat(['--max-budget-usd', @options.max_budget_usd.to_s]) if @options.max_budget_usd
87
+ # Note: max_thinking_tokens is stored in options but not yet supported by Claude CLI
88
+
89
+ # JSON schema for structured output
90
+ # Accepts either:
91
+ # 1. Direct schema: { type: 'object', properties: {...} }
92
+ # 2. Wrapped format: { type: 'json_schema', schema: {...} }
93
+ if @options.output_format
94
+ schema = if @options.output_format.is_a?(Hash) && @options.output_format[:type] == 'json_schema'
95
+ @options.output_format[:schema]
96
+ elsif @options.output_format.is_a?(Hash) && @options.output_format['type'] == 'json_schema'
97
+ @options.output_format['schema']
98
+ else
99
+ @options.output_format
100
+ end
101
+ schema_json = schema.is_a?(String) ? schema : JSON.generate(schema)
102
+ cmd.concat(['--json-schema', schema_json])
103
+ end
104
+
84
105
  # Add directories
85
106
  @options.add_dirs.each do |dir|
86
107
  cmd.concat(['--add-dir', dir.to_s])
@@ -121,6 +142,14 @@ module ClaudeAgentSDK
121
142
  cmd.concat(['--agents', JSON.generate(agents_dict)])
122
143
  end
123
144
 
145
+ # Plugins
146
+ if @options.plugins && !@options.plugins.empty?
147
+ plugins_config = @options.plugins.map do |plugin|
148
+ plugin.is_a?(SdkPluginConfig) ? plugin.to_h : plugin
149
+ end
150
+ cmd.concat(['--plugins', JSON.generate(plugins_config)])
151
+ end
152
+
124
153
  # Setting sources
125
154
  sources_value = @options.setting_sources ? @options.setting_sources.join(',') : ''
126
155
  cmd.concat(['--setting-sources', sources_value])
@@ -159,7 +188,7 @@ module ClaudeAgentSDK
159
188
  process_env['PWD'] = @cwd.to_s if @cwd
160
189
 
161
190
  # Determine stderr handling
162
- should_pipe_stderr = @options.stderr || @options.extra_args.key?('debug-to-stderr')
191
+ should_pipe_stderr = @options.stderr || @options.debug_stderr || @options.extra_args.key?('debug-to-stderr')
163
192
 
164
193
  begin
165
194
  # Start process using Open3
@@ -205,7 +234,17 @@ module ClaudeAgentSDK
205
234
  line_str = line.chomp
206
235
  next if line_str.empty?
207
236
 
237
+ # Call stderr callback if provided
208
238
  @options.stderr&.call(line_str)
239
+
240
+ # Write to debug_stderr file/IO if provided
241
+ if @options.debug_stderr
242
+ if @options.debug_stderr.respond_to?(:puts)
243
+ @options.debug_stderr.puts(line_str)
244
+ elsif @options.debug_stderr.is_a?(String)
245
+ File.open(@options.debug_stderr, 'a') { |f| f.puts(line_str) }
246
+ end
247
+ end
209
248
  end
210
249
  rescue StandardError
211
250
  # Ignore errors during stderr reading
@@ -1,6 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeAgentSDK
4
+ # Type constants for permission modes
5
+ PERMISSION_MODES = %w[default acceptEdits plan bypassPermissions].freeze
6
+
7
+ # Type constants for setting sources
8
+ SETTING_SOURCES = %w[user project local].freeze
9
+
10
+ # Type constants for permission update destinations
11
+ PERMISSION_UPDATE_DESTINATIONS = %w[userSettings projectSettings localSettings session].freeze
12
+
13
+ # Type constants for permission behaviors
14
+ PERMISSION_BEHAVIORS = %w[allow deny ask].freeze
15
+
16
+ # Type constants for hook events
17
+ HOOK_EVENTS = %w[PreToolUse PostToolUse UserPromptSubmit Stop SubagentStop PreCompact].freeze
18
+
19
+ # Type constants for assistant message errors
20
+ ASSISTANT_MESSAGE_ERRORS = %w[authentication_failed billing_error rate_limit invalid_request server_error unknown].freeze
21
+
4
22
  # Content Blocks
5
23
 
6
24
  # Text content block
@@ -58,12 +76,13 @@ module ClaudeAgentSDK
58
76
 
59
77
  # Assistant message with content blocks
60
78
  class AssistantMessage
61
- attr_accessor :content, :model, :parent_tool_use_id
79
+ attr_accessor :content, :model, :parent_tool_use_id, :error
62
80
 
63
- def initialize(content:, model:, parent_tool_use_id: nil)
81
+ def initialize(content:, model:, parent_tool_use_id: nil, error: nil)
64
82
  @content = content
65
83
  @model = model
66
84
  @parent_tool_use_id = parent_tool_use_id
85
+ @error = error # One of: authentication_failed, billing_error, rate_limit, invalid_request, server_error, unknown
67
86
  end
68
87
  end
69
88
 
@@ -80,10 +99,10 @@ module ClaudeAgentSDK
80
99
  # Result message with cost and usage information
81
100
  class ResultMessage
82
101
  attr_accessor :subtype, :duration_ms, :duration_api_ms, :is_error,
83
- :num_turns, :session_id, :total_cost_usd, :usage, :result
102
+ :num_turns, :session_id, :total_cost_usd, :usage, :result, :structured_output
84
103
 
85
104
  def initialize(subtype:, duration_ms:, duration_api_ms:, is_error:,
86
- num_turns:, session_id:, total_cost_usd: nil, usage: nil, result: nil)
105
+ num_turns:, session_id:, total_cost_usd: nil, usage: nil, result: nil, structured_output: nil)
87
106
  @subtype = subtype
88
107
  @duration_ms = duration_ms
89
108
  @duration_api_ms = duration_api_ms
@@ -93,6 +112,7 @@ module ClaudeAgentSDK
93
112
  @total_cost_usd = total_cost_usd
94
113
  @usage = usage
95
114
  @result = result
115
+ @structured_output = structured_output # Structured output when output_format is specified
96
116
  end
97
117
  end
98
118
 
@@ -201,11 +221,215 @@ module ClaudeAgentSDK
201
221
 
202
222
  # Hook matcher configuration
203
223
  class HookMatcher
204
- attr_accessor :matcher, :hooks
224
+ attr_accessor :matcher, :hooks, :timeout
205
225
 
206
- def initialize(matcher: nil, hooks: [])
226
+ def initialize(matcher: nil, hooks: [], timeout: nil)
207
227
  @matcher = matcher
208
228
  @hooks = hooks
229
+ @timeout = timeout # Timeout in seconds for hook execution
230
+ end
231
+ end
232
+
233
+ # Hook context passed to hook callbacks
234
+ class HookContext
235
+ attr_accessor :signal
236
+
237
+ def initialize(signal: nil)
238
+ @signal = signal
239
+ end
240
+ end
241
+
242
+ # Base hook input with common fields
243
+ class BaseHookInput
244
+ attr_accessor :session_id, :transcript_path, :cwd, :permission_mode
245
+
246
+ def initialize(session_id: nil, transcript_path: nil, cwd: nil, permission_mode: nil)
247
+ @session_id = session_id
248
+ @transcript_path = transcript_path
249
+ @cwd = cwd
250
+ @permission_mode = permission_mode
251
+ end
252
+ end
253
+
254
+ # PreToolUse hook input
255
+ class PreToolUseHookInput < BaseHookInput
256
+ attr_accessor :hook_event_name, :tool_name, :tool_input
257
+
258
+ def initialize(hook_event_name: 'PreToolUse', tool_name: nil, tool_input: nil, **base_args)
259
+ super(**base_args)
260
+ @hook_event_name = hook_event_name
261
+ @tool_name = tool_name
262
+ @tool_input = tool_input
263
+ end
264
+ end
265
+
266
+ # PostToolUse hook input
267
+ class PostToolUseHookInput < BaseHookInput
268
+ attr_accessor :hook_event_name, :tool_name, :tool_input, :tool_response
269
+
270
+ def initialize(hook_event_name: 'PostToolUse', tool_name: nil, tool_input: nil, tool_response: nil, **base_args)
271
+ super(**base_args)
272
+ @hook_event_name = hook_event_name
273
+ @tool_name = tool_name
274
+ @tool_input = tool_input
275
+ @tool_response = tool_response
276
+ end
277
+ end
278
+
279
+ # UserPromptSubmit hook input
280
+ class UserPromptSubmitHookInput < BaseHookInput
281
+ attr_accessor :hook_event_name, :prompt
282
+
283
+ def initialize(hook_event_name: 'UserPromptSubmit', prompt: nil, **base_args)
284
+ super(**base_args)
285
+ @hook_event_name = hook_event_name
286
+ @prompt = prompt
287
+ end
288
+ end
289
+
290
+ # Stop hook input
291
+ class StopHookInput < BaseHookInput
292
+ attr_accessor :hook_event_name, :stop_hook_active
293
+
294
+ def initialize(hook_event_name: 'Stop', stop_hook_active: false, **base_args)
295
+ super(**base_args)
296
+ @hook_event_name = hook_event_name
297
+ @stop_hook_active = stop_hook_active
298
+ end
299
+ end
300
+
301
+ # SubagentStop hook input
302
+ class SubagentStopHookInput < BaseHookInput
303
+ attr_accessor :hook_event_name, :stop_hook_active
304
+
305
+ def initialize(hook_event_name: 'SubagentStop', stop_hook_active: false, **base_args)
306
+ super(**base_args)
307
+ @hook_event_name = hook_event_name
308
+ @stop_hook_active = stop_hook_active
309
+ end
310
+ end
311
+
312
+ # PreCompact hook input
313
+ class PreCompactHookInput < BaseHookInput
314
+ attr_accessor :hook_event_name, :trigger, :custom_instructions
315
+
316
+ def initialize(hook_event_name: 'PreCompact', trigger: nil, custom_instructions: nil, **base_args)
317
+ super(**base_args)
318
+ @hook_event_name = hook_event_name
319
+ @trigger = trigger
320
+ @custom_instructions = custom_instructions
321
+ end
322
+ end
323
+
324
+ # PreToolUse hook specific output
325
+ class PreToolUseHookSpecificOutput
326
+ attr_accessor :hook_event_name, :permission_decision, :permission_decision_reason, :updated_input
327
+
328
+ def initialize(permission_decision: nil, permission_decision_reason: nil, updated_input: nil)
329
+ @hook_event_name = 'PreToolUse'
330
+ @permission_decision = permission_decision # 'allow', 'deny', or 'ask'
331
+ @permission_decision_reason = permission_decision_reason
332
+ @updated_input = updated_input
333
+ end
334
+
335
+ def to_h
336
+ result = { hookEventName: @hook_event_name }
337
+ result[:permissionDecision] = @permission_decision if @permission_decision
338
+ result[:permissionDecisionReason] = @permission_decision_reason if @permission_decision_reason
339
+ result[:updatedInput] = @updated_input if @updated_input
340
+ result
341
+ end
342
+ end
343
+
344
+ # PostToolUse hook specific output
345
+ class PostToolUseHookSpecificOutput
346
+ attr_accessor :hook_event_name, :additional_context
347
+
348
+ def initialize(additional_context: nil)
349
+ @hook_event_name = 'PostToolUse'
350
+ @additional_context = additional_context
351
+ end
352
+
353
+ def to_h
354
+ result = { hookEventName: @hook_event_name }
355
+ result[:additionalContext] = @additional_context if @additional_context
356
+ result
357
+ end
358
+ end
359
+
360
+ # UserPromptSubmit hook specific output
361
+ class UserPromptSubmitHookSpecificOutput
362
+ attr_accessor :hook_event_name, :additional_context
363
+
364
+ def initialize(additional_context: nil)
365
+ @hook_event_name = 'UserPromptSubmit'
366
+ @additional_context = additional_context
367
+ end
368
+
369
+ def to_h
370
+ result = { hookEventName: @hook_event_name }
371
+ result[:additionalContext] = @additional_context if @additional_context
372
+ result
373
+ end
374
+ end
375
+
376
+ # SessionStart hook specific output
377
+ class SessionStartHookSpecificOutput
378
+ attr_accessor :hook_event_name, :additional_context
379
+
380
+ def initialize(additional_context: nil)
381
+ @hook_event_name = 'SessionStart'
382
+ @additional_context = additional_context
383
+ end
384
+
385
+ def to_h
386
+ result = { hookEventName: @hook_event_name }
387
+ result[:additionalContext] = @additional_context if @additional_context
388
+ result
389
+ end
390
+ end
391
+
392
+ # Async hook JSON output
393
+ class AsyncHookJSONOutput
394
+ attr_accessor :async, :async_timeout
395
+
396
+ def initialize(async: true, async_timeout: nil)
397
+ @async = async
398
+ @async_timeout = async_timeout
399
+ end
400
+
401
+ def to_h
402
+ result = { async: @async }
403
+ result[:asyncTimeout] = @async_timeout if @async_timeout
404
+ result
405
+ end
406
+ end
407
+
408
+ # Sync hook JSON output
409
+ class SyncHookJSONOutput
410
+ attr_accessor :continue, :suppress_output, :stop_reason, :decision,
411
+ :system_message, :reason, :hook_specific_output
412
+
413
+ def initialize(continue: true, suppress_output: false, stop_reason: nil, decision: nil,
414
+ system_message: nil, reason: nil, hook_specific_output: nil)
415
+ @continue = continue
416
+ @suppress_output = suppress_output
417
+ @stop_reason = stop_reason
418
+ @decision = decision
419
+ @system_message = system_message
420
+ @reason = reason
421
+ @hook_specific_output = hook_specific_output
422
+ end
423
+
424
+ def to_h
425
+ result = { continue: @continue }
426
+ result[:suppressOutput] = @suppress_output if @suppress_output
427
+ result[:stopReason] = @stop_reason if @stop_reason
428
+ result[:decision] = @decision if @decision
429
+ result[:systemMessage] = @system_message if @system_message
430
+ result[:reason] = @reason if @reason
431
+ result[:hookSpecificOutput] = @hook_specific_output.to_h if @hook_specific_output
432
+ result
209
433
  end
210
434
  end
211
435
 
@@ -274,6 +498,20 @@ module ClaudeAgentSDK
274
498
  end
275
499
  end
276
500
 
501
+ # SDK Plugin configuration
502
+ class SdkPluginConfig
503
+ attr_accessor :type, :path
504
+
505
+ def initialize(path:)
506
+ @type = 'plugin'
507
+ @path = path
508
+ end
509
+
510
+ def to_h
511
+ { type: @type, path: @path }
512
+ end
513
+ end
514
+
277
515
  # Claude Agent Options for configuring queries
278
516
  class ClaudeAgentOptions
279
517
  attr_accessor :allowed_tools, :system_prompt, :mcp_servers, :permission_mode,
@@ -281,7 +519,10 @@ module ClaudeAgentSDK
281
519
  :model, :permission_prompt_tool_name, :cwd, :cli_path, :settings,
282
520
  :add_dirs, :env, :extra_args, :max_buffer_size, :stderr,
283
521
  :can_use_tool, :hooks, :user, :include_partial_messages,
284
- :fork_session, :agents, :setting_sources
522
+ :fork_session, :agents, :setting_sources,
523
+ # New options added to match Python SDK
524
+ :output_format, :max_budget_usd, :max_thinking_tokens,
525
+ :fallback_model, :plugins, :debug_stderr
285
526
 
286
527
  def initialize(
287
528
  allowed_tools: [],
@@ -308,7 +549,14 @@ module ClaudeAgentSDK
308
549
  include_partial_messages: false,
309
550
  fork_session: false,
310
551
  agents: nil,
311
- setting_sources: nil
552
+ setting_sources: nil,
553
+ # New options added to match Python SDK
554
+ output_format: nil,
555
+ max_budget_usd: nil,
556
+ max_thinking_tokens: nil,
557
+ fallback_model: nil,
558
+ plugins: nil,
559
+ debug_stderr: nil
312
560
  )
313
561
  @allowed_tools = allowed_tools
314
562
  @system_prompt = system_prompt
@@ -335,6 +583,13 @@ module ClaudeAgentSDK
335
583
  @fork_session = fork_session
336
584
  @agents = agents
337
585
  @setting_sources = setting_sources
586
+ # New options added to match Python SDK
587
+ @output_format = output_format # JSON schema for structured output
588
+ @max_budget_usd = max_budget_usd # Spending cap in dollars
589
+ @max_thinking_tokens = max_thinking_tokens # Extended thinking token budget
590
+ @fallback_model = fallback_model # Backup model if primary unavailable
591
+ @plugins = plugins # Array of SdkPluginConfig
592
+ @debug_stderr = debug_stderr # Debug output file object/path
338
593
  end
339
594
 
340
595
  def dup_with(**changes)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeAgentSDK
4
- VERSION = '0.2.1'
4
+ VERSION = '0.3.0'
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: claude-agent-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Community Contributors
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-10-17 00:00:00.000000000 Z
10
+ date: 2025-12-11 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: async