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 +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +9 -31
- data/lib/claude_agent_sdk/message_parser.rb +16 -8
- data/lib/claude_agent_sdk/sessions.rb +30 -0
- data/lib/claude_agent_sdk/types.rb +21 -0
- data/lib/claude_agent_sdk/version.rb +1 -1
- data/lib/claude_agent_sdk.rb +1 -5
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9c6e7dfb9b25404169db87bff2a8688c9bd1103bb04ebc980bd8206091b33f87
|
|
4
|
+
data.tar.gz: 1c921d464b1b8272742f8de36800635e5f777fb88a42304ac9a5a1d7f9027beb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
302
|
+
TextBlock.new(text: get.call(:text))
|
|
295
303
|
when 'thinking'
|
|
296
|
-
ThinkingBlock.new(thinking:
|
|
304
|
+
ThinkingBlock.new(thinking: get.call(:thinking), signature: get.call(:signature))
|
|
297
305
|
when 'tool_use'
|
|
298
|
-
ToolUseBlock.new(id:
|
|
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:
|
|
302
|
-
content:
|
|
303
|
-
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:
|
|
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
|
data/lib/claude_agent_sdk.rb
CHANGED
|
@@ -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
|