claude-agent-sdk 0.16.4 → 0.16.6

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.
@@ -57,80 +57,148 @@ module ClaudeAgentSDK
57
57
  # Available beta features that can be enabled via the betas option
58
58
  SDK_BETAS = %w[context-1m-2025-08-07].freeze
59
59
 
60
+ # Base class for all types.
61
+ class Type
62
+ def self.wrap(object)
63
+ return object if object.is_a?(self)
64
+ return nil if object.nil?
65
+
66
+ new(object)
67
+ end
68
+
69
+ def self.from_hash(hash)
70
+ return unless hash.is_a?(Hash)
71
+
72
+ new(hash)
73
+ end
74
+
75
+ def initialize(attributes = {})
76
+ assign_attributes(attributes) if attributes
77
+ super()
78
+ end
79
+
80
+ def [](name)
81
+ read_attribute(name)
82
+ end
83
+
84
+ def []=(name, value)
85
+ assign_attribute(name, value)
86
+ end
87
+
88
+ # Subclasses should override this to return a hash representation of the object.
89
+ def to_h
90
+ {}
91
+ end
92
+
93
+ private
94
+
95
+ # Allow camelCase attribute access
96
+ def method_missing(method_name, ...)
97
+ normalized = normalize_name(method_name)
98
+
99
+ if normalized != method_name.to_s && respond_to?(normalized)
100
+ public_send(normalized, ...)
101
+ else
102
+ super
103
+ end
104
+ end
105
+
106
+ def respond_to_missing?(method_name, include_private = false)
107
+ normalized = normalize_name(method_name)
108
+ (normalized != method_name.to_s && respond_to?(normalized)) || super
109
+ end
110
+
111
+ def assign_attributes(attributes)
112
+ raise ArgumentError, "When assigning attributes, you must pass a hash as an argument, #{attributes.inspect} passed." unless attributes.respond_to?(:each_pair)
113
+
114
+ return if attributes.empty?
115
+
116
+ attributes.each_pair { |name, value| assign_attribute(name, value) }
117
+ end
118
+
119
+ def assign_attribute(name, value)
120
+ setter = :"#{normalize_name(name)}="
121
+ public_send(setter, value) if respond_to?(setter)
122
+ end
123
+
124
+ def read_attribute(name)
125
+ getter = normalize_name(name)
126
+ public_send(getter) if respond_to?(getter)
127
+ end
128
+
129
+ def normalize_name(name)
130
+ name = name.dup.to_s
131
+ name.gsub!(/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-z\d])(?=[A-Z])/, "_")
132
+ name.tr!("-", "_")
133
+ name.downcase!
134
+ name
135
+ end
136
+
137
+ FALSE_VALUES = [
138
+ false, 0,
139
+ "0", :'0',
140
+ "f", :f,
141
+ "F", :F,
142
+ "false", :false, # rubocop:disable Lint/BooleanSymbol
143
+ "FALSE", :FALSE,
144
+ "off", :off,
145
+ "OFF", :OFF
146
+ ].to_set.freeze
147
+
148
+ private_constant :FALSE_VALUES
149
+
150
+ def coerce_boolean(value)
151
+ return if value.nil?
152
+
153
+ if value == ""
154
+ nil
155
+ else
156
+ !FALSE_VALUES.include?(value)
157
+ end
158
+ end
159
+ end
160
+
60
161
  # Content Blocks
61
162
 
62
163
  # Text content block
63
- class TextBlock
164
+ class TextBlock < Type
64
165
  attr_accessor :text
65
-
66
- def initialize(text:)
67
- @text = text
68
- end
69
166
  end
70
167
 
71
168
  # Thinking content block
72
- class ThinkingBlock
169
+ class ThinkingBlock < Type
73
170
  attr_accessor :thinking, :signature
74
-
75
- def initialize(thinking:, signature:)
76
- @thinking = thinking
77
- @signature = signature
78
- end
79
171
  end
80
172
 
81
173
  # Tool use content block
82
- class ToolUseBlock
174
+ class ToolUseBlock < Type
83
175
  attr_accessor :id, :name, :input
84
-
85
- def initialize(id:, name:, input:)
86
- @id = id
87
- @name = name
88
- @input = input
89
- end
90
176
  end
91
177
 
92
178
  # Tool result content block
93
- class ToolResultBlock
179
+ class ToolResultBlock < Type
94
180
  attr_accessor :tool_use_id, :content, :is_error
95
-
96
- def initialize(tool_use_id:, content: nil, is_error: nil)
97
- @tool_use_id = tool_use_id
98
- @content = content
99
- @is_error = is_error
100
- end
101
181
  end
102
182
 
103
183
  # Generic content block for types the SDK doesn't explicitly handle (e.g., "document", "image").
104
184
  # Preserves the raw hash data for forward compatibility with newer CLI versions.
105
- class UnknownBlock
185
+ class UnknownBlock < Type
106
186
  attr_accessor :type, :data
107
-
108
- def initialize(type:, data:)
109
- @type = type
110
- @data = data
111
- end
112
187
  end
113
188
 
114
189
  # Message Types
115
190
 
116
191
  # User message
117
- class UserMessage
192
+ class UserMessage < Type
118
193
  attr_accessor :content, :uuid, :parent_tool_use_id, :tool_use_result
119
194
 
120
- def initialize(content:, uuid: nil, parent_tool_use_id: nil, tool_use_result: nil)
121
- @content = content
122
- @uuid = uuid # Unique identifier for rewind support
123
- @parent_tool_use_id = parent_tool_use_id
124
- @tool_use_result = tool_use_result # Tool result data when message is a tool response
125
- end
126
-
127
195
  # Concatenated text of this message. Handles both String content
128
196
  # (plain-text user prompt) and Array-of-blocks content (typed content).
129
197
  # Returns "" when there is no text.
130
198
  def text
131
- case @content
132
- when String then @content
133
- when Array then @content.grep(TextBlock).map(&:text).join("\n\n")
199
+ case content
200
+ when String then content
201
+ when Array then content.grep(TextBlock).map(&:text).join("\n\n")
134
202
  else ''
135
203
  end
136
204
  end
@@ -139,39 +207,28 @@ module ClaudeAgentSDK
139
207
  end
140
208
 
141
209
  # Assistant message with content blocks
142
- class AssistantMessage
210
+ class AssistantMessage < Type
143
211
  attr_accessor :content, :model, :parent_tool_use_id, :error, :usage,
144
212
  :message_id, :stop_reason, :session_id, :uuid
145
213
 
146
- def initialize(content:, model:, parent_tool_use_id: nil, error: nil, usage: nil,
147
- message_id: nil, stop_reason: nil, session_id: nil, uuid: nil)
148
- @content = content
149
- @model = model
150
- @parent_tool_use_id = parent_tool_use_id
151
- @error = error # One of: authentication_failed, billing_error, rate_limit, invalid_request, server_error, unknown
152
- @usage = usage # Token usage info from the API response
153
- @message_id = message_id # Unique message identifier from the API (message.id)
154
- @stop_reason = stop_reason # Why the assistant stopped (e.g., "end_turn", "max_tokens")
155
- @session_id = session_id # Session the message belongs to
156
- @uuid = uuid # Unique message UUID in the session transcript
157
- end
158
-
159
214
  # Concatenated text across every TextBlock in this message's content.
160
215
  # Returns "" when the message has no text (e.g., a pure tool_use turn).
161
216
  def text
162
- Array(@content).grep(TextBlock).map(&:text).join("\n\n")
217
+ Array(content).grep(TextBlock).map(&:text).join("\n\n")
163
218
  end
164
219
 
165
220
  alias to_s text
166
221
  end
167
222
 
168
- # System message with metadata
169
- class SystemMessage
223
+ # System message with metadata.
224
+ # When constructed from a raw CLI hash, the whole hash is stored in `#data`
225
+ # unless the caller explicitly provides a `:data` entry.
226
+ class SystemMessage < Type
170
227
  attr_accessor :subtype, :data
171
228
 
172
- def initialize(subtype:, data:)
173
- @subtype = subtype
174
- @data = data
229
+ def initialize(attributes = {})
230
+ super
231
+ @data ||= attributes if attributes.is_a?(Hash)
175
232
  end
176
233
  end
177
234
 
@@ -180,229 +237,84 @@ module ClaudeAgentSDK
180
237
  attr_accessor :uuid, :session_id, :agents, :api_key_source, :betas,
181
238
  :claude_code_version, :cwd, :tools, :mcp_servers, :model,
182
239
  :permission_mode, :slash_commands, :output_style, :skills, :plugins,
183
- :fast_mode_state
184
-
185
- def initialize(subtype:, data:, uuid: nil, session_id: nil, agents: nil,
186
- api_key_source: nil, betas: nil, claude_code_version: nil,
187
- cwd: nil, tools: nil, mcp_servers: nil, model: nil,
188
- permission_mode: nil, slash_commands: nil, output_style: nil,
189
- skills: nil, plugins: nil, fast_mode_state: nil)
190
- super(subtype: subtype, data: data)
191
- @uuid = uuid
192
- @session_id = session_id
193
- @agents = agents
194
- @api_key_source = api_key_source
195
- @betas = betas
196
- @claude_code_version = claude_code_version
197
- @cwd = cwd
198
- @tools = tools
199
- @mcp_servers = mcp_servers
200
- @model = model
201
- @permission_mode = permission_mode
202
- @slash_commands = slash_commands
203
- @output_style = output_style
204
- @skills = skills
205
- @plugins = plugins
206
- @fast_mode_state = fast_mode_state # "off", "cooldown", or "on"
207
- end
240
+ :fast_mode_state # "off", "cooldown", or "on"
208
241
  end
209
242
 
210
243
  # Compact boundary system message (emitted after context compaction completes)
211
244
  class CompactBoundaryMessage < SystemMessage
212
- attr_accessor :uuid, :session_id, :compact_metadata
245
+ attr_accessor :uuid, :session_id
246
+ attr_reader :compact_metadata
213
247
 
214
- def initialize(subtype:, data:, uuid: nil, session_id: nil, compact_metadata: nil)
215
- super(subtype: subtype, data: data)
216
- @uuid = uuid
217
- @session_id = session_id
218
- @compact_metadata = compact_metadata
248
+ def compact_metadata=(value)
249
+ @compact_metadata = value.is_a?(Hash) ? CompactMetadata.new(value) : value
219
250
  end
220
251
  end
221
252
 
222
253
  # Metadata about a compaction event
223
- class CompactMetadata
254
+ class CompactMetadata < Type
224
255
  attr_accessor :pre_tokens, :post_tokens, :trigger, :custom_instructions, :preserved_segment
225
-
226
- def initialize(pre_tokens: nil, post_tokens: nil, trigger: nil, custom_instructions: nil, preserved_segment: nil)
227
- @pre_tokens = pre_tokens
228
- @post_tokens = post_tokens
229
- @trigger = trigger # "manual" or "auto"
230
- @custom_instructions = custom_instructions
231
- @preserved_segment = preserved_segment # Hash with head_uuid, anchor_uuid, tail_uuid
232
- end
233
-
234
- def self.from_hash(hash)
235
- return nil unless hash.is_a?(Hash)
236
-
237
- preserved = hash[:preserved_segment] || hash['preserved_segment'] ||
238
- hash[:preservedSegment] || hash['preservedSegment']
239
-
240
- new(
241
- pre_tokens: hash[:pre_tokens] || hash['pre_tokens'] || hash[:preTokens] || hash['preTokens'],
242
- post_tokens: hash[:post_tokens] || hash['post_tokens'] || hash[:postTokens] || hash['postTokens'],
243
- trigger: hash[:trigger] || hash['trigger'],
244
- custom_instructions: hash[:custom_instructions] || hash['custom_instructions'] ||
245
- hash[:customInstructions] || hash['customInstructions'],
246
- preserved_segment: preserved
247
- )
248
- end
249
256
  end
250
257
 
251
258
  # Status system message (compacting status, permission mode changes)
