claude-agent-sdk 0.37.0 → 1.1.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -0
  3. data/README.md +6 -2
  4. data/UPGRADING-1.0.md +151 -0
  5. data/docs/client.md +11 -0
  6. data/docs/configuration.md +42 -0
  7. data/docs/errors.md +6 -0
  8. data/docs/sessions.md +20 -1
  9. data/docs/types.md +17 -14
  10. data/lib/claude_agent_sdk/cli_installer.rb +1 -1
  11. data/lib/claude_agent_sdk/deprecation.rb +1 -40
  12. data/lib/claude_agent_sdk/errors.rb +10 -0
  13. data/lib/claude_agent_sdk/query.rb +320 -56
  14. data/lib/claude_agent_sdk/session_resume.rb +11 -5
  15. data/lib/claude_agent_sdk/subprocess_cli_transport.rb +25 -0
  16. data/lib/claude_agent_sdk/types/attributes.rb +14 -49
  17. data/lib/claude_agent_sdk/types/base.rb +2 -0
  18. data/lib/claude_agent_sdk/types/messages.rb +7 -1
  19. data/lib/claude_agent_sdk/types/options.rb +69 -12
  20. data/lib/claude_agent_sdk/version.rb +1 -1
  21. data/lib/claude_agent_sdk.rb +28 -18
  22. data/sig/claude_agent_sdk/cancellation_signal.rbs +14 -0
  23. data/sig/claude_agent_sdk/configuration.rbs +14 -0
  24. data/sig/claude_agent_sdk/errors.rbs +86 -0
  25. data/sig/claude_agent_sdk/observer.rbs +42 -0
  26. data/sig/claude_agent_sdk/railtie.rbs +10 -0
  27. data/sig/claude_agent_sdk/sdk_mcp_server.rbs +76 -0
  28. data/sig/claude_agent_sdk/session_store.rbs +105 -0
  29. data/sig/claude_agent_sdk/streaming.rbs +15 -0
  30. data/sig/claude_agent_sdk/transport.rbs +98 -0
  31. data/sig/claude_agent_sdk/types/base.rbs +39 -0
  32. data/sig/claude_agent_sdk/types/content_blocks.rbs +79 -0
  33. data/sig/claude_agent_sdk/types/hooks.rbs +528 -0
  34. data/sig/claude_agent_sdk/types/mcp.rbs +216 -0
  35. data/sig/claude_agent_sdk/types/messages.rbs +586 -0
  36. data/sig/claude_agent_sdk/types/option_values.rbs +245 -0
  37. data/sig/claude_agent_sdk/types/options.rbs +297 -0
  38. data/sig/claude_agent_sdk/types/permissions.rbs +108 -0
  39. data/sig/claude_agent_sdk/types/sessions.rbs +66 -0
  40. data/sig/claude_agent_sdk.rbs +231 -0
  41. data/sig/manifest.yaml +5 -0
  42. metadata +23 -2
@@ -1,20 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../deprecation'
4
-
5
3
  module ClaudeAgentSDK
6
4
  # Attribute declarations and the rules #[], #[]=, .new and the camelCase
7
5
  # readers follow (issue #126). Loaded by base.rb right after Type itself,
8
6
  # before any subclass declares an attribute.
9
7
  class Type
10
- # The 1.0 switch for attribute access, false through 0.x. When true:
11
- # an unknown key on a strict type (see .strict_attributes) raises
12
- # ArgumentError, and #[], #[]= and the camelCase readers reach declared
13
- # attributes only. While false, both cases warn once per class and name.
14
- ENFORCE_ATTRIBUTES = false
15
-
16
8
  LENIENT_KEY = :__claude_agent_sdk_lenient_attributes
17
- private_constant :ENFORCE_ATTRIBUTES, :LENIENT_KEY
9
+ private_constant :LENIENT_KEY
18
10
 
19
11
  class << self
20
12
  # attr_accessor / attr_reader / attr_writer also declare the names as
@@ -108,9 +100,9 @@ module ClaudeAgentSDK
108
100
 
109
101
  # Declares a type the user constructs and passes IN (option values, hook
110
102
  # matchers and outputs, permission results and updates). Constructing
