claude-agent-sdk 0.16.7 → 0.16.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/README.md +84 -1656
- data/docs/client.md +157 -0
- data/docs/configuration.md +215 -0
- data/docs/errors.md +95 -0
- data/docs/hooks-and-permissions.md +110 -0
- data/docs/mcp-servers.md +153 -0
- data/docs/observability.md +126 -0
- data/docs/rails.md +199 -0
- data/docs/sessions.md +101 -0
- data/docs/types.md +187 -0
- data/lib/claude_agent_sdk/command_builder.rb +5 -0
- data/lib/claude_agent_sdk/message_parser.rb +8 -0
- data/lib/claude_agent_sdk/query.rb +46 -17
- data/lib/claude_agent_sdk/sdk_mcp_server.rb +12 -6
- data/lib/claude_agent_sdk/session_mutations.rb +46 -12
- data/lib/claude_agent_sdk/sessions.rb +43 -3
- data/lib/claude_agent_sdk/subprocess_cli_transport.rb +78 -23
- data/lib/claude_agent_sdk/types.rb +46 -5
- data/lib/claude_agent_sdk/version.rb +1 -1
- metadata +10 -1
|
@@ -30,6 +30,12 @@ module ClaudeAgentSDK
|
|
|
30
30
|
@stderr_task = nil
|
|
31
31
|
@recent_stderr = []
|
|
32
32
|
@recent_stderr_mutex = Mutex.new
|
|
33
|
+
# Serializes stdin access across the reactor fiber (transport writes
|
|
34
|
+
# from inside Async) and user-callback threads spawned via FiberBoundary
|
|
35
|
+
# (tool handlers / hooks calling Client#query). Without this lock,
|
|
36
|
+
# close can nil @stdin between write's readiness check and the actual
|
|
37
|
+
# @stdin.write call, producing NoMethodError on nil.
|
|
38
|
+
@stdin_mutex = Mutex.new
|
|
33
39
|
end
|
|
34
40
|
|
|
35
41
|
def find_cli
|
|
@@ -148,20 +154,33 @@ module ClaudeAgentSDK
|
|
|
148
154
|
|
|
149
155
|
record_bounded_stderr(line_str)
|
|
150
156
|
|
|
151
|
-
#
|
|
152
|
-
|
|
157
|
+
# Per-line isolation: a callback that raises (e.g. user's logger
|
|
158
|
+
# transiently failing) must not poison the rest of the stderr stream.
|
|
159
|
+
# Without this, the first exception terminates the each_line loop and
|
|
160
|
+
# the SDK silently stops capturing stderr for the lifetime of the
|
|
161
|
+
# process. Matches Python SDK v0.2.82 (PR #932).
|
|
162
|
+
begin
|
|
163
|
+
@options.stderr&.call(line_str)
|
|
164
|
+
rescue StandardError
|
|
165
|
+
# Drop the callback error; the line is already in the recent-stderr
|
|
166
|
+
# ring buffer, which is what ProcessError surfaces on non-zero exit.
|
|
167
|
+
end
|
|
153
168
|
|
|
154
|
-
# Write to debug_stderr file/IO if provided
|
|
155
|
-
|
|
156
|
-
if @options.debug_stderr
|
|
157
|
-
@options.debug_stderr.puts
|
|
158
|
-
|
|
159
|
-
|
|
169
|
+
# Write to debug_stderr file/IO if provided, also isolated.
|
|
170
|
+
begin
|
|
171
|
+
if @options.debug_stderr
|
|
172
|
+
if @options.debug_stderr.respond_to?(:puts)
|
|
173
|
+
@options.debug_stderr.puts(line_str)
|
|
174
|
+
elsif @options.debug_stderr.is_a?(String)
|
|
175
|
+
File.open(@options.debug_stderr, 'a') { |f| f.puts(line_str) }
|
|
176
|
+
end
|
|
160
177
|
end
|
|
178
|
+
rescue StandardError
|
|
179
|
+
# Drop debug_stderr write errors so they never interrupt the loop.
|
|
161
180
|
end
|
|
162
181
|
end
|
|
163
182
|
rescue StandardError
|
|
164
|
-
#
|
|
183
|
+
# Stream-level error (pipe closed mid-read); the loop naturally ends here.
|
|
165
184
|
end
|
|
166
185
|
|
|
167
186
|
def drain_stderr_with_accumulation
|
|
@@ -191,13 +210,18 @@ module ClaudeAgentSDK
|
|
|
191
210
|
end
|
|
192
211
|
end
|
|
193
212
|
|
|
194
|
-
# Close
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
213
|
+
# Close stdin under the same lock that guards write — otherwise a
|
|
214
|
+
# concurrent writer (callbacks running on FiberBoundary threads) can
|
|
215
|
+
# see @stdin nilled mid-write and hit NoMethodError on nil.
|
|
216
|
+
@stdin_mutex.synchronize do
|
|
217
|
+
begin
|
|
218
|
+
@stdin&.close
|
|
219
|
+
rescue IOError
|
|
220
|
+
# Already closed, ignore
|
|
221
|
+
rescue StandardError => e
|
|
222
|
+
cleanup_errors << "stdin: #{e.message}"
|
|
223
|
+
end
|
|
224
|
+
@stdin = nil
|
|
201
225
|
end
|
|
202
226
|
|
|
203
227
|
begin
|
|
@@ -251,7 +275,7 @@ module ClaudeAgentSDK
|
|
|
251
275
|
|
|
252
276
|
@process = nil
|
|
253
277
|
@stdout = nil
|
|
254
|
-
@stdin
|
|
278
|
+
# @stdin already nilled under the mutex above.
|
|
255
279
|
@stderr = nil
|
|
256
280
|
@stderr_task = nil
|
|
257
281
|
@exit_error = nil
|
|
@@ -274,13 +298,28 @@ module ClaudeAgentSDK
|
|
|
274
298
|
end
|
|
275
299
|
|
|
276
300
|
def write(data)
|
|
277
|
-
raise CLIConnectionError, 'ProcessTransport is not ready for writing' unless @ready && @stdin
|
|
278
301
|
raise CLIConnectionError, "Cannot write to terminated process" if @process && !@process.alive?
|
|
279
302
|
raise CLIConnectionError, "Cannot write to process that exited with error: #{@exit_error}" if @exit_error
|
|
280
303
|
|
|
304
|
+
# Snapshot @stdin under the lock so close() nilling it concurrently is
|
|
305
|
+
# safe, but do the actual blocking IO *outside* the lock. Holding the
|
|
306
|
+
# mutex across @stdin.write would let a full pipe buffer block the
|
|
307
|
+
# writer indefinitely and block close() (which also needs the lock)
|
|
308
|
+
# from killing the subprocess — a hang on disconnect.
|
|
309
|
+
#
|
|
310
|
+
# If close() runs while we are inside the IO call, it will close the
|
|
311
|
+
# underlying stream and Ruby raises IOError("stream closed in another
|
|
312
|
+
# thread") inside @stdin.write — the rescue below converts that into a
|
|
313
|
+
# standard CLIConnectionError so callers see a clean shutdown error.
|
|
314
|
+
stdin = @stdin_mutex.synchronize do
|
|
315
|
+
raise CLIConnectionError, 'ProcessTransport is not ready for writing' unless @ready && @stdin
|
|
316
|
+
|
|
317
|
+
@stdin
|
|
318
|
+
end
|
|
319
|
+
|
|
281
320
|
begin
|
|
282
|
-
|
|
283
|
-
|
|
321
|
+
stdin.write(data)
|
|
322
|
+
stdin.flush
|
|
284
323
|
rescue StandardError => e
|
|
285
324
|
@ready = false
|
|
286
325
|
@exit_error = CLIConnectionError.new("Failed to write to process stdin: #{e}")
|
|
@@ -317,6 +356,14 @@ module ClaudeAgentSDK
|
|
|
317
356
|
json_line = json_line.strip
|
|
318
357
|
next if json_line.empty?
|
|
319
358
|
|
|
359
|
+
# When no partial JSON is buffered, the next line must start with
|
|
360
|
+
# `{` to be a valid stream-json message. Stray stderr-like text
|
|
361
|
+
# (e.g., debug warnings the CLI occasionally writes to stdout)
|
|
362
|
+
# would otherwise be appended into json_buffer, poisoning every
|
|
363
|
+
# subsequent parse until the buffer overflows. Matches the Python
|
|
364
|
+
# SDK's `if not json_buffer and not json_line.startswith("{")` guard.
|
|
365
|
+
next if json_buffer.empty? && !json_line.start_with?('{')
|
|
366
|
+
|
|
320
367
|
json_buffer += json_line
|
|
321
368
|
|
|
322
369
|
if json_buffer.bytesize > @max_buffer_size
|
|
@@ -344,9 +391,17 @@ module ClaudeAgentSDK
|
|
|
344
391
|
# Client disconnected
|
|
345
392
|
end
|
|
346
393
|
|
|
347
|
-
# Check process completion
|
|
348
|
-
|
|
349
|
-
|
|
394
|
+
# Check process completion. @process may already be nil (close() ran
|
|
395
|
+
# concurrently and reset it) or already waited on (Errno::ECHILD on
|
|
396
|
+
# double-wait). Both are non-fatal — the message loop just exits.
|
|
397
|
+
returncode = nil
|
|
398
|
+
begin
|
|
399
|
+
status = @process&.value
|
|
400
|
+
returncode = status&.exitstatus
|
|
401
|
+
rescue Errno::ECHILD
|
|
402
|
+
# Process was already reaped (e.g., by close()); no exit status to surface.
|
|
403
|
+
returncode = nil
|
|
404
|
+
end
|
|
350
405
|
|
|
351
406
|
if returncode && returncode != 0
|
|
352
407
|
# Wait briefly for stderr thread to finish draining
|
|
@@ -180,12 +180,32 @@ module ClaudeAgentSDK
|
|
|
180
180
|
attr_accessor :tool_use_id, :content, :is_error
|
|
181
181
|
end
|
|
182
182
|
|
|
183
|
+
# Server-side tool use (CLI's built-in tools that execute server-side
|
|
184
|
+
# rather than as MCP tools — advisor, web_search, code_execution, etc.).
|
|
185
|
+
# Mirrors Python's `ServerToolUseBlock`.
|
|
186
|
+
class ServerToolUseBlock < Type
|
|
187
|
+
attr_accessor :id, :name, :input
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Result of a server-side tool execution. Mirrors Python's
|
|
191
|
+
# `ServerToolResultBlock`.
|
|
192
|
+
class ServerToolResultBlock < Type
|
|
193
|
+
attr_accessor :tool_use_id, :content, :is_error
|
|
194
|
+
end
|
|
195
|
+
|
|
183
196
|
# Generic content block for types the SDK doesn't explicitly handle (e.g., "document", "image").
|
|
184
197
|
# Preserves the raw hash data for forward compatibility with newer CLI versions.
|
|
185
198
|
class UnknownBlock < Type
|
|
186
199
|
attr_accessor :type, :data
|
|
187
200
|
end
|
|
188
201
|
|
|
202
|
+
# Deferred tool use, emitted on `ResultMessage` when a PreToolUse hook
|
|
203
|
+
# returned `permissionDecision: "defer"`. The session can be resumed later
|
|
204
|
+
# to execute the deferred call. Mirrors Python's `DeferredToolUse`.
|
|
205
|
+
class DeferredToolUse < Type
|
|
206
|
+
attr_accessor :id, :name, :input
|
|
207
|
+
end
|
|
208
|
+
|
|
189
209
|
# Message Types
|
|
190
210
|
|
|
191
211
|
# User message
|
|
@@ -343,7 +363,14 @@ module ClaudeAgentSDK
|
|
|
343
363
|
:permission_denials, # Array of { tool_name:, tool_use_id:, tool_input: }
|
|
344
364
|
:errors, # Array of error strings (present on error subtypes)
|
|
345
365
|
:uuid,
|
|
346
|
-
:fast_mode_state
|
|
366
|
+
:fast_mode_state, # "off", "cooldown", or "on"
|
|
367
|
+
:api_error_status # Integer HTTP status (429, 500, 529) on api_error subtype (CLI 2.1.110+)
|
|
368
|
+
|
|
369
|
+
attr_reader :deferred_tool_use # DeferredToolUse, populated when a PreToolUse hook deferred
|
|
370
|
+
|
|
371
|
+
def deferred_tool_use=(value)
|
|
372
|
+
@deferred_tool_use = value.is_a?(Hash) ? DeferredToolUse.from_hash(value) : value
|
|
373
|
+
end
|
|
347
374
|
end
|
|
348
375
|
|
|
349
376
|
# Stream event for partial message updates
|
|
@@ -518,9 +545,16 @@ module ClaudeAgentSDK
|
|
|
518
545
|
end
|
|
519
546
|
end
|
|
520
547
|
|
|
521
|
-
# Tool permission context
|
|
548
|
+
# Tool permission context delivered to `can_use_tool` callbacks.
|
|
549
|
+
# CLI 2.1.110+ began populating the four pre-formatted display fields
|
|
550
|
+
# (`title`, `display_name`, `description`, `blocked_path`,
|
|
551
|
+
# `decision_reason`) so the SDK consumer can render the same prompt UI
|
|
552
|
+
# the CLI would have shown. Older fields (`signal`, `suggestions`,
|
|
553
|
+
# `tool_use_id`, `agent_id`) remain unchanged.
|
|
522
554
|
class ToolPermissionContext < Type
|
|
523
|
-
attr_accessor :signal, :suggestions, :tool_use_id, :agent_id
|
|
555
|
+
attr_accessor :signal, :suggestions, :tool_use_id, :agent_id,
|
|
556
|
+
:title, :display_name, :description,
|
|
557
|
+
:blocked_path, :decision_reason
|
|
524
558
|
|
|
525
559
|
def initialize(attributes = {})
|
|
526
560
|
super
|
|
@@ -885,9 +919,15 @@ module ClaudeAgentSDK
|
|
|
885
919
|
end
|
|
886
920
|
end
|
|
887
921
|
|
|
888
|
-
# PostToolUse hook specific output
|
|
922
|
+
# PostToolUse hook specific output.
|
|
923
|
+
#
|
|
924
|
+
# `updated_tool_output` (CLI 2.1.110+) replaces the tool's output entirely
|
|
925
|
+
# — works for any tool, MCP or built-in. `updated_mcp_tool_output` is the
|
|
926
|
+
# legacy MCP-only field that pre-dates the unified one; the CLI still
|
|
927
|
+
# honors it, so both are emitted when set. Mirrors Python's
|
|
928
|
+
# `PostToolUseHookSpecificOutput`.
|
|
889
929
|
class PostToolUseHookSpecificOutput < Type
|
|
890
|
-
attr_accessor :additional_context, :updated_mcp_tool_output
|
|
930
|
+
attr_accessor :additional_context, :updated_mcp_tool_output, :updated_tool_output
|
|
891
931
|
attr_reader :hook_event_name
|
|
892
932
|
|
|
893
933
|
def initialize(attributes = {})
|
|
@@ -898,6 +938,7 @@ module ClaudeAgentSDK
|
|
|
898
938
|
def to_h
|
|
899
939
|
result = { hookEventName: @hook_event_name }
|
|
900
940
|
result[:additionalContext] = @additional_context if @additional_context
|
|
941
|
+
result[:updatedToolOutput] = @updated_tool_output unless @updated_tool_output.nil?
|
|
901
942
|
result[:updatedMCPToolOutput] = @updated_mcp_tool_output if @updated_mcp_tool_output
|
|
902
943
|
result
|
|
903
944
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: claude-agent-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.16.
|
|
4
|
+
version: 0.16.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Community Contributors
|
|
@@ -104,6 +104,15 @@ files:
|
|
|
104
104
|
- CHANGELOG.md
|
|
105
105
|
- LICENSE
|
|
106
106
|
- README.md
|
|
107
|
+
- docs/client.md
|
|
108
|
+
- docs/configuration.md
|
|
109
|
+
- docs/errors.md
|
|
110
|
+
- docs/hooks-and-permissions.md
|
|
111
|
+
- docs/mcp-servers.md
|
|
112
|
+
- docs/observability.md
|
|
113
|
+
- docs/rails.md
|
|
114
|
+
- docs/sessions.md
|
|
115
|
+
- docs/types.md
|
|
107
116
|
- lib/claude_agent_sdk.rb
|
|
108
117
|
- lib/claude_agent_sdk/command_builder.rb
|
|
109
118
|
- lib/claude_agent_sdk/configuration.rb
|