claude-agent-sdk 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 16f0d3cdbf4ed8d0c3468e3247500e2e75bb01100314f8412ccb52ce7789c4a0
4
- data.tar.gz: b7c4afd9e0e1f585be2eff7104b107af864c47afb3a2c9575f73c001e013d6c7
3
+ metadata.gz: 9c6e7dfb9b25404169db87bff2a8688c9bd1103bb04ebc980bd8206091b33f87
4
+ data.tar.gz: 1c921d464b1b8272742f8de36800635e5f777fb88a42304ac9a5a1d7f9027beb
5
5
  SHA512:
6
- metadata.gz: a63193e6e476a0053217fd596efcfa8f3c0a2a2411d1cf0c14c241687201922089f5dc28a0e7417c5de6ad716388320038f040368a2266a97152a05980c9357d
7
- data.tar.gz: b78ef0598c31580c6a9d168fe8ba39245133bbb4d7624b0e47dfdd74339ff54d2f88913b64f952d8eecff8e15655048dba2bd178508d8cf042884905e1eee9c2
6
+ metadata.gz: 5b7b2c4e0503db55b36d761bec12bdc43619e16688a3b3e38c79d50a34af92d68e6ccea29e523eaaa0279f577a296cb25ba0c447cafb388c2127bc870b3cc703
7
+ data.tar.gz: bd05dac1831ce8b77462019f026f88b2618ca6df4be31bc84c86c145fbc59fb54738530a8b6f188c654d588171acfe28efd216a8c12b846d4d3e5ae1cfe2d37f
data/CHANGELOG.md CHANGED
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.16.0] - 2026-04-22
11
+
12
+ ### Added
13
+ - **`#text` on every message type that carries content.** No more hand-rolling a `select { TextBlock }.map(&:text).join` in every consumer.
14
+ - `AssistantMessage#text` — joins text across `TextBlock`s in the content array.
15
+ - `UserMessage#text` — handles both String content (plain prompt) and Array-of-blocks content.
16
+ - `SessionMessage#text` — joins text across parsed content blocks from a historical transcript.
17
+ - `#to_s` on each message type is aliased to `#text`, so `puts message` and string interpolation just work.
18
+ - Non-text blocks (`ToolUseBlock`, `ThinkingBlock`, `ToolResultBlock`, `UnknownBlock`) intentionally do **not** answer `#text` — only `TextBlock` is textual. The message helpers use `Array#grep(TextBlock)` to select text blocks.
19
+ - **`SessionMessage#content_blocks`** returns typed block objects (`TextBlock`, `ThinkingBlock`, `ToolUseBlock`, `ToolResultBlock`, `UnknownBlock`) instead of the raw hash blocks from the JSONL transcript. Unknown block types become `UnknownBlock` for forward compatibility with newer CLI versions.
20
+
21
+ ### Changed
22
+ - Rails Integration / Quick Start / Observability / File Checkpointing README examples dropped the `content.select { is_a?(TextBlock) }.map(&:text).join` dance in favor of `message.text`.
23
+
24
+ ## [0.15.1] - 2026-04-22
25
+
26
+ ### Fixed
27
+ - **Thread-keyed libraries are now safe inside SDK callbacks.** The SDK internally hops to a plain thread at every user-callback boundary — blocks passed to `ClaudeAgentSDK.query` / `Client#receive_messages`, SDK MCP tool handlers, hooks, permission callbacks, and observer methods — so the `async` gem's Fiber scheduler is no longer visible to user code. Previously, any library that keys state on `Thread.current` (ActiveRecord and every DB driver keyed by thread — `pg`, `mysql2`, `sqlite3` — plus per-thread HTTP/cache pools, request stores, etc.) could be corrupted by the scheduler interleaving two fibers onto one checked-out connection. Rails/Sidekiq/Kamal consumers no longer need a caller-side wrapper to avoid this. See the "Thread-keyed libraries are safe inside SDK callbacks" subsection under Rails Integration in the README.
28
+
29
+ ### Changed
30
+ - **Callbacks run on a plain thread, not inside `Async::Task`.** Fiber-specific primitives (e.g. `Async::Task.current.sleep`, `Async::Task.current.async { ... }`) are no longer available inside tool handlers, hooks, permission callbacks, message blocks, or observers. Callbacks that want cooperative concurrency can open their own `Async { }` block. In practice callbacks do ordinary Ruby work and return a value, so this rarely affects real code.
31
+
10
32
  ## [0.15.0] - 2026-04-17