111
- # one directly (.new or #[]=) with a key that is not an attribute warns
112
- # once per class and key — a typo would otherwise be dropped silently —
113
- # and raises ArgumentError from 1.0. A read-only attribute (the +type+,
103
+ # one directly (.new or #[]=) with a key that is not an attribute raises
104
+ # ArgumentError — a typo would otherwise be dropped silently (0.37 warned
105
+ # here; 1.0 raises). A read-only attribute (the +type+,
114
106
  # +hook_event_name+ or +behavior+ discriminator a type sets itself, which
115
107
  # its own #to_h emits) is accepted and ignored. Types the SDK parses from
116
108
  # CLI output stay lenient so a newer CLI's extra fields never break an
@@ -151,15 +143,14 @@ module ClaudeAgentSDK
151
143
  self.class.cache_attribute_reader(method_name, normalized.to_sym)
152
144
  return public_send(normalized, ...)
153
145
  end
154
- return public_send(normalized, ...) if reachable?(normalized, method_name, :camel_case)
146
+ return public_send(normalized, ...) if user_defined_method?(normalized)
155
147
  end
156
148
  super
157
149
  end
158
150
 
159
151
  def respond_to_missing?(method_name, include_private = false)
160
152
  normalized = normalize_name(method_name)
161
- (normalized != method_name.to_s && respond_to?(normalized) &&
162
- (!ENFORCE_ATTRIBUTES || attribute_method?(normalized))) || super
153
+ (normalized != method_name.to_s && respond_to?(normalized) && attribute_method?(normalized)) || super
163
154
  end
164
155
 
165
156
  def assign_attributes(attributes)
@@ -184,12 +175,12 @@ module ClaudeAgentSDK
184
175
  self.class.cache_attribute_writer(name, setter)
185
176
  return public_send(setter, value)
186
177
  end
187
- return public_send(setter, value) if reachable?(setter.to_s, name, :write)
178
+ return public_send(setter, value) if user_defined_method?(setter.to_s)
188
179
  end
189
180
  return unless self.class.strict_attributes? && !Thread.current[LENIENT_KEY] && !self.class.attribute?(normalized)
190
181
  return if respond_to?(normalized) && user_defined_method?(normalized)
191
182
 
192
- unknown_attribute(name, normalized)
183
+ unknown_attribute(name)
193
184
  end
194
185
 
195
186
  def read_attribute(name)
@@ -202,7 +193,7 @@ module ClaudeAgentSDK
202
193
  if self.class.attribute_method?(getter)
203
194
  self.class.cache_attribute_reader(name, getter.to_sym)
204
195
  public_send(getter)
205
- elsif reachable?(getter, name, :read)
196
+ elsif user_defined_method?(getter)
206
197
  public_send(getter)
207
198
  end
208
199
  end
@@ -234,38 +225,12 @@ module ClaudeAgentSDK
234
225
  !name.nil? && (name == 'ClaudeAgentSDK' || name.start_with?('ClaudeAgentSDK::'))
235
226
  end
236
227
 
237
- # #[], #[]= (and so .new) or a camelCase call resolved to +method_name+, a
238
- # public method. An attribute passes. Any other method (to_h, freeze, ...)
239
- # passes with a warning once per class and name through 0.x, and is
240
- # treated as undefined from 1.0.
241
- def reachable?(method_name, name, access)
242
- return true if attribute_method?(method_name)
243
- return false if ENFORCE_ATTRIBUTES
244
-
245
- class_name = self.class.name || self.class.inspect
246
- message = case access
247
- when :read then "#{class_name}#[]: #{name.inspect} is not an attribute; " \
248
- 'Type#[] will only read attributes in 1.0'
249
- when :write then "#{class_name}#[]=: #{name.inspect} is not an attribute; " \
250
- 'Type#[]= and .new will only write attributes in 1.0'
251
- else "#{class_name}##{name}: #{method_name} is not an attribute; " \
252
- 'camelCase methods will only reach attributes in 1.0'
253
- end
254
- Deprecation.warn_once_at_caller([:non_attribute, self.class, method_name], message)
255
- true
256
- end
257
-
258
- def unknown_attribute(name, normalized)
228
+ # A key that is not an attribute, on a strict type: a typo the user
229
+ # would otherwise never see.
230
+ def unknown_attribute(name)
259
231
  klass = self.class