252
259
  class StatusMessage < SystemMessage
253
260
  attr_accessor :uuid, :session_id, :status, :permission_mode
254
-
255
- def initialize(subtype:, data:, uuid: nil, session_id: nil, status: nil, permission_mode: nil)
256
- super(subtype: subtype, data: data)
257
- @uuid = uuid
258
- @session_id = session_id
259
- @status = status # "compacting" or nil
260
- @permission_mode = permission_mode
261
- end
262
261
  end
263
262
 
264
263
  # API retry system message
265
264
  class APIRetryMessage < SystemMessage
266
265
  attr_accessor :uuid, :session_id, :attempt, :max_retries, :retry_delay_ms, :error_status, :error
267
-
268
- def initialize(subtype:, data:, uuid: nil, session_id: nil, attempt: nil, max_retries: nil,
269
- retry_delay_ms: nil, error_status: nil, error: nil)
270
- super(subtype: subtype, data: data)
271
- @uuid = uuid
272
- @session_id = session_id
273
- @attempt = attempt
274
- @max_retries = max_retries
275
- @retry_delay_ms = retry_delay_ms
276
- @error_status = error_status
277
- @error = error
278
- end
279
266
  end
280
267
 
281
268
  # Local command output system message
282
269
  class LocalCommandOutputMessage < SystemMessage
283
270
  attr_accessor :uuid, :session_id, :content
284
-
285
- def initialize(subtype:, data:, uuid: nil, session_id: nil, content: nil)
286
- super(subtype: subtype, data: data)
287
- @uuid = uuid
288
- @session_id = session_id
289
- @content = content
290
- end
291
271
  end
292
272
 
293
273
  # Hook started system message
294
274
  class HookStartedMessage < SystemMessage
295
275
  attr_accessor :uuid, :session_id, :hook_id, :hook_name, :hook_event
296
-
297
- def initialize(subtype:, data:, uuid: nil, session_id: nil, hook_id: nil, hook_name: nil, hook_event: nil)
298
- super(subtype: subtype, data: data)
299
- @uuid = uuid
300
- @session_id = session_id
301
- @hook_id = hook_id
302
- @hook_name = hook_name
303
- @hook_event = hook_event
304
- end
305
276
  end
306
277
 
307
278
  # Hook progress system message
308
279
  class HookProgressMessage < SystemMessage
309
280
  attr_accessor :uuid, :session_id, :hook_id, :hook_name, :hook_event, :stdout, :stderr, :output
310
-
311
- def initialize(subtype:, data:, uuid: nil, session_id: nil, hook_id: nil, hook_name: nil,
312
- hook_event: nil, stdout: nil, stderr: nil, output: nil)
313
- super(subtype: subtype, data: data)
314
- @uuid = uuid
315
- @session_id = session_id
316
- @hook_id = hook_id
317
- @hook_name = hook_name
318
- @hook_event = hook_event
319
- @stdout = stdout
320
- @stderr = stderr
321
- @output = output
322
- end
323
281
  end
324
282
 
325
283
  # Hook response system message
326
284
  class HookResponseMessage < SystemMessage
327
285
  attr_accessor :uuid, :session_id, :hook_id, :hook_name, :hook_event,
328
- :output, :stdout, :stderr, :exit_code, :outcome
329
-
330
- def initialize(subtype:, data:, uuid: nil, session_id: nil, hook_id: nil, hook_name: nil,
331
- hook_event: nil, output: nil, stdout: nil, stderr: nil, exit_code: nil, outcome: nil)
332
- super(subtype: subtype, data: data)
333
- @uuid = uuid
334
- @session_id = session_id
335
- @hook_id = hook_id
336
- @hook_name = hook_name
337
- @hook_event = hook_event
338
- @output = output
339
- @stdout = stdout
340
- @stderr = stderr
341
- @exit_code = exit_code
342
- @outcome = outcome # "success", "error", or "cancelled"
343
- end
286
+ :output, :stdout, :stderr, :exit_code,
287
+ :outcome # "success", "error", or "cancelled"
344
288
  end
345
289
 
346
290
  # Session state changed system message
347
291
  class SessionStateChangedMessage < SystemMessage
348
- attr_accessor :uuid, :session_id, :state
349
-
350
- def initialize(subtype:, data:, uuid: nil, session_id: nil, state: nil)
351
- super(subtype: subtype, data: data)
352
- @uuid = uuid
353
- @session_id = session_id
354
- @state = state # "idle", "running", or "requires_action"
355
- end
292
+ attr_accessor :uuid, :session_id,
293
+ :state # "idle", "running", or "requires_action"
356
294
  end
357
295
 
358
296
  # Files persisted system message
359
297
  class FilesPersistedMessage < SystemMessage
360
298
  attr_accessor :uuid, :session_id, :files, :failed, :processed_at
361
-
362
- def initialize(subtype:, data:, uuid: nil, session_id: nil, files: nil, failed: nil, processed_at: nil)
363
- super(subtype: subtype, data: data)
364
- @uuid = uuid
365
- @session_id = session_id
366
- @files = files # Array of { filename:, file_id: }
367
- @failed = failed # Array of { filename:, error: }
368
- @processed_at = processed_at
369
- end
370
299
  end
371
300
 
372
301
  # Elicitation complete system message
373
302
  class ElicitationCompleteMessage < SystemMessage
374
303
  attr_accessor :uuid, :session_id, :mcp_server_name, :elicitation_id
375
-
376
- def initialize(subtype:, data:, uuid: nil, session_id: nil, mcp_server_name: nil, elicitation_id: nil)
377
- super(subtype: subtype, data: data)
378
- @uuid = uuid
379
- @session_id = session_id
380
- @mcp_server_name = mcp_server_name
381
- @elicitation_id = elicitation_id
382
- end
383
304
  end
384
305
 
385
306
  # Task lifecycle notification statuses
386
307
  TASK_NOTIFICATION_STATUSES = %w[completed failed stopped].freeze
387
308
 
388
309
  # Typed usage data for task progress and notifications
389
- class TaskUsage
310
+ class TaskUsage < Type
390
311
  attr_accessor :total_tokens, :tool_uses, :duration_ms
391
312
 
392
- def initialize(total_tokens: 0, tool_uses: 0, duration_ms: 0)
393
- @total_tokens = total_tokens
394
- @tool_uses = tool_uses
395
- @duration_ms = duration_ms
396
- end
397
-
398
- def self.from_hash(hash)
399
- return nil unless hash.is_a?(Hash)
400
-
401
- new(
402
- total_tokens: hash[:total_tokens] || hash['total_tokens'] || hash[:totalTokens] || hash['totalTokens'] || 0,
403
- tool_uses: hash[:tool_uses] || hash['tool_uses'] || hash[:toolUses] || hash['toolUses'] || 0,
404
- duration_ms: hash[:duration_ms] || hash['duration_ms'] || hash[:durationMs] || hash['durationMs'] || 0
405
- )
313
+ def initialize(attributes = {})
314
+ super
315
+ @total_tokens ||= 0
316
+ @tool_uses ||= 0
317
+ @duration_ms ||= 0
406
318
  end
407
319
  end
408
320
 
@@ -410,151 +322,54 @@ module ClaudeAgentSDK
410
322
  class TaskStartedMessage < SystemMessage
411
323
  attr_accessor :task_id, :description, :uuid, :session_id, :tool_use_id, :task_type,
412
324
  :workflow_name, :prompt
413
-
414
- def initialize(subtype:, data:, task_id:, description:, uuid:, session_id:,
415
- tool_use_id: nil, task_type: nil, workflow_name: nil, prompt: nil)
416
- super(subtype: subtype, data: data)
417
- @task_id = task_id
418
- @description = description
419
- @uuid = uuid
420
- @session_id = session_id
421
- @tool_use_id = tool_use_id
422
- @task_type = task_type
423
- @workflow_name = workflow_name
424
- @prompt = prompt
425
- end
426
325
  end
427
326
 
428
327
  # Task progress system message (periodic update from a running task)
429
328
  class TaskProgressMessage < SystemMessage
430
329
  attr_accessor :task_id, :description, :usage, :uuid, :session_id, :tool_use_id, :last_tool_name, :summary
431
-
432
- def initialize(subtype:, data:, task_id:, description:, usage:, uuid:, session_id:,
433
- tool_use_id: nil, last_tool_name: nil, summary: nil)
434
- super(subtype: subtype, data: data)
435
- @task_id = task_id
436
- @description = description
437
- @usage = usage
438
- @uuid = uuid
439
- @session_id = session_id
440
- @tool_use_id = tool_use_id
441
- @last_tool_name = last_tool_name
442
- @summary = summary
443
- end
444
330
  end
445
331
 
446
332
  # Task notification system message (task completed/failed/stopped)
447
333
  class TaskNotificationMessage < SystemMessage
448
334
  attr_accessor :task_id, :status, :output_file, :summary, :uuid, :session_id, :tool_use_id, :usage
449
-
450
- def initialize(subtype:, data:, task_id:, status:, output_file:, summary:, uuid:, session_id:,
451
- tool_use_id: nil, usage: nil)
452
- super(subtype: subtype, data: data)
453
- @task_id = task_id
454
- @status = status
455
- @output_file = output_file
456
- @summary = summary
457
- @uuid = uuid
458
- @session_id = session_id
459
- @tool_use_id = tool_use_id
460
- @usage = usage
461
- end
462
335
  end
463
336
 
464
337
  # Result message with cost and usage information
465
- class ResultMessage
338
+ class ResultMessage < Type
466
339
  attr_accessor :subtype, :duration_ms, :duration_api_ms, :is_error,
467
340
  :num_turns, :session_id, :stop_reason, :total_cost_usd, :usage,
468
- :result, :structured_output, :model_usage, :permission_denials, :errors,
469
- :uuid, :fast_mode_state
470
-
471
- def initialize(subtype:, duration_ms:, duration_api_ms:, is_error:,
472
- num_turns:, session_id:, stop_reason: nil, total_cost_usd: nil,
473
- usage: nil, result: nil, structured_output: nil,
474
- model_usage: nil, permission_denials: nil, errors: nil,
475
- uuid: nil, fast_mode_state: nil)
476
- @subtype = subtype
477
- @duration_ms = duration_ms
478
- @duration_api_ms = duration_api_ms
479
- @is_error = is_error
480
- @num_turns = num_turns
481
- @session_id = session_id
482
- @stop_reason = stop_reason
483
- @total_cost_usd = total_cost_usd
484
- @usage = usage
485
- @result = result
486
- @structured_output = structured_output
487
- @model_usage = model_usage # Hash of { model_name => usage_data }
488
- @permission_denials = permission_denials # Array of { tool_name:, tool_use_id:, tool_input: }
489
- @errors = errors # Array of error strings (present on error subtypes)
490
- @uuid = uuid
491
- @fast_mode_state = fast_mode_state # "off", "cooldown", or "on"
492
- end
341
+ :result, :structured_output,
342
+ :model_usage, # Hash of { model_name => usage_data }
343
+ :permission_denials, # Array of { tool_name:, tool_use_id:, tool_input: }
344
+ :errors, # Array of error strings (present on error subtypes)
345
+ :uuid,
346
+ :fast_mode_state # "off", "cooldown", or "on"
493
347
  end
494
348
 
495
349
  # Stream event for partial message updates
496
- class StreamEvent
350
+ class StreamEvent < Type
497
351
  attr_accessor :uuid, :session_id, :event, :parent_tool_use_id
498
-
499
- def initialize(uuid:, session_id:, event:, parent_tool_use_id: nil)
500
- @uuid = uuid
501
- @session_id = session_id
502
- @event = event
503
- @parent_tool_use_id = parent_tool_use_id
504
- end
505
352
  end
506
353
 
507
354
  # Tool progress message (type: 'tool_progress')
508
- class ToolProgressMessage
355
+ class ToolProgressMessage < Type
509
356
  attr_accessor :uuid, :session_id, :tool_use_id, :tool_name, :parent_tool_use_id,
