claude-agent-sdk 0.36.0 → 0.37.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/README.md +2 -2
  4. data/docs/client.md +26 -1
  5. data/docs/hooks-and-permissions.md +5 -3
  6. data/docs/mcp-servers.md +1 -2
  7. data/docs/sessions.md +81 -2
  8. data/docs/types.md +106 -4
  9. data/lib/claude_agent_sdk/cli_installer.rb +30 -3
  10. data/lib/claude_agent_sdk/command_builder.rb +109 -98
  11. data/lib/claude_agent_sdk/deprecation.rb +39 -0
  12. data/lib/claude_agent_sdk/fiber_boundary.rb +2 -0
  13. data/lib/claude_agent_sdk/instrumentation/otel.rb +15 -7
  14. data/lib/claude_agent_sdk/message_parser.rb +23 -9
  15. data/lib/claude_agent_sdk/observer.rb +2 -1
  16. data/lib/claude_agent_sdk/option_warnings.rb +2 -0
  17. data/lib/claude_agent_sdk/query.rb +50 -43
  18. data/lib/claude_agent_sdk/sdk_mcp_server.rb +22 -13
  19. data/lib/claude_agent_sdk/session_mutations.rb +20 -8
  20. data/lib/claude_agent_sdk/session_resume.rb +20 -11
  21. data/lib/claude_agent_sdk/session_store.rb +7 -3
  22. data/lib/claude_agent_sdk/session_summary.rb +4 -2
  23. data/lib/claude_agent_sdk/sessions.rb +8 -6
  24. data/lib/claude_agent_sdk/streaming.rb +1 -1
  25. data/lib/claude_agent_sdk/subprocess_cli_transport.rb +72 -40
  26. data/lib/claude_agent_sdk/testing/session_store_conformance.rb +14 -10
  27. data/lib/claude_agent_sdk/transcript_mirror_batcher.rb +2 -0
  28. data/lib/claude_agent_sdk/types/attributes.rb +271 -0
  29. data/lib/claude_agent_sdk/types/base.rb +320 -0
  30. data/lib/claude_agent_sdk/types/content_blocks.rb +57 -0
  31. data/lib/claude_agent_sdk/types/hooks.rb +640 -0
  32. data/lib/claude_agent_sdk/types/mcp.rb +232 -0
  33. data/lib/claude_agent_sdk/types/messages.rb +614 -0
  34. data/lib/claude_agent_sdk/types/option_values.rb +302 -0
  35. data/lib/claude_agent_sdk/types/options.rb +352 -0
  36. data/lib/claude_agent_sdk/types/permissions.rb +107 -0
  37. data/lib/claude_agent_sdk/types/sessions.rb +10 -0
  38. data/lib/claude_agent_sdk/types.rb +13 -2534
  39. data/lib/claude_agent_sdk/version.rb +1 -1
  40. data/lib/claude_agent_sdk.rb +51 -17
  41. metadata +11 -1
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeAgentSDK
4
- VERSION = '0.36.0'
4
+ VERSION = '0.37.0'
5
5
  end
@@ -29,9 +29,11 @@ require 'async'
29
29
  require 'securerandom'
30
30
 
31
31
  # Claude Agent SDK for Ruby
32
- module ClaudeAgentSDK
32
+ module ClaudeAgentSDK # rubocop:disable Metrics/ModuleLength -- the public entry points (query, ask, sessions API) live on the root module
33
33
  # The duck-typed observer surface probed by resolve_observers — implementing
34
34
  # any one of these counts as an observer (see Observer's no-op defaults).
35
+ #
36
+ # @api private
35
37
  OBSERVER_INTERFACE = %i[on_user_prompt on_message on_error on_close].freeze
36
38
 
37
39
  # Resolve observers array: callables (Proc/lambda) are invoked to produce
@@ -117,7 +119,9 @@ module ClaudeAgentSDK
117
119
  return options unless options.can_use_tool
118
120
 
119
121
  # can_use_tool and permission_prompt_tool_name are mutually exclusive
120
- raise ArgumentError, 'can_use_tool callback cannot be used with permission_prompt_tool_name' if options.permission_prompt_tool_name
122
+ if options.permission_prompt_tool_name
123
+ raise ArgumentError, 'can_use_tool callback cannot be used with permission_prompt_tool_name'
124
+ end
121
125
 
122
126
  # Advisory: warn if other options shadow the callback. After the
123
127
  # ArgumentError above so invalid configs raise, not warn.