260
- class_name = klass.name || klass.inspect
261
- known = klass.attribute_names.join(', ')
262
- raise ArgumentError, "#{class_name}: unknown attribute #{name.inspect} (known: #{known})" if ENFORCE_ATTRIBUTES
263
-
264
- Deprecation.warn_once_at_caller(
265
- [:unknown_attribute, klass, normalized],
266
- "#{class_name}: unknown attribute #{name.inspect} ignored; " \
267
- "this will raise ArgumentError in 1.0 (known: #{known})"
268
- )
232
+ raise ArgumentError, "#{klass.name || klass.inspect}: unknown attribute #{name.inspect} " \
233
+ "(known: #{klass.attribute_names.join(', ')})"
269
234
  end
270
235
  end
271
236
  end
@@ -158,6 +158,8 @@ module ClaudeAgentSDK
158
158
  # `seen` holds the Types/containers on the current rendering path (not
159
159
  # every one rendered so far), so a shared-but-acyclic value still renders
160
160
  # in full wherever it appears.
161
+ #
162
+ # @api private
161
163
  def inspect_with(depth, seen)
162
164
  return "#<#{inspect_class_name} …>" if depth > INSPECT_MAX_DEPTH || seen.key?(self)
163
165
 
@@ -192,7 +192,13 @@ module ClaudeAgentSDK
192
192
  :outcome # "success", "error", or "cancelled"
193
193
  end
194
194
 
195
- # Session state changed system message
195
+ # Session state changed system message.
196
+ #
197
+ # Reaches your code only when you opt in with
198
+ # `CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS=1` in ClaudeAgentOptions#env. The
199
+ # SDK also asks the CLI for these frames on its own behalf (to tell when a
200
+ # query() run is over), but those arrive marked `sdk_host_only` and are
201
+ # dropped before the message stream.
196
202
  class SessionStateChangedMessage < SystemMessage
197
203
  attr_accessor :uuid, :session_id,
198
204
  :state # "idle", "running", or "requires_action"
@@ -81,21 +81,11 @@ module ClaudeAgentSDK
81
81
  self.include_hook_events = false
82
82
  self.strict_mcp_config = false
83
83
  self.forward_subagent_text = false
84
+ self.verbatim_prompts = false
84
85
 
85
86
  super(merge_with_defaults(attributes || {}))
86
87
 
87
- # Non-nil defaults for options that need them.
88
- self.env ||= {}
89
- self.extra_args ||= {}
90
- self.mcp_servers ||= {}
91
- self.add_dirs ||= []
92
- self.observers ||= []
93
- self.allowed_tools ||= []
94
- self.disallowed_tools ||= []
95
- self.session_store_flush ||= 'batched'
96
- # 0 is a valid (immediate) timeout, so only fill in the default for nil.
97
- self.load_timeout_ms = 60_000 if load_timeout_ms.nil?
98
- self.callback_scheduling = :thread if callback_scheduling.nil?
88
+ fill_nil_defaults
99
89
  end
100
90
 
101
91
  def dup_with(**changes)
@@ -203,6 +193,58 @@ module ClaudeAgentSDK
203
193
  @forward_subagent_text = coerce_boolean(value)
204
194
  end
205
195
 
