claude-agent-sdk 0.15.1 → 0.16.1
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 +19 -0
- data/README.md +11 -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 +8 -6
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 784f87932778dac72fe69ebbe3236e62a7df31f9671b52150fae392aa3d57f05
|
|
4
|
+
data.tar.gz: cd743d1cabb8bd151457f0a4b18be725f25b5de684ba2430df8a76037900dd98
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 68b582ef69fec09c0487d6d5600c76ac5d5a8420264f2b151cc7f52f95c7fb975339d6afa9fe519d4e75b48781c4f6132446fb03b2e9995decc3088ccd66c0f1
|
|
7
|
+
data.tar.gz: d119aaba15cb76997ceb58abbc43176a82df9ad4b697089a6b26ffbe342fb50ea10173890dddb03d5dc13397989abb774b617f140b1f92b3fdb0a8d16e3298d2
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.16.1] - 2026-04-21
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `Client#receive_response` no longer raises `LocalJumpError: break from proc-closure` when called inside `Async { }`. The 0.15.1 thread-hop severed `break`'s unwind target; replaced with a flag so the loop exits via the natural `end` marker after `ResultMessage`.
|
|
14
|
+
|
|
15
|
+
## [0.16.0] - 2026-04-22
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **`#text` on every message type that carries content.** No more hand-rolling a `select { TextBlock }.map(&:text).join` in every consumer.
|
|
19
|
+
- `AssistantMessage#text` — joins text across `TextBlock`s in the content array.
|
|
20
|
+
- `UserMessage#text` — handles both String content (plain prompt) and Array-of-blocks content.
|
|
21
|
+
- `SessionMessage#text` — joins text across parsed content blocks from a historical transcript.
|
|
22
|
+
- `#to_s` on each message type is aliased to `#text`, so `puts message` and string interpolation just work.
|
|
23
|
+
- 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.
|
|
24
|
+
- **`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.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- 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`.
|
|
28
|
+
|
|
10
29
|
## [0.15.1] - 2026-04-22
|
|
11
30
|
|
|
12
31
|
### Fixed
|
data/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Claude Agent SDK for Ruby
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
[](https://badge.fury.io/rb/claude-agent-sdk)
|
|
4
6
|
|
|
5
7
|
An **unofficial, community-maintained** Ruby SDK for the [Claude Code](https://docs.claude.com/en/docs/claude-code-overview) agent runtime. Not affiliated with or supported by Anthropic.
|
|
@@ -106,7 +108,7 @@ Add this line to your application's Gemfile:
|
|
|
106
108
|
gem 'claude-agent-sdk', github: 'ya-luotao/claude-agent-sdk-ruby'
|
|
107
109
|
|
|
108
110
|
# Or use a stable version from RubyGems
|
|
109
|
-
gem 'claude-agent-sdk', '~> 0.
|
|
111
|
+
gem 'claude-agent-sdk', '~> 0.16.1'
|
|
110
112
|
```
|
|
111
113
|
|
|
112
114
|
And then execute:
|
|
@@ -159,11 +161,7 @@ require 'claude_agent_sdk'
|
|
|
159
161
|
|
|
160
162
|
# Simple query
|
|
161
163
|
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
|
|
164
|
+
puts message.text if message.is_a?(ClaudeAgentSDK::AssistantMessage)
|
|
167
165
|
end
|
|
168
166
|
|
|
169
167
|
# With options
|
|
@@ -260,11 +258,10 @@ Async do
|
|
|
260
258
|
|
|
261
259
|
# Receive the response
|
|
262
260
|
client.receive_response do |msg|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
elsif msg.is_a?(ClaudeAgentSDK::ResultMessage)
|
|
261
|
+
case msg
|
|
262
|
+
when ClaudeAgentSDK::AssistantMessage
|
|
263
|
+
puts msg.text
|
|
264
|
+
when ClaudeAgentSDK::ResultMessage
|
|
268
265
|
puts "Cost: $#{msg.total_cost_usd}" if msg.total_cost_usd
|
|
269
266
|
end
|
|
270
267
|
end
|
|
@@ -928,10 +925,7 @@ Async do
|
|
|
928
925
|
# Capture UUID for rewind capability
|
|
929
926
|
user_message_uuids << message.uuid if message.uuid
|
|
930
927
|
when ClaudeAgentSDK::AssistantMessage
|
|
931
|
-
|
|
932
|
-
message.content.each do |block|
|
|
933
|
-
puts block.text if block.is_a?(ClaudeAgentSDK::TextBlock)
|
|
934
|
-
end
|
|
928
|
+
puts message.text
|
|
935
929
|
when ClaudeAgentSDK::ResultMessage
|
|
936
930
|
puts "Query completed (cost: $#{message.total_cost_usd})"
|
|
937
931
|
end
|
|
@@ -1140,11 +1134,7 @@ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
|
|
|
1140
1134
|
)
|
|
1141
1135
|
|
|
1142
1136
|
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
|
|
1137
|
+
puts msg.text if msg.is_a?(ClaudeAgentSDK::AssistantMessage)
|
|
1148
1138
|
end
|
|
1149
1139
|
|
|
1150
1140
|
# For long-running apps, flush before exit:
|
|
@@ -1258,8 +1248,7 @@ class ChatAgentJob < ApplicationJob
|
|
|
1258
1248
|
client.receive_response do |message|
|
|
1259
1249
|
case message
|
|
1260
1250
|
when ClaudeAgentSDK::AssistantMessage
|
|
1261
|
-
|
|
1262
|
-
ChatChannel.broadcast_to(chat_id, { type: 'chunk', content: text })
|
|
1251
|
+
ChatChannel.broadcast_to(chat_id, { type: 'chunk', content: message.text })
|
|
1263
1252
|
|
|
1264
1253
|
when ClaudeAgentSDK::ResultMessage
|
|
1265
1254
|
ChatChannel.broadcast_to(chat_id, {
|
|
@@ -1274,15 +1263,6 @@ class ChatAgentJob < ApplicationJob
|
|
|
1274
1263
|
end
|
|
1275
1264
|
end.wait
|
|
1276
1265
|
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
1266
|
end
|
|
1287
1267
|
```
|
|
1288
1268
|
|
|
@@ -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
|
|
@@ -422,9 +418,15 @@ module ClaudeAgentSDK
|
|
|
422
418
|
def receive_response(&block)
|
|
423
419
|
return enum_for(:receive_response) unless block
|
|
424
420
|
|
|
421
|
+
# Flag-based rather than `break`: `receive_messages` hops this
|
|
422
|
+
# block onto a plain thread via `FiberBoundary.invoke`, which
|
|
423
|
+
# severs break's unwind target.
|
|
424
|
+
result_seen = false
|
|
425
425
|
receive_messages do |message|
|
|
426
|
+
next if result_seen
|
|
427
|
+
|
|
426
428
|
block.call(message)
|
|
427
|
-
|
|
429
|
+
result_seen = true if message.is_a?(ResultMessage)
|
|
428
430
|
end
|
|
429
431
|
end
|
|
430
432
|
|
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.
|
|
4
|
+
version: 0.16.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Community Contributors
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-04-
|
|
10
|
+
date: 2026-04-22 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: async
|