claude-agent-sdk 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c64664d64c01dd3a8eaaf3acb9f9710088c063403430f9ca9d974c91a14f5c33
4
+ data.tar.gz: edc92a78c207df18290335002c68c443ca398127c22979b5e97e6c3838052381
5
+ SHA512:
6
+ metadata.gz: b6ae823488c9e208461a630d844fa270b84d1cc34ae98ef87f0893b4b302bcacbddc98644d4d29d49c65388e0b6ed2f91afa79a964eae58415435c2607fb0dde
7
+ data.tar.gz: e3fe5655912e84519af489f2391e9d81c913e256fe49ae1522e0eced2dcb31f8d4443d757c154530b23d94b677c8526bdfe10623055aa305e742f407eea17bb3
data/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2025-10-14
9
+
10
+ ### Added
11
+ - Initial release of Claude Agent SDK for Ruby
12
+ - Support for `query()` function for simple one-shot interactions
13
+ - `ClaudeSDKClient` class for bidirectional, stateful conversations
14
+ - Custom tool support via SDK MCP servers
15
+ - Hook support for all major hook events
16
+ - Comprehensive error handling
17
+ - Full async/await support using the `async` gem
18
+ - Examples demonstrating common use cases
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Anthropic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,432 @@
1
+ # Claude Agent SDK for Ruby
2
+
3
+ > **⚠️ DISCLAIMER**: This is an **unofficial, community-maintained** Ruby SDK for Claude Agent. It is not officially supported or maintained by Anthropic. For official SDK support, please refer to the [Python SDK](https://github.com/anthropics/claude-code/tree/main/agent-sdk/python).
4
+ >
5
+ > This implementation is based on the official Python SDK and aims to provide feature parity for Ruby developers. Use at your own risk.
6
+
7
+ Ruby SDK for Claude Agent. See the [Claude Agent SDK documentation](https://docs.anthropic.com/en/docs/claude-code/sdk) for more information.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'claude-agent-sdk'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ ```bash
20
+ bundle install
21
+ ```
22
+
23
+ Or install it yourself as:
24
+
25
+ ```bash
26
+ gem install claude-agent-sdk
27
+ ```
28
+
29
+ **Prerequisites:**
30
+ - Ruby 3.0+
31
+ - Node.js
32
+ - Claude Code 2.0.0+: `npm install -g @anthropic-ai/claude-code`
33
+
34
+ ## Quick Start
35
+
36
+ ```ruby
37
+ require 'claude_agent_sdk'
38
+
39
+ ClaudeAgentSDK.query(prompt: "What is 2 + 2?") do |message|
40
+ puts message
41
+ end
42
+ ```
43
+
44
+ ## Basic Usage: query()
45
+
46
+ `query()` is a function for querying Claude Code. It yields response messages to a block. See [lib/claude_agent_sdk.rb](lib/claude_agent_sdk.rb).
47
+
48
+ ```ruby
49
+ require 'claude_agent_sdk'
50
+
51
+ # Simple query
52
+ ClaudeAgentSDK.query(prompt: "Hello Claude") do |message|
53
+ if message.is_a?(ClaudeAgentSDK::AssistantMessage)
54
+ message.content.each do |block|
55
+ puts block.text if block.is_a?(ClaudeAgentSDK::TextBlock)
56
+ end
57
+ end
58
+ end
59
+
60
+ # With options
61
+ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
62
+ system_prompt: "You are a helpful assistant",
63
+ max_turns: 1
64
+ )
65
+
66
+ ClaudeAgentSDK.query(prompt: "Tell me a joke", options: options) do |message|
67
+ puts message
68
+ end
69
+ ```
70
+
71
+ ### Using Tools
72
+
73
+ ```ruby
74
+ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
75
+ allowed_tools: ['Read', 'Write', 'Bash'],
76
+ permission_mode: 'acceptEdits' # auto-accept file edits
77
+ )
78
+
79
+ ClaudeAgentSDK.query(
80
+ prompt: "Create a hello.rb file",
81
+ options: options
82
+ ) do |message|
83
+ # Process tool use and results
84
+ end
85
+ ```
86
+
87
+ ### Working Directory
88
+
89
+ ```ruby
90
+ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
91
+ cwd: "/path/to/project"
92
+ )
93
+ ```
94
+
95
+ ## Client
96
+
97
+ `ClaudeAgentSDK::Client` supports bidirectional, interactive conversations with Claude Code. Unlike `query()`, `Client` enables **custom tools**, **hooks**, and **permission callbacks**, all of which can be defined as Ruby procs/lambdas. See [lib/claude_agent_sdk.rb](lib/claude_agent_sdk.rb).
98
+
99
+ ### Custom Tools (SDK MCP Servers)
100
+
101
+ A **custom tool** is a Ruby proc/lambda that you can offer to Claude, for Claude to invoke as needed.
102
+
103
+ Custom tools are implemented as in-process MCP servers that run directly within your Ruby application, eliminating the need for separate processes that regular MCP servers require.
104
+
105
+ For a complete example, see [examples/mcp_calculator.rb](examples/mcp_calculator.rb).
106
+
107
+ #### Creating a Simple Tool
108
+
109
+ ```ruby
110
+ require 'claude_agent_sdk'
111
+ require 'async'
112
+
113
+ # Define a tool using create_tool
114
+ greet_tool = ClaudeAgentSDK.create_tool('greet', 'Greet a user', { name: :string }) do |args|
115
+ { content: [{ type: 'text', text: "Hello, #{args[:name]}!" }] }
116
+ end
117
+
118
+ # Create an SDK MCP server
119
+ server = ClaudeAgentSDK.create_sdk_mcp_server(
120
+ name: 'my-tools',
121
+ version: '1.0.0',
122
+ tools: [greet_tool]
123
+ )
124
+
125
+ # Use it with Claude
126
+ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
127
+ mcp_servers: { tools: server },
128
+ allowed_tools: ['mcp__tools__greet']
129
+ )
130
+
131
+ Async do
132
+ client = ClaudeAgentSDK::Client.new(options: options)
133
+ client.connect
134
+
135
+ client.query("Greet Alice")
136
+ client.receive_response { |msg| puts msg }
137
+
138
+ client.disconnect
139
+ end.wait
140
+ ```
141
+
142
+ #### Benefits Over External MCP Servers
143
+
144
+ - **No subprocess management** - Runs in the same process as your application
145
+ - **Better performance** - No IPC overhead for tool calls
146
+ - **Simpler deployment** - Single Ruby process instead of multiple
147
+ - **Easier debugging** - All code runs in the same process
148
+ - **Direct access** - Tools can directly access your application's state
149
+
150
+ #### Calculator Example
151
+
152
+ ```ruby
153
+ # Define calculator tools
154
+ add_tool = ClaudeAgentSDK.create_tool('add', 'Add two numbers', { a: :number, b: :number }) do |args|
155
+ result = args[:a] + args[:b]
156
+ { content: [{ type: 'text', text: "#{args[:a]} + #{args[:b]} = #{result}" }] }
157
+ end
158
+
159
+ divide_tool = ClaudeAgentSDK.create_tool('divide', 'Divide numbers', { a: :number, b: :number }) do |args|
160
+ if args[:b] == 0
161
+ { content: [{ type: 'text', text: 'Error: Division by zero' }], is_error: true }
162
+ else
163
+ result = args[:a] / args[:b]
164
+ { content: [{ type: 'text', text: "Result: #{result}" }] }
165
+ end
166
+ end
167
+
168
+ # Create server
169
+ calculator = ClaudeAgentSDK.create_sdk_mcp_server(
170
+ name: 'calculator',
171
+ tools: [add_tool, divide_tool]
172
+ )
173
+
174
+ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
175
+ mcp_servers: { calc: calculator },
176
+ allowed_tools: ['mcp__calc__add', 'mcp__calc__divide']
177
+ )
178
+ ```
179
+
180
+ #### Mixed Server Support
181
+
182
+ You can use both SDK and external MCP servers together:
183
+
184
+ ```ruby
185
+ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
186
+ mcp_servers: {
187
+ internal: sdk_server, # In-process SDK server
188
+ external: { # External subprocess server
189
+ type: 'stdio',
190
+ command: 'external-server'
191
+ }
192
+ }
193
+ )
194
+ ```
195
+
196
+ ### Basic Client Usage
197
+
198
+ ```ruby
199
+ require 'claude_agent_sdk'
200
+ require 'async'
201
+
202
+ Async do
203
+ client = ClaudeAgentSDK::Client.new
204
+
205
+ begin
206
+ client.connect
207
+
208
+ # Send a query
209
+ client.query("What is the capital of France?")
210
+
211
+ # Receive the response
212
+ client.receive_response do |msg|
213
+ if msg.is_a?(ClaudeAgentSDK::AssistantMessage)
214
+ msg.content.each do |block|
215
+ puts block.text if block.is_a?(ClaudeAgentSDK::TextBlock)
216
+ end
217
+ elsif msg.is_a?(ClaudeAgentSDK::ResultMessage)
218
+ puts "Cost: $#{msg.total_cost_usd}" if msg.total_cost_usd
219
+ end
220
+ end
221
+
222
+ ensure
223
+ client.disconnect
224
+ end
225
+ end.wait
226
+ ```
227
+
228
+ ### Hooks
229
+
230
+ A **hook** is a Ruby proc/lambda that the Claude Code *application* (*not* Claude) invokes at specific points of the Claude agent loop. Hooks can provide deterministic processing and automated feedback for Claude. Read more in [Claude Code Hooks Reference](https://docs.anthropic.com/en/docs/claude-code/hooks).
231
+
232
+ For more examples, see [examples/hooks_example.rb](examples/hooks_example.rb).
233
+
234
+ #### Example
235
+
236
+ ```ruby
237
+ require 'claude_agent_sdk'
238
+ require 'async'
239
+
240
+ Async do
241
+ # Define a hook that blocks dangerous bash commands
242
+ bash_hook = lambda do |input, tool_use_id, context|
243
+ tool_name = input[:tool_name]
244
+ tool_input = input[:tool_input]
245
+
246
+ return {} unless tool_name == 'Bash'
247
+
248
+ command = tool_input[:command] || ''
249
+ block_patterns = ['rm -rf', 'foo.sh']
250
+
251
+ block_patterns.each do |pattern|
252
+ if command.include?(pattern)
253
+ return {
254
+ hookSpecificOutput: {
255
+ hookEventName: 'PreToolUse',
256
+ permissionDecision: 'deny',
257
+ permissionDecisionReason: "Command contains forbidden pattern: #{pattern}"
258
+ }
259
+ }
260
+ end
261
+ end
262
+
263
+ {} # Allow if no patterns match
264
+ end
265
+
266
+ # Create options with hook
267
+ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
268
+ allowed_tools: ['Bash'],
269
+ hooks: {
270
+ 'PreToolUse' => [
271
+ ClaudeAgentSDK::HookMatcher.new(
272
+ matcher: 'Bash',
273
+ hooks: [bash_hook]
274
+ )
275
+ ]
276
+ }
277
+ )
278
+
279
+ client = ClaudeAgentSDK::Client.new(options: options)
280
+ client.connect
281
+
282
+ # Test: Command with forbidden pattern (will be blocked)
283
+ client.query("Run the bash command: ./foo.sh --help")
284
+ client.receive_response { |msg| puts msg }
285
+
286
+ client.disconnect
287
+ end.wait
288
+ ```
289
+
290
+ ### Permission Callbacks
291
+
292
+ A **permission callback** is a Ruby proc/lambda that allows you to programmatically control tool execution. This gives you fine-grained control over what tools Claude can use and with what inputs.
293
+
294
+ For more examples, see [examples/permission_callback_example.rb](examples/permission_callback_example.rb).
295
+
296
+ #### Example
297
+
298
+ ```ruby
299
+ require 'claude_agent_sdk'
300
+ require 'async'
301
+
302
+ Async do
303
+ # Define a permission callback
304
+ permission_callback = lambda do |tool_name, input, context|
305
+ # Allow Read operations
306
+ if tool_name == 'Read'
307
+ return ClaudeAgentSDK::PermissionResultAllow.new
308
+ end
309
+
310
+ # Block Write to sensitive files
311
+ if tool_name == 'Write'
312
+ file_path = input[:file_path] || input['file_path']
313
+ if file_path && file_path.include?('/etc/')
314
+ return ClaudeAgentSDK::PermissionResultDeny.new(
315
+ message: 'Cannot write to sensitive system files',
316
+ interrupt: false
317
+ )
318
+ end
319
+ return ClaudeAgentSDK::PermissionResultAllow.new
320
+ end
321
+
322
+ # Default: allow
323
+ ClaudeAgentSDK::PermissionResultAllow.new
324
+ end
325
+
326
+ # Create options with permission callback
327
+ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
328
+ allowed_tools: ['Read', 'Write', 'Bash'],
329
+ can_use_tool: permission_callback
330
+ )
331
+
332
+ client = ClaudeAgentSDK::Client.new(options: options)
333
+ client.connect
334
+
335
+ # This will be allowed
336
+ client.query("Create a file called test.txt with content 'Hello'")
337
+ client.receive_response { |msg| puts msg }
338
+
339
+ # This will be blocked
340
+ client.query("Write to /etc/passwd")
341
+ client.receive_response { |msg| puts msg }
342
+
343
+ client.disconnect
344
+ end.wait
345
+ ```
346
+
347
+ ### Advanced Client Features
348
+
349
+ The Client class supports several advanced features:
350
+
351
+ ```ruby
352
+ Async do
353
+ client = ClaudeAgentSDK::Client.new
354
+ client.connect
355
+
356
+ # Send interrupt signal
357
+ client.interrupt
358
+
359
+ # Change permission mode during conversation
360
+ client.set_permission_mode('acceptEdits')
361
+
362
+ # Change AI model during conversation
363
+ client.set_model('claude-sonnet-4-5')
364
+
365
+ # Get server initialization info
366
+ info = client.server_info
367
+ puts "Available commands: #{info}"
368
+
369
+ client.disconnect
370
+ end.wait
371
+ ```
372
+
373
+ ## Types
374
+
375
+ See [lib/claude_agent_sdk/types.rb](lib/claude_agent_sdk/types.rb) for complete type definitions:
376
+ - `ClaudeAgentOptions` - Configuration options
377
+ - `AssistantMessage`, `UserMessage`, `SystemMessage`, `ResultMessage` - Message types
378
+ - `TextBlock`, `ToolUseBlock`, `ToolResultBlock` - Content blocks
379
+
380
+ ## Error Handling
381
+
382
+ ```ruby
383
+ require 'claude_agent_sdk'
384
+
385
+ begin
386
+ ClaudeAgentSDK.query(prompt: "Hello") do |message|
387
+ puts message
388
+ end
389
+ rescue ClaudeAgentSDK::CLINotFoundError
390
+ puts "Please install Claude Code"
391
+ rescue ClaudeAgentSDK::ProcessError => e
392
+ puts "Process failed with exit code: #{e.exit_code}"
393
+ rescue ClaudeAgentSDK::CLIJSONDecodeError => e
394
+ puts "Failed to parse response: #{e}"
395
+ end
396
+ ```
397
+
398
+ Error types:
399
+ - `ClaudeSDKError` - Base error
400
+ - `CLINotFoundError` - Claude Code not installed
401
+ - `CLIConnectionError` - Connection issues
402
+ - `ProcessError` - Process failed
403
+ - `CLIJSONDecodeError` - JSON parsing issues
404
+ - `MessageParseError` - Message parsing issues
405
+
406
+ See [lib/claude_agent_sdk/errors.rb](lib/claude_agent_sdk/errors.rb) for all error types.
407
+
408
+ ## Available Tools
409
+
410
+ See the [Claude Code documentation](https://docs.anthropic.com/en/docs/claude-code/settings#tools-available-to-claude) for a complete list of available tools.
411
+
412
+ ## Examples
413
+
414
+ See the following examples for complete working code:
415
+
416
+ - [examples/quick_start.rb](examples/quick_start.rb) - Basic `query()` usage with options
417
+ - [examples/client_example.rb](examples/client_example.rb) - Interactive Client usage
418
+ - [examples/mcp_calculator.rb](examples/mcp_calculator.rb) - Custom tools with SDK MCP servers
419
+ - [examples/hooks_example.rb](examples/hooks_example.rb) - Using hooks to control tool execution
420
+ - [examples/permission_callback_example.rb](examples/permission_callback_example.rb) - Dynamic tool permission control
421
+
422
+ ## Development
423
+
424
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rspec` to run the tests.
425
+
426
+ ## Contributing
427
+
428
+ Bug reports and pull requests are welcome on GitHub at https://github.com/anthropics/claude-agent-sdk-ruby.
429
+
430
+ ## License
431
+
432
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeAgentSDK
4
+ # Base exception for all Claude SDK errors
5
+ class ClaudeSDKError < StandardError; end
6
+
7
+ # Raised when unable to connect to Claude Code
8
+ class CLIConnectionError < ClaudeSDKError; end
9
+
10
+ # Raised when Claude Code is not found or not installed
11
+ class CLINotFoundError < CLIConnectionError
12
+ def initialize(message = 'Claude Code not found', cli_path: nil)
13
+ message = "#{message}: #{cli_path}" if cli_path
14
+ super(message)
15
+ end
16
+ end
17
+
18
+ # Raised when the CLI process fails
19
+ class ProcessError < ClaudeSDKError
20
+ attr_reader :exit_code, :stderr
21
+
22
+ def initialize(message, exit_code: nil, stderr: nil)
23
+ @exit_code = exit_code
24
+ @stderr = stderr
25
+
26
+ message = "#{message} (exit code: #{exit_code})" if exit_code
27
+ message = "#{message}\nError output: #{stderr}" if stderr
28
+
29
+ super(message)
30
+ end
31
+ end
32
+
33
+ # Raised when unable to decode JSON from CLI output
34
+ class CLIJSONDecodeError < ClaudeSDKError
35
+ attr_reader :line, :original_error
36
+
37
+ def initialize(line, original_error)
38
+ @line = line
39
+ @original_error = original_error
40
+ super("Failed to decode JSON: #{line[0...100]}...")
41
+ end
42
+ end
43
+
44
+ # Raised when unable to parse a message from CLI output
45
+ class MessageParseError < ClaudeSDKError
46
+ attr_reader :data
47
+
48
+ def initialize(message, data: nil)
49
+ @data = data
50
+ super(message)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'types'
4
+ require_relative 'errors'
5
+
6
+ module ClaudeAgentSDK
7
+ # Parse message from CLI output into typed Message objects
8
+ class MessageParser
9
+ def self.parse(data)
10
+ raise MessageParseError.new("Invalid message data type", data: data) unless data.is_a?(Hash)
11
+
12
+ message_type = data[:type]
13
+ raise MessageParseError.new("Message missing 'type' field", data: data) unless message_type
14
+
15
+ case message_type
16
+ when 'user'
17
+ parse_user_message(data)
18
+ when 'assistant'
19
+ parse_assistant_message(data)
20
+ when 'system'
21
+ parse_system_message(data)
22
+ when 'result'
23
+ parse_result_message(data)
24
+ when 'stream_event'
25
+ parse_stream_event(data)
26
+ else
27
+ raise MessageParseError.new("Unknown message type: #{message_type}", data: data)
28
+ end
29
+ rescue KeyError => e
30
+ raise MessageParseError.new("Missing required field: #{e.message}", data: data)
31
+ end
32
+
33
+ def self.parse_user_message(data)
34
+ parent_tool_use_id = data[:parent_tool_use_id]
35
+ message_data = data[:message]
36
+ raise MessageParseError.new("Missing message field in user message", data: data) unless message_data
37
+
38
+ content = message_data[:content]
39
+ raise MessageParseError.new("Missing content in user message", data: data) unless content
40
+
41
+ if content.is_a?(Array)
42
+ content_blocks = content.map { |block| parse_content_block(block) }
43
+ UserMessage.new(content: content_blocks, parent_tool_use_id: parent_tool_use_id)
44
+ else
45
+ UserMessage.new(content: content, parent_tool_use_id: parent_tool_use_id)
46
+ end
47
+ end
48
+
49
+ def self.parse_assistant_message(data)
50
+ content = data.dig(:message, :content)
51
+ raise MessageParseError.new("Missing content in assistant message", data: data) unless content
52
+
53
+ content_blocks = content.map { |block| parse_content_block(block) }
54
+ AssistantMessage.new(
55
+ content: content_blocks,
56
+ model: data.dig(:message, :model),
57
+ parent_tool_use_id: data[:parent_tool_use_id]
58
+ )
59
+ end
60
+
61
+ def self.parse_system_message(data)
62
+ SystemMessage.new(
63
+ subtype: data[:subtype],
64
+ data: data
65
+ )
66
+ end
67
+
68
+ def self.parse_result_message(data)
69
+ ResultMessage.new(
70
+ subtype: data[:subtype],
71
+ duration_ms: data[:duration_ms],
72
+ duration_api_ms: data[:duration_api_ms],
73
+ is_error: data[:is_error],
74
+ num_turns: data[:num_turns],
75
+ session_id: data[:session_id],
76
+ total_cost_usd: data[:total_cost_usd],
77
+ usage: data[:usage],
78
+ result: data[:result]
79
+ )
80
+ end
81
+
82
+ def self.parse_stream_event(data)
83
+ StreamEvent.new(
84
+ uuid: data[:uuid],
85
+ session_id: data[:session_id],
86
+ event: data[:event],
87
+ parent_tool_use_id: data[:parent_tool_use_id]
88
+ )
89
+ end
90
+
91
+ def self.parse_content_block(block)
92
+ case block[:type]
93
+ when 'text'
94
+ TextBlock.new(text: block[:text])
95
+ when 'thinking'
96
+ ThinkingBlock.new(thinking: block[:thinking], signature: block[:signature])
97
+ when 'tool_use'
98
+ ToolUseBlock.new(id: block[:id], name: block[:name], input: block[:input])
99
+ when 'tool_result'
100
+ ToolResultBlock.new(
101
+ tool_use_id: block[:tool_use_id],
102
+ content: block[:content],
103
+ is_error: block[:is_error]
104
+ )
105
+ else
106
+ raise MessageParseError.new("Unknown content block type: #{block[:type]}")
107
+ end
108
+ end
109
+ end
110
+ end