196
+ # Deliver every prompt to Claude as written.
197
+ #
198
+ # When true, every user message the SDK sends is marked `client_composed`:
199
+ # a String prompt to {ClaudeAgentSDK.query}, {Client#connect} or
200
+ # {Client#query}, and every message of a streamed (Enumerable) prompt to
201
+ # any of them. Claude Code then delivers the text exactly as given: no
202
+ # `@path` file-mention expansion and no slash-command dispatch. Use it when
203
+ # the prompt is assembled from content your end user did not type (earlier
204
+ # turns, tool output, third-party text), so an `@/absolute/path` inside it
205
+ # cannot make Claude Code read a local file. `tools: []`, `allowed_tools`
206
+ # and `disallowed_tools` do not stop that expansion: it happens before the
207
+ # model runs, without a tool call.
208
+ #
209
+ # While the option is on there is no per-message opt-out: a
210
+ # `client_composed` key on a streamed message Hash (either spelling) is
211
+ # overwritten. For per-turn control, leave the option off and set
212
+ # `client_composed: true` on individual streamed messages. The caller's
213
+ # Hashes are never mutated. A streamed JSONL String is parsed, marked and
214
+ # re-serialized; one that is not a single JSON object raises
215
+ # `ArgumentError` rather than being sent unmarked (on the background
216
+ # streaming paths that ends the stream with a warning, like any other
217
+ # stream error).
218
+ #
219
+ # On current Claude Code versions a turn delivered this way also skips the
220
+ # turn-start attachment pass as a whole: `@server:resource` MCP mentions
221
+ # are not expanded either, and the prompt goes without the context Claude
222
+ # Code normally attaches (nested `CLAUDE.md` and rules files, skill and
223
+ # tool listings, other per-turn reminders). The pass between tool calls is
224
+ # unaffected, so most of that context arrives after the turn's first tool
225
+ # call instead.
226
+ #
227
+ # Requires Claude Code 2.1.248 or later; older versions ignore the field,
228
+ # so prompts are still expanded there, and the SDK warns when it connects
229
+ # to one with this option on. Read once when the session starts. Not a
230
+ # CLI flag. Matches the Python SDK's `verbatim_prompts`.
231
+ #
232
+ # Assigning coerces to a Boolean; {#verbatim_prompts?} is the predicate
233
+ # form.
234
+ #
235
+ # @return [Boolean]
236
+ attr_reader :verbatim_prompts
237
+
238
+ # @return [Boolean] {#verbatim_prompts}, as a strict Boolean.
239
+ def verbatim_prompts?
240
+ !!verbatim_prompts
241
+ end
242
+
243
+ # @see #verbatim_prompts
244
+ def verbatim_prompts=(value)
245
+ @verbatim_prompts = coerce_boolean(value)
246
+ end
247
+
206
248
  # Request model-generated progress summaries for subagent (`local_agent`)
207
249
  # tasks. `true` *requests* generation: while the CLI has it enabled, a
208
250
  # subagent's {TaskProgressMessage#summary} **may** carry a one-line status.
@@ -288,6 +330,21 @@ module ClaudeAgentSDK
288
330
 
289
331
  private
290
332
 
333
+ # Non-nil defaults for options that need them.
334
+ def fill_nil_defaults
335
+ self.env ||= {}
336
+ self.extra_args ||= {}
337
+ self.mcp_servers ||= {}
338
+ self.add_dirs ||= []
339
+ self.observers ||= []
340
+ self.allowed_tools ||= []
341
+ self.disallowed_tools ||= []
342
+ self.session_store_flush ||= 'batched'
343
+ # 0 is a valid (immediate) timeout, so only fill in the default for nil.
344
+ self.load_timeout_ms = 60_000 if load_timeout_ms.nil?
345
+ self.callback_scheduling = :thread if callback_scheduling.nil?
346
+ end
347
+
291
348
  # Strict key validation: unlike other Type subclasses (which silently drop
292
349
  # unknown keys for forward-compat with newer CLI output), ClaudeAgentOptions
293
350
  # is a developer-facing config object — typos should fail loudly.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeAgentSDK
4
- VERSION = '0.37.0'
4
+ VERSION = '1.1.0'
5
5
  end
@@ -541,27 +541,27 @@ module ClaudeAgentSDK # rubocop:disable Metrics/ModuleLength -- the public entry
541
541
  SessionSummary.fold_session_summary(prev, key, entries)
542
542
  end
543
543
 
544
- # ---- Deprecated store twins (removed in 1.0) ----
544
+ # ---- Deprecated store twins (removed in 2.0) ----
545
545
  #
546
546
  # Each forwards to the same implementation as before — not to the merged
547
547
  # function, so a nil session_store keeps failing as it always did instead
548
548
  # of silently reading local disk — after one warning per method per process.
549
549
 
550
- # @deprecated Use {.list_sessions} with +session_store:+. Removed in 1.0.
550
+ # @deprecated Use {.list_sessions} with +session_store:+. Removed in 2.0.
551
551
  # @return [Array<SDKSessionInfo>] sorted by last_modified descending
552
552
  def self.list_sessions_from_store(session_store:, directory: nil, limit: nil, offset: 0)
553
553
  Deprecation.warn_once(:list_sessions_from_store, 'list_sessions(session_store: store)')
554
554
  Sessions.list_sessions_from_store(session_store: session_store, directory: directory, limit: limit, offset: offset)
555
555
  end
556
556
 
557
- # @deprecated Use {.get_session_info} with +session_store:+. Removed in 1.0.
557
+ # @deprecated Use {.get_session_info} with +session_store:+. Removed in 2.0.
558
558
  # @return [SDKSessionInfo, nil]
