claude-agent-sdk 0.8.0 → 0.8.1
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 +30 -0
- data/lib/claude_agent_sdk/query.rb +54 -4
- data/lib/claude_agent_sdk/subprocess_cli_transport.rb +110 -89
- data/lib/claude_agent_sdk/types.rb +4 -2
- data/lib/claude_agent_sdk/version.rb +1 -1
- data/lib/claude_agent_sdk.rb +45 -14
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b625dde6e1777c93be6e4bcfb6c9565087d798d414a03078f8de4184c39dfa7f
|
|
4
|
+
data.tar.gz: 30117f8eac89f94aabae5c37fdc12c012ed779a0f1c58062d70e9b727ee5ce3c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2a36e3ff75415bb4c2f42b27a8280b4828199af2972d20aa5fa4366a24daefd325e5a11c3e39ee9396144561c98e2ea1ddcc94157b80fc7aeef72f58a526e36b
|
|
7
|
+
data.tar.gz: f0fde6a0779679869bd4d6607f7220ea016c021896a97a421be650e169ef53719d5810cf614613c7cb52b24c19ebbf2f3908f3cc49f07bc5b13aa58f377e1a8b
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,36 @@ 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.8.1] - 2026-03-08
|
|
9
|
+
|
|
10
|
+
Python SDK parity fixes for one-shot `query()` control protocol and CLI transport.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
#### One-Shot Query Control Protocol
|
|
15
|
+
- **Hooks and `can_use_tool` in `query()`:** One-shot `query()` now passes `hooks`, `can_use_tool`, and SDK MCP servers through to the `Query` handler, matching the Python SDK (previously these were Client-only)
|
|
16
|
+
- **`can_use_tool` validation:** String prompts with `can_use_tool` now raise `ArgumentError` (streaming mode required); conflicting `permission_prompt_tool_name` also raises early
|
|
17
|
+
- **`session_id` parity:** One-shot queries now send `session_id: ''` (was `'default'`), matching Python SDK behavior
|
|
18
|
+
- **Premature stdin close:** Added `wait_for_result_and_end_input` that holds stdin open until the first result when hooks or SDK MCP servers need control message exchange
|
|
19
|
+
- **`stream_input` stdin leak:** Moved `end_input` to `ensure` block so stdin is always closed even when the stream enumerator raises
|
|
20
|
+
- **`Async::Condition` race:** Added `@first_result_received` flag guard to prevent lost signals when result arrives before `wait` is called
|
|
21
|
+
|
|
22
|
+
#### CLI Transport Parity
|
|
23
|
+
- **File checkpointing:** Moved from deprecated `--enable-file-checkpointing` CLI flag to `CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING` environment variable
|
|
24
|
+
- **Partial messages:** Now also sets `CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING=1` environment variable when `include_partial_messages` is enabled
|
|
25
|
+
- **Tools preset:** `ToolsPreset` objects and preset hashes now map to `--tools default` (was `--tools <json>`)
|
|
26
|
+
- **Plugins:** Changed from `--plugins <json>` to `--plugin-dir <path>` per-plugin, matching current CLI interface
|
|
27
|
+
- **Plugin type:** `SdkPluginConfig` now defaults to `type: 'local'` (was `'plugin'`), normalizes legacy `'plugin'` type
|
|
28
|
+
- **Rewind control request:** Changed key from `userMessageUuid` to `user_message_id` for Python SDK parity
|
|
29
|
+
- **Settings file with sandbox:** When sandbox is enabled and settings is a file path, now reads and parses the file to merge sandbox settings (raises on missing/invalid files instead of silently dropping settings)
|
|
30
|
+
|
|
31
|
+
#### Hook Input Parsing
|
|
32
|
+
- **Falsy value preservation:** `parse_hook_input` now uses `key?`-based lookup instead of `||`, correctly preserving `false` and `nil` values (e.g., `stop_hook_active: false`)
|
|
33
|
+
- **Empty hooks normalization:** `query()` now skips empty matcher lists and normalizes hooks to `nil` when no matchers survive, preventing unnecessary 60s close-wait timeout
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
- **`build_command` refactored:** Extracted `build_settings_args`, `build_tools_args`, `build_output_format_args`, `build_mcp_servers_args`, `build_plugins_args` private helpers to reduce method complexity
|
|
37
|
+
|
|
8
38
|
## [0.8.0] - 2026-03-05
|
|
9
39
|
|
|
10
40
|
Port of Python SDK v0.1.46 features.
|
|
@@ -22,6 +22,8 @@ module ClaudeAgentSDK
|
|
|
22
22
|
|
|
23
23
|
CONTROL_REQUEST_TIMEOUT_ENV_VAR = 'CLAUDE_AGENT_SDK_CONTROL_REQUEST_TIMEOUT_SECONDS'
|
|
24
24
|
DEFAULT_CONTROL_REQUEST_TIMEOUT_SECONDS = 1200.0
|
|
25
|
+
STREAM_CLOSE_TIMEOUT_ENV_VAR = 'CLAUDE_CODE_STREAM_CLOSE_TIMEOUT'
|
|
26
|
+
DEFAULT_STREAM_CLOSE_TIMEOUT_SECONDS = 60.0
|
|
25
27
|
|
|
26
28
|
def initialize(transport:, is_streaming_mode:, can_use_tool: nil, hooks: nil, sdk_mcp_servers: nil, agents: nil)
|
|
27
29
|
@transport = transport
|
|
@@ -42,6 +44,8 @@ module ClaudeAgentSDK
|
|
|
42
44
|
|
|
43
45
|
# Message stream
|
|
44
46
|
@message_queue = Async::Queue.new
|
|
47
|
+
@first_result_received = false
|
|
48
|
+
@first_result_condition = Async::Condition.new
|
|
45
49
|
@task = nil
|
|
46
50
|
@initialized = false
|
|
47
51
|
@closed = false
|
|
@@ -124,6 +128,16 @@ module ClaudeAgentSDK
|
|
|
124
128
|
DEFAULT_CONTROL_REQUEST_TIMEOUT_SECONDS
|
|
125
129
|
end
|
|
126
130
|
|
|
131
|
+
def stream_close_timeout_seconds
|
|
132
|
+
raw_value = ENV.fetch(STREAM_CLOSE_TIMEOUT_ENV_VAR, nil)
|
|
133
|
+
return DEFAULT_STREAM_CLOSE_TIMEOUT_SECONDS if raw_value.nil? || raw_value.strip.empty?
|
|
134
|
+
|
|
135
|
+
value = Float(raw_value) / 1000.0
|
|
136
|
+
value.positive? ? value : DEFAULT_STREAM_CLOSE_TIMEOUT_SECONDS
|
|
137
|
+
rescue ArgumentError
|
|
138
|
+
DEFAULT_STREAM_CLOSE_TIMEOUT_SECONDS
|
|
139
|
+
end
|
|
140
|
+
|
|
127
141
|
def read_messages
|
|
128
142
|
@transport.read_messages do |message|
|
|
129
143
|
break if @closed
|
|
@@ -150,6 +164,10 @@ module ClaudeAgentSDK
|
|
|
150
164
|
task&.stop
|
|
151
165
|
next
|
|
152
166
|
else
|
|
167
|
+
if message[:type] == 'result' && !@first_result_received
|
|
168
|
+
@first_result_received = true
|
|
169
|
+
@first_result_condition.signal
|
|
170
|
+
end
|
|
153
171
|
# Regular SDK messages go to the queue
|
|
154
172
|
@message_queue.enqueue(message)
|
|
155
173
|
end
|
|
@@ -164,6 +182,10 @@ module ClaudeAgentSDK
|
|
|
164
182
|
# Put error in queue so iterators can handle it
|
|
165
183
|
@message_queue.enqueue({ type: 'error', error: e })
|
|
166
184
|
ensure
|
|
185
|
+
unless @first_result_received
|
|
186
|
+
@first_result_received = true
|
|
187
|
+
@first_result_condition.signal
|
|
188
|
+
end
|
|
167
189
|
# Always signal end of stream
|
|
168
190
|
@message_queue.enqueue({ type: 'end' })
|
|
169
191
|
end
|
|
@@ -308,7 +330,13 @@ module ClaudeAgentSDK
|
|
|
308
330
|
|
|
309
331
|
def parse_hook_input(input_data)
|
|
310
332
|
event_name = input_data[:hook_event_name] || input_data['hook_event_name']
|
|
311
|
-
fetch =
|
|
333
|
+
fetch = lambda do |key|
|
|
334
|
+
if input_data.key?(key)
|
|
335
|
+
input_data[key]
|
|
336
|
+
elsif input_data.key?(key.to_s)
|
|
337
|
+
input_data[key.to_s]
|
|
338
|
+
end
|
|
339
|
+
end
|
|
312
340
|
base_args = {
|
|
313
341
|
session_id: fetch.call(:session_id),
|
|
314
342
|
transcript_path: fetch.call(:transcript_path),
|
|
@@ -704,20 +732,42 @@ module ClaudeAgentSDK
|
|
|
704
732
|
def rewind_files(user_message_uuid)
|
|
705
733
|
send_control_request({
|
|
706
734
|
subtype: 'rewind_files',
|
|
707
|
-
|
|
735
|
+
user_message_id: user_message_uuid
|
|
708
736
|
})
|
|
709
737
|
end
|
|
710
738
|
|
|
739
|
+
# Wait for the first result before closing stdin when hooks or SDK MCP
|
|
740
|
+
# servers may still need to exchange control messages with the CLI.
|
|
741
|
+
def wait_for_result_and_end_input
|
|
742
|
+
if !@first_result_received &&
|
|
743
|
+
((@sdk_mcp_servers && !@sdk_mcp_servers.empty?) || (@hooks && !@hooks.empty?))
|
|
744
|
+
Async::Task.current.with_timeout(stream_close_timeout_seconds) do
|
|
745
|
+
@first_result_condition.wait unless @first_result_received
|
|
746
|
+
end
|
|
747
|
+
end
|
|
748
|
+
rescue Async::TimeoutError
|
|
749
|
+
nil
|
|
750
|
+
ensure
|
|
751
|
+
@transport.end_input
|
|
752
|
+
end
|
|
753
|
+
|
|
711
754
|
# Stream input messages to transport
|
|
712
755
|
def stream_input(stream)
|
|
713
756
|
stream.each do |message|
|
|
714
757
|
break if @closed
|
|
715
|
-
|
|
758
|
+
serialized = if message.is_a?(Hash)
|
|
759
|
+
JSON.generate(message) + "\n"
|
|
760
|
+
else
|
|
761
|
+
message.to_s
|
|
762
|
+
end
|
|
763
|
+
serialized += "\n" unless serialized.end_with?("\n")
|
|
764
|
+
@transport.write(serialized)
|
|
716
765
|
end
|
|
717
|
-
@transport.end_input
|
|
718
766
|
rescue StandardError => e
|
|
719
767
|
# Log error but don't raise
|
|
720
768
|
warn "Error streaming input: #{e.message}"
|
|
769
|
+
ensure
|
|
770
|
+
wait_for_result_and_end_input
|
|
721
771
|
end
|
|
722
772
|
|
|
723
773
|
# Receive SDK messages (not control messages)
|
|
@@ -98,46 +98,7 @@ module ClaudeAgentSDK
|
|
|
98
98
|
cmd.concat(['--resume', @options.resume]) if @options.resume
|
|
99
99
|
|
|
100
100
|
# Settings handling with sandbox merge
|
|
101
|
-
|
|
102
|
-
if @options.settings || @options.sandbox
|
|
103
|
-
settings_hash = {}
|
|
104
|
-
settings_is_path = false
|
|
105
|
-
|
|
106
|
-
# Parse existing settings if provided
|
|
107
|
-
if @options.settings
|
|
108
|
-
if @options.settings.is_a?(String)
|
|
109
|
-
begin
|
|
110
|
-
settings_hash = JSON.parse(@options.settings)
|
|
111
|
-
rescue JSON::ParserError
|
|
112
|
-
# If not valid JSON, treat as file path and pass as-is
|
|
113
|
-
settings_is_path = true
|
|
114
|
-
cmd.concat(['--settings', @options.settings])
|
|
115
|
-
if @options.sandbox
|
|
116
|
-
warn "Warning: Cannot merge sandbox settings when settings is a file path. " \
|
|
117
|
-
"Sandbox settings will be ignored. Use a Hash or JSON string for settings " \
|
|
118
|
-
"to enable sandbox merging."
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
elsif @options.settings.is_a?(Hash)
|
|
122
|
-
settings_hash = @options.settings.dup
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
# Merge sandbox settings if provided (only when settings is not a file path)
|
|
127
|
-
if !settings_is_path && @options.sandbox
|
|
128
|
-
sandbox_hash = if @options.sandbox.is_a?(SandboxSettings)
|
|
129
|
-
@options.sandbox.to_h
|
|
130
|
-
else
|
|
131
|
-
@options.sandbox
|
|
132
|
-
end
|
|
133
|
-
settings_hash[:sandbox] = sandbox_hash unless sandbox_hash.empty?
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Output merged settings (only when settings is not a file path)
|
|
137
|
-
if !settings_is_path && !settings_hash.empty?
|
|
138
|
-
cmd.concat(['--settings', JSON.generate(settings_hash)])
|
|
139
|
-
end
|
|
140
|
-
end
|
|
101
|
+
build_settings_args(cmd)
|
|
141
102
|
|
|
142
103
|
# Budget limit option
|
|
143
104
|
cmd.concat(['--max-budget-usd', @options.max_budget_usd.to_s]) if @options.max_budget_usd
|
|
@@ -155,39 +116,15 @@ module ClaudeAgentSDK
|
|
|
155
116
|
end
|
|
156
117
|
|
|
157
118
|
# Tools option for base tools selection
|
|
158
|
-
|
|
159
|
-
if @options.tools.is_a?(Array)
|
|
160
|
-
cmd.concat(['--tools', @options.tools.join(',')])
|
|
161
|
-
elsif @options.tools.is_a?(ToolsPreset)
|
|
162
|
-
cmd.concat(['--tools', JSON.generate(@options.tools.to_h)])
|
|
163
|
-
elsif @options.tools.is_a?(Hash)
|
|
164
|
-
cmd.concat(['--tools', JSON.generate(@options.tools)])
|
|
165
|
-
end
|
|
166
|
-
end
|
|
119
|
+
build_tools_args(cmd)
|
|
167
120
|
|
|
168
121
|
# Append allowed tools option
|
|
169
122
|
if @options.append_allowed_tools && !@options.append_allowed_tools.empty?
|
|
170
123
|
cmd.concat(['--append-allowed-tools', @options.append_allowed_tools.join(',')])
|
|
171
124
|
end
|
|
172
125
|
|
|
173
|
-
# File checkpointing for rewind support
|
|
174
|
-
cmd << '--enable-file-checkpointing' if @options.enable_file_checkpointing
|
|
175
|
-
|
|
176
126
|
# JSON schema for structured output
|
|
177
|
-
|
|
178
|
-
# 1. Direct schema: { type: 'object', properties: {...} }
|
|
179
|
-
# 2. Wrapped format: { type: 'json_schema', schema: {...} }
|
|
180
|
-
if @options.output_format
|
|
181
|
-
schema = if @options.output_format.is_a?(Hash) && @options.output_format[:type] == 'json_schema'
|
|
182
|
-
@options.output_format[:schema]
|
|
183
|
-
elsif @options.output_format.is_a?(Hash) && @options.output_format['type'] == 'json_schema'
|
|
184
|
-
@options.output_format['schema']
|
|
185
|
-
else
|
|
186
|
-
@options.output_format
|
|
187
|
-
end
|
|
188
|
-
schema_json = schema.is_a?(String) ? schema : JSON.generate(schema)
|
|
189
|
-
cmd.concat(['--json-schema', schema_json])
|
|
190
|
-
end
|
|
127
|
+
build_output_format_args(cmd)
|
|
191
128
|
|
|
192
129
|
# Add directories
|
|
193
130
|
@options.add_dirs.each do |dir|
|
|
@@ -195,23 +132,7 @@ module ClaudeAgentSDK
|
|
|
195
132
|
end
|
|
196
133
|
|
|
197
134
|
# MCP servers
|
|
198
|
-
|
|
199
|
-
if @options.mcp_servers.is_a?(Hash)
|
|
200
|
-
servers_for_cli = {}
|
|
201
|
-
@options.mcp_servers.each do |name, config|
|
|
202
|
-
if config.is_a?(Hash) && config[:type] == 'sdk'
|
|
203
|
-
# For SDK servers, exclude instance field
|
|
204
|
-
sdk_config = config.reject { |k, _| k == :instance }
|
|
205
|
-
servers_for_cli[name] = sdk_config
|
|
206
|
-
else
|
|
207
|
-
servers_for_cli[name] = config
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
|
-
cmd.concat(['--mcp-config', JSON.generate({ mcpServers: servers_for_cli })]) unless servers_for_cli.empty?
|
|
211
|
-
else
|
|
212
|
-
cmd.concat(['--mcp-config', @options.mcp_servers.to_s])
|
|
213
|
-
end
|
|
214
|
-
end
|
|
135
|
+
build_mcp_servers_args(cmd)
|
|
215
136
|
|
|
216
137
|
cmd << '--include-partial-messages' if @options.include_partial_messages
|
|
217
138
|
cmd << '--fork-session' if @options.fork_session
|
|
@@ -220,12 +141,7 @@ module ClaudeAgentSDK
|
|
|
220
141
|
# to avoid OS ARG_MAX limits with large agent configurations.
|
|
221
142
|
|
|
222
143
|
# Plugins
|
|
223
|
-
|
|
224
|
-
plugins_config = @options.plugins.map do |plugin|
|
|
225
|
-
plugin.is_a?(SdkPluginConfig) ? plugin.to_h : plugin
|
|
226
|
-
end
|
|
227
|
-
cmd.concat(['--plugins', JSON.generate(plugins_config)])
|
|
228
|
-
end
|
|
144
|
+
build_plugins_args(cmd)
|
|
229
145
|
|
|
230
146
|
# Setting sources
|
|
231
147
|
sources_value = @options.setting_sources ? @options.setting_sources.join(',') : ''
|
|
@@ -264,6 +180,10 @@ module ClaudeAgentSDK
|
|
|
264
180
|
# the env hash on top of the parent environment; a nil value actively unsets.
|
|
265
181
|
process_env = ENV.to_h.merge('CLAUDECODE' => nil, 'CLAUDE_AGENT_SDK_VERSION' => VERSION).merge(custom_env)
|
|
266
182
|
process_env['CLAUDE_CODE_ENTRYPOINT'] ||= 'sdk-rb'
|
|
183
|
+
process_env['CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING'] = 'true' if @options.enable_file_checkpointing
|
|
184
|
+
if @options.include_partial_messages
|
|
185
|
+
process_env['CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING'] ||= '1'
|
|
186
|
+
end
|
|
267
187
|
process_env['PWD'] = @cwd.to_s if @cwd
|
|
268
188
|
|
|
269
189
|
# Determine stderr handling
|
|
@@ -554,6 +474,107 @@ module ClaudeAgentSDK
|
|
|
554
474
|
|
|
555
475
|
private
|
|
556
476
|
|
|
477
|
+
def build_settings_args(cmd)
|
|
478
|
+
return unless @options.settings || @options.sandbox
|
|
479
|
+
|
|
480
|
+
settings_hash = {}
|
|
481
|
+
settings_is_path = false
|
|
482
|
+
|
|
483
|
+
if @options.settings
|
|
484
|
+
if @options.settings.is_a?(String)
|
|
485
|
+
begin
|
|
486
|
+
settings_hash = JSON.parse(@options.settings)
|
|
487
|
+
rescue JSON::ParserError
|
|
488
|
+
if @options.sandbox
|
|
489
|
+
settings_hash = load_settings_file(@options.settings)
|
|
490
|
+
else
|
|
491
|
+
settings_is_path = true
|
|
492
|
+
cmd.concat(['--settings', @options.settings])
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
elsif @options.settings.is_a?(Hash)
|
|
496
|
+
settings_hash = @options.settings.dup
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
if !settings_is_path && @options.sandbox
|
|
501
|
+
sandbox_hash = @options.sandbox.is_a?(SandboxSettings) ? @options.sandbox.to_h : @options.sandbox
|
|
502
|
+
settings_hash[:sandbox] = sandbox_hash unless sandbox_hash.empty?
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
cmd.concat(['--settings', JSON.generate(settings_hash)]) if !settings_is_path && !settings_hash.empty?
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
def build_tools_args(cmd)
|
|
509
|
+
return if @options.tools.nil?
|
|
510
|
+
|
|
511
|
+
if @options.tools.is_a?(Array)
|
|
512
|
+
tools_value = @options.tools.empty? ? '' : @options.tools.join(',')
|
|
513
|
+
cmd.concat(['--tools', tools_value])
|
|
514
|
+
elsif @options.tools.is_a?(ToolsPreset)
|
|
515
|
+
cmd.concat(['--tools', 'default'])
|
|
516
|
+
elsif @options.tools.is_a?(Hash)
|
|
517
|
+
if (@options.tools[:type] || @options.tools['type']) == 'preset'
|
|
518
|
+
cmd.concat(['--tools', 'default'])
|
|
519
|
+
else
|
|
520
|
+
cmd.concat(['--tools', JSON.generate(@options.tools)])
|
|
521
|
+
end
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
def build_output_format_args(cmd)
|
|
526
|
+
return unless @options.output_format
|
|
527
|
+
|
|
528
|
+
schema = if @options.output_format.is_a?(Hash) && @options.output_format[:type] == 'json_schema'
|
|
529
|
+
@options.output_format[:schema]
|
|
530
|
+
elsif @options.output_format.is_a?(Hash) && @options.output_format['type'] == 'json_schema'
|
|
531
|
+
@options.output_format['schema']
|
|
532
|
+
else
|
|
533
|
+
@options.output_format
|
|
534
|
+
end
|
|
535
|
+
schema_json = schema.is_a?(String) ? schema : JSON.generate(schema)
|
|
536
|
+
cmd.concat(['--json-schema', schema_json])
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
def build_mcp_servers_args(cmd)
|
|
540
|
+
return unless @options.mcp_servers && !@options.mcp_servers.empty?
|
|
541
|
+
|
|
542
|
+
if @options.mcp_servers.is_a?(Hash)
|
|
543
|
+
servers_for_cli = {}
|
|
544
|
+
@options.mcp_servers.each do |name, config|
|
|
545
|
+
servers_for_cli[name] = if config.is_a?(Hash) && config[:type] == 'sdk'
|
|
546
|
+
config.reject { |k, _| k == :instance }
|
|
547
|
+
else
|
|
548
|
+
config
|
|
549
|
+
end
|
|
550
|
+
end
|
|
551
|
+
cmd.concat(['--mcp-config', JSON.generate({ mcpServers: servers_for_cli })]) unless servers_for_cli.empty?
|
|
552
|
+
else
|
|
553
|
+
cmd.concat(['--mcp-config', @options.mcp_servers.to_s])
|
|
554
|
+
end
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
def build_plugins_args(cmd)
|
|
558
|
+
return unless @options.plugins && !@options.plugins.empty?
|
|
559
|
+
|
|
560
|
+
@options.plugins.each do |plugin|
|
|
561
|
+
plugin_config = plugin.is_a?(SdkPluginConfig) ? plugin.to_h : plugin
|
|
562
|
+
plugin_type = plugin_config[:type] || plugin_config['type']
|
|
563
|
+
plugin_path = plugin_config[:path] || plugin_config['path']
|
|
564
|
+
|
|
565
|
+
raise ArgumentError, "Unsupported plugin type: #{plugin_type.inspect}" unless %w[local plugin].include?(plugin_type)
|
|
566
|
+
next unless plugin_path
|
|
567
|
+
|
|
568
|
+
cmd.concat(['--plugin-dir', plugin_path])
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
def load_settings_file(path)
|
|
573
|
+
raise CLIConnectionError, "Settings file not found: #{path}" unless File.file?(path)
|
|
574
|
+
|
|
575
|
+
JSON.parse(File.read(path))
|
|
576
|
+
end
|
|
577
|
+
|
|
557
578
|
def resolve_thinking_tokens
|
|
558
579
|
if @options.thinking
|
|
559
580
|
case @options.thinking
|
|
@@ -866,8 +866,10 @@ module ClaudeAgentSDK
|
|
|
866
866
|
class SdkPluginConfig
|
|
867
867
|
attr_accessor :type, :path
|
|
868
868
|
|
|
869
|
-
def initialize(path:)
|
|
870
|
-
|
|
869
|
+
def initialize(path:, type: 'local')
|
|
870
|
+
raise ArgumentError, "unsupported plugin type: #{type}" unless %w[local plugin].include?(type)
|
|
871
|
+
|
|
872
|
+
@type = 'local'
|
|
871
873
|
@path = path
|
|
872
874
|
end
|
|
873
875
|
|
data/lib/claude_agent_sdk.rb
CHANGED
|
@@ -82,29 +82,66 @@ module ClaudeAgentSDK
|
|
|
82
82
|
return enum_for(:query, prompt: prompt, options: options) unless block
|
|
83
83
|
|
|
84
84
|
options ||= ClaudeAgentOptions.new
|
|
85
|
-
|
|
85
|
+
|
|
86
|
+
configured_options = options
|
|
87
|
+
if options.can_use_tool
|
|
88
|
+
if prompt.is_a?(String)
|
|
89
|
+
raise ArgumentError,
|
|
90
|
+
'can_use_tool callback requires streaming mode. Please provide prompt as an Enumerator instead of a String.'
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
raise ArgumentError, 'can_use_tool callback cannot be used with permission_prompt_tool_name' if options.permission_prompt_tool_name
|
|
94
|
+
|
|
95
|
+
configured_options = options.dup_with(permission_prompt_tool_name: 'stdio')
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
configured_options = configured_options.dup_with(
|
|
99
|
+
env: (configured_options.env || {}).merge('CLAUDE_CODE_ENTRYPOINT' => 'sdk-rb')
|
|
100
|
+
)
|
|
86
101
|
|
|
87
102
|
Async do
|
|
88
103
|
# Always use streaming mode with control protocol (matches Python SDK).
|
|
89
104
|
# This sends agents via initialize request instead of CLI args,
|
|
90
105
|
# avoiding OS ARG_MAX limits.
|
|
91
|
-
transport = SubprocessCLITransport.new(
|
|
106
|
+
transport = SubprocessCLITransport.new(configured_options)
|
|
92
107
|
begin
|
|
93
108
|
transport.connect
|
|
94
109
|
|
|
95
110
|
# Extract SDK MCP servers
|
|
96
111
|
sdk_mcp_servers = {}
|
|
97
|
-
if
|
|
98
|
-
|
|
112
|
+
if configured_options.mcp_servers.is_a?(Hash)
|
|
113
|
+
configured_options.mcp_servers.each do |name, config|
|
|
99
114
|
sdk_mcp_servers[name] = config[:instance] if config.is_a?(Hash) && config[:type] == 'sdk'
|
|
100
115
|
end
|
|
101
116
|
end
|
|
102
117
|
|
|
118
|
+
hooks = nil
|
|
119
|
+
if configured_options.hooks
|
|
120
|
+
hooks = {}
|
|
121
|
+
configured_options.hooks.each do |event, matchers|
|
|
122
|
+
next if matchers.nil? || matchers.empty?
|
|
123
|
+
|
|
124
|
+
entries = []
|
|
125
|
+
matchers.each do |matcher|
|
|
126
|
+
config = {
|
|
127
|
+
matcher: matcher.matcher,
|
|
128
|
+
hooks: matcher.hooks
|
|
129
|
+
}
|
|
130
|
+
config[:timeout] = matcher.timeout if matcher.timeout
|
|
131
|
+
entries << config
|
|
132
|
+
end
|
|
133
|
+
hooks[event.to_s] = entries unless entries.empty?
|
|
134
|
+
end
|
|
135
|
+
hooks = nil if hooks.empty?
|
|
136
|
+
end
|
|
137
|
+
|
|
103
138
|
# Create Query handler for control protocol
|
|
104
139
|
query_handler = Query.new(
|
|
105
140
|
transport: transport,
|
|
106
141
|
is_streaming_mode: true,
|
|
107
|
-
|
|
142
|
+
can_use_tool: configured_options.can_use_tool,
|
|
143
|
+
hooks: hooks,
|
|
144
|
+
agents: configured_options.agents,
|
|
108
145
|
sdk_mcp_servers: sdk_mcp_servers
|
|
109
146
|
)
|
|
110
147
|
|
|
@@ -120,19 +157,13 @@ module ClaudeAgentSDK
|
|
|
120
157
|
type: 'user',
|
|
121
158
|
message: { role: 'user', content: prompt },
|
|
122
159
|
parent_tool_use_id: nil,
|
|
123
|
-
session_id: '
|
|
160
|
+
session_id: ''
|
|
124
161
|
}
|
|
125
162
|
transport.write(JSON.generate(message) + "\n")
|
|
126
|
-
|
|
163
|
+
query_handler.wait_for_result_and_end_input
|
|
127
164
|
elsif prompt.is_a?(Enumerator) || prompt.respond_to?(:each)
|
|
128
165
|
Async do
|
|
129
|
-
|
|
130
|
-
prompt.each do |message_json|
|
|
131
|
-
transport.write(message_json)
|
|
132
|
-
end
|
|
133
|
-
ensure
|
|
134
|
-
transport.end_input
|
|
135
|
-
end
|
|
166
|
+
query_handler.stream_input(prompt)
|
|
136
167
|
end
|
|
137
168
|
end
|
|
138
169
|
|
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.8.
|
|
4
|
+
version: 0.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Community Contributors
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-03-
|
|
10
|
+
date: 2026-03-07 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: async
|
|
@@ -123,6 +123,7 @@ metadata:
|
|
|
123
123
|
source_code_uri: https://github.com/ya-luotao/claude-agent-sdk-ruby
|
|
124
124
|
changelog_uri: https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/CHANGELOG.md
|
|
125
125
|
documentation_uri: https://docs.anthropic.com/en/docs/claude-code/sdk
|
|
126
|
+
allowed_push_host: https://rubygems.org
|
|
126
127
|
rdoc_options: []
|
|
127
128
|
require_paths:
|
|
128
129
|
- lib
|