11
33
 
12
34
  ### Fixed
data/README.md CHANGED
@@ -106,7 +106,7 @@ Add this line to your application's Gemfile:
106
106
  gem 'claude-agent-sdk', github: 'ya-luotao/claude-agent-sdk-ruby'
107
107
 
108
108
  # Or use a stable version from RubyGems
109
- gem 'claude-agent-sdk', '~> 0.15.0'
109
+ gem 'claude-agent-sdk', '~> 0.16.0'
110
110
  ```
111
111
 
112
112
  And then execute:
@@ -159,11 +159,7 @@ require 'claude_agent_sdk'
159
159
 
160
160
  # Simple query
161
161
  ClaudeAgentSDK.query(prompt: "Hello Claude") do |message|
162
- if message.is_a?(ClaudeAgentSDK::AssistantMessage)
163
- message.content.each do |block|
164
- puts block.text if block.is_a?(ClaudeAgentSDK::TextBlock)
165
- end
166
- end
162
+ puts message.text if message.is_a?(ClaudeAgentSDK::AssistantMessage)
167
163
  end
168
164
 
169
165
  # With options
@@ -260,11 +256,10 @@ Async do
260
256
 
261
257
  # Receive the response
262
258
  client.receive_response do |msg|
263
- if msg.is_a?(ClaudeAgentSDK::AssistantMessage)
264
- msg.content.each do |block|
265
- puts block.text if block.is_a?(ClaudeAgentSDK::TextBlock)
266
- end
267
- elsif msg.is_a?(ClaudeAgentSDK::ResultMessage)
259
+ case msg
260
+ when ClaudeAgentSDK::AssistantMessage
261
+ puts msg.text
262
+ when ClaudeAgentSDK::ResultMessage
268
263
  puts "Cost: $#{msg.total_cost_usd}" if msg.total_cost_usd
269
264
  end
270
265
  end
@@ -928,10 +923,7 @@ Async do
928
923
  # Capture UUID for rewind capability
929
924
  user_message_uuids << message.uuid if message.uuid
930
925
  when ClaudeAgentSDK::AssistantMessage
931
- # Handle assistant responses
932
- message.content.each do |block|
933
- puts block.text if block.is_a?(ClaudeAgentSDK::TextBlock)
934
- end
926
+ puts message.text
935
927
  when ClaudeAgentSDK::ResultMessage
936
928
  puts "Query completed (cost: $#{message.total_cost_usd})"
937
929
  end
@@ -1140,11 +1132,7 @@ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
1140
1132
  )
1141
1133
 
1142
1134
  ClaudeAgentSDK.query(prompt: "List files in /tmp", options: options) do |msg|
1143
- if msg.is_a?(ClaudeAgentSDK::AssistantMessage)
1144
- msg.content.each do |block|
1145
- puts block.text if block.is_a?(ClaudeAgentSDK::TextBlock)
1146
- end
1147
- end
1135
+ puts msg.text if msg.is_a?(ClaudeAgentSDK::AssistantMessage)
1148
1136
  end
1149
1137
 
1150
1138
  # For long-running apps, flush before exit:
@@ -1194,6 +1182,45 @@ For a complete multi-tool example, see [examples/otel_langfuse_example.rb](examp
1194
1182
 
1195
1183
  The SDK integrates well with Rails applications. Here are common patterns:
1196
1184
 
1185
+ ### Thread-keyed libraries are safe inside SDK callbacks
1186
+
1187
+ The SDK depends on [`async`](https://github.com/socketry/async), which installs
1188
+ a Fiber scheduler that multiplexes fibers onto a single OS thread and
1189
+ intercepts IO so blocking calls yield to siblings. Most mature Ruby libraries
1190
+ are thread-safe but not fiber-safe — they key state (checked-out DB
1191
+ connections, per-thread caches, request stores) on `Thread.current`. When the
1192
+ scheduler interleaves two fibers on one thread, those fibers share the same
1193
+ state slot, and interleaved IO on a shared connection silently corrupts wire
1194
+ protocols. This affects every DB driver keyed by thread (`pg`, `mysql2`,
1195
+ `sqlite3`), ActiveRecord's connection pool, and HTTP/cache clients pooled per
1196
+ thread.
1197
+
1198
+ You do **not** need to think about this. The SDK hops to a plain thread at
1199
+ every user-callback boundary — message blocks given to `query` / `Client`, SDK
1200
+ MCP tool handlers, hooks, permission callbacks, and observer methods — so
1201
+ your code runs with no Fiber scheduler active and inherits the ordinary
1202
+ thread-keyed assumptions every Rails / Sidekiq / Kamal app already makes:
1203
+
1204
+ ```ruby
1205
+ tool = ClaudeAgentSDK.create_tool('lookup_user', 'Look up a user', { id: Integer }) do |args|
1206
+ user = User.find(args[:id]) # just works
1207
+ { content: [{ type: 'text', text: user.name }] }
1208
+ end
1209
+
1210
+ ClaudeAgentSDK.query(prompt: '...') do |message|
1211
+ Message.create!(role: 'assistant', body: message.to_s) # just works
1212
+ end
1213
+ ```
1214
+
1215
+ The trade-off: because callbacks run on a plain thread rather than inside
1216
+ an `Async::Task`, fiber-specific primitives aren't available to them —
1217
+ `Async::Task.current` will raise "No async task available". If a callback
1218
+ wants cooperative concurrency it should open its own `Async { }` block. In
1219
+ practice, callbacks typically do some Ruby work, call external services, and
1220
+ return — so this rarely matters. If you wrap your own call site in an outer
1221
+ `Async { }` block, the scheduler is visible to your code again; you've opted
1222
+ in, and whatever fiber-safety rules your app uses apply there.
1223
+
1197
1224
  ### ActionCable Streaming
1198
1225
 
1199
1226
  Stream Claude responses to the frontend in real-time:
@@ -1219,8 +1246,7 @@ class ChatAgentJob < ApplicationJob
1219
1246
  client.receive_response do |message|
1220
1247
  case message
1221
1248
  when ClaudeAgentSDK::AssistantMessage
1222
- text = extract_text(message)
1223
- ChatChannel.broadcast_to(chat_id, { type: 'chunk', content: text })
1249
+ ChatChannel.broadcast_to(chat_id, { type: 'chunk', content: message.text })
1224
1250
 
1225
1251
  when ClaudeAgentSDK::ResultMessage
1226
1252
  ChatChannel.broadcast_to(chat_id, {
@@ -1235,15 +1261,6 @@ class ChatAgentJob < ApplicationJob
1235
1261
  end
1236
1262
  end.wait
1237
1263
  end
1238
-
1239
- private
1240
-
1241
- def extract_text(message)
1242
- message.content
1243
- .select { |b| b.is_a?(ClaudeAgentSDK::TextBlock) }
1244
- .map(&:text)
1245
- .join("\n\n")
1246
- end
1247
1264
  end
1248
1265
  ```