559
559
  def self.get_session_info_from_store(session_store:, session_id:, directory: nil)
560
560
  Deprecation.warn_once(:get_session_info_from_store, 'get_session_info(session_store: store, ...)')
561
561
  Sessions.get_session_info_from_store(session_store: session_store, session_id: session_id, directory: directory)
562
562
  end
563
563
 
564
- # @deprecated Use {.get_session_messages} with +session_store:+. Removed in 1.0.
564
+ # @deprecated Use {.get_session_messages} with +session_store:+. Removed in 2.0.
565
565
  # @return [Array<SessionMessage>]
566
566
  def self.get_session_messages_from_store(session_store:, session_id:, directory: nil, limit: nil, offset: 0)
567
567
  Deprecation.warn_once(:get_session_messages_from_store, 'get_session_messages(session_store: store, ...)')
@@ -569,14 +569,14 @@ module ClaudeAgentSDK # rubocop:disable Metrics/ModuleLength -- the public entry
569
569
  directory: directory, limit: limit, offset: offset)
570
570
  end
571
571
 
572
- # @deprecated Use {.list_subagents} with +session_store:+. Removed in 1.0.
572
+ # @deprecated Use {.list_subagents} with +session_store:+. Removed in 2.0.
573
573
  # @return [Array<String>]
574
574
  def self.list_subagents_from_store(session_store:, session_id:, directory: nil)
575
575
  Deprecation.warn_once(:list_subagents_from_store, 'list_subagents(session_store: store, ...)')
576
576
  Sessions.list_subagents_from_store(session_store: session_store, session_id: session_id, directory: directory)
577
577
  end
578
578
 
579
- # @deprecated Use {.get_subagent_metadata} with +session_store:+. Removed in 1.0.
579
+ # @deprecated Use {.get_subagent_metadata} with +session_store:+. Removed in 2.0.
580
580
  # @return [Hash{String => Object}, nil]
581
581
  def self.get_subagent_metadata_from_store(session_store:, session_id:, agent_id:, directory: nil)
582
582
  Deprecation.warn_once(:get_subagent_metadata_from_store, 'get_subagent_metadata(session_store: store, ...)')
@@ -584,7 +584,7 @@ module ClaudeAgentSDK # rubocop:disable Metrics/ModuleLength -- the public entry
584
584
  agent_id: agent_id, directory: directory)
585
585
  end
586
586
 
587
- # @deprecated Use {.get_subagent_messages} with +session_store:+. Removed in 1.0.
587
+ # @deprecated Use {.get_subagent_messages} with +session_store:+. Removed in 2.0.
588
588
  # @return [Array<SessionMessage>]
589
589
  def self.get_subagent_messages_from_store(session_store:, session_id:, agent_id:, directory: nil, limit: nil,
590
590
  offset: 0)
@@ -593,28 +593,28 @@ module ClaudeAgentSDK # rubocop:disable Metrics/ModuleLength -- the public entry
593
593
  agent_id: agent_id, directory: directory, limit: limit, offset: offset)
594
594
  end
595
595
 
596
- # @deprecated Use {.rename_session} with +session_store:+. Removed in 1.0.
596
+ # @deprecated Use {.rename_session} with +session_store:+. Removed in 2.0.
597
597
  def self.rename_session_via_store(session_store:, session_id:, title:, directory: nil)
598
598
  Deprecation.warn_once(:rename_session_via_store, 'rename_session(session_store: store, ...)')
599
599
  SessionMutations.rename_session_via_store(session_store: session_store, session_id: session_id,
600
600
  title: title, directory: directory)
601
601
  end
602
602
 
603
- # @deprecated Use {.tag_session} with +session_store:+. Removed in 1.0.
603
+ # @deprecated Use {.tag_session} with +session_store:+. Removed in 2.0.
604
604
  def self.tag_session_via_store(session_store:, session_id:, tag:, directory: nil)
605
605
  Deprecation.warn_once(:tag_session_via_store, 'tag_session(session_store: store, ...)')
606
606
  SessionMutations.tag_session_via_store(session_store: session_store, session_id: session_id,
607
607
  tag: tag, directory: directory)
608
608
  end
609
609
 
610
- # @deprecated Use {.delete_session} with +session_store:+. Removed in 1.0.
610
+ # @deprecated Use {.delete_session} with +session_store:+. Removed in 2.0.
611
611
  def self.delete_session_via_store(session_store:, session_id:, directory: nil)
