claude-agent-sdk 0.7.1 → 0.7.3

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: 303a5cd2321bb9c04f17d68990abfb8f72c6a88624874f199afaf7769befca33
4
- data.tar.gz: 3831d1fdec75b38072083d231dcf03a87407244bda805e51661d771be23105ac
3
+ metadata.gz: d17d03fa867e2779de155d6fba8a86fbc9edbc5662157ca3e21606016c81fb17
4
+ data.tar.gz: 949787994cd58eb5be72b8834a519a704697c61ecbfff9ca1d6f4e1fe921b18f
5
5
  SHA512:
6
- metadata.gz: 569f331952bcef9c7426fc61bd1c36f082acf721e9ab5a899783ea06fdee5accfcb2f903c536ba9cf99ee50e96aceb254d9305751631643f532c549b39b48ff6
7
- data.tar.gz: 2c22f2cde6cf7c5e059ebbdfe515f559979b4cd273b28e1d7113cf55e9e739a201725f81f2ee602623242c12f7c557edbb0b5ade8a1088e997d4ddd01566561c
6
+ metadata.gz: a99fdb7b2dcab7e0286763abfb1f6d0277423a7ef2198a040ef8c76a8959907009ebc21f6523c35ecff1409a66fd8c636648d1f594aaa481119ed3598eb1b06e
7
+ data.tar.gz: 8e3e7e26487dcfe993534d3d1b7f695ba5009ad18e9d173c9e1e0f1353ec54565905f3e74e81a39eb5789aa2df008536a2a5a191095e7d8604f36abd27bb6583
data/CHANGELOG.md CHANGED
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.7.3] - 2026-02-26
9
+
10
+ ### Fixed
11
+ - **String-keyed JSON schema crash:** Libraries like [RubyLLM](https://github.com/crmne/ruby_llm) that deep-stringify schema keys (e.g., `{ 'type' => 'object', 'properties' => { ... } }`) were misidentified as simple type-mapping schemas, causing each top-level key to be treated as a parameter name instead of passing the schema through. Now both symbol-keyed and string-keyed schemas are detected and normalized correctly. (PR #9 by [@iuhoay](https://github.com/iuhoay))
12
+ - **Shallow key symbolization:** `convert_schema` used `transform_keys` (shallow) which left nested property keys as strings, breaking downstream `MCP::Tool::InputSchema` construction. Now uses deep symbolization recursively.
13
+ - **Guard ordering crash:** `convert_schema` and `convert_input_schema` accessed `schema[:type]` before the `schema.is_a?(Hash)` guard, which would raise `NoMethodError` on `nil` input.
14
+ - **Schema detection tightened:** Pre-built schema detection now requires `type == 'object'` and `properties.is_a?(Hash)`, preventing false positives when a simple schema happens to have parameters named `type` and `properties`.
15
+
16
+ ### Added
17
+ - `ClaudeAgentSDK.deep_symbolize_keys` utility method for recursive hash key symbolization
18
+
19
+ ## [0.7.2] - 2026-02-21
20
+
21
+ ### Fixed
22
+ - **Unknown content block crash:** Unrecognized content block types (e.g., `document` blocks from PDF reading) now return `UnknownBlock` instead of raising `MessageParseError`, aligning with the Python SDK's forward-compatible design
23
+ - **Unknown message type crash:** Unrecognized message types now return `nil` (skipped by callers) instead of raising
24
+ - **Empty input schema crash:** Tools with no parameters (`input_schema: {}`) caused `MCP::Tool::InputSchema` validation failure (`required` array must have at least 1 item per JSON Schema draft-04). Now omits `required` when empty.
25
+
26
+ ### Added
27
+ - `UnknownBlock` type that preserves raw data for unrecognized content block types
28
+
29
+ ### Changed
30
+ - **Breaking (minor):** `MessageParser.parse` no longer raises `MessageParseError` for unknown message types — returns `nil` instead. If you were rescuing `MessageParseError` to handle unknown types, check for `nil` return values instead.
31
+ - **Breaking (minor):** `MessageParser.parse_content_block` no longer raises `MessageParseError` for unknown content block types — returns `UnknownBlock` instead. Content block iteration using `is_a?` filtering (e.g., `block.is_a?(TextBlock)`) is unaffected.
32
+
8
33
  ## [0.7.1] - 2026-02-21
9
34
 
10
35
  ### Fixed
data/README.md CHANGED
@@ -39,7 +39,7 @@ Add this line to your application's Gemfile:
39
39
  gem 'claude-agent-sdk', github: 'ya-luotao/claude-agent-sdk-ruby'
40
40
 
41
41
  # Or use a stable version from RubyGems
42
- gem 'claude-agent-sdk', '~> 0.7.0'
42
+ gem 'claude-agent-sdk', '~> 0.7.3'
43
43
  ```
44
44
 
45
45
  And then execute:
@@ -282,6 +282,26 @@ Async do
282
282
  end.wait
283
283
  ```
284
284
 
285
+ ### Pre-built JSON Schemas
286
+
287
+ If your schemas come from another library (e.g., [RubyLLM](https://github.com/crmne/ruby_llm)) that deep-stringifies keys, the SDK handles them transparently — both symbol-keyed and string-keyed schemas are accepted and normalized:
288
+
289
+ ```ruby
290
+ # Symbol keys (standard Ruby)
291
+ tool = ClaudeAgentSDK.create_tool('save', 'Save a fact', {
292
+ type: 'object',
293
+ properties: { fact: { type: 'string' } },
294
+ required: ['fact']
295
+ }) { |args| { content: [{ type: 'text', text: "Saved: #{args[:fact]}" }] } }
296
+
297
+ # String keys (e.g., from RubyLLM or JSON.parse)
298
+ tool = ClaudeAgentSDK.create_tool('save', 'Save a fact', {
299
+ 'type' => 'object',
300
+ 'properties' => { 'fact' => { 'type' => 'string' } },
301
+ 'required' => ['fact']
302
+ }) { |args| { content: [{ type: 'text', text: "Saved: #{args[:fact]}" }] } }
303
+ ```
304
+
285
305
  ### Benefits Over External MCP Servers
286
306
 
287
307
  - **No subprocess management** - Runs in the same process as your application
@@ -978,7 +998,7 @@ end
978
998
 
979
999
  ```ruby
980
1000
  # Union type of all content blocks
981
- ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock
1001
+ ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock | UnknownBlock
982
1002
  ```
983
1003
 
984
1004
  #### TextBlock
@@ -1026,6 +1046,17 @@ class ToolResultBlock
1026
1046
  end
1027
1047
  ```
1028
1048
 
1049
+ #### UnknownBlock
1050
+
1051
+ Generic content block for types the SDK doesn't explicitly handle (e.g., `document` for PDFs, `image` for inline images). Preserves the raw data for forward compatibility with newer CLI versions.
1052
+
1053
+ ```ruby
1054
+ class UnknownBlock
1055
+ attr_accessor :type, # String — the original block type (e.g., "document")
1056
+ :data # Hash — the full raw block hash
1057
+ end
1058
+ ```
1059
+
1029
1060
  ### Error Types
1030
1061
 
1031
1062
  ```ruby
@@ -25,9 +25,9 @@ module ClaudeAgentSDK
25
25
  parse_stream_event(data)
26
26
  when 'rate_limit_event'
27
27
  parse_rate_limit_event(data)
28
- else
29
- raise MessageParseError.new("Unknown message type: #{message_type}", data: data)
30
28
  end
29
+ # Forward-compatible: returns nil for unrecognized message types so
30
+ # newer CLI versions don't crash older SDK versions.
31
31
  rescue KeyError => e
32
32
  raise MessageParseError.new("Missing required field: #{e.message}", data: data)
33
33
  end
@@ -115,7 +115,9 @@ module ClaudeAgentSDK
115
115
  is_error: block[:is_error]
116
116
  )
117
117
  else
118
- raise MessageParseError.new("Unknown content block type: #{block[:type]}")
118
+ # Forward-compatible: preserve unrecognized content block types (e.g., "document", "image")
119
+ # so newer CLI versions don't crash older SDK versions.
120
+ UnknownBlock.new(type: block[:type], data: block)
119
121
  end
120
122
  end
121
123
  end
@@ -3,6 +3,15 @@
3
3
  require 'mcp'
4
4
 
5
5
  module ClaudeAgentSDK
6
+ # Recursively convert all hash keys to symbols
7
+ def self.deep_symbolize_keys(obj)
8
+ case obj
9
+ when Hash then obj.transform_keys(&:to_sym).transform_values { |v| deep_symbolize_keys(v) }
10
+ when Array then obj.map { |v| deep_symbolize_keys(v) }
11
+ else obj
12
+ end
13
+ end
14
+
6
15
  # SDK MCP Server - wraps official MCP::Server with block-based API
7
16
  #
8
17
  # Unlike external MCP servers that run as separate processes, SDK MCP servers
@@ -164,10 +173,9 @@ module ClaudeAgentSDK
164
173
 
165
174
  def input_schema_value
166
175
  schema = convert_schema(@tool_def.input_schema)
167
- MCP::Tool::InputSchema.new(
168
- properties: schema[:properties] || {},
169
- required: schema[:required] || []
170
- )
176
+ opts = { properties: schema[:properties] || {} }
177
+ opts[:required] = schema[:required] if schema[:required]&.any?
178
+ MCP::Tool::InputSchema.new(**opts)
171
179
  end
172
180
 
173
181
  def call(server_context: nil, **args)
@@ -192,9 +200,12 @@ module ClaudeAgentSDK
192
200
  private
193
201
 
194
202
  def convert_schema(schema)
195
- # If it's already a proper JSON schema, return it
196
- if schema.is_a?(Hash) && schema[:type] && schema[:properties]
197
- return schema
203
+ # If it's already a proper JSON schema (symbol or string keys), normalize
204
+ # to symbol keys so downstream code (schema[:properties]) works uniformly.
205
+ if schema.is_a?(Hash)
206
+ type_val = schema[:type] || schema['type']
207
+ props_val = schema[:properties] || schema['properties']
208
+ return ClaudeAgentSDK.deep_symbolize_keys(schema) if type_val == 'object' && props_val.is_a?(Hash)
198
209
  end
199
210
 
200
211
  # Simple schema: hash mapping parameter names to types
@@ -204,11 +215,10 @@ module ClaudeAgentSDK
204
215
  properties[param_name] = type_to_json_schema(param_type)
205
216
  end
206
217
 
207
- return {
208
- type: 'object',
209
- properties: properties,
210
- required: properties.keys.map(&:to_s)
211
- }
218
+ result = { type: 'object', properties: properties }
219
+ required_keys = properties.keys.map(&:to_s)
220
+ result[:required] = required_keys unless required_keys.empty?
221
+ return result
212
222
  end
213
223
 
214
224
  # Default fallback
@@ -320,9 +330,12 @@ module ClaudeAgentSDK
320
330
  end
321
331
 
322
332
  def convert_input_schema(schema)
323
- # If it's already a proper JSON schema, return it
324
- if schema.is_a?(Hash) && schema[:type] && schema[:properties]
325
- return schema
333
+ # If it's already a proper JSON schema (symbol or string keys), normalize
334
+ # to symbol keys for consistent output.
335
+ if schema.is_a?(Hash)
336
+ type_val = schema[:type] || schema['type']
337
+ props_val = schema[:properties] || schema['properties']
338
+ return ClaudeAgentSDK.deep_symbolize_keys(schema) if type_val == 'object' && props_val.is_a?(Hash)
326
339
  end
327
340
 
328
341
  # Simple schema: hash mapping parameter names to types
@@ -332,11 +345,9 @@ module ClaudeAgentSDK
332
345
  properties[param_name] = type_to_json_schema(param_type)
333
346
  end
334
347
 
335
- return {
336
- type: 'object',
337
- properties: properties,
338
- required: properties.keys
339
- }
348
+ result = { type: 'object', properties: properties }
349
+ result[:required] = properties.keys unless properties.empty?
350
+ return result
340
351
  end
341
352
 
342
353
  # Default fallback
@@ -77,6 +77,17 @@ module ClaudeAgentSDK
77
77
  end
78
78
  end
79
79
 
80
+ # Generic content block for types the SDK doesn't explicitly handle (e.g., "document", "image").
81
+ # Preserves the raw hash data for forward compatibility with newer CLI versions.
82
+ class UnknownBlock
83
+ attr_accessor :type, :data
84
+
85
+ def initialize(type:, data:)
86
+ @type = type
87
+ @data = data
88
+ end
89
+ end
90
+
80
91
  # Message Types
81
92
 
82
93
  # User message
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeAgentSDK
4
- VERSION = '0.7.1'
4
+ VERSION = '0.7.3'
5
5
  end
@@ -119,7 +119,7 @@ module ClaudeAgentSDK
119
119
  # Read and yield messages from the query handler (filters out control messages)
120
120
  query_handler.receive_messages do |data|
121
121
  message = MessageParser.parse(data)
122
- block.call(message)
122
+ block.call(message) if message
123
123
  end
124
124
  ensure
125
125
  # query_handler.close stops the background read task and closes the transport
@@ -275,7 +275,7 @@ module ClaudeAgentSDK
275
275
 
276
276
  @query_handler.receive_messages do |data|
277
277
  message = MessageParser.parse(data)
278
- block.call(message)
278
+ block.call(message) if message
279
279
  end
280
280
  end
281
281
 
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.7.1
4
+ version: 0.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Community Contributors
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-02-20 00:00:00.000000000 Z
10
+ date: 2026-02-25 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: async