510
357
  :elapsed_time_seconds, :task_id
511
-
512
- def initialize(uuid: nil, session_id: nil, tool_use_id: nil, tool_name: nil,
513
- parent_tool_use_id: nil, elapsed_time_seconds: nil, task_id: nil)
514
- @uuid = uuid
515
- @session_id = session_id
516
- @tool_use_id = tool_use_id
517
- @tool_name = tool_name
518
- @parent_tool_use_id = parent_tool_use_id
519
- @elapsed_time_seconds = elapsed_time_seconds
520
- @task_id = task_id
521
- end
522
358
  end
523
359
 
524
360
  # Auth status message (type: 'auth_status')
525
- class AuthStatusMessage
361
+ class AuthStatusMessage < Type
526
362
  attr_accessor :uuid, :session_id, :is_authenticating, :output, :error
527
-
528
- def initialize(uuid: nil, session_id: nil, is_authenticating: nil, output: nil, error: nil)
529
- @uuid = uuid
530
- @session_id = session_id
531
- @is_authenticating = is_authenticating
532
- @output = output
533
- @error = error
534
- end
535
363
  end
536
364
 
537
365
  # Tool use summary message (type: 'tool_use_summary')
538
- class ToolUseSummaryMessage
366
+ class ToolUseSummaryMessage < Type
539
367
  attr_accessor :uuid, :session_id, :summary, :preceding_tool_use_ids
540
-
541
- def initialize(uuid: nil, session_id: nil, summary: nil, preceding_tool_use_ids: nil)
542
- @uuid = uuid
543
- @session_id = session_id
544
- @summary = summary
545
- @preceding_tool_use_ids = preceding_tool_use_ids
546
- end
547
368
  end
548
369
 
549
370
  # Prompt suggestion message (type: 'prompt_suggestion')
550
- class PromptSuggestionMessage
371
+ class PromptSuggestionMessage < Type
551
372
  attr_accessor :uuid, :session_id, :suggestion
552
-
553
- def initialize(uuid: nil, session_id: nil, suggestion: nil)
554
- @uuid = uuid
555
- @session_id = session_id
556
- @suggestion = suggestion
557
- end
558
373
  end
559
374
 
560
375
  # Type constants for rate limit statuses
@@ -564,32 +379,28 @@ module ClaudeAgentSDK
564
379
  RATE_LIMIT_TYPES = %w[five_hour seven_day seven_day_opus seven_day_sonnet overage].freeze
565
380
 
566
381
  # Rate limit info with typed fields
567
- class RateLimitInfo
382
+ class RateLimitInfo < Type
568
383
  attr_accessor :status, :resets_at, :rate_limit_type, :utilization,
569
384
  :overage_status, :overage_resets_at, :overage_disabled_reason, :raw
570
385
 
571
- def initialize(status:, resets_at: nil, rate_limit_type: nil, utilization: nil,
572
- overage_status: nil, overage_resets_at: nil, overage_disabled_reason: nil, raw: {})
573
- @status = status
574
- @resets_at = resets_at
575
- @rate_limit_type = rate_limit_type
576
- @utilization = utilization
577
- @overage_status = overage_status
578
- @overage_resets_at = overage_resets_at
579
- @overage_disabled_reason = overage_disabled_reason
580
- @raw = raw
386
+ def initialize(attributes = {})
387
+ super
388
+ @raw ||= {}
581
389
  end
582
390
  end
583
391
 
584
392
  # Rate limit event emitted when rate limit info changes
585
- class RateLimitEvent
586
- attr_accessor :rate_limit_info, :uuid, :session_id
393
+ class RateLimitEvent < Type
394
+ attr_accessor :uuid, :session_id, :raw_data
395
+ attr_reader :rate_limit_info
396
+
397
+ def initialize(attributes = {})
398
+ super
399
+ @rate_limit_info ||= RateLimitInfo.new
400
+ end
587
401
 
588
- def initialize(rate_limit_info:, uuid:, session_id:, raw_data: nil)
589
- @rate_limit_info = rate_limit_info
590
- @uuid = uuid
591
- @session_id = session_id
592
- @raw_data = raw_data
402
+ def rate_limit_info=(value)
403
+ @rate_limit_info = value.is_a?(Hash) ? RateLimitInfo.new(value.merge(raw: value)) : value
593
404
  end
594
405
 
595
406
  # Backward-compatible accessor returning the full raw event payload
@@ -599,82 +410,89 @@ module ClaudeAgentSDK
599
410
  end
600
411
 
601
412
  # Thinking configuration types
413
+ #
414
+ # `display` controls how thinking content appears in responses. Valid values
415
+ # are `"summarized"` (plaintext summary) and `"omitted"` (empty thinking
416
+ # field, signature only). Defaults are model-dependent: Opus 4.6/Sonnet 4.6
417
+ # default to `"summarized"`; Opus 4.7 and Mythos Preview default to
418
+ # `"omitted"`. Pass `display: "summarized"` explicitly on Opus 4.7 to get
419
+ # visible thinking text. Not supported with `ThinkingConfigDisabled`.
420
+ THINKING_DISPLAY_VALUES = %w[summarized omitted].freeze
602
421
 
603
422
  # Adaptive thinking: uses a default budget of 32000 tokens
604
- class ThinkingConfigAdaptive
605
- attr_accessor :type
423
+ class ThinkingConfigAdaptive < Type
424
+ attr_reader :type, :display
606
425
 
607
- def initialize
426
+ def initialize(attributes = {})
427
+ super
608
428
  @type = 'adaptive'
609
429
  end
430
+
431
+ def display=(value)
432
+ @display = validate_display(value)
433
+ end
434
+
435
+ private
436
+
437
+ def validate_display(value)
438
+ return nil if value.nil?
439
+ return value if THINKING_DISPLAY_VALUES.include?(value.to_s)
440
+
441
+ raise ArgumentError,
442
+ "invalid thinking display #{value.inspect}; expected one of #{THINKING_DISPLAY_VALUES.inspect}"
443
+ end
610
444
  end
611
445
 
612
446
  # Enabled thinking: uses a user-specified budget
613
- class ThinkingConfigEnabled
614
- attr_accessor :type, :budget_tokens
447
+ class ThinkingConfigEnabled < Type
448
+ attr_accessor :budget_tokens
449
+ attr_reader :type, :display
615
450
 
616
- def initialize(budget_tokens:)
451
+ def initialize(attributes = {})
452
+ super
617
453
  @type = 'enabled'
618
- @budget_tokens = budget_tokens
454
+ end
455
+
456
+ def display=(value)
457
+ @display = validate_display(value)
458
+ end
459
+
460
+ private
461
+
462
+ def validate_display(value)
463
+ return nil if value.nil?
464
+ return value if THINKING_DISPLAY_VALUES.include?(value.to_s)
465
+
466
+ raise ArgumentError,
467
+ "invalid thinking display #{value.inspect}; expected one of #{THINKING_DISPLAY_VALUES.inspect}"
619
468
  end
620
469
  end
621
470
 
622
471
  # Disabled thinking: sets thinking tokens to 0
623
- class ThinkingConfigDisabled
624
- attr_accessor :type
472
+ class ThinkingConfigDisabled < Type
473
+ attr_reader :type
625
474
 
626
- def initialize
475
+ def initialize(attributes = {})
476
+ super
627
477
  @type = 'disabled'
628
478
  end
629
479
  end
630
480
 
631
481
  # Agent definition configuration
632
- class AgentDefinition
482
+ class AgentDefinition < Type
633
483
  attr_accessor :description, :prompt, :tools, :disallowed_tools, :model, :skills, :memory, :mcp_servers,
634
484
  :initial_prompt, :max_turns, :background, :effort, :permission_mode
635
-
636
- def initialize(description:, prompt:, tools: nil, disallowed_tools: nil, model: nil, skills: nil,
637
- memory: nil, mcp_servers: nil, initial_prompt: nil, max_turns: nil,
638
- background: nil, effort: nil, permission_mode: nil)
639
- @description = description
640
- @prompt = prompt
641
- @tools = tools
642
- @disallowed_tools = disallowed_tools # Array of tool names to disallow
643
- @model = model
644
- @skills = skills # Array of skill names
645
- @memory = memory # One of: 'user', 'project', 'local'
646
- @mcp_servers = mcp_servers # Array of server names or config hashes
647
- @initial_prompt = initial_prompt # Initial prompt sent when agent starts
648
- @max_turns = max_turns # Maximum conversation turns for the agent
649
- @background = background # Whether this agent runs in background
650
- @effort = effort # See ClaudeAgentSDK::EFFORT_LEVELS or an Integer
651
- @permission_mode = permission_mode # Permission mode for the agent
652
- end
653
485
  end
654
486
 
655
487
  # Permission rule value
656
- class PermissionRuleValue
488
+ class PermissionRuleValue < Type
657
489
  attr_accessor :tool_name, :rule_content
658
-
659
- def initialize(tool_name:, rule_content: nil)
660
- @tool_name = tool_name
661
- @rule_content = rule_content
662
- end
663
490
  end
664
491
 
665
492
  # Permission update configuration
666
- class PermissionUpdate
493
+ class PermissionUpdate < Type
667
494
  attr_accessor :type, :rules, :behavior, :mode, :directories, :destination
668
495
 
669
- def initialize(type:, rules: nil, behavior: nil, mode: nil, directories: nil, destination: nil)
670
- @type = type
671
- @rules = rules
672
- @behavior = behavior
673
- @mode = mode
674
- @directories = directories
675
- @destination = destination
676
- end
677
-
678
496
  def to_h
679
497
  result = { type: @type }
680
498
  result[:destination] = @destination if @destination
@@ -701,452 +519,342 @@ module ClaudeAgentSDK
701
519
  end
702
520
 
703
521
  # Tool permission context
704
- class ToolPermissionContext
522
+ class ToolPermissionContext < Type
705
523
  attr_accessor :signal, :suggestions, :tool_use_id, :agent_id
706
524
 
707
- def initialize(signal: nil, suggestions: [], tool_use_id: nil, agent_id: nil)
708
- @signal = signal
709
- @suggestions = suggestions
710
- @tool_use_id = tool_use_id # Unique ID for this tool call within the assistant message
711
- @agent_id = agent_id # Sub-agent ID if running within an agent context
525
+ def initialize(attributes = {})
526
+ super
527
+ @suggestions ||= []
712
528
  end
713
529
  end
714
530
 
715
531
  # Permission results
716
- class PermissionResultAllow
717
- attr_accessor :behavior, :updated_input, :updated_permissions
532
+ class PermissionResultAllow < Type
533
+ attr_accessor :updated_input, :updated_permissions
534
+ attr_reader :behavior
718
535
 
719
- def initialize(updated_input: nil, updated_permissions: nil)
536
+ def initialize(attributes = {})
537
+ super
720
538
  @behavior = 'allow'
721
- @updated_input = updated_input
722
- @updated_permissions = updated_permissions
723
539
  end
724
540
  end
725
541
 
726
- class PermissionResultDeny
727
- attr_accessor :behavior, :message, :interrupt
542
+ class PermissionResultDeny < Type
543
+ attr_accessor :message, :interrupt
544
+ attr_reader :behavior
728
545
 
729
- def initialize(message: '', interrupt: false)
546
+ def initialize(attributes = {})
547
+ super
730
548
  @behavior = 'deny'
731
- @message = message
732
- @interrupt = interrupt
549
+ @message ||= ''
550
+ @interrupt = false if @interrupt.nil?
733
551
  end
734
552
  end
735
553
 
736
554
  # Hook matcher configuration
737
- class HookMatcher
555
+ class HookMatcher < Type
738
556
  attr_accessor :matcher, :hooks, :timeout
739
557
 
740
- def initialize(matcher: nil, hooks: [], timeout: nil)
741
- @matcher = matcher
742
- @hooks = hooks
743
- @timeout = timeout # Timeout in seconds for hook execution
558
+ def initialize(attributes = {})
559
+ super
560
+ @hooks ||= []
744
561
  end
