claude-agent-sdk 0.6.3 → 0.7.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: 1ee66bd44d402ecac03c5928cc485360342b64510985de4966a627b5ada3919f
4
- data.tar.gz: d69ea31b3f6dd26679aeb9c8857616d757861a54a9f9803ce7b0575043348bef
3
+ metadata.gz: de5807b36fd822ee89e4a793a536b4ba8804c168259d17cc1a03974425f128ea
4
+ data.tar.gz: c40d5dad151728b8b2e131ad96da616bd03b4972f6d5807d5375ea24d499ceb6
5
5
  SHA512:
6
- metadata.gz: 5e381c777261d67d8e95638874455cd13cda055b7524ff20459dc5e53ca2d8e70c4580637f3da34d83e22090becae0b4a9bcdd15ffb9613db3c03133fe3c4255
7
- data.tar.gz: 2400f7ccb58c48b8c12d4fc3843781b5545d2ff80c030daa32be4529ae0c2b4f59e3114138e6b21f7c3923c4e3ccca9594fbf6e6722c8d540b729c18e47767f3
6
+ metadata.gz: f34dc590db10f0f4ebe1360a5318617fd0b7c6299fcf83d45438d1bb1a9a106734e06268f7d4361bbdf575d660afa63b97586c56235fa9d617a6b891a0e1e158
7
+ data.tar.gz: e6548661fbcd10c96b5b64b4efc6676d5ad47f28435a23e4b8045a590df107bedea2ee8d089a230c32aaa84dafdbb0239bbeffb1b7b7aba255196d2ded16903e
data/CHANGELOG.md CHANGED
@@ -5,6 +5,49 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.7.0] - 2026-02-20
9
+
10
+ ### Added
11
+
12
+ #### Thinking Configuration
13
+ - `ThinkingConfigAdaptive`, `ThinkingConfigEnabled`, `ThinkingConfigDisabled` classes for structured thinking control
14
+ - `thinking` option on `ClaudeAgentOptions` — takes precedence over deprecated `max_thinking_tokens`
15
+ - `ThinkingConfigAdaptive` → 32,000 token default budget
16
+ - `ThinkingConfigEnabled(budget_tokens:)` → explicit budget
17
+ - `ThinkingConfigDisabled` → 0 tokens (thinking off)
18
+ - `effort` option on `ClaudeAgentOptions` — maps to `--effort` CLI flag (`'low'`, `'medium'`, `'high'`)
19
+
20
+ #### Tool Annotations
21
+ - `annotations` attribute on `SdkMcpTool` for MCP tool annotations (e.g., `readOnlyHint`, `title`)
22
+ - `annotations:` keyword on `ClaudeAgentSDK.create_tool`
23
+ - Annotations included in `SdkMcpServer#list_tools` responses
24
+
25
+ #### Hook Enhancements
26
+ - `tool_use_id` attribute on `PreToolUseHookInput` and `PostToolUseHookInput`
27
+ - `additional_context` attribute on `PreToolUseHookSpecificOutput`
28
+
29
+ #### Message Enhancements
30
+ - `tool_use_result` attribute on `UserMessage` for tool response data
31
+ - `MessageParser` populates `tool_use_result` from CLI output
32
+
33
+ ### Changed
34
+
35
+ #### Architecture: Always Streaming Mode (BREAKING for internal API)
36
+ - **`SubprocessCLITransport`** now always uses `--input-format stream-json` — removed `--print` mode and `--agents` CLI flag
37
+ - **`SubprocessCLITransport.new`** still accepts `(prompt, options)` for compatibility but ignores the prompt argument (always uses streaming mode)
38
+ - **`query()`** now uses the full control protocol internally (Query handler + initialize handshake), matching the Python SDK
39
+ - **Agents** are sent via the `initialize` control request over stdin instead of CLI `--agents` flag, avoiding OS ARG_MAX limits
40
+ - **`query()`** now supports SDK MCP servers and `can_use_tool` callbacks (previously Client-only)
41
+
42
+ #### Empty System Prompt
43
+ - When `system_prompt` is `nil`, passes `--system-prompt ""` to CLI for predictable behavior without the default Claude Code system prompt
44
+
45
+ ## [0.6.3] - 2026-02-18
46
+
47
+ ### Fixed
48
+ - **ProcessError stderr:** Real stderr output is now included in `ProcessError` exceptions (was always "No stderr output captured")
49
+ - **Rate limit events:** Added `RateLimitEvent` type and `rate_limit_event` message parsing support
50
+
8
51
  ## [0.6.2] - 2026-02-17