612
612
  Deprecation.warn_once(:delete_session_via_store, 'delete_session(session_store: store, ...)')
613
613
  SessionMutations.delete_session_via_store(session_store: session_store, session_id: session_id,
614
614
  directory: directory)
615
615
  end
616
616
 
617
- # @deprecated Use {.fork_session} with +session_store:+. Removed in 1.0.
617
+ # @deprecated Use {.fork_session} with +session_store:+. Removed in 2.0.
618
618
  # @return [ForkSessionResult]
619
619
  def self.fork_session_via_store(session_store:, session_id:, directory: nil, up_to_message_id: nil, title: nil)
620
620
  Deprecation.warn_once(:fork_session_via_store, 'fork_session(session_store: store, ...)')
@@ -749,7 +749,9 @@ module ClaudeAgentSDK # rubocop:disable Metrics/ModuleLength -- the public entry
749
749
  forward_subagent_text: configured_options.forward_subagent_text?,
750
750
  agent_progress_summaries: configured_options.agent_progress_summaries,
751
751
  callback_scheduling: callback_scheduling,
752
- callback_wrapper: callback_wrapper
752
+ callback_wrapper: callback_wrapper,
753
+ verbatim_prompts: configured_options.verbatim_prompts?,
754
+ run_end_ceiling_ms: Query.run_end_ceiling_ms(configured_options.env)
753
755
  )
754
756
 
755
757
  # Mirror transcripts to the session_store, if configured. Installed
@@ -782,7 +784,7 @@ module ClaudeAgentSDK # rubocop:disable Metrics/ModuleLength -- the public entry
782
784
  parent_tool_use_id: nil,
783
785
  session_id: ''
784
786
  }
785
- transport.write("#{JSON.generate(message)}\n")
787
+ transport.write("#{Query.serialize_user_message(message, configured_options.verbatim_prompts?)}\n")
786
788
  # Background-spawn so messages stream to the user block while stdin
787
789
  # close waits (without timeout) for the first result; a synchronous
788
790
  # call would defer all delivery until the turn completes (mirrors
@@ -1084,7 +1086,7 @@ module ClaudeAgentSDK # rubocop:disable Metrics/ModuleLength -- the public entry
1084
1086
  parent_tool_use_id: nil,
1085
1087
  session_id: session_id
1086
1088
  }
1087
- writeln(JSON.generate(message))
1089
+ writeln(Query.serialize_user_message(message, @verbatim_prompts))
1088
1090
  elsif prompt.respond_to?(:each)
1089
1091
  # Inline iteration on the caller, Python client.py parity — NOT
1090
1092
  # Query#stream_input, whose ensure always ends input after
@@ -1394,6 +1396,10 @@ module ClaudeAgentSDK # rubocop:disable Metrics/ModuleLength -- the public entry
1394
1396
  exclude_dynamic_sections = ClaudeAgentSDK.extract_exclude_dynamic_sections(configured_options.system_prompt)
1395
1397
  system_prompt_snapshot = ClaudeAgentSDK.extract_system_prompt_snapshot(configured_options.system_prompt)
1396
1398
 
1399
+ # Captured once, so String and streamed prompts in one session are
1400
+ # stamped alike (the Query stamps the streamed ones with this value).
1401
+ @verbatim_prompts = configured_options.verbatim_prompts?
1402
+
1397
1403
  # Create Query handler
1398
1404
  @query_handler = Query.new(
1399
1405
  transport: @transport,
@@ -1408,7 +1414,9 @@ module ClaudeAgentSDK # rubocop:disable Metrics/ModuleLength -- the public entry
1408
1414
  forward_subagent_text: configured_options.forward_subagent_text?,
1409
1415
  agent_progress_summaries: configured_options.agent_progress_summaries,
1410
1416
  callback_scheduling: @callback_scheduling,
1411
- callback_wrapper: @callback_wrapper
1417
+ callback_wrapper: @callback_wrapper,
1418
+ verbatim_prompts: @verbatim_prompts,
1419
+ run_end_ceiling_ms: Query.run_end_ceiling_ms(configured_options.env)
1412
1420
  )
1413
1421
 
1414
1422
  # Mirror transcripts to the session_store, if configured.