745
562
  end
746
563
 
747
564
  # Hook context passed to hook callbacks
748
- class HookContext
565
+ class HookContext < Type
749
566
  attr_accessor :signal
750
-
751
- def initialize(signal: nil)
752
- @signal = signal
753
- end
754
567
  end
755
568
 
756
569
  # Base hook input with common fields
757
- class BaseHookInput
570
+ class BaseHookInput < Type
758
571
  attr_accessor :session_id, :transcript_path, :cwd, :permission_mode
759
-
760
- def initialize(session_id: nil, transcript_path: nil, cwd: nil, permission_mode: nil)
761
- @session_id = session_id
762
- @transcript_path = transcript_path
763
- @cwd = cwd
764
- @permission_mode = permission_mode
765
- end
572
+ attr_reader :hook_event_name
766
573
  end
767
574
 
768
575
  # PreToolUse hook input
769
576
  class PreToolUseHookInput < BaseHookInput
770
- attr_accessor :hook_event_name, :tool_name, :tool_input, :tool_use_id, :agent_id, :agent_type
577
+ attr_accessor :tool_name, :tool_input, :tool_use_id, :agent_id, :agent_type
771
578
 
772
- def initialize(hook_event_name: 'PreToolUse', tool_name: nil, tool_input: nil, tool_use_id: nil,
773
- agent_id: nil, agent_type: nil, **base_args)
774
- super(**base_args)
775
- @hook_event_name = hook_event_name
776
- @tool_name = tool_name
777
- @tool_input = tool_input
778
- @tool_use_id = tool_use_id
779
- @agent_id = agent_id
780
- @agent_type = agent_type
579
+ def initialize(attributes = {})
580
+ super
581
+ @hook_event_name = 'PreToolUse'
781
582
  end
782
583
  end
783
584
 
784
585
  # PostToolUse hook input
785
586
  class PostToolUseHookInput < BaseHookInput
786
- attr_accessor :hook_event_name, :tool_name, :tool_input, :tool_response, :tool_use_id, :agent_id, :agent_type
587
+ attr_accessor :tool_name, :tool_input, :tool_response, :tool_use_id, :agent_id, :agent_type
787
588
 
788
- def initialize(hook_event_name: 'PostToolUse', tool_name: nil, tool_input: nil, tool_response: nil,
789
- tool_use_id: nil, agent_id: nil, agent_type: nil, **base_args)
790
- super(**base_args)
791
- @hook_event_name = hook_event_name
792
- @tool_name = tool_name
793
- @tool_input = tool_input
794
- @tool_response = tool_response
795
- @tool_use_id = tool_use_id
796
- @agent_id = agent_id
797
- @agent_type = agent_type
589
+ def initialize(attributes = {})
590
+ super
591
+ @hook_event_name = 'PostToolUse'
798
592
  end
799
593
  end
800
594
 
801
595
  # UserPromptSubmit hook input
802
596
  class UserPromptSubmitHookInput < BaseHookInput
803
- attr_accessor :hook_event_name, :prompt
597
+ attr_accessor :prompt
804
598
 
805
- def initialize(hook_event_name: 'UserPromptSubmit', prompt: nil, **base_args)
806
- super(**base_args)
807
- @hook_event_name = hook_event_name
808
- @prompt = prompt
599
+ def initialize(attributes = {})
600
+ super
601
+ @hook_event_name = 'UserPromptSubmit'
809
602
  end
810
603
  end
811
604
 
812
605
  # Stop hook input
813
606
  class StopHookInput < BaseHookInput
814
- attr_accessor :hook_event_name, :stop_hook_active, :last_assistant_message
607
+ attr_accessor :stop_hook_active, :last_assistant_message
815
608
 
816
- def initialize(hook_event_name: 'Stop', stop_hook_active: false, last_assistant_message: nil, **base_args)
817
- super(**base_args)
818
- @hook_event_name = hook_event_name
819
- @stop_hook_active = stop_hook_active
820
- @last_assistant_message = last_assistant_message
609
+ def initialize(attributes = {})
610
+ super
611
+ @hook_event_name = 'Stop'
612
+ @stop_hook_active = false if @stop_hook_active.nil?
821
613
  end
822
614
  end
823
615
 
824
616
  # SubagentStop hook input
825
617
  class SubagentStopHookInput < BaseHookInput
826
- attr_accessor :hook_event_name, :stop_hook_active, :agent_id, :agent_transcript_path, :agent_type,
618
+ attr_accessor :stop_hook_active, :agent_id, :agent_transcript_path, :agent_type,
827
619
  :last_assistant_message
828
620
 
829
- def initialize(hook_event_name: 'SubagentStop', stop_hook_active: false, agent_id: nil,
830
- agent_transcript_path: nil, agent_type: nil, last_assistant_message: nil, **base_args)
831
- super(**base_args)
832
- @hook_event_name = hook_event_name
833
- @stop_hook_active = stop_hook_active
834
- @agent_id = agent_id
835
- @agent_transcript_path = agent_transcript_path
836
- @agent_type = agent_type
837
- @last_assistant_message = last_assistant_message
621
+ def initialize(attributes = {})
622
+ super
623
+ @hook_event_name = 'SubagentStop'
624
+ @stop_hook_active = false if @stop_hook_active.nil?
838
625
  end
839
626
  end
840
627
 
841
628
  # PostToolUseFailure hook input
842
629
  class PostToolUseFailureHookInput < BaseHookInput
843
- attr_accessor :hook_event_name, :tool_name, :tool_input, :tool_use_id, :error, :is_interrupt,
630
+ attr_accessor :tool_name, :tool_input, :tool_use_id, :error, :is_interrupt,
844
631
  :agent_id, :agent_type
845
632
 
846
- def initialize(hook_event_name: 'PostToolUseFailure', tool_name: nil, tool_input: nil, tool_use_id: nil,
847
- error: nil, is_interrupt: nil, agent_id: nil, agent_type: nil, **base_args)
848
- super(**base_args)
849
- @hook_event_name = hook_event_name
850
- @tool_name = tool_name
851
- @tool_input = tool_input
852
- @tool_use_id = tool_use_id
853
- @error = error
854
- @is_interrupt = is_interrupt
855
- @agent_id = agent_id
856
- @agent_type = agent_type
633
+ def initialize(attributes = {})
634
+ super
635
+ @hook_event_name = 'PostToolUseFailure'
857
636
  end
858
637
  end
859
638
 
860
639
  # Notification hook input
861
640
  class NotificationHookInput < BaseHookInput
862
- attr_accessor :hook_event_name, :message, :title, :notification_type
641
+ attr_accessor :message, :title, :notification_type
863
642
 
864
- def initialize(hook_event_name: 'Notification', message: nil, title: nil, notification_type: nil, **base_args)
865
- super(**base_args)
866
- @hook_event_name = hook_event_name
867
- @message = message
868
- @title = title
869
- @notification_type = notification_type
643
+ def initialize(attributes = {})
644
+ super
645
+ @hook_event_name = 'Notification'
870
646
  end
871
647
  end
872
648
 
873
649
  # SubagentStart hook input
874
650
  class SubagentStartHookInput < BaseHookInput
875
- attr_accessor :hook_event_name, :agent_id, :agent_type
651
+ attr_accessor :agent_id, :agent_type
876
652
 
877
- def initialize(hook_event_name: 'SubagentStart', agent_id: nil, agent_type: nil, **base_args)
878
- super(**base_args)
879
- @hook_event_name = hook_event_name
880
- @agent_id = agent_id
881
- @agent_type = agent_type
653
+ def initialize(attributes = {})
654
+ super
655
+ @hook_event_name = 'SubagentStart'
882
656
  end
883
657
  end
884
658
 
885
659
  # PermissionRequest hook input
886
660
  class PermissionRequestHookInput < BaseHookInput
887
- attr_accessor :hook_event_name, :tool_name, :tool_input, :permission_suggestions, :agent_id, :agent_type
661
+ attr_accessor :tool_name, :tool_input, :permission_suggestions, :agent_id, :agent_type
888
662
 
889
- def initialize(hook_event_name: 'PermissionRequest', tool_name: nil, tool_input: nil, permission_suggestions: nil,
890
- agent_id: nil, agent_type: nil, **base_args)
891
- super(**base_args)
892
- @hook_event_name = hook_event_name
893
- @tool_name = tool_name
894
- @tool_input = tool_input
895
- @permission_suggestions = permission_suggestions
896
- @agent_id = agent_id
897
- @agent_type = agent_type
663
+ def initialize(attributes = {})
664
+ super
665
+ @hook_event_name = 'PermissionRequest'
898
666
  end
899
667
  end
900
668
 
901
669
  # PreCompact hook input
902
670
  class PreCompactHookInput < BaseHookInput
903
- attr_accessor :hook_event_name, :trigger, :custom_instructions
671
+ attr_accessor :trigger, :custom_instructions
904
672
 
905
- def initialize(hook_event_name: 'PreCompact', trigger: nil, custom_instructions: nil, **base_args)
906
- super(**base_args)
907
- @hook_event_name = hook_event_name
908
- @trigger = trigger
909
- @custom_instructions = custom_instructions
673
+ def initialize(attributes = {})
674
+ super
675
+ @hook_event_name = 'PreCompact'
910
676
  end
911
677
  end
912
678
 
913
679
  # SessionStart hook input
914
680
  class SessionStartHookInput < BaseHookInput
915
- attr_accessor :hook_event_name, :source, :agent_type, :model
681
+ attr_accessor :source, :agent_type, :model
916
682
 
917
- def initialize(hook_event_name: 'SessionStart', source: nil, agent_type: nil, model: nil, **base_args)
918
- super(**base_args)
919
- @hook_event_name = hook_event_name
920
- @source = source # "startup", "resume", "clear", "compact"
921
- @agent_type = agent_type
922
- @model = model
683
+ def initialize(attributes = {})
684
+ super
685
+ @hook_event_name = 'SessionStart'
923
686
  end
924
687
  end
925
688
 
926
689
  # SessionEnd hook input
927
690
  class SessionEndHookInput < BaseHookInput
928
- attr_accessor :hook_event_name, :reason
691
+ attr_accessor :reason
929
692
 
930
- def initialize(hook_event_name: 'SessionEnd', reason: nil, **base_args)
931
- super(**base_args)
932
- @hook_event_name = hook_event_name
933
- @reason = reason
693
+ def initialize(attributes = {})
694
+ super
695
+ @hook_event_name = 'SessionEnd'
934
696
  end
935
697
  end
936
698
 
937
699
  # Setup hook input
938
700
  class SetupHookInput < BaseHookInput
939
- attr_accessor :hook_event_name, :trigger
701
+ attr_accessor :trigger
940
702
 
941
- def initialize(hook_event_name: 'Setup', trigger: nil, **base_args)
942
- super(**base_args)
943
- @hook_event_name = hook_event_name
944
- @trigger = trigger # "init" or "maintenance"
703
+ def initialize(attributes = {})
704
+ super
705
+ @hook_event_name = 'Setup'
945
706
  end
946
707
  end
947
708
 
948
709
  # TeammateIdle hook input
949
710
  class TeammateIdleHookInput < BaseHookInput
950
- attr_accessor :hook_event_name, :teammate_name, :team_name
711
+ attr_accessor :teammate_name, :team_name
951
712
 
952
- def initialize(hook_event_name: 'TeammateIdle', teammate_name: nil, team_name: nil, **base_args)
953
- super(**base_args)
954
- @hook_event_name = hook_event_name
955
- @teammate_name = teammate_name
956
- @team_name = team_name
713
+ def initialize(attributes = {})
714
+ super
715
+ @hook_event_name = 'TeammateIdle'
957
716
  end
958
717
  end
959
718
 
960
719
  # TaskCompleted hook input
961
720
  class TaskCompletedHookInput < BaseHookInput
962
- attr_accessor :hook_event_name, :task_id, :task_subject, :task_description, :teammate_name, :team_name
721
+ attr_accessor :task_id, :task_subject, :task_description, :teammate_name, :team_name
963
722
 