9
52
 
10
53
  ### Fixed
data/README.md CHANGED
@@ -16,6 +16,7 @@
16
16
  - [Hooks](#hooks)
17
17
  - [Permission Callbacks](#permission-callbacks)
18
18
  - [Structured Output](#structured-output)
19
+ - [Thinking Configuration](#thinking-configuration)
19
20
  - [Budget Control](#budget-control)
20
21
  - [Fallback Model](#fallback-model)
21
22
  - [Beta Features](#beta-features)
@@ -38,7 +39,7 @@ Add this line to your application's Gemfile:
38
39
  gem 'claude-agent-sdk', github: 'ya-luotao/claude-agent-sdk-ruby'
39
40
 
40
41
  # Or use a stable version from RubyGems
41
- gem 'claude-agent-sdk', '~> 0.6.0'
42
+ gem 'claude-agent-sdk', '~> 0.7.0'
42
43
  ```
43
44
 
44
45
  And then execute:
@@ -249,8 +250,11 @@ Custom tools are implemented as in-process MCP servers that run directly within
249
250
  require 'claude_agent_sdk'
250
251
  require 'async'
251
252
 
252
- # Define a tool using create_tool
253
- greet_tool = ClaudeAgentSDK.create_tool('greet', 'Greet a user', { name: :string }) do |args|
253
+ # Define a tool using create_tool (with optional annotations)
254
+ greet_tool = ClaudeAgentSDK.create_tool(
255
+ 'greet', 'Greet a user', { name: :string },
256
+ annotations: { title: 'Greeter', readOnlyHint: true }
257
+ ) do |args|
254
258
  { content: [{ type: 'text', text: "Hello, #{args[:name]}!" }] }
255
259
  end
256
260
 
@@ -392,8 +396,8 @@ A **hook** is a Ruby proc/lambda that the Claude Code *application* (*not* Claud
392
396
 
393
397
  All hook input objects include common fields like `session_id`, `transcript_path`, `cwd`, and `permission_mode`.
394
398
 
395
- - `PreToolUse` → `PreToolUseHookInput` (`tool_name`, `tool_input`)
396
- - `PostToolUse` → `PostToolUseHookInput` (`tool_name`, `tool_input`, `tool_response`)
399
+ - `PreToolUse` → `PreToolUseHookInput` (`tool_name`, `tool_input`, `tool_use_id`)
400
+ - `PostToolUse` → `PostToolUseHookInput` (`tool_name`, `tool_input`, `tool_response`, `tool_use_id`)
397
401
  - `PostToolUseFailure` → `PostToolUseFailureHookInput` (`tool_name`, `tool_input`, `tool_use_id`, `error`, `is_interrupt`)
398
402
  - `UserPromptSubmit` → `UserPromptSubmitHookInput` (`prompt`)
399
403
  - `Stop` → `StopHookInput` (`stop_hook_active`)
@@ -566,6 +570,37 @@ end
566
570
 
567
571
  For complete examples, see [examples/structured_output_example.rb](examples/structured_output_example.rb).
568
572
 
573
+ ## Thinking Configuration
574
+
575
+ Control extended thinking behavior with typed configuration objects. The `thinking` option takes precedence over the deprecated `max_thinking_tokens`.
576
+
577
+ ```ruby
578
+ # Adaptive thinking — uses a default budget of 32,000 tokens
579
+ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
580
+ thinking: ClaudeAgentSDK::ThinkingConfigAdaptive.new
581
+ )
582
+
583
+ # Enabled thinking with custom budget
584
+ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
585
+ thinking: ClaudeAgentSDK::ThinkingConfigEnabled.new(budget_tokens: 50_000)
586
+ )
587
+
588
+ # Explicitly disabled thinking
589
+ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
590
+ thinking: ClaudeAgentSDK::ThinkingConfigDisabled.new
591
+ )
592
+ ```
593
+
594
+ Use the `effort` option to control the model's effort level:
595
+
596
+ ```ruby
597
+ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
598
+ effort: 'high' # 'low', 'medium', or 'high'
599
+ )
600
+ ```
601
+
602
+ > **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`.
603
+
569
604
  ## Budget Control
570
605
 
571
606
  Use `max_budget_usd` to set a spending cap for your queries:
@@ -891,7 +926,8 @@ User input message.
891
926
  class UserMessage
892
927
  attr_accessor :content, # String | Array<ContentBlock>
893
928
  :uuid, # String | nil - Unique ID for rewind support
894
- :parent_tool_use_id # String | nil
929
+ :parent_tool_use_id, # String | nil
930
+ :tool_use_result # Hash | nil - Tool result data when message is a tool response
895
931
  end
896
932
  ```
897
933
 
@@ -1036,6 +1072,10 @@ end
1036
1072
  | `PermissionResultAllow` | Permission callback result to allow tool use |
1037
1073
  | `PermissionResultDeny` | Permission callback result to deny tool use |
1038
1074
  | `AgentDefinition` | Agent definition with description, prompt, tools, model |
1075
+ | `ThinkingConfigAdaptive` | Adaptive thinking mode (32,000 token default budget) |
1076
+ | `ThinkingConfigEnabled` | Enabled thinking with explicit `budget_tokens` |
1077
+ | `ThinkingConfigDisabled` | Disabled thinking (0 tokens) |
1078
+ | `SdkMcpTool` | SDK MCP tool definition with name, description, input_schema, handler, annotations |
1039
1079
  | `McpStdioServerConfig` | MCP server config for stdio transport |
1040
1080
  | `McpSSEServerConfig` | MCP server config for SSE transport |
1041
1081
  | `McpHttpServerConfig` | MCP server config for HTTP transport |
@@ -35,6 +35,7 @@ module ClaudeAgentSDK
35
35
  def self.parse_user_message(data)
36
36
  parent_tool_use_id = data[:parent_tool_use_id]
37
37
  uuid = data[:uuid] # UUID for rewind support
38
+ tool_use_result = data[:tool_use_result]
38
39
  message_data = data[:message]
39
40
  raise MessageParseError.new("Missing message field in user message", data: data) unless message_data
40
41
 
@@ -43,9 +44,11 @@ module ClaudeAgentSDK
43
44
 
44
45
  if content.is_a?(Array)
45
46
  content_blocks = content.map { |block| parse_content_block(block) }
46
- UserMessage.new(content: content_blocks, uuid: uuid, parent_tool_use_id: parent_tool_use_id)
47
+ UserMessage.new(content: content_blocks, uuid: uuid, parent_tool_use_id: parent_tool_use_id,
48
+ tool_use_result: tool_use_result)
47
49
  else
48
- UserMessage.new(content: content, uuid: uuid, parent_tool_use_id: parent_tool_use_id)
50
+ UserMessage.new(content: content, uuid: uuid, parent_tool_use_id: parent_tool_use_id,
51
+ tool_use_result: tool_use_result)
49
52
  end
50
53
  end
51
54
 
@@ -23,12 +23,13 @@ module ClaudeAgentSDK
23
23
  CONTROL_REQUEST_TIMEOUT_ENV_VAR = 'CLAUDE_AGENT_SDK_CONTROL_REQUEST_TIMEOUT_SECONDS'
24
24
  DEFAULT_CONTROL_REQUEST_TIMEOUT_SECONDS = 1200.0
25
25
 
26
- def initialize(transport:, is_streaming_mode:, can_use_tool: nil, hooks: nil, sdk_mcp_servers: nil)
26
+ def initialize(transport:, is_streaming_mode:, can_use_tool: nil, hooks: nil, sdk_mcp_servers: nil, agents: nil)
27
27
  @transport = transport
28
28
  @is_streaming_mode = is_streaming_mode
29
29
  @can_use_tool = can_use_tool
30
30
  @hooks = hooks || {}
31
31
  @sdk_mcp_servers = sdk_mcp_servers || {}
32
+ @agents = agents
32
33
 
33
34
  # Control protocol state
34
35
  @pending_control_responses = {}
@@ -76,10 +77,24 @@ module ClaudeAgentSDK
76
77
  end
77
78
  end
78
79
 
80
+ # Build agents dict for initialization
81
+ agents_dict = nil
82
+ if @agents
83
+ agents_dict = @agents.transform_values do |agent_def|
84
+ {
85
+ description: agent_def.description,
86
+ prompt: agent_def.prompt,
87
+ tools: agent_def.tools,
88
+ model: agent_def.model
89
+ }.compact
90
+ end
91
+ end
92
+
79
93
  # Send initialize request
80
94
  request = {
81
95
  subtype: 'initialize',
82
- hooks: hooks_config.empty? ? nil : hooks_config
96
+ hooks: hooks_config.empty? ? nil : hooks_config,
97
+ agents: agents_dict
83
98
  }
84
99
 
85
100
  response = send_control_request(request)
@@ -306,6 +321,7 @@ module ClaudeAgentSDK
306
321
  PreToolUseHookInput.new(
307
322
  tool_name: fetch.call(:tool_name),
308
323
  tool_input: fetch.call(:tool_input),
324
+ tool_use_id: fetch.call(:tool_use_id),
309
325
  **base_args
310
326
  )
311
327
  when 'PostToolUse'
@@ -313,6 +329,7 @@ module ClaudeAgentSDK
313
329
  tool_name: fetch.call(:tool_name),
314
330
  tool_input: fetch.call(:tool_input),
315
331
  tool_response: fetch.call(:tool_response),
332
+ tool_use_id: fetch.call(:tool_use_id),
316
333
  **base_args
317
334
  )
318
335
  when 'PostToolUseFailure'
@@ -51,11 +51,13 @@ module ClaudeAgentSDK
51
51
  # @return [Array<Hash>] Array of tool definitions
52
52
  def list_tools
53
53
  @tools.map do |tool|
54
- {
54
+ entry = {
55
55
  name: tool.name,
56
56
  description: tool.description,
57
57
  inputSchema: convert_input_schema(tool.input_schema)
58
58
  }
59
+ entry[:annotations] = tool.annotations if tool.annotations
60
+ entry
59
61
  end
60
62
  end
61
63
 
@@ -387,14 +389,15 @@ module ClaudeAgentSDK
387
389
  # { content: [{ type: 'text', text: "Result: #{result}" }] }
388
390
  # end
389
391
  # end
390
- def self.create_tool(name, description, input_schema, &handler)
392
+ def self.create_tool(name, description, input_schema, annotations: nil, &handler)
391
393
  raise ArgumentError, 'Block required for tool handler' unless handler
392
394
 
393
395
  SdkMcpTool.new(
394
396
  name: name,
395
397
  description: description,
396
398
  input_schema: input_schema,
397
- handler: handler
399
+ handler: handler,
400
+ annotations: annotations
398
401
  )
399
402
  end
400
403
 
@@ -13,15 +13,9 @@ module ClaudeAgentSDK
13
13
  DEFAULT_MAX_BUFFER_SIZE = 1024 * 1024 # 1MB buffer limit
14
14
  MINIMUM_CLAUDE_CODE_VERSION = '2.0.0'
15
15
 
16
- # Prompts larger than this are piped via stdin instead of CLI args
17
- # to avoid Errno::E2BIG (ARG_MAX is typically 1MB on macOS/Linux,
18
- # shared with environment variables).
19
- PROMPT_STDIN_THRESHOLD = 200 * 1024 # 200KB
20
-
21
- def initialize(prompt, options)
22
- @prompt = prompt
23
- @is_streaming = !prompt.is_a?(String)
24
- @options = options
16
+ def initialize(options_or_prompt = nil, options = nil)
17
+ # Support both new single-arg form and legacy two-arg form
18
+ @options = options.nil? ? options_or_prompt : options
25
19
  @cli_path = options.cli_path || find_cli
26
20
  @cwd = options.cwd
27
21
  @process = nil
@@ -34,7 +28,6 @@ module ClaudeAgentSDK
34
28
  @stderr_task = nil
35
29
  @recent_stderr = []
36
30
  @recent_stderr_mutex = Mutex.new
37
- @pipe_prompt_via_stdin = false
38
31
  end
39
32
 
40
33
  def find_cli
@@ -76,20 +69,21 @@ module ClaudeAgentSDK
76
69
  cmd = [@cli_path, '--output-format', 'stream-json', '--verbose']
77
70
 
78
71
  # System prompt handling
79
- if @options.system_prompt
80
- if @options.system_prompt.is_a?(String)
81
- cmd.concat(['--system-prompt', @options.system_prompt])
82
- elsif @options.system_prompt.is_a?(SystemPromptPreset)
83
- cmd.concat(['--system-prompt-preset', @options.system_prompt.preset]) if @options.system_prompt.preset
84
- cmd.concat(['--append-system-prompt', @options.system_prompt.append]) if @options.system_prompt.append
85
- elsif @options.system_prompt.is_a?(Hash)
86
- prompt_type = @options.system_prompt[:type] || @options.system_prompt['type']
87
- if prompt_type == 'preset'
88
- preset = @options.system_prompt[:preset] || @options.system_prompt['preset']
89
- append = @options.system_prompt[:append] || @options.system_prompt['append']
90
- cmd.concat(['--system-prompt-preset', preset]) if preset
91
- cmd.concat(['--append-system-prompt', append]) if append
92
- end
72
+ # When nil, pass empty string to ensure predictable behavior without default Claude Code system prompt
73
+ if @options.system_prompt.nil?
74
+ cmd.concat(['--system-prompt', ''])
75
+ elsif @options.system_prompt.is_a?(String)
76
+ cmd.concat(['--system-prompt', @options.system_prompt])
77
+ elsif @options.system_prompt.is_a?(SystemPromptPreset)
78
+ cmd.concat(['--system-prompt-preset', @options.system_prompt.preset]) if @options.system_prompt.preset
79
+ cmd.concat(['--append-system-prompt', @options.system_prompt.append]) if @options.system_prompt.append
80
+ elsif @options.system_prompt.is_a?(Hash)
81
+ prompt_type = @options.system_prompt[:type] || @options.system_prompt['type']
82
+ if prompt_type == 'preset'
83
+ preset = @options.system_prompt[:preset] || @options.system_prompt['preset']
84
+ append = @options.system_prompt[:append] || @options.system_prompt['append']
85
+ cmd.concat(['--system-prompt-preset', preset]) if preset
86
+ cmd.concat(['--append-system-prompt', append]) if append
93
87
  end
94
88
  end
95
89
 
@@ -147,7 +141,13 @@ module ClaudeAgentSDK
147
141
 
148
142
  # Budget limit option
149
143
  cmd.concat(['--max-budget-usd', @options.max_budget_usd.to_s]) if @options.max_budget_usd
150
- # Note: max_thinking_tokens is stored in options but not yet supported by Claude CLI
144
+
145
+ # Thinking configuration (takes precedence over deprecated max_thinking_tokens)
146
+ thinking_tokens = resolve_thinking_tokens
147
+ cmd.concat(['--max-thinking-tokens', thinking_tokens.to_s]) unless thinking_tokens.nil?
148
+
149
+ # Effort level
150
+ cmd.concat(['--effort', @options.effort.to_s]) if @options.effort
151
151
 
152
152
  # Betas option for enabling experimental features
153
153
  if @options.betas && !@options.betas.empty?
@@ -216,18 +216,8 @@ module ClaudeAgentSDK
216
216
  cmd << '--include-partial-messages' if @options.include_partial_messages
217
217
  cmd << '--fork-session' if @options.fork_session
218
218
 
219
- # Agents
220
- if @options.agents
221
- agents_dict = @options.agents.transform_values do |agent_def|
222
- {
223
- description: agent_def.description,
224
- prompt: agent_def.prompt,
225
- tools: agent_def.tools,
226
- model: agent_def.model
227
- }.compact
228
- end
229
- cmd.concat(['--agents', JSON.generate(agents_dict)])
230
- end
219
+ # Note: agents are now sent via the initialize control request (not CLI args)
220
+ # to avoid OS ARG_MAX limits with large agent configurations.
231
221
 
232
222
  # Plugins
233
223
  if @options.plugins && !@options.plugins.empty?
@@ -250,17 +240,10 @@ module ClaudeAgentSDK
250
240
  end
251
241
  end
252
242
 
253
- # Prompt handling
254
- if @is_streaming
255
- cmd.concat(['--input-format', 'stream-json'])
256
- elsif @prompt.to_s.bytesize > PROMPT_STDIN_THRESHOLD
257
- # Large prompts are piped via stdin to avoid OS argument size limits.
258
- # Claude CLI reads from stdin when --print is used without a trailing argument.
259
- cmd << '--print'
260
- @pipe_prompt_via_stdin = true
261
- else
262
- cmd.concat(['--print', '--', @prompt.to_s])
263
- end
243
+ # Always use streaming mode for bidirectional control protocol.
244
+ # Prompts and agents are sent via stdin (initialize + user messages),
245
+ # which avoids OS ARG_MAX limits for large prompts and agent configurations.
246
+ cmd.concat(['--input-format', 'stream-json'])
264
247
 
265
248
  cmd
266
249
  end
@@ -313,16 +296,7 @@ module ClaudeAgentSDK
313
296
  end
314
297
  end
315
298
 
316
- # For large prompts, pipe the prompt text to stdin before closing.
317
- # This avoids Errno::E2BIG when the prompt exceeds ARG_MAX.
318
- if @pipe_prompt_via_stdin && @stdin
319
- @stdin.write(@prompt.to_s)
320
- end
321
-
322
- # Close stdin for non-streaming mode
323
- @stdin.close unless @is_streaming
324
- @stdin = nil unless @is_streaming
325
-
299
+ # Always keep stdin open streaming mode uses it for the control protocol
326
300
  @ready = true
327
301
  rescue Errno::ENOENT => e
328
302
  # Check if error is from cwd or CLI
@@ -575,5 +549,24 @@ module ClaudeAgentSDK
575
549
  def ready?
576
550
  @ready
577
551
  end
552
+
553
+ DEFAULT_ADAPTIVE_THINKING_TOKENS = 32_000
554
+
555
+ private
556
+
557
+ def resolve_thinking_tokens
558
+ if @options.thinking
559
+ case @options.thinking
560
+ when ThinkingConfigAdaptive
561
+ DEFAULT_ADAPTIVE_THINKING_TOKENS
562
+ when ThinkingConfigEnabled
563
+ @options.thinking.budget_tokens
564
+ when ThinkingConfigDisabled
565
+ 0
566
+ end
567
+ elsif @options.max_thinking_tokens
568
+ @options.max_thinking_tokens
569
+ end
570
+ end
578
571
  end
579
572
  end
@@ -81,12 +81,13 @@ module ClaudeAgentSDK
81
81
 
82
82
  # User message
83
83
  class UserMessage
84
- attr_accessor :content, :uuid, :parent_tool_use_id
84
+ attr_accessor :content, :uuid, :parent_tool_use_id, :tool_use_result
85
85
 
86
- def initialize(content:, uuid: nil, parent_tool_use_id: nil)
86
+ def initialize(content:, uuid: nil, parent_tool_use_id: nil, tool_use_result: nil)
87
87
  @content = content
88
88
  @uuid = uuid # Unique identifier for rewind support
89
89
  @parent_tool_use_id = parent_tool_use_id
90
+ @tool_use_result = tool_use_result # Tool result data when message is a tool response
90
91
  end
91
92
  end
92
93
 
@@ -153,6 +154,36 @@ module ClaudeAgentSDK
153
154
  end
154
155
  end
155
156
 
157
+ # Thinking configuration types
158
+
159
+ # Adaptive thinking: uses a default budget of 32000 tokens
160
+ class ThinkingConfigAdaptive
161
+ attr_accessor :type
162
+
163
+ def initialize
164
+ @type = 'adaptive'
165
+ end
166
+ end
167
+
168
+ # Enabled thinking: uses a user-specified budget
169
+ class ThinkingConfigEnabled
170
+ attr_accessor :type, :budget_tokens
171
+
172
+ def initialize(budget_tokens:)
173
+ @type = 'enabled'
174
+ @budget_tokens = budget_tokens
175
+ end
176
+ end
177
+
178
+ # Disabled thinking: sets thinking tokens to 0
179
+ class ThinkingConfigDisabled
180
+ attr_accessor :type
181
+
182
+ def initialize
183
+ @type = 'disabled'
184
+ end
185
+ end
186
+
156
187
  # Agent definition configuration
157
188
  class AgentDefinition
158
189
  attr_accessor :description, :prompt, :tools, :model
@@ -278,26 +309,29 @@ module ClaudeAgentSDK
278
309
 
279
310
  # PreToolUse hook input
280
311
  class PreToolUseHookInput < BaseHookInput
281
- attr_accessor :hook_event_name, :tool_name, :tool_input
312
+ attr_accessor :hook_event_name, :tool_name, :tool_input, :tool_use_id
282
313
 
283
- def initialize(hook_event_name: 'PreToolUse', tool_name: nil, tool_input: nil, **base_args)
314
+ def initialize(hook_event_name: 'PreToolUse', tool_name: nil, tool_input: nil, tool_use_id: nil, **base_args)
284
315
  super(**base_args)
285
316
  @hook_event_name = hook_event_name
286
317
  @tool_name = tool_name
287
318
  @tool_input = tool_input
319
+ @tool_use_id = tool_use_id
288
320
  end
289
321
  end
290
322
 
291
323
  # PostToolUse hook input
292
324
  class PostToolUseHookInput < BaseHookInput
293
- attr_accessor :hook_event_name, :tool_name, :tool_input, :tool_response
325
+ attr_accessor :hook_event_name, :tool_name, :tool_input, :tool_response, :tool_use_id
294
326
 
295
- def initialize(hook_event_name: 'PostToolUse', tool_name: nil, tool_input: nil, tool_response: nil, **base_args)
327
+ def initialize(hook_event_name: 'PostToolUse', tool_name: nil, tool_input: nil, tool_response: nil,
328
+ tool_use_id: nil, **base_args)
296
329
  super(**base_args)
297
330
  @hook_event_name = hook_event_name
298
331
  @tool_name = tool_name
299
332
  @tool_input = tool_input
300
333
  @tool_response = tool_response
334
+ @tool_use_id = tool_use_id
301
335
  end
302
336
  end
303
337
 
@@ -407,13 +441,16 @@ module ClaudeAgentSDK
407
441
 
408
442
  # PreToolUse hook specific output
409
443
  class PreToolUseHookSpecificOutput
410
- attr_accessor :hook_event_name, :permission_decision, :permission_decision_reason, :updated_input
444
+ attr_accessor :hook_event_name, :permission_decision, :permission_decision_reason,
445
+ :updated_input, :additional_context
411
446
 
412
- def initialize(permission_decision: nil, permission_decision_reason: nil, updated_input: nil)
447
+ def initialize(permission_decision: nil, permission_decision_reason: nil, updated_input: nil,
448
+ additional_context: nil)
413
449
  @hook_event_name = 'PreToolUse'
414
450
  @permission_decision = permission_decision # 'allow', 'deny', or 'ask'
415
451
  @permission_decision_reason = permission_decision_reason
416
452
  @updated_input = updated_input
453
+ @additional_context = additional_context
417
454
  end
418
455
 
419
456
  def to_h
@@ -421,6 +458,7 @@ module ClaudeAgentSDK
421
458
  result[:permissionDecision] = @permission_decision if @permission_decision
422
459
  result[:permissionDecisionReason] = @permission_decision_reason if @permission_decision_reason
423
460
  result[:updatedInput] = @updated_input if @updated_input
461
+ result[:additionalContext] = @additional_context if @additional_context
424
462
  result
425
463
  end
426
464
  end
@@ -791,7 +829,8 @@ module ClaudeAgentSDK
791
829
  :fork_session, :agents, :setting_sources,
792
830
  :output_format, :max_budget_usd, :max_thinking_tokens,
793
831
  :fallback_model, :plugins, :debug_stderr,
794
- :betas, :tools, :sandbox, :enable_file_checkpointing, :append_allowed_tools
832
+ :betas, :tools, :sandbox, :enable_file_checkpointing, :append_allowed_tools,
833
+ :thinking, :effort
795
834
 
796
835
  # Non-nil defaults for options that need them.
797
836
  # Keys absent from here default to nil.
@@ -853,13 +892,14 @@ module ClaudeAgentSDK
853
892
 
854
893
  # SDK MCP Tool definition
855
894
  class SdkMcpTool
856
- attr_accessor :name, :description, :input_schema, :handler
895
+ attr_accessor :name, :description, :input_schema, :handler, :annotations
857
896
 
858
- def initialize(name:, description:, input_schema:, handler:)
897
+ def initialize(name:, description:, input_schema:, handler:, annotations: nil)
859
898
  @name = name
860
899
  @description = description
861
900
  @input_schema = input_schema
862
901
  @handler = handler
902
+ @annotations = annotations # MCP tool annotations (e.g., { title: '...', readOnlyHint: true })
863
903
  end
864
904
  end
865
905
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeAgentSDK
4
- VERSION = '0.6.3'
4
+ VERSION = '0.7.0'
5
5
  end
@@ -65,12 +65,46 @@ module ClaudeAgentSDK
65
65
  options = options.dup_with(env: (options.env || {}).merge('CLAUDE_CODE_ENTRYPOINT' => 'sdk-rb'))
66
66
 
67
67
  Async do
68
- transport = SubprocessCLITransport.new(prompt, options)
68
+ # Always use streaming mode with control protocol (matches Python SDK).
69
+ # This sends agents via initialize request instead of CLI args,
70
+ # avoiding OS ARG_MAX limits.
71
+ transport = SubprocessCLITransport.new(options)
69
72
  begin
70
73
  transport.connect
71
74
 
72
- # If prompt is an Enumerator, write each message to stdin
73
- if prompt.is_a?(Enumerator) || prompt.respond_to?(:each)
75
+ # Extract SDK MCP servers
76
+ sdk_mcp_servers = {}
77
+ if options.mcp_servers.is_a?(Hash)
78
+ options.mcp_servers.each do |name, config|
79
+ sdk_mcp_servers[name] = config[:instance] if config.is_a?(Hash) && config[:type] == 'sdk'
80
+ end
81
+ end
82
+
83
+ # Create Query handler for control protocol
84
+ query_handler = Query.new(
85
+ transport: transport,
86
+ is_streaming_mode: true,
87
+ agents: options.agents,
88
+ sdk_mcp_servers: sdk_mcp_servers
89
+ )
90
+
91
+ # Start reading messages in background
92
+ query_handler.start
93
+
94
+ # Initialize the control protocol (sends agents)
95
+ query_handler.initialize_protocol
96
+
97
+ # Send prompt(s) as user messages, then close stdin
98
+ if prompt.is_a?(String)
99
+ message = {
100
+ type: 'user',
101
+ message: { role: 'user', content: prompt },
102
+ parent_tool_use_id: nil,
103
+ session_id: 'default'
104
+ }
105
+ transport.write(JSON.generate(message) + "\n")
106
+ transport.end_input
107
+ elsif prompt.is_a?(Enumerator) || prompt.respond_to?(:each)
74
108
  Async do
75
109
  begin
76
110
  prompt.each do |message_json|
@@ -82,13 +116,18 @@ module ClaudeAgentSDK
82
116
  end
83
117
  end
84
118
 
85
- # Read and yield messages
86
- transport.read_messages do |data|
119
+ # Read and yield messages from the query handler (filters out control messages)
120
+ query_handler.receive_messages do |data|
87
121
  message = MessageParser.parse(data)
88
122
  block.call(message)
89
123
  end
90
124
  ensure
91
- transport.close
125
+ # query_handler.close stops the background read task and closes the transport
126
+ if query_handler
127
+ query_handler.close
128
+ else
129
+ transport.close
130
+ end
92
131
  end
93
132
  end.wait
94
133
  end
@@ -167,7 +206,7 @@ module ClaudeAgentSDK
167
206
  )
168
207
 
169
208
  # Client always uses streaming mode; keep stdin open for bidirectional communication.
170
- @transport = SubprocessCLITransport.new([].to_enum, configured_options)
209
+ @transport = SubprocessCLITransport.new(configured_options)
171
210
  @transport.connect
172
211
 
173
212
  # Extract SDK MCP servers
@@ -187,7 +226,8 @@ module ClaudeAgentSDK
187
226
  is_streaming_mode: true,
188
227
  can_use_tool: configured_options.can_use_tool,
189
228
  hooks: hooks,
190
- sdk_mcp_servers: sdk_mcp_servers
229
+ sdk_mcp_servers: sdk_mcp_servers,
230
+ agents: configured_options.agents
191
231
  )
192
232
 
193
233
  # Start query handler and initialize
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.6.3
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Community Contributors
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-02-18 00:00:00.000000000 Z
10
+ date: 2026-02-20 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: async