@@ -621,6 +625,7 @@ module ClaudeAgentSDK
621
625
  # Replay a local on-disk session transcript into a SessionStore (migration /
622
626
  # gap-backfill). Keys under the on-disk project dir so the imported session is
623
627
  # resumable via session_store + resume from the original cwd.
628
+ # @param batch_size [Integer] entries per SessionStore#append call (default 500)
624
629
  # @raise [ArgumentError] if session_id is not a valid UUID
625
630
  # @raise [Errno::ENOENT] if the session JSONL cannot be found
626
631
  def self.import_session_to_store(session_id:, session_store:, directory: nil, include_subagents: true,
@@ -664,13 +669,17 @@ module ClaudeAgentSDK
664
669
  # ClaudeAgentSDK.query(prompt: messages) do |message|
665
670
  # puts message
666
671
  # end
667
- def self.query(prompt:, options: nil, transport: nil, &block)
672
+ def self.query(prompt:, options: nil, transport: nil, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity -- one-shot lifecycle (validate, resume, connect, stream, teardown) kept linear
668
673
  # Validate BEFORE the block-less enum_for return so a bad prompt fails at
669
674
  # the call site, not on first iteration. Mirrors Client#query: a bare Hash
670
675
  # responds to #each and would stream [key, value] pairs' to_s garbage to
671
676
  # the CLI; nil/Integer would hang forever waiting for input.
672
- raise ArgumentError, 'prompt must be a String or an Enumerable of message Hashes/JSONL Strings (got Hash)' if prompt.is_a?(Hash)
673
- raise ArgumentError, "prompt must be a String or respond to #each (got #{prompt.class})" unless prompt.is_a?(String) || prompt.respond_to?(:each)
677
+ if prompt.is_a?(Hash)
678
+ raise ArgumentError, 'prompt must be a String or an Enumerable of message Hashes/JSONL Strings (got Hash)'
679
+ end
680
+ unless prompt.is_a?(String) || prompt.respond_to?(:each)
681
+ raise ArgumentError, "prompt must be a String or respond to #each (got #{prompt.class})"
682
+ end
674
683
 
675
684
  return enum_for(:query, prompt: prompt, options: options, transport: transport) unless block
676
685
 
@@ -690,9 +699,11 @@ module ClaudeAgentSDK
690
699
  callback_wrapper = configured_options.callback_wrapper
691
700
  ClaudeAgentSDK.check_inline_isolation(callback_scheduling)
692
701
 
693
- raise ArgumentError, 'transport must respond to #connect (see ClaudeAgentSDK::Transport)' if transport && !transport.respond_to?(:connect)
702
+ if transport && !transport.respond_to?(:connect)
703
+ raise ArgumentError, 'transport must respond to #connect (see ClaudeAgentSDK::Transport)'
704
+ end
694
705
 
695
- Async(&FiberBoundary.capture_otel_context do
706
+ Async(&FiberBoundary.capture_otel_context do # rubocop:disable Metrics/BlockLength -- the reactor task body of query()
696
707
  materialized = nil
697
708
  query_handler = nil
698
709
  begin
@@ -705,7 +716,9 @@ module ClaudeAgentSDK
705
716
  # env/--resume only apply to the CLI subprocess (Python parity:
706
717
  # client.py skips materialization when a transport is supplied).
707
718
  materialized = SessionResume.materialize_resume_session(configured_options)
708
- configured_options = SessionResume.apply_materialized_options(configured_options, materialized) if materialized
719
+ if materialized
720
+ configured_options = SessionResume.apply_materialized_options(configured_options, materialized)
721
+ end
709
722
 
710
723
  # Always use streaming mode with control protocol (matches Python
711
724
  # SDK). This sends agents via initialize request instead of CLI
@@ -769,7 +782,7 @@ module ClaudeAgentSDK
769
782
  parent_tool_use_id: nil,
770
783
  session_id: ''
771
784
  }
772
- transport.write(JSON.generate(message) + "\n")
785
+ transport.write("#{JSON.generate(message)}\n")
773
786
  # Background-spawn so messages stream to the user block while stdin
774
787
  # close waits (without timeout) for the first result; a synchronous
775
788
  # call would defer all delivery until the turn completes (mirrors
@@ -780,8 +793,9 @@ module ClaudeAgentSDK
780
793
  # here kept the root reactor alive forever when the read loop died
781
794
  # while the user enumerator was still blocked (matches Python's
782
795
  # query.spawn_task(query.stream_input(prompt))).
783
- observed_prompt = ClaudeAgentSDK.observing_prompt_stream(prompt, resolved_observers,
784
- scheduling: callback_scheduling, wrapper: callback_wrapper)
796
+ observed_prompt = ClaudeAgentSDK.observing_prompt_stream(
797
+ prompt, resolved_observers, scheduling: callback_scheduling, wrapper: callback_wrapper
798
+ )
785
799
  query_handler.spawn_task { query_handler.stream_input(observed_prompt) }
786
800
  end
787
801
 
@@ -916,7 +930,10 @@ module ClaudeAgentSDK
916
930
  # }
917
931
  # )
918
932
  # client = ClaudeAgentSDK::Client.new(options: options)
919
- class Client
933
+ class Client # rubocop:disable Metrics/ClassLength -- public session API: lifecycle, control methods and their Ruby aliases
934
+ # The session's control-protocol handler (nil until #connect).
935
+ #
936
+ # @api private
920
937
  attr_reader :query_handler
921
938
 
922
939
  # @param options [ClaudeAgentOptions, nil] Configuration options
@@ -983,8 +1000,12 @@ module ClaudeAgentSDK
983
1000
  def connect(prompt = nil)
984
1001
  return if @connected
985
1002
 
986
- raise ArgumentError, 'prompt must be a String or an Enumerable of message Hashes/JSONL Strings (got Hash)' if prompt.is_a?(Hash)
987
- raise ArgumentError, "prompt must be a String, an Enumerator, or nil (got #{prompt.class})" unless prompt.nil? || prompt.is_a?(String) || prompt.respond_to?(:each)
1003
+ if prompt.is_a?(Hash)
1004
+ raise ArgumentError, 'prompt must be a String or an Enumerable of message Hashes/JSONL Strings (got Hash)'
1005
+ end
1006
+ unless prompt.nil? || prompt.is_a?(String) || prompt.respond_to?(:each)
1007
+ raise ArgumentError, "prompt must be a String, an Enumerator, or nil (got #{prompt.class})"
1008
+ end
988
1009
 
989
1010
  # Validate and configure permission settings
990
1011
  configured_options = ClaudeAgentSDK.configure_can_use_tool(@options)
@@ -1049,7 +1070,9 @@ module ClaudeAgentSDK
1049
1070
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
1050
1071
  # A bare Hash responds to #each and would silently iterate [key, value]
1051
1072
  # pairs (Python's async-for over a dict raises TypeError).
1052
- raise ArgumentError, 'prompt must be a String or an Enumerable of message Hashes/JSONL Strings (got Hash)' if prompt.is_a?(Hash)
1073
+ if prompt.is_a?(Hash)
1074
+ raise ArgumentError, 'prompt must be a String or an Enumerable of message Hashes/JSONL Strings (got Hash)'
1075
+ end
1053
1076
 
1054
1077
  begin
1055
1078
  if prompt.is_a?(String)
@@ -1139,6 +1162,7 @@ module ClaudeAgentSDK
1139
1162
  # Send interrupt signal
1140
1163
  def interrupt
1141
1164
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
1165
+
1142
1166
  @query_handler.interrupt
1143
1167
  end
1144
1168
 
@@ -1146,6 +1170,7 @@ module ClaudeAgentSDK
1146
1170
  # @param mode [String] Permission mode ('default', 'acceptEdits', 'bypassPermissions')
1147
1171
  def set_permission_mode(mode)
1148
1172
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
1173
+
1149
1174
  @query_handler.set_permission_mode(mode)
1150
1175
  end
1151
1176
 
@@ -1159,6 +1184,7 @@ module ClaudeAgentSDK
1159
1184
  # @param model [String, nil] Model name or nil for default
1160
1185
  def set_model(model)
1161
1186
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
1187
+
1162
1188
  @query_handler.set_model(model)
1163
1189
  end
1164
1190
 
@@ -1171,6 +1197,7 @@ module ClaudeAgentSDK
1171
1197
  # @param server_name [String] Name of the MCP server to reconnect
1172
1198
  def reconnect_mcp_server(server_name)
1173
1199
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
1200
+
1174
1201
  @query_handler.reconnect_mcp_server(server_name)
1175
1202
  end
1176
1203
 
@@ -1179,6 +1206,7 @@ module ClaudeAgentSDK
1179
1206
  # @param enabled [Boolean] Whether to enable or disable
1180
1207
  def toggle_mcp_server(server_name, enabled)
1181
1208
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
1209
+
1182
1210
  @query_handler.toggle_mcp_server(server_name, enabled)
1183
1211
  end
1184
1212
 
@@ -1186,6 +1214,7 @@ module ClaudeAgentSDK
1186
1214
  # @param task_id [String] The ID of the task to stop
1187
1215
  def stop_task(task_id)
1188
1216
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
1217
+
1189
1218
  @query_handler.stop_task(task_id)
1190
1219
  end
1191
1220
 
@@ -1211,6 +1240,7 @@ module ClaudeAgentSDK
1211
1240
  # @raise [ArgumentError] if tool_use_id is neither nil nor a non-empty String
1212
1241
  def background_tasks(tool_use_id: nil)
1213
1242
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
1243
+
1214
1244
  @query_handler.background_tasks(tool_use_id: tool_use_id)
1215
1245
  end
1216
1246
 
@@ -1220,13 +1250,14 @@ module ClaudeAgentSDK
1220
1250
  # @param user_message_uuid [String] The UUID of the UserMessage to rewind to
1221
1251
  def rewind_files(user_message_uuid)
1222
1252
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
1253
+
1223
1254
  @query_handler.rewind_files(user_message_uuid)
1224
1255
  end
1225
1256
 
1226
1257
  # Get server initialization info
1227
1258
  # @return [Hash, nil] Server info or nil
1228
1259
  def server_info
1229
- @query_handler&.instance_variable_get(:@initialization_result)
1260
+ @query_handler&.initialization_result
1230
1261
  end
1231
1262
 
1232
1263
  # Get a breakdown of current context window usage by category.
@@ -1235,6 +1266,7 @@ module ClaudeAgentSDK
1235
1266
  # @return [Hash] Context usage response
1236
1267
  def get_context_usage
1237
1268
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
1269
+
1238
1270
  @query_handler.get_context_usage
1239
1271
  end
1240
1272
 
@@ -1248,6 +1280,7 @@ module ClaudeAgentSDK
1248
1280
  # @return [Hash] MCP status information, including mcpServers list
1249
1281
  def get_mcp_status
1250
1282
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
1283
+
1251
1284
  @query_handler.get_mcp_status
1252
1285
  end
1253
1286
 
@@ -1261,6 +1294,7 @@ module ClaudeAgentSDK
1261
1294
  # @return [Hash] Server info
1262
1295
  def get_server_info
1263
1296
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
1297
+
1264
1298
  server_info
1265
1299
  end
1266
1300
 
@@ -1343,7 +1377,7 @@ module ClaudeAgentSDK
1343
1377
  end
1344
1378
 
1345
1379
  # The connect body, wrapped by #connect so a failure triggers cleanup.
1346
- def connect_inner(configured_options, prompt)
1380
+ def connect_inner(configured_options, prompt) # rubocop:disable Metrics/MethodLength -- connect sequence kept in order; #connect wraps it for cleanup
1347
1381
  # Client always uses streaming mode; keep stdin open for bidirectional
1348
1382
  # communication. Observers were already resolved by #connect.
1349
1383
  @transport = @transport_class.new(configured_options, **@transport_args)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: claude-agent-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.36.0
4
+ version: 0.37.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ya-luotao
@@ -162,6 +162,16 @@ files:
162
162
  - lib/claude_agent_sdk/transcript_mirror_batcher.rb
163
163
  - lib/claude_agent_sdk/transport.rb
164
164
  - lib/claude_agent_sdk/types.rb
165
+ - lib/claude_agent_sdk/types/attributes.rb
166
+ - lib/claude_agent_sdk/types/base.rb
167
+ - lib/claude_agent_sdk/types/content_blocks.rb
168
+ - lib/claude_agent_sdk/types/hooks.rb
169
+ - lib/claude_agent_sdk/types/mcp.rb
170
+ - lib/claude_agent_sdk/types/messages.rb
171
+ - lib/claude_agent_sdk/types/option_values.rb
172
+ - lib/claude_agent_sdk/types/options.rb
173
+ - lib/claude_agent_sdk/types/permissions.rb
174
+ - lib/claude_agent_sdk/types/sessions.rb
165
175
  - lib/claude_agent_sdk/version.rb
166
176
  - lib/generators/claude_agent_sdk/install/install_generator.rb
167
177
  - lib/generators/claude_agent_sdk/install/templates/claude_agent_sdk.rb.tt