964
- def initialize(hook_event_name: 'TaskCompleted', task_id: nil, task_subject: nil, task_description: nil,
965
- teammate_name: nil, team_name: nil, **base_args)
966
- super(**base_args)
967
- @hook_event_name = hook_event_name
968
- @task_id = task_id
969
- @task_subject = task_subject
970
- @task_description = task_description
971
- @teammate_name = teammate_name
972
- @team_name = team_name
723
+ def initialize(attributes = {})
724
+ super
725
+ @hook_event_name = 'TaskCompleted'
973
726
  end
974
727
  end
975
728
 
976
729
  # ConfigChange hook input
977
730
  class ConfigChangeHookInput < BaseHookInput
978
- attr_accessor :hook_event_name, :source, :file_path
731
+ attr_accessor :source, :file_path
979
732
 
980
- def initialize(hook_event_name: 'ConfigChange', source: nil, file_path: nil, **base_args)
981
- super(**base_args)
982
- @hook_event_name = hook_event_name
983
- @source = source # "user_settings", "project_settings", "local_settings", "policy_settings", "skills"
984
- @file_path = file_path
733
+ def initialize(attributes = {})
734
+ super
735
+ @hook_event_name = 'ConfigChange'
985
736
  end
986
737
  end
987
738
 
988
739
  # WorktreeCreate hook input
989
740
  class WorktreeCreateHookInput < BaseHookInput
990
- attr_accessor :hook_event_name, :name
741
+ attr_accessor :name
991
742
 
992
- def initialize(hook_event_name: 'WorktreeCreate', name: nil, **base_args)
993
- super(**base_args)
994
- @hook_event_name = hook_event_name
995
- @name = name
743
+ def initialize(attributes = {})
744
+ super
745
+ @hook_event_name = 'WorktreeCreate'
996
746
  end
997
747
  end
998
748
 
999
749
  # WorktreeRemove hook input
1000
750
  class WorktreeRemoveHookInput < BaseHookInput
1001
- attr_accessor :hook_event_name, :worktree_path
751
+ attr_accessor :worktree_path
1002
752
 
1003
- def initialize(hook_event_name: 'WorktreeRemove', worktree_path: nil, **base_args)
1004
- super(**base_args)
1005
- @hook_event_name = hook_event_name
1006
- @worktree_path = worktree_path
753
+ def initialize(attributes = {})
754
+ super
755
+ @hook_event_name = 'WorktreeRemove'
1007
756
  end
1008
757
  end
1009
758
 
1010
759
  # StopFailure hook input
1011
760
  class StopFailureHookInput < BaseHookInput
1012
- attr_accessor :hook_event_name, :error, :error_details, :last_assistant_message
761
+ attr_accessor :error, :error_details, :last_assistant_message
1013
762
 
1014
- def initialize(hook_event_name: 'StopFailure', error: nil, error_details: nil,
1015
- last_assistant_message: nil, **base_args)
1016
- super(**base_args)
1017
- @hook_event_name = hook_event_name
1018
- @error = error
1019
- @error_details = error_details
1020
- @last_assistant_message = last_assistant_message
763
+ def initialize(attributes = {})
764
+ super
765
+ @hook_event_name = 'StopFailure'
1021
766
  end
1022
767
  end
1023
768
 
1024
769
  # PostCompact hook input
1025
770
  class PostCompactHookInput < BaseHookInput
1026
- attr_accessor :hook_event_name, :trigger, :compact_summary
771
+ attr_accessor :trigger, :compact_summary
1027
772
 
1028
- def initialize(hook_event_name: 'PostCompact', trigger: nil, compact_summary: nil, **base_args)
1029
- super(**base_args)
1030
- @hook_event_name = hook_event_name
1031
- @trigger = trigger # "manual" or "auto"
1032
- @compact_summary = compact_summary
773
+ def initialize(attributes = {})
774
+ super
775
+ @hook_event_name = 'PostCompact'
1033
776
  end
1034
777
  end
1035
778
 
1036
779
  # PermissionDenied hook input
1037
780
  class PermissionDeniedHookInput < BaseHookInput
1038
- attr_accessor :hook_event_name, :tool_name, :tool_input, :tool_use_id, :reason, :agent_id, :agent_type
781
+ attr_accessor :tool_name, :tool_input, :tool_use_id, :reason, :agent_id, :agent_type
1039
782
 
1040
- def initialize(hook_event_name: 'PermissionDenied', tool_name: nil, tool_input: nil, tool_use_id: nil,
1041
- reason: nil, agent_id: nil, agent_type: nil, **base_args)
1042
- super(**base_args)
1043
- @hook_event_name = hook_event_name
1044
- @tool_name = tool_name
1045
- @tool_input = tool_input
1046
- @tool_use_id = tool_use_id
1047
- @reason = reason
1048
- @agent_id = agent_id
1049
- @agent_type = agent_type
783
+ def initialize(attributes = {})
784
+ super
785
+ @hook_event_name = 'PermissionDenied'
1050
786
  end
1051
787
  end
1052
788
 
1053
789
  # TaskCreated hook input
1054
790
  class TaskCreatedHookInput < BaseHookInput
1055
- attr_accessor :hook_event_name, :task_id, :task_subject, :task_description, :teammate_name, :team_name
791
+ attr_accessor :task_id, :task_subject, :task_description, :teammate_name, :team_name
1056
792
 
1057
- def initialize(hook_event_name: 'TaskCreated', task_id: nil, task_subject: nil, task_description: nil,
1058
- teammate_name: nil, team_name: nil, **base_args)
1059
- super(**base_args)
1060
- @hook_event_name = hook_event_name
1061
- @task_id = task_id
1062
- @task_subject = task_subject
1063
- @task_description = task_description
1064
- @teammate_name = teammate_name
1065
- @team_name = team_name
793
+ def initialize(attributes = {})
794
+ super
795
+ @hook_event_name = 'TaskCreated'
1066
796
  end
1067
797
  end
1068
798
 
1069
799
  # Elicitation hook input
1070
800
  class ElicitationHookInput < BaseHookInput
1071
- attr_accessor :hook_event_name, :mcp_server_name, :message, :mode, :url,
801
+ attr_accessor :mcp_server_name, :message, :mode, :url,
1072
802
  :elicitation_id, :requested_schema
1073
803
 
1074
- def initialize(hook_event_name: 'Elicitation', mcp_server_name: nil, message: nil, mode: nil,
1075
- url: nil, elicitation_id: nil, requested_schema: nil, **base_args)
1076
- super(**base_args)
1077
- @hook_event_name = hook_event_name
1078
- @mcp_server_name = mcp_server_name
1079
- @message = message
1080
- @mode = mode
1081
- @url = url
1082
- @elicitation_id = elicitation_id
1083
- @requested_schema = requested_schema
804
+ def initialize(attributes = {})
805
+ super
806
+ @hook_event_name = 'Elicitation'
1084
807
  end
1085
808
  end
1086
809
 
1087
810
  # ElicitationResult hook input
1088
811
  class ElicitationResultHookInput < BaseHookInput
1089
- attr_accessor :hook_event_name, :mcp_server_name, :elicitation_id, :mode, :action, :content
812
+ attr_accessor :mcp_server_name, :elicitation_id, :mode, :action, :content
1090
813
 
1091
- def initialize(hook_event_name: 'ElicitationResult', mcp_server_name: nil, elicitation_id: nil,
1092
- mode: nil, action: nil, content: nil, **base_args)
1093
- super(**base_args)
1094
- @hook_event_name = hook_event_name
1095
- @mcp_server_name = mcp_server_name
1096
- @elicitation_id = elicitation_id
1097
- @mode = mode
1098
- @action = action
1099
- @content = content
814
+ def initialize(attributes = {})
815
+ super
816
+ @hook_event_name = 'ElicitationResult'
1100
817
  end
1101
818
  end
1102
819
 
1103
820
  # InstructionsLoaded hook input
1104
821
  class InstructionsLoadedHookInput < BaseHookInput
1105
- attr_accessor :hook_event_name, :file_path, :memory_type, :load_reason, :globs, :trigger_file_path
822
+ attr_accessor :file_path, :memory_type, :load_reason, :globs, :trigger_file_path
1106
823
 
1107
- def initialize(hook_event_name: 'InstructionsLoaded', file_path: nil, memory_type: nil,
1108
- load_reason: nil, globs: nil, trigger_file_path: nil, **base_args)
1109
- super(**base_args)
1110
- @hook_event_name = hook_event_name
1111
- @file_path = file_path
1112
- @memory_type = memory_type
1113
- @load_reason = load_reason
1114
- @globs = globs
1115
- @trigger_file_path = trigger_file_path
824
+ def initialize(attributes = {})
825
+ super
826
+ @hook_event_name = 'InstructionsLoaded'
1116
827
  end
1117
828
  end
1118
829
 
1119
830
  # CwdChanged hook input
1120
831
  class CwdChangedHookInput < BaseHookInput
1121
- attr_accessor :hook_event_name, :old_cwd, :new_cwd
832
+ attr_accessor :old_cwd, :new_cwd
1122
833
 
1123
- def initialize(hook_event_name: 'CwdChanged', old_cwd: nil, new_cwd: nil, **base_args)
1124
- super(**base_args)
1125
- @hook_event_name = hook_event_name
1126
- @old_cwd = old_cwd
1127
- @new_cwd = new_cwd
834
+ def initialize(attributes = {})
835
+ super
836
+ @hook_event_name = 'CwdChanged'
1128
837
  end
1129
838
  end
1130
839
 
1131
840
  # FileChanged hook input
1132
841
  class FileChangedHookInput < BaseHookInput
1133
- attr_accessor :hook_event_name, :file_path, :event
842
+ attr_accessor :file_path, :event
1134
843
 
1135
- def initialize(hook_event_name: 'FileChanged', file_path: nil, event: nil, **base_args)
1136
- super(**base_args)
1137
- @hook_event_name = hook_event_name
1138
- @file_path = file_path
1139
- @event = event # "change", "add", or "unlink"
844
+ def initialize(attributes = {})
845
+ super
846
+ @hook_event_name = 'FileChanged'
1140
847
  end
1141
848
  end
1142
849
 
1143
850
  # Setup hook specific output
1144
- class SetupHookSpecificOutput
1145
- attr_accessor :hook_event_name, :additional_context
851
+ class SetupHookSpecificOutput < Type
852
+ attr_accessor :additional_context
853
+ attr_reader :hook_event_name
1146
854
 
1147
- def initialize(additional_context: nil)
855
+ def initialize(attributes = {})
856
+ super
1148
857
  @hook_event_name = 'Setup'
1149
- @additional_context = additional_context
1150
858
  end
1151
859
 
1152
860
  def to_h
@@ -1157,17 +865,14 @@ module ClaudeAgentSDK
1157
865
  end
1158
866
 
1159
867
  # PreToolUse hook specific output
1160
- class PreToolUseHookSpecificOutput
1161
- attr_accessor :hook_event_name, :permission_decision, :permission_decision_reason,
868
+ class PreToolUseHookSpecificOutput < Type
869
+ attr_accessor :permission_decision, :permission_decision_reason,
1162
870
  :updated_input, :additional_context
871
+ attr_reader :hook_event_name
1163
872
 
1164
- def initialize(permission_decision: nil, permission_decision_reason: nil, updated_input: nil,
1165
- additional_context: nil)
873
+ def initialize(attributes = {})
874
+ super
1166
875
  @hook_event_name = 'PreToolUse'
1167
- @permission_decision = permission_decision # 'allow', 'deny', or 'ask'
1168
- @permission_decision_reason = permission_decision_reason
1169
- @updated_input = updated_input
1170
- @additional_context = additional_context
1171
876
  end
1172
877
 
1173
878
  def to_h
@@ -1181,13 +886,13 @@ module ClaudeAgentSDK
1181
886
  end
1182
887
 
