claude-agent-sdk 0.15.1 → 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: 9a037f9cca486fc8d6e94269b9f5f7b686574665ff403457761db764f9f50f07
4
- data.tar.gz: 01bff518e3306d1fb23b8bc1e9b11b72785a98aecf3636a1096c5522f4720444
3
+ metadata.gz: 9c6e7dfb9b25404169db87bff2a8688c9bd1103bb04ebc980bd8206091b33f87
4
+ data.tar.gz: 1c921d464b1b8272742f8de36800635e5f777fb88a42304ac9a5a1d7f9027beb
5
5
  SHA512:
6
- metadata.gz: 253df532bcdc4a6e0b13e0d4438944bcf9c6018b5d1062b4831efc6dac7a332c1759f85137e6de4fc44de0e8b1eb7ccfd0033698392c8faf6ca40f7e237b2e51
7
- data.tar.gz: 143d4ee8780d1a675e1531edc944de1756bff03faa9c42513b9672a3f37ac670dc17929a936cf0f1520d1cfdc45c53042d8f8fa07ad5a559e4b59439e79848f5
6
+ metadata.gz: 5b7b2c4e0503db55b36d761bec12bdc43619e16688a3b3e38c79d50a34af92d68e6ccea29e523eaaa0279f577a296cb25ba0c447cafb388c2127bc870b3cc703
7
+ data.tar.gz: bd05dac1831ce8b77462019f026f88b2618ca6df4be31bc84c86c145fbc59fb54738530a8b6f188c654d588171acfe28efd216a8c12b846d4d3e5ae1cfe2d37f
data/CHANGELOG.md CHANGED
@@ -7,6 +7,20 @@ 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
+
10
24
  ## [0.15.1] - 2026-04-22
11
25
 
12
26
  ### 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.1'
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:
@@ -1258,8 +1246,7 @@ class ChatAgentJob < ApplicationJob
1258
1246
  client.receive_response do |message|
1259
1247
  case message
1260
1248
  when ClaudeAgentSDK::AssistantMessage
1261
- text = extract_text(message)
1262
- ChatChannel.broadcast_to(chat_id, { type: 'chunk', content: text })
1249
+ ChatChannel.broadcast_to(chat_id, { type: 'chunk', content: message.text })
1263
1250
 
1264
1251
  when ClaudeAgentSDK::ResultMessage
1265
1252
  ChatChannel.broadcast_to(chat_id, {
@@ -1274,15 +1261,6 @@ class ChatAgentJob < ApplicationJob
1274
1261
  end
1275
1262
  end.wait
1276
1263
  end
1277
-
1278
- private
1279
-
1280
- def extract_text(message)
1281
- message.content
1282
- .select { |b| b.is_a?(ClaudeAgentSDK::TextBlock) }
1283
- .map(&:text)
1284
- .join("\n\n")
1285
- end
1286
1264
  end
1287
1265
  ```
1288
1266
 
@@ -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
@@ -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.1'
4
+ VERSION = '0.16.0'
5
5
  end
@@ -71,11 +71,7 @@ module ClaudeAgentSDK
71
71
  # permission_mode: 'acceptEdits'
72
72
  # )
73
73
  # ClaudeAgentSDK.query(prompt: "Create a hello.rb file", options: options) do |msg|
74
- # if msg.is_a?(ClaudeAgentSDK::AssistantMessage)
75
- # msg.content.each do |block|
76
- # puts block.text if block.is_a?(ClaudeAgentSDK::TextBlock)
77
- # end
78
- # end
74
+ # puts msg.text if msg.is_a?(ClaudeAgentSDK::AssistantMessage)
79
75
  # end
80
76
  #
81
77
  # @example Streaming input
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.15.1
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Community Contributors