1249
1266
 
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeAgentSDK
4
+ # Internal. Consumers of the SDK should never need this directly.
5
+ #
6
+ # The SDK depends on `async`, which installs a Fiber scheduler whenever an
7
+ # `Async { }` block is active. That scheduler multiplexes fibers onto a
8
+ # single OS thread and intercepts IO so blocking calls yield to siblings.
9
+ #
10
+ # Most mature Ruby libraries are thread-safe but not fiber-safe: they key
11
+ # state (checked-out DB connections, per-thread caches, request stores)
12
+ # on `Thread.current`. When the scheduler interleaves two fibers on one
13
+ # thread, those fibers share one state slot — and interleaved IO on a
14
+ # shared connection silently corrupts wire protocols. This bites every
15
+ # DB driver keyed by thread (pg, mysql2, sqlite3), ActiveRecord's
16
+ # connection pool, and any HTTP/cache client pooled per-thread.
17
+ #
18
+ # The SDK invokes user-supplied callbacks (tool handlers, hooks,
19
+ # permission callbacks, message blocks, observer methods) from inside
20
+ # its reactor. `FiberBoundary.invoke` hops those calls to a plain
21
+ # Ruby thread so user code runs on a fiber-scheduler-free thread and
22
+ # inherits the same thread-keyed state assumptions the rest of the
23
+ # user's app makes.
24
+ #
25
+ # No-op when no scheduler is active, so it's cheap to use unconditionally.
26
+ module FiberBoundary
27
+ module_function
28
+
29
+ # Run the given block on a plain thread when a Fiber scheduler is active.
30
+ # Returns the block's value. Exceptions propagate to the caller.
31
+ def invoke(&block)
32
+ return block.call unless Fiber.scheduler
33
+
34
+ thread = Thread.new(&block)
35
+ thread.report_on_exception = false
36
+ thread.value
37
+ end
38
+ end
39
+ end
@@ -288,24 +288,32 @@ module ClaudeAgentSDK
288
288
  )