1183
888
  # PostToolUse hook specific output
1184
- class PostToolUseHookSpecificOutput
1185
- attr_accessor :hook_event_name, :additional_context, :updated_mcp_tool_output
889
+ class PostToolUseHookSpecificOutput < Type
890
+ attr_accessor :additional_context, :updated_mcp_tool_output
891
+ attr_reader :hook_event_name
1186
892
 
1187
- def initialize(additional_context: nil, updated_mcp_tool_output: nil)
893
+ def initialize(attributes = {})
894
+ super
1188
895
  @hook_event_name = 'PostToolUse'
1189
- @additional_context = additional_context
1190
- @updated_mcp_tool_output = updated_mcp_tool_output
1191
896
  end
1192
897
 
1193
898
  def to_h
@@ -1199,12 +904,13 @@ module ClaudeAgentSDK
1199
904
  end
1200
905
 
1201
906
  # PostToolUseFailure hook specific output
1202
- class PostToolUseFailureHookSpecificOutput
1203
- attr_accessor :hook_event_name, :additional_context
907
+ class PostToolUseFailureHookSpecificOutput < Type
908
+ attr_accessor :additional_context
909
+ attr_reader :hook_event_name
1204
910
 
1205
- def initialize(additional_context: nil)
911
+ def initialize(attributes = {})
912
+ super
1206
913
  @hook_event_name = 'PostToolUseFailure'
1207
- @additional_context = additional_context
1208
914
  end
1209
915
 
1210
916
  def to_h
@@ -1215,12 +921,13 @@ module ClaudeAgentSDK
1215
921
  end
1216
922
 
1217
923
  # UserPromptSubmit hook specific output
1218
- class UserPromptSubmitHookSpecificOutput
1219
- attr_accessor :hook_event_name, :additional_context
924
+ class UserPromptSubmitHookSpecificOutput < Type
925
+ attr_accessor :additional_context
926
+ attr_reader :hook_event_name
1220
927
 
1221
- def initialize(additional_context: nil)
928
+ def initialize(attributes = {})
929
+ super
1222
930
  @hook_event_name = 'UserPromptSubmit'
1223
- @additional_context = additional_context
1224
931
  end
1225
932
 
1226
933
  def to_h
@@ -1231,12 +938,13 @@ module ClaudeAgentSDK
1231
938
  end
1232
939
 
1233
940
  # Notification hook specific output
1234
- class NotificationHookSpecificOutput
1235
- attr_accessor :hook_event_name, :additional_context
941
+ class NotificationHookSpecificOutput < Type
942
+ attr_accessor :additional_context
943
+ attr_reader :hook_event_name
1236
944
 
1237
- def initialize(additional_context: nil)
945
+ def initialize(attributes = {})
946
+ super
1238
947
  @hook_event_name = 'Notification'
1239
- @additional_context = additional_context
1240
948
  end
1241
949
 
1242
950
  def to_h
@@ -1247,12 +955,13 @@ module ClaudeAgentSDK
1247
955
  end
1248
956
 
1249
957
  # SubagentStart hook specific output
1250
- class SubagentStartHookSpecificOutput
1251
- attr_accessor :hook_event_name, :additional_context
958
+ class SubagentStartHookSpecificOutput < Type
959
+ attr_accessor :additional_context
960
+ attr_reader :hook_event_name
1252
961
 
1253
- def initialize(additional_context: nil)
962
+ def initialize(attributes = {})
963
+ super
1254
964
  @hook_event_name = 'SubagentStart'
1255
- @additional_context = additional_context
1256
965
  end
1257
966
 
1258
967
  def to_h
@@ -1263,12 +972,13 @@ module ClaudeAgentSDK
1263
972
  end
1264
973
 
1265
974
  # PermissionRequest hook specific output
1266
- class PermissionRequestHookSpecificOutput
1267
- attr_accessor :hook_event_name, :decision
975
+ class PermissionRequestHookSpecificOutput < Type
976
+ attr_accessor :decision
977
+ attr_reader :hook_event_name
1268
978
 
1269
- def initialize(decision: nil)
979
+ def initialize(attributes = {})
980
+ super
1270
981
  @hook_event_name = 'PermissionRequest'
1271
- @decision = decision
1272
982
  end
1273
983
 
1274
984
  def to_h
@@ -1279,12 +989,13 @@ module ClaudeAgentSDK
1279
989
  end
1280
990
 
1281
991
  # SessionStart hook specific output
1282
- class SessionStartHookSpecificOutput
1283
- attr_accessor :hook_event_name, :additional_context
992
+ class SessionStartHookSpecificOutput < Type
993
+ attr_accessor :additional_context
994
+ attr_reader :hook_event_name
1284
995
 
1285
- def initialize(additional_context: nil)
996
+ def initialize(attributes = {})
997
+ super
1286
998
  @hook_event_name = 'SessionStart'
1287
- @additional_context = additional_context
1288
999
  end
1289
1000
 
1290
1001
  def to_h
@@ -1295,12 +1006,14 @@ module ClaudeAgentSDK
1295
1006
  end
1296
1007
 
1297
1008
  # PermissionDenied hook specific output
1298
- class PermissionDeniedHookSpecificOutput
1299
- attr_accessor :hook_event_name, :retry
1009
+ class PermissionDeniedHookSpecificOutput < Type
1010
+ attr_accessor :retry
1011
+ attr_reader :hook_event_name
1300
1012
 
1301
- def initialize(retry_: false)
1013
+ def initialize(attributes = {})
1014
+ super
1302
1015
  @hook_event_name = 'PermissionDenied'
1303
- @retry = retry_
1016
+ @retry = false if @retry.nil?
1304
1017
  end
1305
1018
 
1306
1019
  def to_h
@@ -1311,12 +1024,13 @@ module ClaudeAgentSDK
1311
1024
  end
1312
1025
 
1313
1026
  # CwdChanged hook specific output
1314
- class CwdChangedHookSpecificOutput
1315
- attr_accessor :hook_event_name, :watch_paths
1027
+ class CwdChangedHookSpecificOutput < Type
1028
+ attr_accessor :watch_paths
1029
+ attr_reader :hook_event_name
1316
1030
 
1317
- def initialize(watch_paths: nil)
1031
+ def initialize(attributes = {})
1032
+ super
1318
1033
  @hook_event_name = 'CwdChanged'
1319
- @watch_paths = watch_paths
1320
1034
  end
1321
1035
 
1322
1036
  def to_h
@@ -1327,12 +1041,13 @@ module ClaudeAgentSDK
1327
1041
  end
1328
1042
 
1329
1043
  # FileChanged hook specific output
1330
- class FileChangedHookSpecificOutput
1331
- attr_accessor :hook_event_name, :watch_paths
1044
+ class FileChangedHookSpecificOutput < Type
1045
+ attr_accessor :watch_paths
1046
+ attr_reader :hook_event_name
1332
1047
 
1333
- def initialize(watch_paths: nil)
1048
+ def initialize(attributes = {})
1049
+ super
1334
1050
  @hook_event_name = 'FileChanged'
1335
- @watch_paths = watch_paths
1336
1051
  end
1337
1052
 
1338
1053
  def to_h
@@ -1343,12 +1058,12 @@ module ClaudeAgentSDK
1343
1058
  end
1344
1059
 
1345
1060
  # Async hook JSON output
1346
- class AsyncHookJSONOutput
1061
+ class AsyncHookJSONOutput < Type
1347
1062
  attr_accessor :async, :async_timeout
1348
1063
 
1349
- def initialize(async: true, async_timeout: nil)
1350
- @async = async
1351
- @async_timeout = async_timeout
1064
+ def initialize(attributes = {})
1065
+ super
1066
+ @async = true if @async.nil?
1352
1067
  end
1353
1068
 
1354
1069
  def to_h
@@ -1359,19 +1074,14 @@ module ClaudeAgentSDK
1359
1074
  end
1360
1075
 
1361
1076
  # Sync hook JSON output
1362
- class SyncHookJSONOutput
1077
+ class SyncHookJSONOutput < Type
1363
1078
  attr_accessor :continue, :suppress_output, :stop_reason, :decision,
1364
1079
  :system_message, :reason, :hook_specific_output
1365
1080
 
1366
- def initialize(continue: true, suppress_output: false, stop_reason: nil, decision: nil,
1367
- system_message: nil, reason: nil, hook_specific_output: nil)
1368
- @continue = continue
1369
- @suppress_output = suppress_output
1370
- @stop_reason = stop_reason
1371
- @decision = decision
1372
- @system_message = system_message
1373
- @reason = reason
1374
- @hook_specific_output = hook_specific_output
1081
+ def initialize(attributes = {})
1082
+ super
1083
+ @continue = true if @continue.nil?
1084
+ @suppress_output = false if @suppress_output.nil?
1375
1085
  end
1376
1086
 
1377
1087
  def to_h
@@ -1392,63 +1102,44 @@ module ClaudeAgentSDK
1392
1102
  MCP_SERVER_CONNECTION_STATUSES = %w[connected failed needs-auth pending disabled].freeze
1393
1103
 
1394
1104
  # MCP server info (name and version)
1395
- class McpServerInfo
1105
+ class McpServerInfo < Type
1396
1106
  attr_accessor :name, :version
1397
-
1398
- def initialize(name:, version: nil)
1399
- @name = name
1400
- @version = version
1401
- end
1402
1107
  end
1403
1108
 
1404
1109
  # MCP tool annotation hints
1405
- class McpToolAnnotations
1110
+ class McpToolAnnotations < Type
1406
1111
  attr_accessor :read_only, :destructive, :open_world
1407
1112
 
1408
- def initialize(read_only: nil, destructive: nil, open_world: nil)
1409
- @read_only = read_only
1410
- @destructive = destructive
1411
- @open_world = open_world
1412
- end
1413
-
1113
+ # Backwards-compatible parse; returns nil for nil input.
1414
1114
  def self.parse(data)
1415
- return nil unless data
1416
-
1417
- new(
1418
- read_only: data.key?(:readOnly) ? data[:readOnly] : data[:read_only],
1419
- destructive: data[:destructive],
1420
- open_world: data.key?(:openWorld) ? data[:openWorld] : data[:open_world]
1421
- )
1115
+ from_hash(data)
1422
1116
  end
1423
1117
  end
1424
1118
 
1425
1119
  # MCP tool info (name, description, annotations)
1426
- class McpToolInfo
1427
- attr_accessor :name, :description, :annotations
1120
+ class McpToolInfo < Type
1121
+ attr_accessor :name, :description
1122
+ attr_reader :annotations
1428
1123
 
1429
- def initialize(name:, description: nil, annotations: nil)
1430
- @name = name
1431
- @description = description
1432
- @annotations = annotations
1124
+ def annotations=(value)
1125
+ @annotations = value.is_a?(Hash) ? McpToolAnnotations.new(value) : value
1433
1126
  end
1434
1127
 
1128
+ # Backwards-compatible parse; returns nil for nil input.
1435
1129
  def self.parse(data)
1436
- new(
1437
- name: data[:name],
1438
- description: data[:description],
1439
- annotations: McpToolAnnotations.parse(data[:annotations])
1440
- )
1130
+ from_hash(data)
1441
1131
  end
1442
1132
  end
1443
1133
 
1444
1134
  # Output-only serializable version of McpSdkServerConfig (without live instance)
1445
1135
  # Returned in MCP status responses
1446
- class McpSdkServerConfigStatus
1447
- attr_accessor :type, :name
1136
+ class McpSdkServerConfigStatus < Type
1137
+ attr_accessor :name
1138
+ attr_reader :type
1448
1139
 
1449
- def initialize(type: 'sdk', name:)
1450
- @type = type
1451
- @name = name
1140
+ def initialize(attributes = {})
1141
+ super
1142
+ @type = 'sdk'
1452
1143
  end
1453
1144
 
1454
1145
  def to_h
@@ -1458,13 +1149,13 @@ module ClaudeAgentSDK
1458
1149
 
