claude-agent-sdk 0.6.2 → 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 +4 -4
- data/CHANGELOG.md +43 -0
- data/README.md +46 -6
- data/lib/claude_agent_sdk/message_parser.rb +11 -2
- data/lib/claude_agent_sdk/query.rb +19 -2
- data/lib/claude_agent_sdk/sdk_mcp_server.rb +6 -3
- data/lib/claude_agent_sdk/subprocess_cli_transport.rb +89 -65
- data/lib/claude_agent_sdk/types.rb +60 -11
- data/lib/claude_agent_sdk/version.rb +1 -1
- data/lib/claude_agent_sdk.rb +48 -8
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: de5807b36fd822ee89e4a793a536b4ba8804c168259d17cc1a03974425f128ea
|
|
4
|
+
data.tar.gz: c40d5dad151728b8b2e131ad96da616bd03b4972f6d5807d5375ea24d499ceb6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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(
|
|
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 |
|
|
@@ -23,6 +23,8 @@ module ClaudeAgentSDK
|
|
|
23
23
|
parse_result_message(data)
|
|
24
24
|
when 'stream_event'
|
|
25
25
|
parse_stream_event(data)
|
|
26
|
+
when 'rate_limit_event'
|
|
27
|
+
parse_rate_limit_event(data)
|
|
26
28
|
else
|
|
27
29
|
raise MessageParseError.new("Unknown message type: #{message_type}", data: data)
|
|
28
30
|
end
|
|
@@ -33,6 +35,7 @@ module ClaudeAgentSDK
|
|
|
33
35
|
def self.parse_user_message(data)
|
|
34
36
|
parent_tool_use_id = data[:parent_tool_use_id]
|
|
35
37
|
uuid = data[:uuid] # UUID for rewind support
|
|
38
|
+
tool_use_result = data[:tool_use_result]
|
|
36
39
|
message_data = data[:message]
|
|
37
40
|
raise MessageParseError.new("Missing message field in user message", data: data) unless message_data
|
|
38
41
|
|
|
@@ -41,9 +44,11 @@ module ClaudeAgentSDK
|
|
|
41
44
|
|
|
42
45
|
if content.is_a?(Array)
|
|
43
46
|
content_blocks = content.map { |block| parse_content_block(block) }
|
|
44
|
-
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)
|
|
45
49
|
else
|
|
46
|
-
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)
|
|
47
52
|
end
|
|
48
53
|
end
|
|
49
54
|
|
|
@@ -91,6 +96,10 @@ module ClaudeAgentSDK
|
|
|
91
96
|
)
|
|
92
97
|
end
|
|
93
98
|
|
|
99
|
+
def self.parse_rate_limit_event(data)
|
|
100
|
+
RateLimitEvent.new(data: data)
|
|
101
|
+
end
|
|
102
|
+
|
|
94
103
|
def self.parse_content_block(block)
|
|
95
104
|
case block[:type]
|
|
96
105
|
when 'text'
|
|
@@ -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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
@@ -32,7 +26,8 @@ module ClaudeAgentSDK
|
|
|
32
26
|
@exit_error = nil
|
|
33
27
|
@max_buffer_size = options.max_buffer_size || DEFAULT_MAX_BUFFER_SIZE
|
|
34
28
|
@stderr_task = nil
|
|
35
|
-
@
|
|
29
|
+
@recent_stderr = []
|
|
30
|
+
@recent_stderr_mutex = Mutex.new
|
|
36
31
|
end
|
|
37
32
|
|
|
38
33
|
def find_cli
|
|
@@ -74,20 +69,21 @@ module ClaudeAgentSDK
|
|
|
74
69
|
cmd = [@cli_path, '--output-format', 'stream-json', '--verbose']
|
|
75
70
|
|
|
76
71
|
# System prompt handling
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
91
87
|
end
|
|
92
88
|
end
|
|
93
89
|
|
|
@@ -145,7 +141,13 @@ module ClaudeAgentSDK
|
|
|
145
141
|
|
|
146
142
|
# Budget limit option
|
|
147
143
|
cmd.concat(['--max-budget-usd', @options.max_budget_usd.to_s]) if @options.max_budget_usd
|
|
148
|
-
|
|
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
|
|
149
151
|
|
|
150
152
|
# Betas option for enabling experimental features
|
|
151
153
|
if @options.betas && !@options.betas.empty?
|
|
@@ -214,18 +216,8 @@ module ClaudeAgentSDK
|
|
|
214
216
|
cmd << '--include-partial-messages' if @options.include_partial_messages
|
|
215
217
|
cmd << '--fork-session' if @options.fork_session
|
|
216
218
|
|
|
217
|
-
#
|
|
218
|
-
|
|
219
|
-
agents_dict = @options.agents.transform_values do |agent_def|
|
|
220
|
-
{
|
|
221
|
-
description: agent_def.description,
|
|
222
|
-
prompt: agent_def.prompt,
|
|
223
|
-
tools: agent_def.tools,
|
|
224
|
-
model: agent_def.model
|
|
225
|
-
}.compact
|
|
226
|
-
end
|
|
227
|
-
cmd.concat(['--agents', JSON.generate(agents_dict)])
|
|
228
|
-
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.
|
|
229
221
|
|
|
230
222
|
# Plugins
|
|
231
223
|
if @options.plugins && !@options.plugins.empty?
|
|
@@ -248,17 +240,10 @@ module ClaudeAgentSDK
|
|
|
248
240
|
end
|
|
249
241
|
end
|
|
250
242
|
|
|
251
|
-
#
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
# Large prompts are piped via stdin to avoid OS argument size limits.
|
|
256
|
-
# Claude CLI reads from stdin when --print is used without a trailing argument.
|
|
257
|
-
cmd << '--print'
|
|
258
|
-
@pipe_prompt_via_stdin = true
|
|
259
|
-
else
|
|
260
|
-
cmd.concat(['--print', '--', @prompt.to_s])
|
|
261
|
-
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'])
|
|
262
247
|
|
|
263
248
|
cmd
|
|
264
249
|
end
|
|
@@ -273,9 +258,11 @@ module ClaudeAgentSDK
|
|
|
273
258
|
# Build environment
|
|
274
259
|
# Convert symbol keys to strings for spawn compatibility
|
|
275
260
|
custom_env = @options.env.transform_keys { |k| k.to_s }
|
|
276
|
-
#
|
|
277
|
-
# launches Claude Code from within an existing Claude Code terminal
|
|
278
|
-
|
|
261
|
+
# Explicitly unset CLAUDECODE to prevent "nested session" detection when the SDK
|
|
262
|
+
# launches Claude Code from within an existing Claude Code terminal.
|
|
263
|
+
# NOTE: Must set to nil (not just omit the key) — Ruby's spawn only overlays
|
|
264
|
+
# the env hash on top of the parent environment; a nil value actively unsets.
|
|
265
|
+
process_env = ENV.to_h.merge('CLAUDECODE' => nil, 'CLAUDE_AGENT_SDK_VERSION' => VERSION).merge(custom_env)
|
|
279
266
|
process_env['CLAUDE_CODE_ENTRYPOINT'] ||= 'sdk-rb'
|
|
280
267
|
process_env['PWD'] = @cwd.to_s if @cwd
|
|
281
268
|
|
|
@@ -292,32 +279,24 @@ module ClaudeAgentSDK
|
|
|
292
279
|
# Without this, --verbose output fills the OS pipe buffer (~64KB),
|
|
293
280
|
# the subprocess blocks on write, and all pipes stall → EPIPE.
|
|
294
281
|
if @stderr
|
|
295
|
-
if should_pipe_stderr
|
|
282
|
+
if should_pipe_stderr # rubocop:disable Style/ConditionalAssignment
|
|
296
283
|
@stderr_task = Thread.new do
|
|
297
284
|
handle_stderr
|
|
298
285
|
rescue StandardError
|
|
299
286
|
# Ignore errors during stderr reading
|
|
300
287
|
end
|
|
301
288
|
else
|
|
302
|
-
# Silently drain stderr so the subprocess never blocks
|
|
289
|
+
# Silently drain stderr so the subprocess never blocks,
|
|
290
|
+
# but still accumulate recent lines for error reporting.
|
|
303
291
|
@stderr_task = Thread.new do
|
|
304
|
-
|
|
292
|
+
drain_stderr_with_accumulation
|
|
305
293
|
rescue StandardError
|
|
306
294
|
# Ignore — process may have already exited
|
|
307
295
|
end
|
|
308
296
|
end
|
|
309
297
|
end
|
|
310
298
|
|
|
311
|
-
#
|
|
312
|
-
# This avoids Errno::E2BIG when the prompt exceeds ARG_MAX.
|
|
313
|
-
if @pipe_prompt_via_stdin && @stdin
|
|
314
|
-
@stdin.write(@prompt.to_s)
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
# Close stdin for non-streaming mode
|
|
318
|
-
@stdin.close unless @is_streaming
|
|
319
|
-
@stdin = nil unless @is_streaming
|
|
320
|
-
|
|
299
|
+
# Always keep stdin open — streaming mode uses it for the control protocol
|
|
321
300
|
@ready = true
|
|
322
301
|
rescue Errno::ENOENT => e
|
|
323
302
|
# Check if error is from cwd or CLI
|
|
@@ -343,6 +322,12 @@ module ClaudeAgentSDK
|
|
|
343
322
|
line_str = line.chomp
|
|
344
323
|
next if line_str.empty?
|
|
345
324
|
|
|
325
|
+
# Accumulate recent lines for inclusion in ProcessError
|
|
326
|
+
@recent_stderr_mutex.synchronize do
|
|
327
|
+
@recent_stderr << line_str
|
|
328
|
+
@recent_stderr.shift if @recent_stderr.size > 20
|
|
329
|
+
end
|
|
330
|
+
|
|
346
331
|
# Call stderr callback if provided
|
|
347
332
|
@options.stderr&.call(line_str)
|
|
348
333
|
|
|
@@ -359,6 +344,20 @@ module ClaudeAgentSDK
|
|
|
359
344
|
# Ignore errors during stderr reading
|
|
360
345
|
end
|
|
361
346
|
|
|
347
|
+
def drain_stderr_with_accumulation
|
|
348
|
+
return unless @stderr
|
|
349
|
+
|
|
350
|
+
@stderr.each_line do |line|
|
|
351
|
+
line_str = line.chomp
|
|
352
|
+
next if line_str.empty?
|
|
353
|
+
|
|
354
|
+
@recent_stderr_mutex.synchronize do
|
|
355
|
+
@recent_stderr << line_str
|
|
356
|
+
@recent_stderr.shift if @recent_stderr.size > 20
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
362
361
|
def close
|
|
363
362
|
@ready = false
|
|
364
363
|
return unless @process
|
|
@@ -511,10 +510,16 @@ module ClaudeAgentSDK
|
|
|
511
510
|
returncode = status.exitstatus
|
|
512
511
|
|
|
513
512
|
if returncode && returncode != 0
|
|
513
|
+
# Wait briefly for stderr thread to finish draining
|
|
514
|
+
@stderr_task&.join(1)
|
|
515
|
+
|
|
516
|
+
stderr_text = @recent_stderr_mutex.synchronize { @recent_stderr.last(10).join("\n") }
|
|
517
|
+
stderr_text = 'No stderr output captured' if stderr_text.empty?
|
|
518
|
+
|
|
514
519
|
@exit_error = ProcessError.new(
|
|
515
520
|
"Command failed with exit code #{returncode}",
|
|
516
521
|
exit_code: returncode,
|
|
517
|
-
stderr:
|
|
522
|
+
stderr: stderr_text
|
|
518
523
|
)
|
|
519
524
|
raise @exit_error
|
|
520
525
|
end
|
|
@@ -544,5 +549,24 @@ module ClaudeAgentSDK
|
|
|
544
549
|
def ready?
|
|
545
550
|
@ready
|
|
546
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
|
|
547
571
|
end
|
|
548
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
|
|
|
@@ -144,6 +145,45 @@ module ClaudeAgentSDK
|
|
|
144
145
|
end
|
|
145
146
|
end
|
|
146
147
|
|
|
148
|
+
# Rate limit event emitted by Claude Code CLI when API rate limits are hit
|
|
149
|
+
class RateLimitEvent
|
|
150
|
+
attr_accessor :data
|
|
151
|
+
|
|
152
|
+
def initialize(data:)
|
|
153
|
+
@data = data
|
|
154
|
+
end
|
|
155
|
+
end
|
|
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
|
+
|
|
147
187
|
# Agent definition configuration
|
|
148
188
|
class AgentDefinition
|
|
149
189
|
attr_accessor :description, :prompt, :tools, :model
|
|
@@ -269,26 +309,29 @@ module ClaudeAgentSDK
|
|
|
269
309
|
|
|
270
310
|
# PreToolUse hook input
|
|
271
311
|
class PreToolUseHookInput < BaseHookInput
|
|
272
|
-
attr_accessor :hook_event_name, :tool_name, :tool_input
|
|
312
|
+
attr_accessor :hook_event_name, :tool_name, :tool_input, :tool_use_id
|
|
273
313
|
|
|
274
|
-
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)
|
|
275
315
|
super(**base_args)
|
|
276
316
|
@hook_event_name = hook_event_name
|
|
277
317
|
@tool_name = tool_name
|
|
278
318
|
@tool_input = tool_input
|
|
319
|
+
@tool_use_id = tool_use_id
|
|
279
320
|
end
|
|
280
321
|
end
|
|
281
322
|
|
|
282
323
|
# PostToolUse hook input
|
|
283
324
|
class PostToolUseHookInput < BaseHookInput
|
|
284
|
-
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
|
|
285
326
|
|
|
286
|
-
def initialize(hook_event_name: 'PostToolUse', tool_name: nil, tool_input: nil, tool_response: nil,
|
|
327
|
+
def initialize(hook_event_name: 'PostToolUse', tool_name: nil, tool_input: nil, tool_response: nil,
|
|
328
|
+
tool_use_id: nil, **base_args)
|
|
287
329
|
super(**base_args)
|
|
288
330
|
@hook_event_name = hook_event_name
|
|
289
331
|
@tool_name = tool_name
|
|
290
332
|
@tool_input = tool_input
|
|
291
333
|
@tool_response = tool_response
|
|
334
|
+
@tool_use_id = tool_use_id
|
|
292
335
|
end
|
|
293
336
|
end
|
|
294
337
|
|
|
@@ -398,13 +441,16 @@ module ClaudeAgentSDK
|
|
|
398
441
|
|
|
399
442
|
# PreToolUse hook specific output
|
|
400
443
|
class PreToolUseHookSpecificOutput
|
|
401
|
-
attr_accessor :hook_event_name, :permission_decision, :permission_decision_reason,
|
|
444
|
+
attr_accessor :hook_event_name, :permission_decision, :permission_decision_reason,
|
|
445
|
+
:updated_input, :additional_context
|
|
402
446
|
|
|
403
|
-
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)
|
|
404
449
|
@hook_event_name = 'PreToolUse'
|
|
405
450
|
@permission_decision = permission_decision # 'allow', 'deny', or 'ask'
|
|
406
451
|
@permission_decision_reason = permission_decision_reason
|
|
407
452
|
@updated_input = updated_input
|
|
453
|
+
@additional_context = additional_context
|
|
408
454
|
end
|
|
409
455
|
|
|
410
456
|
def to_h
|
|
@@ -412,6 +458,7 @@ module ClaudeAgentSDK
|
|
|
412
458
|
result[:permissionDecision] = @permission_decision if @permission_decision
|
|
413
459
|
result[:permissionDecisionReason] = @permission_decision_reason if @permission_decision_reason
|
|
414
460
|
result[:updatedInput] = @updated_input if @updated_input
|
|
461
|
+
result[:additionalContext] = @additional_context if @additional_context
|
|
415
462
|
result
|
|
416
463
|
end
|
|
417
464
|
end
|
|
@@ -782,7 +829,8 @@ module ClaudeAgentSDK
|
|
|
782
829
|
:fork_session, :agents, :setting_sources,
|
|
783
830
|
:output_format, :max_budget_usd, :max_thinking_tokens,
|
|
784
831
|
:fallback_model, :plugins, :debug_stderr,
|
|
785
|
-
:betas, :tools, :sandbox, :enable_file_checkpointing, :append_allowed_tools
|
|
832
|
+
:betas, :tools, :sandbox, :enable_file_checkpointing, :append_allowed_tools,
|
|
833
|
+
:thinking, :effort
|
|
786
834
|
|
|
787
835
|
# Non-nil defaults for options that need them.
|
|
788
836
|
# Keys absent from here default to nil.
|
|
@@ -844,13 +892,14 @@ module ClaudeAgentSDK
|
|
|
844
892
|
|
|
845
893
|
# SDK MCP Tool definition
|
|
846
894
|
class SdkMcpTool
|
|
847
|
-
attr_accessor :name, :description, :input_schema, :handler
|
|
895
|
+
attr_accessor :name, :description, :input_schema, :handler, :annotations
|
|
848
896
|
|
|
849
|
-
def initialize(name:, description:, input_schema:, handler:)
|
|
897
|
+
def initialize(name:, description:, input_schema:, handler:, annotations: nil)
|
|
850
898
|
@name = name
|
|
851
899
|
@description = description
|
|
852
900
|
@input_schema = input_schema
|
|
853
901
|
@handler = handler
|
|
902
|
+
@annotations = annotations # MCP tool annotations (e.g., { title: '...', readOnlyHint: true })
|
|
854
903
|
end
|
|
855
904
|
end
|
|
856
905
|
|
data/lib/claude_agent_sdk.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
#
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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-
|
|
10
|
+
date: 2026-02-20 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: async
|