289
289
  end
290
290
 
291
+ # Accepts blocks with either symbol or string keys — live CLI messages
292
+ # arrive symbol-keyed (parsed via `symbolize_names: true`), session
293
+ # transcripts arrive string-keyed (parsed via `symbolize_names: false`).
294
+ # Uses a nil-aware fallback so `is_error: false` survives.
291
295
  def self.parse_content_block(block)
292
- case block[:type]
296
+ get = lambda do |key|
297
+ v = block[key]
298
+ v.nil? ? block[key.to_s] : v
299
+ end
300
+ case get.call(:type)
293
301
  when 'text'
294
- TextBlock.new(text: block[:text])
302
+ TextBlock.new(text: get.call(:text))
295
303
  when 'thinking'
296
- ThinkingBlock.new(thinking: block[:thinking], signature: block[:signature])
304
+ ThinkingBlock.new(thinking: get.call(:thinking), signature: get.call(:signature))
297
305
  when 'tool_use'
298
- ToolUseBlock.new(id: block[:id], name: block[:name], input: block[:input])
306
+ ToolUseBlock.new(id: get.call(:id), name: get.call(:name), input: get.call(:input))
299
307
  when 'tool_result'
300
308
  ToolResultBlock.new(
301
- tool_use_id: block[:tool_use_id],
302
- content: block[:content],
303
- is_error: block[:is_error]
309
+ tool_use_id: get.call(:tool_use_id),
310
+ content: get.call(:content),
311
+ is_error: get.call(:is_error)
304
312
  )
305
313
  else
306
314
  # Forward-compatible: preserve unrecognized content block types (e.g., "document", "image")
307
315
  # so newer CLI versions don't crash older SDK versions.
308
- UnknownBlock.new(type: block[:type], data: block)
316
+ UnknownBlock.new(type: get.call(:type), data: block)
309
317
  end
310
318
  end
311
319
  end
@@ -300,11 +300,11 @@ module ClaudeAgentSDK
300
300
  agent_id: request_data[:agent_id]
301
301
  )
302
302
 
303
- response = @can_use_tool.call(
304
- request_data[:tool_name],
305
- request_data[:input],
306
- context
307
- )
303
+ # User-supplied permission callback runs on a plain thread, not the
304
+ # Async reactor, so AR/PG calls inside it aren't intercepted.
305
+ response = FiberBoundary.invoke do
306
+ @can_use_tool.call(request_data[:tool_name], request_data[:input], context)
307
+ end
308
308
 
309
309
  # Convert PermissionResult to expected format
310
310
  case response
@@ -338,19 +338,21 @@ module ClaudeAgentSDK
338
338
  # Create typed HookContext
339
339
  context = HookContext.new(signal: nil)
340
340
 
341
- hook_output = callback.call(
342
- hook_input,
343
- request_data[:tool_use_id],
344
- context
345
- ) unless @hook_callback_timeouts[callback_id]
341
+ # Hop off the Fiber scheduler before invoking user hook code. The
342
+ # Async-side timeout still wraps the hop; if it fires, .value returns
343
+ # early with an exception and the worker thread is left to finish on
344
+ # its own (matches prior best-effort cancellation semantics).
345
+ unless @hook_callback_timeouts[callback_id]
346
+ hook_output = FiberBoundary.invoke do
347
+ callback.call(hook_input, request_data[:tool_use_id], context)
348
+ end
349
+ end
346
350
 