1459
1150
  # Claude.ai proxy MCP server config
1460
1151
  # Output-only type that appears in status responses for servers proxied through Claude.ai
1461
- class McpClaudeAIProxyServerConfig
1462
- attr_accessor :type, :url, :id
1152
+ class McpClaudeAIProxyServerConfig < Type
1153
+ attr_accessor :url, :id
1154
+ attr_reader :type
1463
1155
 
1464
- def initialize(type: 'claudeai-proxy', url:, id:)
1465
- @type = type
1466
- @url = url
1467
- @id = id
1156
+ def initialize(attributes = {})
1157
+ super
1158
+ @type = 'claudeai-proxy'
1468
1159
  end
1469
1160
 
1470
1161
  def to_h
@@ -1473,37 +1164,34 @@ module ClaudeAgentSDK
1473
1164
  end
1474
1165
 
1475
1166
  # Status of a single MCP server connection
1476
- class McpServerStatus
1477
- attr_accessor :name, :status, :server_info, :error, :config, :scope, :tools
1167
+ class McpServerStatus < Type
1168
+ attr_accessor :name, :status, :error, :scope
1169
+ attr_reader :server_info, :config, :tools
1478
1170
 
1479
- def initialize(name:, status:, server_info: nil, error: nil, config: nil, scope: nil, tools: nil)
1480
- @name = name
1481
- @status = status
1482
- @server_info = server_info
1483
- @error = error
1484
- @config = config
1485
- @scope = scope
1486
- @tools = tools
1171
+ def server_info=(value)
1172
+ @server_info = value.is_a?(Hash) ? McpServerInfo.new(value) : value
1487
1173
  end
1488
1174
 
1489
- def self.parse(data)
1490
- server_info = (McpServerInfo.new(name: data[:serverInfo][:name], version: data[:serverInfo][:version]) if data[:serverInfo])
1491
- tools = data[:tools]&.map { |t| McpToolInfo.parse(t) }
1492
- config = parse_config(data[:config])
1175
+ def tools=(value)
1176
+ @tools = if value.is_a?(Array)
1177
+ value.map { |t| t.is_a?(Hash) ? McpToolInfo.new(t) : t }
1178
+ else
1179
+ value
1180
+ end
1181
+ end
1182
+
1183
+ def config=(value)
1184
+ @config = self.class.parse_config(value) || value
1185
+ end
1493
1186
 
1494
- new(
1495
- name: data[:name],
1496
- status: data[:status],
1497
- server_info: server_info,
1498
- error: data[:error],
1499
- config: config,
1500
- scope: data[:scope],
1501
- tools: tools
1502
- )
1187
+ # Backwards-compatible parse; normalizes camelCase `serverInfo` and
1188
+ # polymorphically builds the nested `config`.
1189
+ def self.parse(data)
1190
+ from_hash(data)
1503
1191
  end
1504
1192
 
1505
1193
  def self.parse_config(config)
1506
- return config unless config.is_a?(Hash) && config[:type]
1194
+ return nil unless config.is_a?(Hash) && config[:type]
1507
1195
 
1508
1196
  case config[:type]
1509
1197
  when 'claudeai-proxy'
@@ -1517,28 +1205,31 @@ module ClaudeAgentSDK
1517
1205
  end
1518
1206
 
1519
1207
  # Response from get_mcp_status containing all server statuses
1520
- class McpStatusResponse
1521
- attr_accessor :mcp_servers
1208
+ class McpStatusResponse < Type
1209
+ attr_reader :mcp_servers
1522
1210
 
1523
- def initialize(mcp_servers:)
1524
- @mcp_servers = mcp_servers
1211
+ def mcp_servers=(value)
1212
+ @mcp_servers = if value.is_a?(Array)
1213
+ value.map { |s| s.is_a?(Hash) ? McpServerStatus.new(s) : s }
1214
+ else
1215
+ value
1216
+ end
1525
1217
  end
1526
1218
 
1219
+ # Backwards-compatible parse; returns nil for nil input.
1527
1220
  def self.parse(data)
1528
- servers = (data[:mcpServers] || []).map { |s| McpServerStatus.parse(s) }
1529
- new(mcp_servers: servers)
1221
+ from_hash(data)
1530
1222
  end
1531
1223
  end
1532
1224
 
1533
1225
  # MCP Server configurations
1534
- class McpStdioServerConfig
1535
- attr_accessor :type, :command, :args, :env
1226
+ class McpStdioServerConfig < Type
1227
+ attr_accessor :command, :args, :env
1228
+ attr_reader :type
1536
1229
 
1537
- def initialize(command:, args: nil, env: nil, type: 'stdio')
1538
- @type = type
1539
- @command = command
1540
- @args = args
1541
- @env = env
1230
+ def initialize(attributes = {})
1231
+ super
1232
+ @type = 'stdio'
1542
1233
  end
1543
1234
 
1544
1235
  def to_h
@@ -1549,13 +1240,13 @@ module ClaudeAgentSDK
1549
1240
  end
1550
1241
  end
1551
1242
 
1552
- class McpSSEServerConfig
1553
- attr_accessor :type, :url, :headers
1243
+ class McpSSEServerConfig < Type
1244
+ attr_accessor :url, :headers
1245
+ attr_reader :type
1554
1246
 
1555
- def initialize(url:, headers: nil)
1247
+ def initialize(attributes = {})
1248
+ super
1556
1249
  @type = 'sse'
1557
- @url = url
1558
- @headers = headers
1559
1250
  end
1560
1251
 
1561
1252
  def to_h
@@ -1565,13 +1256,13 @@ module ClaudeAgentSDK
1565
1256
  end
1566
1257
  end
1567
1258
 
1568
- class McpHttpServerConfig
1569
- attr_accessor :type, :url, :headers
1259
+ class McpHttpServerConfig < Type
1260
+ attr_accessor :url, :headers
1261
+ attr_reader :type
1570
1262
 
1571
- def initialize(url:, headers: nil)
1263
+ def initialize(attributes = {})
1264
+ super
1572
1265
  @type = 'http'
1573
- @url = url
1574
- @headers = headers
1575
1266
  end
1576
1267
 
1577
1268
  def to_h
@@ -1581,13 +1272,13 @@ module ClaudeAgentSDK
1581
1272
  end
1582
1273
  end
1583
1274
 
1584
- class McpSdkServerConfig
1585
- attr_accessor :type, :name, :instance
1275
+ class McpSdkServerConfig < Type
1276
+ attr_accessor :name, :instance
1277
+ attr_reader :type
1586
1278
 
1587
- def initialize(name:, instance:)
1279
+ def initialize(attributes = {})
1280
+ super
1588
1281
  @type = 'sdk'
1589
- @name = name
1590
- @instance = instance
1591
1282
  end
1592
1283
 
1593
1284
  def to_h
@@ -1596,14 +1287,13 @@ module ClaudeAgentSDK
1596
1287
  end
1597
1288
 
1598
1289
  # SDK Plugin configuration
1599
- class SdkPluginConfig
1600
- attr_accessor :type, :path
1601
-
1602
- def initialize(path:, type: 'local')
1603
- raise ArgumentError, "unsupported plugin type: #{type}" unless %w[local plugin].include?(type)
1290
+ class SdkPluginConfig < Type
1291
+ attr_accessor :path
1292
+ attr_reader :type
1604
1293
 
1294
+ def initialize(attributes = {})
1295
+ super
1605
1296
  @type = 'local'
1606
- @path = path
1607
1297
  end
1608
1298
 
1609
1299
  def to_h
@@ -1612,29 +1302,11 @@ module ClaudeAgentSDK
1612
1302
  end
1613
1303
 
1614
1304
  # Sandbox network configuration
1615
- class SandboxNetworkConfig
1305
+ class SandboxNetworkConfig < Type
1616
1306
  attr_accessor :allowed_domains, :allow_managed_domains_only,
1617
1307
  :allow_unix_sockets, :allow_all_unix_sockets, :allow_local_binding,
1618
1308
  :http_proxy_port, :socks_proxy_port
1619
1309
 
1620
- def initialize(
1621
- allowed_domains: nil,
1622
- allow_managed_domains_only: nil,
1623
- allow_unix_sockets: nil,
1624
- allow_all_unix_sockets: nil,
1625
- allow_local_binding: nil,
1626
- http_proxy_port: nil,
1627
- socks_proxy_port: nil
1628
- )
1629
- @allowed_domains = allowed_domains # Array of domain strings
1630
- @allow_managed_domains_only = allow_managed_domains_only
1631
- @allow_unix_sockets = allow_unix_sockets # macOS only: Array of socket paths
1632
- @allow_all_unix_sockets = allow_all_unix_sockets
1633
- @allow_local_binding = allow_local_binding
1634
- @http_proxy_port = http_proxy_port
1635
- @socks_proxy_port = socks_proxy_port
1636
- end
1637
-
1638
1310
  def to_h
1639
1311
  result = {}
1640
1312
  result[:allowedDomains] = @allowed_domains if @allowed_domains
@@ -1649,18 +1321,9 @@ module ClaudeAgentSDK
1649
1321
  end
1650
1322
 
1651
1323
  # Sandbox filesystem configuration
1652
- class SandboxFilesystemConfig
1324
+ class SandboxFilesystemConfig < Type
1653
1325
  attr_accessor :allow_write, :deny_write, :deny_read, :allow_read, :allow_managed_read_paths_only
1654
1326
 
1655
- def initialize(allow_write: nil, deny_write: nil, deny_read: nil, allow_read: nil,
1656
- allow_managed_read_paths_only: nil)
1657
- @allow_write = allow_write # Array of paths to allow writing
1658
- @deny_write = deny_write # Array of paths to deny writing
1659
- @deny_read = deny_read # Array of paths to deny reading
1660
- @allow_read = allow_read # Array of paths to re-allow reading within denyRead
1661
- @allow_managed_read_paths_only = allow_managed_read_paths_only
1662
- end
1663
-
1664
1327
  def to_h
1665
1328
  result = {}
1666
1329
  result[:allowWrite] = @allow_write if @allow_write
@@ -1673,38 +1336,12 @@ module ClaudeAgentSDK
1673
1336
  end
1674
1337
 
1675
1338
  # Sandbox settings for isolated command execution
1676
- class SandboxSettings
1339
+ class SandboxSettings < Type
1677
1340
  attr_accessor :enabled, :fail_if_unavailable, :auto_allow_bash_if_sandboxed,
1678
1341
  :excluded_commands, :allow_unsandboxed_commands, :network, :filesystem,
1679
1342
  :ignore_violations, :enable_weaker_nested_sandbox,
1680
1343
  :enable_weaker_network_isolation, :ripgrep
1681
1344
 
1682
- def initialize(
1683
- enabled: nil,
1684
- fail_if_unavailable: nil,
1685
- auto_allow_bash_if_sandboxed: nil,
1686
- excluded_commands: nil,
1687
- allow_unsandboxed_commands: nil,
1688
- network: nil,
1689
- filesystem: nil,
1690
- ignore_violations: nil,
1691
- enable_weaker_nested_sandbox: nil,
1692
- enable_weaker_network_isolation: nil,
1693
- ripgrep: nil
1694
- )
1695
- @enabled = enabled
1696
- @fail_if_unavailable = fail_if_unavailable
1697
- @auto_allow_bash_if_sandboxed = auto_allow_bash_if_sandboxed
1698
- @excluded_commands = excluded_commands # Array of commands to exclude
1699
- @allow_unsandboxed_commands = allow_unsandboxed_commands
1700
- @network = network # SandboxNetworkConfig instance
1701
- @filesystem = filesystem # SandboxFilesystemConfig instance
1702
- @ignore_violations = ignore_violations # Hash of { category => [patterns] }
1703
- @enable_weaker_nested_sandbox = enable_weaker_nested_sandbox
1704
- @enable_weaker_network_isolation = enable_weaker_network_isolation # macOS only
1705
- @ripgrep = ripgrep # Hash with :command and optional :args
1706
- end
1707
-
1708
1345
  def to_h