@@ -1450,7 +1458,9 @@ module ClaudeAgentSDK # rubocop:disable Metrics/ModuleLength -- the public entry
1450
1458
  # key styles — an explicit nil is preserved, mirroring Python's
1451
1459
  # `"session_id" not in msg`). Strings pass through verbatim (Ruby
1452
1460
  # superset: Streaming.user_message emits pre-serialized JSONL; no
1453
- # parse-stamp-regenerate, which would block the reactor on huge frames).
1461
+ # parse-stamp-regenerate, which would block the reactor on huge frames),
1462
+ # except that verbatim_prompts must mark them `client_composed`, so with
1463
+ # that option on they are parsed and re-serialized (Query.stamp_user_message).
1454
1464
  def stream_query_messages(prompt, session_id)
1455
1465
  prompt.each do |msg|
1456
1466
  case msg
@@ -1460,13 +1470,13 @@ module ClaudeAgentSDK # rubocop:disable Metrics/ModuleLength -- the public entry
1460
1470
  ClaudeAgentSDK.notify_observers(@resolved_observers, :on_user_prompt, text,
1461
1471
  scheduling: @callback_scheduling, wrapper: @callback_wrapper)
1462
1472
  end
1463
- writeln(JSON.generate(msg))
1473
+ writeln(Query.serialize_user_message(msg, @verbatim_prompts))
1464
1474
  when String
1465
1475
  if (text = ClaudeAgentSDK.extract_user_prompt_text(msg))
1466
1476
  ClaudeAgentSDK.notify_observers(@resolved_observers, :on_user_prompt, text,
1467
1477
  scheduling: @callback_scheduling, wrapper: @callback_wrapper)
1468
1478
  end
1469
- writeln(msg)
1479
+ writeln(Query.serialize_user_message(msg, @verbatim_prompts))
1470
1480
  else
1471
1481
  # No to_s fallback — silently serializing arbitrary objects is the
1472
1482
  # exact inspect-garbage bug class this method exists to prevent.