347
351
  if (timeout = @hook_callback_timeouts[callback_id])
348
352
  hook_output = Async::Task.current.with_timeout(timeout) do
349
- callback.call(
350
- hook_input,
351
- request_data[:tool_use_id],
352
- context
353
- )
353
+ FiberBoundary.invoke do
354
+ callback.call(hook_input, request_data[:tool_use_id], context)
355
+ end
354
356
  end
355
357
  end
356
358
 
@@ -79,8 +79,9 @@ module ClaudeAgentSDK
79
79
  tool = @tools.find { |t| t.name == name }
80
80
  raise "Tool '#{name}' not found" unless tool
81
81
 
82
- # Call the tool's handler
83
- result = tool.handler.call(arguments)
82
+ # Call the tool's handler on a plain thread so the async gem's
83
+ # Fiber scheduler is not visible to user code (which may hit AR/PG).
84
+ result = FiberBoundary.invoke { tool.handler.call(arguments) }
84
85
 
85
86
  # Ensure result has the expected format
86
87
  unless result.is_a?(Hash) && result[:content]
@@ -180,8 +181,9 @@ module ClaudeAgentSDK
180
181
  end
181
182
 
182
183
  def call(server_context: nil, **args)
183
- # Filter out server_context and pass remaining args to handler
184
- result = @tool_def.handler.call(args)
184
+ # Filter out server_context and pass remaining args to handler.
185
+ # Hop to a plain thread so user handlers don't see the Fiber scheduler.
186
+ result = FiberBoundary.invoke { @tool_def.handler.call(args) }
185
187
 
186
188
  content = ClaudeAgentSDK.flexible_fetch(result, 'content', 'content')
187
189
  unless result.is_a?(Hash) && content
@@ -38,6 +38,36 @@ module ClaudeAgentSDK
38
38
  @message = message
39
39
  @parent_tool_use_id = parent_tool_use_id
40
40
  end
41
+
42
+ # Concatenated text across every TextBlock in this message.
43
+ # Returns "" when the message has no text content (nil message,
44
+ # non-Hash message, empty content, or only non-text blocks).
45
+ def text
46
+ raw = @message.is_a?(Hash) ? (@message['content'] || @message[:content]) : nil
47
+ case raw
48
+ when String then raw
49
+ when Array then content_blocks.grep(TextBlock).map(&:text).join("\n\n")
50
+ else ''
51
+ end
52
+ end
53
+
54
+ alias to_s text
55
+
56
+ # Typed content blocks for this message. Each entry is one of
57
+ # TextBlock, ThinkingBlock, ToolUseBlock, ToolResultBlock, or
58
+ # UnknownBlock (for forward-compatibility with newer CLI block types).
59
+ # Returns [] when the message has no array-of-blocks content (nil
60
+ # message, non-Hash message, String content, missing content).
61
+ def content_blocks
62
+ return [] unless @message.is_a?(Hash)
63
+
64
+ raw = @message['content'] || @message[:content]
65
+ return [] unless raw.is_a?(Array)
66
+
67
+ raw.filter_map do |block|
68
+ MessageParser.parse_content_block(block) if block.is_a?(Hash)
69
+ end
70
+ end
41
71
  end
42
72
 
43
73
  # Session browsing functions
@@ -123,6 +123,19 @@ module ClaudeAgentSDK
123
123
  @parent_tool_use_id = parent_tool_use_id
124
124
  @tool_use_result = tool_use_result # Tool result data when message is a tool response
125
125
  end
126
+
127
+ # Concatenated text of this message. Handles both String content
128
+ # (plain-text user prompt) and Array-of-blocks content (typed content).
129
+ # Returns "" when there is no text.
130
+ def text
131
+ case @content
132
+ when String then @content
133
+ when Array then @content.grep(TextBlock).map(&:text).join("\n\n")
134
+ else ''
135
+ end
136
+ end
137
+
138
+ alias to_s text
126
139
  end
127
140
 
128
141
  # Assistant message with content blocks
@@ -142,6 +155,14 @@ module ClaudeAgentSDK
142
155
  @session_id = session_id # Session the message belongs to
