claude-agent-sdk 0.33.1 → 0.35.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 +93 -0
- data/README.md +54 -19
- data/docs/cli-installer.md +40 -9
- data/docs/client.md +27 -18
- data/docs/configuration.md +5 -5
- data/docs/errors.md +4 -3
- data/docs/mcp-servers.md +22 -0
- data/docs/observability.md +6 -0
- data/docs/rails.md +92 -51
- data/docs/sessions.md +66 -15
- data/lib/claude_agent_sdk/cli_installer.rb +34 -18
- data/lib/claude_agent_sdk/command_builder.rb +11 -3
- data/lib/claude_agent_sdk/configuration.rb +54 -2
- data/lib/claude_agent_sdk/errors.rb +11 -3
- data/lib/claude_agent_sdk/fiber_boundary.rb +42 -3
- data/lib/claude_agent_sdk/instrumentation/otel.rb +21 -2
- data/lib/claude_agent_sdk/query.rb +140 -59
- data/lib/claude_agent_sdk/railtie.rb +94 -0
- data/lib/claude_agent_sdk/sdk_mcp_server.rb +46 -4
- data/lib/claude_agent_sdk/session_mutations.rb +39 -12
- data/lib/claude_agent_sdk/session_resume.rb +112 -39
- data/lib/claude_agent_sdk/session_store.rb +19 -3
- data/lib/claude_agent_sdk/session_summary.rb +5 -5
- data/lib/claude_agent_sdk/sessions.rb +123 -55
- data/lib/claude_agent_sdk/streaming.rb +0 -8
- data/lib/claude_agent_sdk/subprocess_cli_transport.rb +319 -54
- data/lib/claude_agent_sdk/tasks/claude_agent_sdk.rake +30 -0
- data/lib/claude_agent_sdk/tasks.rb +13 -0
- data/lib/claude_agent_sdk/testing/session_store_conformance.rb +13 -3
- data/lib/claude_agent_sdk/transcript_mirror_batcher.rb +77 -18
- data/lib/claude_agent_sdk/types.rb +349 -39
- data/lib/claude_agent_sdk/version.rb +1 -1
- data/lib/claude_agent_sdk.rb +53 -44
- 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 +29 -13
|
@@ -9,7 +9,7 @@ module ClaudeAgentSDK
|
|
|
9
9
|
|
|
10
10
|
# Effort levels for `ClaudeAgentOptions#effort`. The CLI (Claude Code 2.1.111+)
|
|
11
11
|
# accepts these values; the set of *supported* levels is model-dependent
|
|
12
|
-
# (e.g. `xhigh`
|
|
12
|
+
# (e.g. `xhigh` arrived with Opus 4.7 and falls back to `high` on
|
|
13
13
|
# Opus 4.6 / Sonnet 4.6). An Integer is also accepted and forwarded verbatim.
|
|
14
14
|
EFFORT_LEVELS = %w[low medium high xhigh max].freeze
|
|
15
15
|
|
|
@@ -90,8 +90,245 @@ module ClaudeAgentSDK
|
|
|
90
90
|
{}
|
|
91
91
|
end
|
|
92
92
|
|
|
93
|
+
# The copy hook used wherever ClaudeAgentOptions are copied (dup_with and
|
|
94
|
+
# the configured-defaults merge). Identity by default: most Type instances
|
|
95
|
+
# are messages or callback payloads that never live inside options, and a
|
|
96
|
+
# user-supplied object that does (an observer, a store adapter) must stay
|
|
97
|
+
# the same object. Option VALUE types include OptionValue to opt in to
|
|
98
|
+
# copying, so a per-session change to e.g. sandbox rules can never reach
|
|
99
|
+
# another session or the configured defaults.
|
|
100
|
+
def dup_for_options
|
|
101
|
+
self
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Mixed into the mutable value types that ClaudeAgentOptions holds
|
|
105
|
+
# (SandboxSettings, SystemPromptPreset, AgentDefinition, ...). The copy
|
|
106
|
+
# recurses into the value's own state with Type.deep_dup_for_options, so
|
|
107
|
+
# nested containers and nested value types (SandboxSettings#network) are
|
|
108
|
+
# copied too while identity leaves (McpSdkServerConfig#instance, the
|
|
109
|
+
# callables in HookMatcher#hooks) stay shared. #dup never copies frozen
|
|
110
|
+
# state, so a copy of a frozen value (the configured-defaults snapshot) is
|
|
111
|
+
# mutable.
|
|
112
|
+
module OptionValue
|
|
113
|
+
def dup_for_options
|
|
114
|
+
copy = dup
|
|
115
|
+
copy.instance_variables.each do |ivar|
|
|
116
|
+
copy.instance_variable_set(ivar, Type.deep_dup_for_options(copy.instance_variable_get(ivar)))
|
|
117
|
+
end
|
|
118
|
+
copy
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Recurse into Hash/Array containers and option value types, and copy
|
|
123
|
+
# mutable (unfrozen) Strings — a caller-built prompt, model or
|
|
124
|
+
# allowed_tools entry is as much shared state as an Array, and `str <<
|
|
125
|
+
# 'x'` on one copy would otherwise change every other. A frozen String
|
|
126
|
+
# (any literal under frozen_string_literal) is immutable and keeps
|
|
127
|
+
# identity. Every other leaf keeps object identity (observer factories,
|
|
128
|
+
# callbacks, SDK MCP server instances must not be duped). Rebuild
|
|
129
|
+
# containers via dup.clear (never Hash#to_h / Array#map) to preserve
|
|
130
|
+
# container SUBCLASSES: to_h flattens e.g. Rails'
|
|
131
|
+
# HashWithIndifferentAccess into a plain Hash, silently breaking symbol
|
|
132
|
+
# lookups on the copy (config[:type] == 'sdk' → nil). Hash keys: Ruby
|
|
133
|
+
# already stores a dup'd, frozen copy of an unfrozen plain String key, but
|
|
134
|
+
# not of a String SUBCLASS key, so those are copied (and frozen, as keys
|
|
135
|
+
# should be) here. A compare_by_identity Hash is left keyed by the
|
|
136
|
+
# caller's objects: copying a key would break the caller's own lookups.
|
|
137
|
+
def self.deep_dup_for_options(value)
|
|
138
|
+
case value
|
|
139
|
+
when Hash
|
|
140
|
+
copy = value.dup.clear
|
|
141
|
+
value.each { |k, v| copy[option_hash_key(k, value)] = deep_dup_for_options(v) }
|
|
142
|
+
copy
|
|
143
|
+
when Array
|
|
144
|
+
copy = value.dup.clear
|
|
145
|
+
value.each { |v| copy << deep_dup_for_options(v) }
|
|
146
|
+
copy
|
|
147
|
+
when Type then value.dup_for_options
|
|
148
|
+
when String then value.frozen? ? value : value.dup # #dup keeps subclass and encoding
|
|
149
|
+
else value
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def self.option_hash_key(key, hash)
|
|
154
|
+
return key unless key.is_a?(String) && !key.frozen? && !hash.compare_by_identity?
|
|
155
|
+
|
|
156
|
+
key.dup.freeze
|
|
157
|
+
end
|
|
158
|
+
private_class_method :option_hash_key
|
|
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
|
+
|
|
93
220
|
private
|
|
94
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, &render)
|
|
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(&render)
|
|
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
|
+
|
|
95
332
|
# Allow camelCase attribute access
|
|
96
333
|
def method_missing(method_name, ...)
|
|
97
334
|
normalized = normalize_name(method_name)
|
|
@@ -163,6 +400,10 @@ module ClaudeAgentSDK
|
|
|
163
400
|
# Text content block
|
|
164
401
|
class TextBlock < Type
|
|
165
402
|
attr_accessor :text
|
|
403
|
+
|
|
404
|
+
def to_s
|
|
405
|
+
text.to_s
|
|
406
|
+
end
|
|
166
407
|
end
|
|
167
408
|
|
|
168
409
|
# Thinking content block
|
|
@@ -308,6 +549,22 @@ module ClaudeAgentSDK
|
|
|
308
549
|
super
|
|
309
550
|
@data ||= attributes if attributes.is_a?(Hash)
|
|
310
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
|
|
311
568
|
end
|
|
312
569
|
|
|
313
570
|
# Init system message (emitted at session start and after /clear)
|
|
@@ -688,6 +945,19 @@ module ClaudeAgentSDK
|
|
|
688
945
|
# @return [Hash{Symbol => Object}, nil]
|
|
689
946
|
# @see UserMessage#origin
|
|
690
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
|
|
691
961
|
end
|
|
692
962
|
|
|
693
963
|
# Stream event for partial message updates
|
|
@@ -785,13 +1055,16 @@ module ClaudeAgentSDK
|
|
|
785
1055
|
# `display` controls how thinking content appears in responses. Valid values
|
|
786
1056
|
# are `"summarized"` (plaintext summary) and `"omitted"` (empty thinking
|
|
787
1057
|
# field, signature only). Defaults are model-dependent: Opus 4.6/Sonnet 4.6
|
|
788
|
-
# default to `"summarized"`; Opus 4.7 and
|
|
789
|
-
# `"omitted"`. Pass `display: "summarized"` explicitly on
|
|
1058
|
+
# default to `"summarized"`; Opus 4.7 and every later model default to
|
|
1059
|
+
# `"omitted"`. Pass `display: "summarized"` explicitly on those to get
|
|
790
1060
|
# visible thinking text. Not supported with `ThinkingConfigDisabled`.
|
|
791
1061
|
THINKING_DISPLAY_VALUES = %w[summarized omitted].freeze
|
|
792
1062
|
|
|
793
|
-
# Adaptive thinking:
|
|
1063
|
+
# Adaptive thinking: the model decides when and how much to think
|
|
1064
|
+
# (sent as `--thinking adaptive`, no budget); control depth with `effort`.
|
|
794
1065
|
class ThinkingConfigAdaptive < Type
|
|
1066
|
+
include Type::OptionValue
|
|
1067
|
+
|
|
795
1068
|
attr_reader :type, :display
|
|
796
1069
|
|
|
797
1070
|
def initialize(attributes = {})
|
|
@@ -816,6 +1089,8 @@ module ClaudeAgentSDK
|
|
|
816
1089
|
|
|
817
1090
|
# Enabled thinking: uses a user-specified budget
|
|
818
1091
|
class ThinkingConfigEnabled < Type
|
|
1092
|
+
include Type::OptionValue
|
|
1093
|
+
|
|
819
1094
|
attr_accessor :budget_tokens
|
|
820
1095
|
attr_reader :type, :display
|
|
821
1096
|
|
|
@@ -841,6 +1116,8 @@ module ClaudeAgentSDK
|
|
|
841
1116
|
|
|
842
1117
|
# Disabled thinking: sets thinking tokens to 0
|
|
843
1118
|
class ThinkingConfigDisabled < Type
|
|
1119
|
+
include Type::OptionValue
|
|
1120
|
+
|
|
844
1121
|
attr_reader :type
|
|
845
1122
|
|
|
846
1123
|
def initialize(attributes = {})
|
|
@@ -851,6 +1128,8 @@ module ClaudeAgentSDK
|
|
|
851
1128
|
|
|
852
1129
|
# Agent definition configuration
|
|
853
1130
|
class AgentDefinition < Type
|
|
1131
|
+
include Type::OptionValue
|
|
1132
|
+
|
|
854
1133
|
attr_accessor :description, :prompt, :tools, :disallowed_tools, :model, :skills, :memory, :mcp_servers,
|
|
855
1134
|
:initial_prompt, :max_turns, :background, :effort, :permission_mode
|
|
856
1135
|
end
|
|
@@ -942,6 +1221,8 @@ module ClaudeAgentSDK
|
|
|
942
1221
|
|
|
943
1222
|
# Hook matcher configuration
|
|
944
1223
|
class HookMatcher < Type
|
|
1224
|
+
include Type::OptionValue
|
|
1225
|
+
|
|
945
1226
|
attr_accessor :matcher, :hooks, :timeout
|
|
946
1227
|
|
|
947
1228
|
def initialize(attributes = {})
|
|
@@ -1640,9 +1921,13 @@ module ClaudeAgentSDK
|
|
|
1640
1921
|
|
|
1641
1922
|
# MCP Server configurations
|
|
1642
1923
|
class McpStdioServerConfig < Type
|
|
1924
|
+
include Type::OptionValue
|
|
1925
|
+
|
|
1643
1926
|
attr_accessor :command, :args, :env
|
|
1644
1927
|
attr_reader :type
|
|
1645
1928
|
|
|
1929
|
+
inspect_filtered :env
|
|
1930
|
+
|
|
1646
1931
|
def initialize(attributes = {})
|
|
1647
1932
|
super
|
|
1648
1933
|
@type = 'stdio'
|
|
@@ -1657,9 +1942,13 @@ module ClaudeAgentSDK
|
|
|
1657
1942
|
end
|
|
1658
1943
|
|
|
1659
1944
|
class McpSSEServerConfig < Type
|
|
1945
|
+
include Type::OptionValue
|
|
1946
|
+
|
|
1660
1947
|
attr_accessor :url, :headers
|
|
1661
1948
|
attr_reader :type
|
|
1662
1949
|
|
|
1950
|
+
inspect_filtered :headers
|
|
1951
|
+
|
|
1663
1952
|
def initialize(attributes = {})
|
|
1664
1953
|
super
|
|
1665
1954
|
@type = 'sse'
|
|
@@ -1673,9 +1962,13 @@ module ClaudeAgentSDK
|
|
|
1673
1962
|
end
|
|
1674
1963
|
|
|
1675
1964
|
class McpHttpServerConfig < Type
|
|
1965
|
+
include Type::OptionValue
|
|
1966
|
+
|
|
1676
1967
|
attr_accessor :url, :headers
|
|
1677
1968
|
attr_reader :type
|
|
1678
1969
|
|
|
1970
|
+
inspect_filtered :headers
|
|
1971
|
+
|
|
1679
1972
|
def initialize(attributes = {})
|
|
1680
1973
|
super
|
|
1681
1974
|
@type = 'http'
|
|
@@ -1689,6 +1982,8 @@ module ClaudeAgentSDK
|
|
|
1689
1982
|
end
|
|
1690
1983
|
|
|
1691
1984
|
class McpSdkServerConfig < Type
|
|
1985
|
+
include Type::OptionValue
|
|
1986
|
+
|
|
1692
1987
|
attr_accessor :name, :instance
|
|
1693
1988
|
attr_reader :type
|
|
1694
1989
|
|
|
@@ -1704,6 +1999,8 @@ module ClaudeAgentSDK
|
|
|
1704
1999
|
|
|
1705
2000
|
# SDK Plugin configuration
|
|
1706
2001
|
class SdkPluginConfig < Type
|
|
2002
|
+
include Type::OptionValue
|
|
2003
|
+
|
|
1707
2004
|
attr_accessor :path
|
|
1708
2005
|
attr_reader :type
|
|
1709
2006
|
|
|
@@ -1719,6 +2016,8 @@ module ClaudeAgentSDK
|
|
|
1719
2016
|
|
|
1720
2017
|
# Sandbox network configuration
|
|
1721
2018
|
class SandboxNetworkConfig < Type
|
|
2019
|
+
include Type::OptionValue
|
|
2020
|
+
|
|
1722
2021
|
attr_accessor :allowed_domains, :denied_domains, :allow_managed_domains_only,
|
|
1723
2022
|
:allow_unix_sockets, :allow_all_unix_sockets, :allow_local_binding,
|
|
1724
2023
|
:allow_mach_lookup, :http_proxy_port, :socks_proxy_port
|
|
@@ -1740,6 +2039,8 @@ module ClaudeAgentSDK
|
|
|
1740
2039
|
|
|
1741
2040
|
# Sandbox filesystem configuration
|
|
1742
2041
|
class SandboxFilesystemConfig < Type
|
|
2042
|
+
include Type::OptionValue
|
|
2043
|
+
|
|
1743
2044
|
attr_accessor :allow_write, :deny_write, :deny_read, :allow_read, :allow_managed_read_paths_only
|
|
1744
2045
|
|
|
1745
2046
|
def to_h
|
|
@@ -1755,6 +2056,8 @@ module ClaudeAgentSDK
|
|
|
1755
2056
|
|
|
1756
2057
|
# Sandbox settings for isolated command execution
|
|
1757
2058
|
class SandboxSettings < Type
|
|
2059
|
+
include Type::OptionValue
|
|
2060
|
+
|
|
1758
2061
|
attr_accessor :enabled, :fail_if_unavailable, :auto_allow_bash_if_sandboxed,
|
|
1759
2062
|
:excluded_commands, :allow_unsandboxed_commands, :network, :filesystem,
|
|
1760
2063
|
:ignore_violations, :enable_weaker_nested_sandbox,
|
|
@@ -1786,6 +2089,8 @@ module ClaudeAgentSDK
|
|
|
1786
2089
|
# When set, the model is made aware of its remaining token budget so it can
|
|
1787
2090
|
# pace tool use and wrap up before the limit.
|
|
1788
2091
|
class TaskBudget < Type
|
|
2092
|
+
include Type::OptionValue
|
|
2093
|
+
|
|
1789
2094
|
attr_accessor :total
|
|
1790
2095
|
|
|
1791
2096
|
def to_h
|
|
@@ -1795,6 +2100,8 @@ module ClaudeAgentSDK
|
|
|
1795
2100
|
|
|
1796
2101
|
# System prompt file configuration — loads system prompt from a file path
|
|
1797
2102
|
class SystemPromptFile < Type
|
|
2103
|
+
include Type::OptionValue
|
|
2104
|
+
|
|
1798
2105
|
attr_accessor :path
|
|
1799
2106
|
attr_reader :type
|
|
1800
2107
|
|
|
@@ -1822,6 +2129,8 @@ module ClaudeAgentSDK
|
|
|
1822
2129
|
# before 2.1.265 a session with an +append+ prompt recorded it only when
|
|
1823
2130
|
# +snapshot+ was true. Older CLIs silently ignore it.
|
|
1824
2131
|
class SystemPromptPreset < Type
|
|
2132
|
+
include Type::OptionValue
|
|
2133
|
+
|
|
1825
2134
|
attr_reader :type
|
|
1826
2135
|
attr_accessor :preset, :append, :exclude_dynamic_sections, :snapshot
|
|
1827
2136
|
|
|
@@ -1844,6 +2153,8 @@ module ClaudeAgentSDK
|
|
|
1844
2153
|
# (+--system-prompt <prompt>+); the object form exists so +snapshot+ can be
|
|
1845
2154
|
# set alongside it (see SystemPromptPreset#snapshot for its semantics).
|
|
1846
2155
|
class SystemPromptCustom < Type
|
|
2156
|
+
include Type::OptionValue
|
|
2157
|
+
|
|
1847
2158
|
attr_reader :type
|
|
1848
2159
|
attr_accessor :prompt, :snapshot
|
|
1849
2160
|
|
|
@@ -1861,6 +2172,8 @@ module ClaudeAgentSDK
|
|
|
1861
2172
|
|
|
1862
2173
|
# Tools preset configuration
|
|
1863
2174
|
class ToolsPreset < Type
|
|
2175
|
+
include Type::OptionValue
|
|
2176
|
+
|
|
1864
2177
|
attr_reader :type
|
|
1865
2178
|
attr_accessor :preset
|
|
1866
2179
|
|
|
@@ -1876,6 +2189,9 @@ module ClaudeAgentSDK
|
|
|
1876
2189
|
|
|
1877
2190
|
# Claude Agent Options for configuring queries
|
|
1878
2191
|
class ClaudeAgentOptions < Type
|
|
2192
|
+
# `env` routinely carries credentials (ANTHROPIC_API_KEY, ...).
|
|
2193
|
+
inspect_filtered :env
|
|
2194
|
+
|
|
1879
2195
|
attr_accessor :allowed_tools, :system_prompt, :mcp_servers, :permission_mode,
|
|
1880
2196
|
:resume, :resume_session_at, :session_id, :max_turns, :disallowed_tools,
|
|
1881
2197
|
:model, :permission_prompt_tool_name, :cwd, :cli_path, :settings,
|
|
@@ -1955,14 +2271,16 @@ module ClaudeAgentSDK
|
|
|
1955
2271
|
|
|
1956
2272
|
def dup_with(**changes)
|
|
1957
2273
|
new_options = self.dup
|
|
1958
|
-
# A shallow #dup shares nested containers
|
|
1959
|
-
# (e.g. `variant.allowed_tools << 'Bash'`
|
|
1960
|
-
#
|
|
1961
|
-
#
|
|
1962
|
-
#
|
|
2274
|
+
# A shallow #dup shares nested containers and typed option values, so
|
|
2275
|
+
# mutating a derived copy (e.g. `variant.allowed_tools << 'Bash'` or
|
|
2276
|
+
# `variant.sandbox.enabled = false`) would bleed into the base and every
|
|
2277
|
+
# sibling — including the security-relevant allow/deny lists and sandbox
|
|
2278
|
+
# rules. Deep-dup Hash/Array containers and option value types (Type#
|
|
2279
|
+
# dup_for_options, including those nested inside containers such as
|
|
2280
|
+
# agents[:x]); every other leaf (procs, SDK MCP server instances, store
|
|
2281
|
+
# adapters) keeps its identity.
|
|
1963
2282
|
new_options.instance_variables.each do |ivar|
|
|
1964
|
-
|
|
1965
|
-
new_options.instance_variable_set(ivar, deep_dup_containers(value)) if value.is_a?(Hash) || value.is_a?(Array)
|
|
2283
|
+
new_options.instance_variable_set(ivar, Type.deep_dup_for_options(new_options.instance_variable_get(ivar)))
|
|
1966
2284
|
end
|
|
1967
2285
|
changes.each { |key, value| new_options[key] = value }
|
|
1968
2286
|
new_options
|
|
@@ -2120,13 +2438,17 @@ module ClaudeAgentSDK
|
|
|
2120
2438
|
# A callable receiving a zero-arg invocation; it MUST call it and
|
|
2121
2439
|
# return its value:
|
|
2122
2440
|
#
|
|
2123
|
-
# callback_wrapper: ->(invocation) {
|
|
2441
|
+
# callback_wrapper: ->(invocation) { MyApm.trace('agent.callback') { invocation.call } }
|
|
2124
2442
|
#
|
|
2125
2443
|
# The wrapper runs on the same execution context as the callback —
|
|
2126
|
-
# inside the worker thread in :thread mode
|
|
2127
|
-
# 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
|
|
2128
2445
|
# fiber in :inline mode. Exceptions propagate through it unchanged; it
|
|
2129
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.
|
|
2130
2452
|
def callback_wrapper=(value)
|
|
2131
2453
|
raise ArgumentError, "callback_wrapper must be a callable or nil (got #{value.inspect})" unless value.nil? || value.respond_to?(:call)
|
|
2132
2454
|
|
|
@@ -2160,12 +2482,20 @@ module ClaudeAgentSDK
|
|
|
2160
2482
|
defaults = ClaudeAgentSDK.default_options
|
|
2161
2483
|
return attributes unless defaults.any?
|
|
2162
2484
|
|
|
2163
|
-
# Start from configured defaults. Container values
|
|
2164
|
-
#
|
|
2165
|
-
#
|
|
2166
|
-
#
|
|
2485
|
+
# Start from configured defaults. Container values, typed option
|
|
2486
|
+
# values (SandboxSettings, SystemPromptPreset, AgentDefinition, ...)
|
|
2487
|
+
# and mutable Strings are recursively copied (Type.deep_dup_for_options)
|
|
2488
|
+
# so per-instance mutation (options.allowed_tools << 'Bash',
|
|
2489
|
+
# options.sandbox.enabled = false) can never corrupt the global
|
|
2490
|
+
# defaults or reach another session; other leaves (frozen Strings,
|
|
2491
|
+
# Procs, SdkMcpServer instances, store adapters) intentionally keep
|
|
2492
|
+
# identity. The stored defaults are a frozen snapshot
|
|
2493
|
+
# (Configuration#default_options=), and the copy is what makes each
|
|
2494
|
+
# session's containers and values mutable again — its Strings stay the
|
|
2495
|
+
# snapshot's frozen ones, so `options.model << 'x'` fails loudly rather
|
|
2496
|
+
# than reaching other sessions; reassign instead.
|
|
2167
2497
|
result = {}
|
|
2168
|
-
defaults.each { |key, value| result[option_key(key)] =
|
|
2498
|
+
defaults.each { |key, value| result[option_key(key)] = Type.deep_dup_for_options(value) }
|
|
2169
2499
|
attributes.each do |key, value|
|
|
2170
2500
|
key = option_key(key)
|
|
2171
2501
|
default_val = result[key]
|
|
@@ -2187,26 +2517,6 @@ module ClaudeAgentSDK
|
|
|
2187
2517
|
normalized = normalize_name(name)
|
|
2188
2518
|
respond_to?(:"#{normalized}=") ? normalized.to_sym : name
|
|
2189
2519
|
end
|
|
2190
|
-
|
|
2191
|
-
# Recurse ONLY into Hash/Array; leaves keep object identity (observer
|
|
2192
|
-
# factories, callbacks, SDK MCP server instances must not be duped).
|
|
2193
|
-
# Rebuild via dup.clear (never Hash#to_h / Array#map) to preserve
|
|
2194
|
-
# container SUBCLASSES: to_h flattens e.g. Rails'
|
|
2195
|
-
# HashWithIndifferentAccess into a plain Hash, silently breaking symbol
|
|
2196
|
-
# lookups on the copy (config[:type] == 'sdk' → nil).
|
|
2197
|
-
def deep_dup_containers(value)
|
|
2198
|
-
case value
|
|
2199
|
-
when Hash
|
|
2200
|
-
copy = value.dup.clear
|
|
2201
|
-
value.each { |k, v| copy[k] = deep_dup_containers(v) }
|
|
2202
|
-
copy
|
|
2203
|
-
when Array
|
|
2204
|
-
copy = value.dup.clear
|
|
2205
|
-
value.each { |v| copy << deep_dup_containers(v) }
|
|
2206
|
-
copy
|
|
2207
|
-
else value
|
|
2208
|
-
end
|
|
2209
|
-
end
|
|
2210
2520
|
end
|
|
2211
2521
|
|
|
2212
2522
|
# SDK MCP Tool definition
|