@@ -0,0 +1,14 @@
1
+ module ClaudeAgentSDK
2
+ # Delivered as ToolPermissionContext#signal and HookContext#signal. It is
3
+ # cancelled when the CLI cancels the request (or the session closes), so a
4
+ # long-running callback can stop early.
5
+ class CancellationSignal
6
+ def initialize: () -> void
7
+
8
+ def cancelled?: () -> bool
9
+
10
+ # Blocks until cancelled or until timeout seconds pass; returns
11
+ # #cancelled?.
12
+ def wait: (?timeout: (Integer | Float)?) -> bool
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module ClaudeAgentSDK
2
+ # Global defaults set via ClaudeAgentSDK.configure, merged under every
3
+ # ClaudeAgentOptions (per-call options win).
4
+ class Configuration
5
+ # A frozen snapshot of the configured defaults ({} when unset). Keys are
6
+ # ClaudeAgentOptions option names.
7
+ attr_reader default_options: Hash[Symbol | String, untyped]
8
+
9
+ def initialize: () -> void
10
+
11
+ # Stores a deep-frozen copy; nil resets to {}.
12
+ def default_options=: (Hash[Symbol | String, untyped]? value) -> Hash[Symbol | String, untyped]?
13
+ end
14
+ end
@@ -0,0 +1,86 @@
1
+ module ClaudeAgentSDK
2
+ # Base exception for all Claude SDK errors.
3
+ class ClaudeSDKError < StandardError
4
+ end
5
+
6
+ # Raised when unable to connect to Claude Code (also: "Not connected").
7
+ class CLIConnectionError < ClaudeSDKError
8
+ end
9
+
10
+ # Raised when the control protocol does not respond in time.
11
+ class ControlRequestTimeoutError < CLIConnectionError
12
+ end
13
+
14
+ # Raised when Claude Code is not found or not installed.
15
+ class CLINotFoundError < CLIConnectionError
16
+ def initialize: (?String message, ?cli_path: (String | Pathname)?) -> void
17
+ end
18
+
19
+ # Raised when CLIInstaller cannot install the CLI binary.
20
+ class CLIInstallError < ClaudeSDKError
21
+ end
22
+
23
+ # Raised by the local-disk session APIs when the Claude config directory
24
+ # cannot be located.
25
+ class ConfigDirError < ClaudeSDKError
26
+ end
27
+
28
+ # Raised by Client#connect, ClaudeAgentSDK.query and .ask when resuming
29
+ # from ClaudeAgentOptions#session_store fails (a store call raised or
30
+ # exceeded load_timeout_ms). #cause holds the adapter's own exception.
31
+ class SessionStoreError < ClaudeSDKError
32
+ end
33
+
34
+ # Raised when the CLI process fails.
35
+ class ProcessError < ClaudeSDKError
36
+ attr_reader exit_code: Integer?
37
+
38
+ attr_reader stderr: String?
39
+
40
+ def initialize: (String message, ?exit_code: Integer?, ?stderr: String?) -> void
41
+ end
42
+
43
+ # Raised when the CLI exits non-zero after an error result. Every
44
+ # structured field is type-narrowed (nil unless the payload held the
45
+ # documented type); #data holds the payload as the CLI sent it.
46
+ class ResultError < ProcessError
47
+ attr_reader subtype: String?
48
+
49
+ attr_reader errors: Array[String]
50
+
51
+ attr_reader result: String?
52
+
53
+ attr_reader api_error_status: Integer?
54
+
55
+ attr_reader terminal_reason: String?
56
+
57
+ attr_reader session_id: String?
58
+
59
+ attr_reader data: Hash[Symbol | String, untyped]
60
+
61
+ attr_reader original_error: ProcessError?
62
+
63
+ # The exception text a `result` payload produces (any payload is
64
+ # accepted; a non-Hash reads as empty).
65
+ def self.error_text: (untyped data) -> String
66
+
67
+ def initialize: (String message, ?data: untyped, ?exit_code: Integer?, ?stderr: String?, ?original_error: ProcessError?) -> void
68
+ end
69
+
70
+ # Raised when a stdout line from the CLI is not valid JSON.
71
+ class CLIJSONDecodeError < ClaudeSDKError
72
+ attr_reader line: String
73
+
74
+ attr_reader original_error: Exception
75
+
76
+ def initialize: (String line, Exception original_error) -> void
77
+ end
78
+
79
+ # Raised when a CLI message cannot be parsed into a typed message.
80
+ class MessageParseError < ClaudeSDKError
81
+ # The offending payload, as received.
82
+ attr_reader data: untyped
83
+
84
+ def initialize: (String message, ?data: untyped) -> void
85
+ end
86
+ end
@@ -0,0 +1,42 @@
1
+ module ClaudeAgentSDK
2
+ # Base module for message observers (ClaudeAgentOptions#observers): include
3
+ # it and override the callbacks you need; each defaults to a no-op.
4
+ # Observer errors are rescued and never reach the pipeline.
5
+ module Observer
6
+ def on_user_prompt: (String prompt) -> void
7
+
8
+ def on_message: (message message) -> void
9
+
10
+ def on_error: (StandardError error) -> void
11
+
12
+ def on_close: () -> void
13
+ end
14
+
15
+ # Loaded by `require 'claude_agent_sdk/instrumentation'` (needs the
16
+ # opentelemetry-api gem).
17
+ module Instrumentation
18
+ # Emits OpenTelemetry spans (gen_ai.* + OpenInference conventions) for a
19
+ # session.
20
+ class OTelObserver
21
+ include Observer
22
+
23
+ TRACER_NAME: String
24
+
25
+ MAX_ATTRIBUTE_LENGTH: Integer
26
+
27
+ # default_attributes are added to every span. Attribute names usually
28
+ # contain dots, so they are commonly given as String keys
29
+ # (`new('user.id' => 'u1')`).
30
+ def initialize: (?tracer_name: String, **untyped default_attributes) -> void
31
+ | (Hash[String | Symbol, untyped] default_attributes) -> void
32
+
33
+ def on_user_prompt: (String prompt) -> void
34
+
35
+ def on_message: (message message) -> void
36
+
37
+ def on_error: (StandardError error) -> void
38
+
39
+ def on_close: () -> void
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,10 @@
1
+ module ClaudeAgentSDK
2
+ # Rails integration (lib/claude_agent_sdk/railtie.rb), loaded only when
3
+ # Rails::Railtie is defined. Declared without its Rails::Railtie superclass
4
+ # so these signatures validate without Rails' own.
5
+ class Railtie
6
+ # A callback_wrapper that runs SDK callbacks in the Rails executor,
7
+ # deadlock-free under development code reloading.
8
+ def self.callback_wrapper: () -> _CallbackWrapper
9
+ end
10
+ end