claude-agent-sdk 0.34.0 → 0.36.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 +80 -0
- data/README.md +68 -24
- data/docs/cli-installer.md +38 -1
- data/docs/client.md +44 -20
- data/docs/errors.md +15 -1
- data/docs/hooks-and-permissions.md +22 -0
- data/docs/mcp-servers.md +37 -7
- data/docs/rails.md +92 -54
- data/docs/sessions.md +69 -33
- data/lib/claude_agent_sdk/cancellation_signal.rb +2 -1
- data/lib/claude_agent_sdk/cli_installer.rb +38 -8
- data/lib/claude_agent_sdk/deprecation.rb +51 -0
- data/lib/claude_agent_sdk/errors.rb +8 -0
- data/lib/claude_agent_sdk/fiber_boundary.rb +111 -2
- data/lib/claude_agent_sdk/option_warnings.rb +0 -2
- data/lib/claude_agent_sdk/query.rb +49 -8
- data/lib/claude_agent_sdk/railtie.rb +105 -0
- data/lib/claude_agent_sdk/sdk_mcp_server.rb +36 -25
- data/lib/claude_agent_sdk/session_mutations.rb +10 -10
- data/lib/claude_agent_sdk/session_resume.rb +19 -24
- data/lib/claude_agent_sdk/session_store.rb +28 -18
- data/lib/claude_agent_sdk/session_summary.rb +8 -3
- data/lib/claude_agent_sdk/sessions.rb +104 -18
- data/lib/claude_agent_sdk/subprocess_cli_transport.rb +4 -13
- data/lib/claude_agent_sdk/tasks/claude_agent_sdk.rake +37 -0
- data/lib/claude_agent_sdk/tasks.rb +13 -0
- data/lib/claude_agent_sdk/testing/session_store_conformance.rb +1 -1
- data/lib/claude_agent_sdk/transcript_mirror_batcher.rb +17 -1
- data/lib/claude_agent_sdk/types.rb +219 -3
- data/lib/claude_agent_sdk/version.rb +1 -1
- data/lib/claude_agent_sdk.rb +261 -56
- data/lib/generators/claude_agent_sdk/install/install_generator.rb +63 -0
- data/lib/generators/claude_agent_sdk/install/templates/claude_agent_sdk.rb.tt +35 -0
- metadata +17 -6
|
@@ -157,8 +157,178 @@ module ClaudeAgentSDK
|
|
|
157
157
|
end
|
|
158
158
|
private_class_method :option_hash_key
|
|
159
159
|
|
|
160
|
+
# Bounded, human-oriented #inspect listing the non-nil instance variables
|
|
161
|
+
# in definition order:
|
|
162
|
+
#
|
|
163
|
+
# #<ClaudeAgentSDK::ResultMessage subtype="success" num_turns=3 ...>
|
|
164
|
+
#
|
|
165
|
+
# Messages carry whole transcripts, tool payloads and usage maps, so the
|
|
166
|
+
# output is bounded rather than faithful: long Strings are truncated,
|
|
167
|
+
# long Arrays/Hashes abbreviated, and nesting past INSPECT_MAX_DEPTH (or
|
|
168
|
+
# a reference cycle) collapses to a placeholder. Other objects keep their
|
|
169
|
+
# own #inspect (truncated) unless they only have Kernel#inspect, which
|
|
170
|
+
# dumps every ivar recursively — those (SDK MCP server instances, store
|
|
171
|
+
# adapters, observers) show as `#<ClassName>`. For display only: nothing
|
|
172
|
+
# sent to the CLI goes through #inspect or #to_s (wire output uses #to_h).
|
|
173
|
+
def inspect
|
|
174
|
+
inspect_with(0, {}.compare_by_identity)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Object#to_s ignores instance variables, so `puts message` would print
|
|
178
|
+
# only a class name and an address. Types with a natural textual form
|
|
179
|
+
# (UserMessage, AssistantMessage, TextBlock, ResultMessage, SystemMessage)
|
|
180
|
+
# override this.
|
|
181
|
+
def to_s
|
|
182
|
+
inspect
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Declares attributes that carry credentials (env vars, auth headers).
|
|
186
|
+
# Objects get logged, so #inspect shows them filtered; #to_h and
|
|
187
|
+
# everything sent to the CLI are unaffected. Inherited by subclasses.
|
|
188
|
+
def self.inspect_filtered(*names)
|
|
189
|
+
@inspect_filtered_attributes = (inspect_filtered_attributes + names.map(&:to_s)).uniq.freeze
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def self.inspect_filtered_attributes
|
|
193
|
+
@inspect_filtered_attributes || (superclass <= Type ? superclass.inspect_filtered_attributes : [].freeze)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
INSPECT_MAX_STRING = 80
|
|
197
|
+
INSPECT_MAX_ITEMS = 5
|
|
198
|
+
INSPECT_MAX_DEPTH = 2
|
|
199
|
+
private_constant :INSPECT_MAX_STRING, :INSPECT_MAX_ITEMS, :INSPECT_MAX_DEPTH
|
|
200
|
+
|
|
201
|
+
protected
|
|
202
|
+
|
|
203
|
+
# `seen` holds the Types/containers on the current rendering path (not
|
|
204
|
+
# every one rendered so far), so a shared-but-acyclic value still renders
|
|
205
|
+
# in full wherever it appears.
|
|
206
|
+
def inspect_with(depth, seen)
|
|
207
|
+
return "#<#{inspect_class_name} …>" if depth > INSPECT_MAX_DEPTH || seen.key?(self)
|
|
208
|
+
|
|
209
|
+
seen[self] = true
|
|
210
|
+
begin
|
|
211
|
+
attributes = inspect_attributes.map do |name, value|
|
|
212
|
+
" #{name}=#{inspect_bounded(value, depth + 1, seen)}"
|
|
213
|
+
end
|
|
214
|
+
"#<#{inspect_class_name}#{attributes.join}>"
|
|
215
|
+
ensure
|
|
216
|
+
seen.delete(self)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
160
220
|
private
|
|
161
221
|
|
|
222
|
+
# [name, value] pairs rendered by #inspect. Subclasses override to hide
|
|
223
|
+
# redundant state or redact secrets — never by mutating the object.
|
|
224
|
+
def inspect_attributes
|
|
225
|
+
filtered = self.class.inspect_filtered_attributes
|
|
226
|
+
instance_variables.filter_map do |ivar|
|
|
227
|
+
value = instance_variable_get(ivar)
|
|
228
|
+
next if value.nil?
|
|
229
|
+
|
|
230
|
+
name = ivar.to_s.delete_prefix('@')
|
|
231
|
+
[name, filtered.include?(name) ? inspect_filter(value) : value]
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# A credential-bearing Hash keeps its keys (useful when debugging which
|
|
236
|
+
# variables are set) with every value replaced; anything else is replaced
|
|
237
|
+
# outright. Builds a new Hash; the object itself is never touched.
|
|
238
|
+
def inspect_filter(value)
|
|
239
|
+
value.respond_to?(:each_key) ? value.each_key.to_h { |key| [key, '[FILTERED]'] } : '[FILTERED]'
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def inspect_class_name
|
|
243
|
+
self.class.name || self.class.inspect
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def inspect_bounded(value, depth, seen)
|
|
247
|
+
case value
|
|
248
|
+
when Type then value.inspect_with(depth, seen)
|
|
249
|
+
when String then inspect_truncated(value)
|
|
250
|
+
when Array then inspect_container(value, '[', ']', depth, seen) { |item| inspect_bounded(item, depth + 1, seen) }
|
|
251
|
+
when Hash
|
|
252
|
+
inspect_container(value, '{', '}', depth, seen) do |key, item|
|
|
253
|
+
"#{inspect_hash_key(key, depth + 1, seen)}#{inspect_bounded(item, depth + 1, seen)}"
|
|
254
|
+
end
|
|
255
|
+
when Proc, Method, UnboundMethod then inspect_callable(value)
|
|
256
|
+
else inspect_leaf(value)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def inspect_container(value, open, close, depth, seen, &)
|
|
261
|
+
return "#{open}#{close}" if value.empty?
|
|
262
|
+
return "#{open}…(#{value.size})#{close}" if depth > INSPECT_MAX_DEPTH || seen.key?(value)
|
|
263
|
+
|
|
264
|
+
seen[value] = true
|
|
265
|
+
begin
|
|
266
|
+
parts = value.first(INSPECT_MAX_ITEMS).map(&)
|
|
267
|
+
parts << "…(+#{value.size - INSPECT_MAX_ITEMS} more)" if value.size > INSPECT_MAX_ITEMS
|
|
268
|
+
"#{open}#{parts.join(', ')}#{close}"
|
|
269
|
+
ensure
|
|
270
|
+
seen.delete(value)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Rendered by hand rather than via Hash#inspect, whose format differs
|
|
275
|
+
# between Ruby 3.3 (`{:a=>1}`) and 3.4 (`{a: 1}`).
|
|
276
|
+
def inspect_hash_key(key, depth, seen)
|
|
277
|
+
return "#{key.name}: " if key.is_a?(Symbol) && key.inspect.match?(/\A:\w+[?!]?\z/)
|
|
278
|
+
|
|
279
|
+
"#{inspect_bounded(key, depth, seen)} => "
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def inspect_truncated(string)
|
|
283
|
+
return string.inspect if string.length <= INSPECT_MAX_STRING
|
|
284
|
+
|
|
285
|
+
"#{string[0, INSPECT_MAX_STRING].inspect}…(+#{string.length - INSPECT_MAX_STRING} chars)"
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Callbacks (can_use_tool, hooks, callback_wrapper, ...) are user-supplied:
|
|
289
|
+
# render them from source_location rather than their own #inspect, which
|
|
290
|
+
# a subclass may override (and raise from) and which embeds an absolute
|
|
291
|
+
# path — `#<Proc(lambda) permissions.rb:17>`, `#<Method Policy#call>`.
|
|
292
|
+
def inspect_callable(value)
|
|
293
|
+
label = if value.is_a?(Proc)
|
|
294
|
+
value.lambda? ? 'Proc(lambda)' : 'Proc'
|
|
295
|
+
else
|
|
296
|
+
"#{value.class.name} #{value.owner.name || value.owner.inspect}##{value.name}"
|
|
297
|
+
end
|
|
298
|
+
file, line = value.source_location
|
|
299
|
+
rendered = file ? "#<#{label} #{File.basename(file)}:#{line}>" : "#<#{label}>"
|
|
300
|
+
inspect_truncated_text(rendered)
|
|
301
|
+
rescue StandardError
|
|
302
|
+
inspect_leaf(value)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def inspect_truncated_text(rendered)
|
|
306
|
+
return rendered if rendered.length <= INSPECT_MAX_STRING
|
|
307
|
+
|
|
308
|
+
"#{rendered[0, INSPECT_MAX_STRING]}…(+#{rendered.length - INSPECT_MAX_STRING} chars)"
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Printing must never raise (it runs inside loggers and `puts`), so an
|
|
312
|
+
# object whose #inspect raises, or a BasicObject without one, falls back
|
|
313
|
+
# to a placeholder.
|
|
314
|
+
def inspect_leaf(value)
|
|
315
|
+
return "#<#{value.class}>" if kernel_inspect_only?(value)
|
|
316
|
+
|
|
317
|
+
inspect_truncated_text(value.inspect)
|
|
318
|
+
rescue StandardError
|
|
319
|
+
begin
|
|
320
|
+
"#<#{value.class}>"
|
|
321
|
+
rescue StandardError
|
|
322
|
+
'#<?>'
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def kernel_inspect_only?(value)
|
|
327
|
+
Kernel.instance_method(:method).bind_call(value, :inspect).owner == Kernel
|
|
328
|
+
rescue TypeError # not a Kernel object: BasicObject, Delegator
|
|
329
|
+
false
|
|
330
|
+
end
|
|
331
|
+
|
|
162
332
|
# Allow camelCase attribute access
|
|
163
333
|
def method_missing(method_name, ...)
|
|
164
334
|
normalized = normalize_name(method_name)
|
|
@@ -230,6 +400,10 @@ module ClaudeAgentSDK
|
|
|
230
400
|
# Text content block
|
|
231
401
|
class TextBlock < Type
|
|
232
402
|
attr_accessor :text
|
|
403
|
+
|
|
404
|
+
def to_s
|
|
405
|
+
text.to_s
|
|
406
|
+
end
|
|
233
407
|
end
|
|
234
408
|
|
|
235
409
|
# Thinking content block
|
|
@@ -375,6 +549,22 @@ module ClaudeAgentSDK
|
|
|
375
549
|
super
|
|
376
550
|
@data ||= attributes if attributes.is_a?(Hash)
|
|
377
551
|
end
|
|
552
|
+
|
|
553
|
+
def to_s
|
|
554
|
+
subtype.nil? ? '[system]' : "[system: #{subtype}]"
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
private
|
|
558
|
+
|
|
559
|
+
# A typed subclass (InitMessage, TaskStartedMessage, ...) already exposes
|
|
560
|
+
# the fields of its raw frame as attributes; repeating @data would double
|
|
561
|
+
# the output. A bare SystemMessage (unrecognized subtype) keeps it, since
|
|
562
|
+
# @data is the only place its payload lives.
|
|
563
|
+
def inspect_attributes
|
|
564
|
+
return super if instance_of?(SystemMessage)
|
|
565
|
+
|
|
566
|
+
super.reject { |pair| pair.first == 'data' }
|
|
567
|
+
end
|
|
378
568
|
end
|
|
379
569
|
|
|
380
570
|
# Init system message (emitted at session start and after /clear)
|
|
@@ -755,6 +945,19 @@ module ClaudeAgentSDK
|
|
|
755
945
|
# @return [Hash{Symbol => Object}, nil]
|
|
756
946
|
# @see UserMessage#origin
|
|
757
947
|
attr_accessor :origin
|
|
948
|
+
|
|
949
|
+
# One human-readable line, e.g. `[result: success, 3 turns, 4.2s, $0.0120]`
|
|
950
|
+
# (parts the CLI did not report are left out). An error result appends its
|
|
951
|
+
# `errors`. Use #inspect for every field.
|
|
952
|
+
def to_s
|
|
953
|
+
parts = [subtype].compact
|
|
954
|
+
parts << "#{num_turns} #{num_turns == 1 ? 'turn' : 'turns'}" unless num_turns.nil?
|
|
955
|
+
parts << format('%.1fs', duration_ms / 1000.0) if duration_ms.is_a?(Numeric)
|
|
956
|
+
parts << format('$%.4f', total_cost_usd) if total_cost_usd.is_a?(Numeric)
|
|
957
|
+
line = parts.empty? ? '[result]' : "[result: #{parts.join(', ')}]"
|
|
958
|
+
line += " - #{Array(errors).join('; ')}" if is_error && !Array(errors).empty?
|
|
959
|
+
line
|
|
960
|
+
end
|
|
758
961
|
end
|
|
759
962
|
|
|
760
963
|
# Stream event for partial message updates
|
|
@@ -1723,6 +1926,8 @@ module ClaudeAgentSDK
|
|
|
1723
1926
|
attr_accessor :command, :args, :env
|
|
1724
1927
|
attr_reader :type
|
|
1725
1928
|
|
|
1929
|
+
inspect_filtered :env
|
|
1930
|
+
|
|
1726
1931
|
def initialize(attributes = {})
|
|
1727
1932
|
super
|
|
1728
1933
|
@type = 'stdio'
|
|
@@ -1742,6 +1947,8 @@ module ClaudeAgentSDK
|
|
|
1742
1947
|
attr_accessor :url, :headers
|
|
1743
1948
|
attr_reader :type
|
|
1744
1949
|
|
|
1950
|
+
inspect_filtered :headers
|
|
1951
|
+
|
|
1745
1952
|
def initialize(attributes = {})
|
|
1746
1953
|
super
|
|
1747
1954
|
@type = 'sse'
|
|
@@ -1760,6 +1967,8 @@ module ClaudeAgentSDK
|
|
|
1760
1967
|
attr_accessor :url, :headers
|
|
1761
1968
|
attr_reader :type
|
|
1762
1969
|
|
|
1970
|
+
inspect_filtered :headers
|
|
1971
|
+
|
|
1763
1972
|
def initialize(attributes = {})
|
|
1764
1973
|
super
|
|
1765
1974
|
@type = 'http'
|
|
@@ -1980,6 +2189,9 @@ module ClaudeAgentSDK
|
|
|
1980
2189
|
|
|
1981
2190
|
# Claude Agent Options for configuring queries
|
|
1982
2191
|
class ClaudeAgentOptions < Type
|
|
2192
|
+
# `env` routinely carries credentials (ANTHROPIC_API_KEY, ...).
|
|
2193
|
+
inspect_filtered :env
|
|
2194
|
+
|
|
1983
2195
|
attr_accessor :allowed_tools, :system_prompt, :mcp_servers, :permission_mode,
|
|
1984
2196
|
:resume, :resume_session_at, :session_id, :max_turns, :disallowed_tools,
|
|
1985
2197
|
:model, :permission_prompt_tool_name, :cwd, :cli_path, :settings,
|
|
@@ -2226,13 +2438,17 @@ module ClaudeAgentSDK
|
|
|
2226
2438
|
# A callable receiving a zero-arg invocation; it MUST call it and
|
|
2227
2439
|
# return its value:
|
|
2228
2440
|
#
|
|
2229
|
-
# callback_wrapper: ->(invocation) {
|
|
2441
|
+
# callback_wrapper: ->(invocation) { MyApm.trace('agent.callback') { invocation.call } }
|
|
2230
2442
|
#
|
|
2231
2443
|
# The wrapper runs on the same execution context as the callback —
|
|
2232
|
-
# inside the worker thread in :thread mode
|
|
2233
|
-
# connections back in when the callback ends), in place on the reactor
|
|
2444
|
+
# inside the worker thread in :thread mode, in place on the reactor
|
|
2234
2445
|
# fiber in :inline mode. Exceptions propagate through it unchanged; it
|
|
2235
2446
|
# must not swallow them. Default nil (no wrapping).
|
|
2447
|
+
#
|
|
2448
|
+
# Rails apps: use ClaudeAgentSDK::Railtie.callback_wrapper, which runs
|
|
2449
|
+
# callbacks in the Rails executor (AR connections check back in when the
|
|
2450
|
+
# callback ends). A bare `Rails.application.executor.wrap` deadlocks
|
|
2451
|
+
# under development code reloading in :thread mode.
|
|
2236
2452
|
def callback_wrapper=(value)
|
|
2237
2453
|
raise ArgumentError, "callback_wrapper must be a callable or nil (got #{value.inspect})" unless value.nil? || value.respond_to?(:call)
|
|
2238
2454
|
|