143
156
  @uuid = uuid # Unique message UUID in the session transcript
144
157
  end
158
+
159
+ # Concatenated text across every TextBlock in this message's content.
160
+ # Returns "" when the message has no text (e.g., a pure tool_use turn).
161
+ def text
162
+ Array(@content).grep(TextBlock).map(&:text).join("\n\n")
163
+ end
164
+
165
+ alias to_s text
145
166
  end
146
167
 
147
168
  # System message with metadata
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeAgentSDK
4
- VERSION = '0.15.0'
4
+ VERSION = '0.16.0'
5
5
  end
@@ -13,6 +13,7 @@ require_relative 'claude_agent_sdk/sdk_mcp_server'
13
13
  require_relative 'claude_agent_sdk/streaming'
14
14
  require_relative 'claude_agent_sdk/sessions'
15
15
  require_relative 'claude_agent_sdk/session_mutations'
16
+ require_relative 'claude_agent_sdk/fiber_boundary'
16
17
  require 'async'
17
18
  require 'securerandom'
18
19
 
@@ -28,9 +29,12 @@ module ClaudeAgentSDK
28
29
  end
29
30
 
30
31
  # Safely call a method on each observer, suppressing any errors.
32
+ # Each observer is invoked through FiberBoundary so that user code runs
33
+ # on a plain thread (no Fiber scheduler) even when called from inside
34
+ # the SDK's Async reactor.
31
35
  def self.notify_observers(observers, method, *args)
32
36
  observers.each do |obs|
33
- obs.send(method, *args)
37
+ FiberBoundary.invoke { obs.send(method, *args) }
34
38
  rescue StandardError
35
39
  nil
36
40
  end
@@ -67,11 +71,7 @@ module ClaudeAgentSDK
67
71
  # permission_mode: 'acceptEdits'
68
72
  # )
69
73
  # ClaudeAgentSDK.query(prompt: "Create a hello.rb file", options: options) do |msg|
70
- # if msg.is_a?(ClaudeAgentSDK::AssistantMessage)
71
- # msg.content.each do |block|
72
- # puts block.text if block.is_a?(ClaudeAgentSDK::TextBlock)
73
- # end
74
- # end
74
+ # puts msg.text if msg.is_a?(ClaudeAgentSDK::AssistantMessage)
75
75
  # end
76
76
  #
77
77
  # @example Streaming input
@@ -230,12 +230,14 @@ module ClaudeAgentSDK
230
230
  end
231
231
  end
232
232
 
233
- # Read and yield messages from the query handler (filters out control messages)
233
+ # Read and yield messages from the query handler (filters out control messages).
234
+ # User block is invoked through FiberBoundary so ActiveRecord / PG calls
235
+ # inside it don't see the async gem's Fiber scheduler.
234
236
  query_handler.receive_messages do |data|
235
237
  message = MessageParser.parse(data)
236
238
  if message
237
239
  ClaudeAgentSDK.notify_observers(resolved_observers, :on_message, message)
238
- block.call(message)
240
+ FiberBoundary.invoke { block.call(message) }
239
241
  end
240
242
  end
241
243
  ensure
@@ -406,7 +408,7 @@ module ClaudeAgentSDK
406
408
  message = MessageParser.parse(data)
407
409
  if message
408
410
  ClaudeAgentSDK.notify_observers(@resolved_observers, :on_message, message)
409
- block.call(message)
411
+ FiberBoundary.invoke { block.call(message) }
410
412
  end
411
413
  end
412
414
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: claude-agent-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Community Contributors
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-04-17 00:00:00.000000000 Z
10
+ date: 2026-04-21 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: async
@@ -106,6 +106,7 @@ files:
106
106
  - lib/claude_agent_sdk.rb
107
107
  - lib/claude_agent_sdk/configuration.rb
108
108
  - lib/claude_agent_sdk/errors.rb
109
+ - lib/claude_agent_sdk/fiber_boundary.rb
109
110
  - lib/claude_agent_sdk/instrumentation.rb
110
111
  - lib/claude_agent_sdk/instrumentation/otel.rb
111
112
  - lib/claude_agent_sdk/message_parser.rb