1709
1346
  result = {}
1710
1347
  result[:enabled] = @enabled unless @enabled.nil?
@@ -1723,36 +1360,29 @@ module ClaudeAgentSDK
1723
1360
  end
1724
1361
 
1725
1362
  # Result of a session fork operation
1726
- class ForkSessionResult
1363
+ class ForkSessionResult < Type
1727
1364
  attr_accessor :session_id
1728
-
1729
- def initialize(session_id:)
1730
- @session_id = session_id
1731
- end
1732
1365
  end
1733
1366
 
1734
1367
  # API-side task budget in tokens.
1735
1368
  # When set, the model is made aware of its remaining token budget so it can
1736
1369
  # pace tool use and wrap up before the limit.
1737
- class TaskBudget
1370
+ class TaskBudget < Type
1738
1371
  attr_accessor :total
1739
1372
 
1740
- def initialize(total:)
1741
- @total = total
1742
- end
1743
-
1744
1373
  def to_h
1745
1374
  { total: @total }
1746
1375
  end
1747
1376
  end
1748
1377
 
1749
1378
  # System prompt file configuration — loads system prompt from a file path
1750
- class SystemPromptFile
1751
- attr_accessor :type, :path
1379
+ class SystemPromptFile < Type
1380
+ attr_accessor :path
1381
+ attr_reader :type
1752
1382
 
1753
- def initialize(path:)
1383
+ def initialize(attributes = {})
1384
+ super
1754
1385
  @type = 'file'
1755
- @path = path
1756
1386
  end
1757
1387
 
1758
1388
  def to_h
@@ -1761,14 +1391,13 @@ module ClaudeAgentSDK
1761
1391
  end
1762
1392
 
1763
1393
  # System prompt preset configuration
1764
- class SystemPromptPreset
1765
- attr_accessor :type, :preset, :append, :exclude_dynamic_sections
1394
+ class SystemPromptPreset < Type
1395
+ attr_reader :type
1396
+ attr_accessor :preset, :append, :exclude_dynamic_sections
1766
1397
 
1767
- def initialize(preset:, append: nil, exclude_dynamic_sections: nil)
1398
+ def initialize(attributes = {})
1399
+ super
1768
1400
  @type = 'preset'
1769
- @preset = preset
1770
- @append = append
1771
- @exclude_dynamic_sections = exclude_dynamic_sections
1772
1401
  end
1773
1402
 
1774
1403
  def to_h
@@ -1780,12 +1409,13 @@ module ClaudeAgentSDK
1780
1409
  end
1781
1410
 
1782
1411
  # Tools preset configuration
1783
- class ToolsPreset
1784
- attr_accessor :type, :preset
1412
+ class ToolsPreset < Type
1413
+ attr_reader :type
1414
+ attr_accessor :preset
1785
1415
 
1786
- def initialize(preset:)
1416
+ def initialize(attributes = {})
1417
+ super
1787
1418
  @type = 'preset'
1788
- @preset = preset
1789
1419
  end
1790
1420
 
1791
1421
  def to_h
@@ -1794,64 +1424,108 @@ module ClaudeAgentSDK
1794
1424
  end
1795
1425
 
1796
1426
  # Claude Agent Options for configuring queries
1797
- class ClaudeAgentOptions
1427
+ class ClaudeAgentOptions < Type
1798
1428
  attr_accessor :allowed_tools, :system_prompt, :mcp_servers, :permission_mode,
1799
- :continue_conversation, :resume, :session_id, :max_turns, :disallowed_tools,
1429
+ :resume, :session_id, :max_turns, :disallowed_tools,
1800
1430
  :model, :permission_prompt_tool_name, :cwd, :cli_path, :settings,
1801
1431
  :add_dirs, :env, :extra_args, :max_buffer_size, :stderr,
1802
- :can_use_tool, :hooks, :user, :include_partial_messages,
1803
- :fork_session, :agents, :setting_sources,
1432
+ :can_use_tool, :hooks, :user,
1433
+ :agents, :setting_sources,
1804
1434
  :output_format, :max_budget_usd, :max_thinking_tokens,
1805
1435
  :fallback_model, :plugins, :debug_stderr,
1806
- :betas, :tools, :sandbox, :enable_file_checkpointing, :append_allowed_tools,
1807
- :thinking, :effort, :bare, :observers, :task_budget
1808
-
1809
- # Non-nil defaults for options that need them.
1810
- # Keys absent from here default to nil.
1811
- OPTION_DEFAULTS = {
1812
- allowed_tools: [], disallowed_tools: [], add_dirs: [],
1813
- mcp_servers: {}, env: {}, extra_args: {},
1814
- continue_conversation: false, include_partial_messages: false,
1815
- fork_session: false, enable_file_checkpointing: false,
1816
- observers: []
1817
- }.freeze
1818
-
1819
- # Valid option names derived from attr_accessor declarations.
1820
- VALID_OPTIONS = instance_methods.grep(/=\z/).map { |m| m.to_s.chomp('=').to_sym }.freeze
1821
-
1822
- # Using **kwargs lets us distinguish "caller passed allowed_tools: []"
1823
- # from "caller omitted allowed_tools" — critical for correct merge with
1824
- # configured defaults.
1825
- def initialize(**kwargs)
1826
- unknown = kwargs.keys - VALID_OPTIONS
1827
- raise ArgumentError, "unknown keyword#{'s' if unknown.size > 1}: #{unknown.join(', ')}" if unknown.any?
1828
-
1829
- merged = merge_with_defaults(kwargs)
1830
- OPTION_DEFAULTS.merge(merged).each do |key, value|
1831
- instance_variable_set(:"@#{key}", value)
1832
- end
1436
+ :betas, :tools, :sandbox, :append_allowed_tools,
1437
+ :thinking, :effort, :observers, :task_budget
1438
+ attr_reader :bare, :fork_session, :enable_file_checkpointing,
1439
+ :include_partial_messages, :continue_conversation
1440
+
1441
+ def initialize(attributes = {})
1442
+ self.fork_session = false
1443
+ self.continue_conversation = false
1444
+ self.include_partial_messages = false
1445
+ self.enable_file_checkpointing = false
1446
+
1447
+ super(merge_with_defaults(attributes || {}))
1448
+
1449
+ # Non-nil defaults for options that need them.
1450
+ self.env ||= {}
1451
+ self.extra_args ||= {}
1452
+ self.mcp_servers ||= {}
1453
+ self.add_dirs ||= []
1454
+ self.observers ||= []
1455
+ self.allowed_tools ||= []
1456
+ self.disallowed_tools ||= []
1833
1457
  end
1834
1458
 
1835
1459
  def dup_with(**changes)
1836
1460
  new_options = self.dup
1837
- changes.each { |key, value| new_options.send(:"#{key}=", value) }
1461
+ changes.each { |key, value| new_options[key] = value }
1838
1462
  new_options
1839
1463
  end
1840
1464
 
1465
+ def bare?
1466
+ !!bare
1467
+ end
1468
+
1469
+ def bare=(value)
1470
+ @bare = coerce_boolean(value)
1471
+ end
1472
+
1473
+ def fork_session?
1474
+ !!fork_session
1475
+ end
1476
+
1477
+ def fork_session=(value)
1478
+ @fork_session = coerce_boolean(value)
1479
+ end
1480
+
1481
+ def enable_file_checkpointing?
1482
+ !!enable_file_checkpointing
1483
+ end
1484
+
1485
+ def enable_file_checkpointing=(value)
1486
+ @enable_file_checkpointing = coerce_boolean(value)
1487
+ end
1488
+
1489
+ def include_partial_messages?
1490
+ !!include_partial_messages
1491
+ end
1492
+
1493
+ def include_partial_messages=(value)
1494
+ @include_partial_messages = coerce_boolean(value)
1495
+ end
1496
+
1497
+ def continue_conversation?
1498
+ !!continue_conversation
1499
+ end
1500
+
1501
+ def continue_conversation=(value)
1502
+ @continue_conversation = coerce_boolean(value)
1503
+ end
1504
+
1841
1505
  private
1842
1506
 
1843
- # Merge caller-provided kwargs with configured defaults.
1507
+ # Strict key validation: unlike other Type subclasses (which silently drop
1508
+ # unknown keys for forward-compat with newer CLI output), ClaudeAgentOptions
1509
+ # is a developer-facing config object — typos should fail loudly.
1510
+ def assign_attribute(name, value)
1511
+ setter = :"#{normalize_name(name)}="
1512
+ raise ArgumentError, "unknown ClaudeAgentOptions option: #{name.inspect}" unless respond_to?(setter)
1513
+
1514
+ public_send(setter, value)
1515
+ end
1516
+
1517
+ # Merge caller-provided attributes with configured defaults.
1844
1518
  # Only keys the caller explicitly passed are treated as overrides;
1845
- # method-signature defaults ([], {}, false) are NOT in kwargs unless the caller wrote them.
1846
- def merge_with_defaults(kwargs)
1847
- return OPTION_DEFAULTS.merge(kwargs) unless defined?(ClaudeAgentSDK) && ClaudeAgentSDK.respond_to?(:default_options)
1519
+ # method-signature defaults ([], {}, false) are NOT present unless the caller wrote them.
1520
+ def merge_with_defaults(attributes)
1521
+ return attributes unless defined?(ClaudeAgentSDK) && ClaudeAgentSDK.respond_to?(:default_options)
1848
1522
 
1849
1523
  defaults = ClaudeAgentSDK.default_options
1850
- return OPTION_DEFAULTS.merge(kwargs) unless defaults.any?
1524
+ return attributes unless defaults.any?
1851
1525
 
1852
1526
  # Start from configured defaults (deep dup hashes to prevent mutation)
1853
1527
  result = defaults.transform_values { |v| v.is_a?(Hash) ? v.dup : v }
1854
- kwargs.each do |key, value|
1528
+ attributes.each do |key, value|
1855
1529
  default_val = result[key]
1856
1530
  result[key] = if value.nil?
1857
1531
  default_val # nil means "no preference" — keep the configured default
@@ -1861,46 +1535,22 @@ module ClaudeAgentSDK
1861
1535
  value
1862
1536
  end
1863
1537
  end
1864
- OPTION_DEFAULTS.merge(result)
1538
+ result
1865
1539
  end
1866
1540
  end
1867
1541
 
1868
1542
  # SDK MCP Tool definition
1869
- class SdkMcpTool
1543
+ class SdkMcpTool < Type
1870
1544
  attr_accessor :name, :description, :input_schema, :handler, :annotations, :meta
1871
-
1872
- def initialize(name:, description:, input_schema:, handler:, annotations: nil, meta: nil)
1873
- @name = name
1874
- @description = description
1875
- @input_schema = input_schema
1876
- @handler = handler
1877
- @annotations = annotations # MCP tool annotations (e.g., { title: '...', readOnlyHint: true })
1878
- @meta = meta # MCP _meta field (e.g., { 'anthropic/maxResultSizeChars' => 100000 })
1879
- end
1880
1545
  end
1881
1546
 
1882
1547
  # SDK MCP Resource definition
1883
- class SdkMcpResource
1548
+ class SdkMcpResource < Type
1884
1549
  attr_accessor :uri, :name, :description, :mime_type, :reader
1885
-
1886
- def initialize(uri:, name:, description: nil, mime_type: nil, reader:)
1887
- @uri = uri
1888
- @name = name
1889
- @description = description
1890
- @mime_type = mime_type
1891
- @reader = reader
1892
- end
1893
1550
  end
1894
1551
 
1895
1552
  # SDK MCP Prompt definition
1896
- class SdkMcpPrompt
1553
+ class SdkMcpPrompt < Type
1897
1554
  attr_accessor :name, :description, :arguments, :generator
1898
-
1899
- def initialize(name:, description: nil, arguments: nil, generator:)
1900
- @name = name
1901
- @description = description
1902
- @arguments = arguments
1903
- @generator = generator
1904
- end
1905
1555
  end
1906
1556
  end