claude_code_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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +38 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +36 -0
- data/CLAUDE.md +86 -0
- data/CONTRIBUTING.md +82 -0
- data/LICENSE +21 -0
- data/README.md +304 -0
- data/Rakefile +32 -0
- data/claude_code_sdk.gemspec +54 -0
- data/examples/debug_process.rb +60 -0
- data/examples/debug_query.rb +34 -0
- data/examples/simple_query.rb +23 -0
- data/examples/simple_test.rb +18 -0
- data/examples/simple_tools.rb +31 -0
- data/examples/simple_tools_fixed.rb +33 -0
- data/examples/streaming_responses.rb +39 -0
- data/examples/test_sdk.rb +45 -0
- data/examples/with_tools.rb +35 -0
- data/lib/claude_code_sdk/configuration.rb +27 -0
- data/lib/claude_code_sdk/errors.rb +45 -0
- data/lib/claude_code_sdk/internal/client.rb +122 -0
- data/lib/claude_code_sdk/messages.rb +101 -0
- data/lib/claude_code_sdk/transport/base.rb +24 -0
- data/lib/claude_code_sdk/transport/simple_cli.rb +108 -0
- data/lib/claude_code_sdk/transport/subprocess_cli.rb +167 -0
- data/lib/claude_code_sdk/types.rb +63 -0
- data/lib/claude_code_sdk/version.rb +5 -0
- data/lib/claude_code_sdk.rb +84 -0
- metadata +262 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "open3"
|
5
|
+
require "json"
|
6
|
+
require "timeout"
|
7
|
+
|
8
|
+
puts "=== Debug Process Example ==="
|
9
|
+
|
10
|
+
cli_path = `which claude`.chomp
|
11
|
+
puts "CLI path: #{cli_path}"
|
12
|
+
|
13
|
+
args = ["--print", "--output-format", "stream-json", "--verbose", "What is 2 + 2?"]
|
14
|
+
puts "Command: #{cli_path} #{args.join(" ")}"
|
15
|
+
|
16
|
+
ENV["ANTHROPIC_API_KEY"] =
|
17
|
+
"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"
|
18
|
+
|
19
|
+
stdin, stdout, stderr, wait_thread = Open3.popen3(cli_path, *args)
|
20
|
+
|
21
|
+
puts "\nProcess started, PID: #{wait_thread.pid}"
|
22
|
+
|
23
|
+
# Read stdout in a thread
|
24
|
+
stdout_thread = Thread.new do
|
25
|
+
puts "\nSTDOUT:"
|
26
|
+
stdout.each_line do |line|
|
27
|
+
puts " > #{line}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Read stderr in a thread
|
32
|
+
stderr_thread = Thread.new do
|
33
|
+
puts "\nSTDERR:"
|
34
|
+
stderr.each_line do |line|
|
35
|
+
puts " ! #{line}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Wait for threads with timeout
|
40
|
+
begin
|
41
|
+
Timeout.timeout(5) do
|
42
|
+
stdout_thread.join
|
43
|
+
stderr_thread.join
|
44
|
+
end
|
45
|
+
rescue Timeout::Error
|
46
|
+
puts "\n[Timeout after 5 seconds]"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Check process status
|
50
|
+
if wait_thread.alive?
|
51
|
+
puts "\nProcess still alive, killing..."
|
52
|
+
Process.kill("TERM", wait_thread.pid)
|
53
|
+
wait_thread.join(1)
|
54
|
+
end
|
55
|
+
|
56
|
+
puts "\nExit status: #{wait_thread.value.exitstatus}"
|
57
|
+
|
58
|
+
stdin.close
|
59
|
+
stdout.close
|
60
|
+
stderr.close
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Add the lib directory to the load path for local development
|
5
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
6
|
+
require "claude_code_sdk"
|
7
|
+
|
8
|
+
# Enable debug output
|
9
|
+
puts "=== Debug Query Example ==="
|
10
|
+
|
11
|
+
# Create a simple transport to see what's happening
|
12
|
+
transport = ClaudeCodeSDK::Transport::SubprocessCLI.new(
|
13
|
+
prompt: "What is 2 + 2?",
|
14
|
+
options: ClaudeCodeSDK::Options.new
|
15
|
+
)
|
16
|
+
|
17
|
+
begin
|
18
|
+
puts "Connecting to CLI..."
|
19
|
+
transport.connect
|
20
|
+
puts "Connected!"
|
21
|
+
|
22
|
+
puts "\nReceiving messages..."
|
23
|
+
transport.receive_messages do |raw_message|
|
24
|
+
puts "Raw message: #{raw_message.inspect}"
|
25
|
+
end
|
26
|
+
rescue StandardError => e
|
27
|
+
puts "Error: #{e.class} - #{e.message}"
|
28
|
+
puts e.backtrace.first(5).join("\n")
|
29
|
+
ensure
|
30
|
+
puts "\nDisconnecting..."
|
31
|
+
transport.disconnect
|
32
|
+
end
|
33
|
+
|
34
|
+
puts "Done!"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Add the lib directory to the load path for local development
|
5
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
6
|
+
require "claude_code_sdk"
|
7
|
+
|
8
|
+
# Simple query with block
|
9
|
+
puts "=== Simple Query Example ==="
|
10
|
+
ClaudeCodeSDK.query("What is 2 + 2?") do |message|
|
11
|
+
case message
|
12
|
+
when ClaudeCodeSDK::AssistantMessage
|
13
|
+
message.content.each do |block|
|
14
|
+
puts block.text if block.is_a?(ClaudeCodeSDK::Content::TextBlock)
|
15
|
+
end
|
16
|
+
when ClaudeCodeSDK::SystemMessage
|
17
|
+
puts "[System] #{message.title}: #{message.message}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
puts "\n=== Using ask method ==="
|
22
|
+
response = ClaudeCodeSDK.ask("What is the capital of France?")
|
23
|
+
puts response
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Test using backticks
|
5
|
+
require "English"
|
6
|
+
ENV["ANTHROPIC_API_KEY"] =
|
7
|
+
"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"
|
8
|
+
|
9
|
+
command = 'claude --print --output-format json "What is 2 + 2?"'
|
10
|
+
puts "Running: #{command}"
|
11
|
+
|
12
|
+
output = `#{command}`
|
13
|
+
puts "Exit status: #{$CHILD_STATUS.exitstatus}"
|
14
|
+
puts "Output: #{output}"
|
15
|
+
|
16
|
+
require "json"
|
17
|
+
data = JSON.parse(output)
|
18
|
+
puts "Result: #{data["result"]}"
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Add the lib directory to the load path for local development
|
5
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
6
|
+
require "claude_code_sdk"
|
7
|
+
|
8
|
+
puts "=== Simple Tools Example ==="
|
9
|
+
|
10
|
+
# Test without cwd to see if that's the issue
|
11
|
+
options = ClaudeCodeSDK::Options.new(
|
12
|
+
allowed_tools: %w[read_file write_file list_files]
|
13
|
+
)
|
14
|
+
|
15
|
+
ClaudeCodeSDK.query("List the Ruby files in the current directory", options) do |message|
|
16
|
+
case message
|
17
|
+
when ClaudeCodeSDK::AssistantMessage
|
18
|
+
message.content.each do |block|
|
19
|
+
case block
|
20
|
+
when ClaudeCodeSDK::Content::TextBlock
|
21
|
+
puts block.text
|
22
|
+
when ClaudeCodeSDK::Content::ToolUseBlock
|
23
|
+
puts "[Tool Use] #{block.name}: #{block.input.inspect}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
when ClaudeCodeSDK::SystemMessage
|
27
|
+
puts "[System] #{message.title}"
|
28
|
+
when ClaudeCodeSDK::ResultMessage
|
29
|
+
puts "[Result] Status: #{message.status}"
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Add the lib directory to the load path for local development
|
5
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
6
|
+
require "claude_code_sdk"
|
7
|
+
|
8
|
+
puts "=== Simple Tools Example (Fixed) ==="
|
9
|
+
|
10
|
+
# Create options without cwd to avoid CLI parsing issues
|
11
|
+
options = ClaudeCodeSDK::Options.new(
|
12
|
+
allowed_tools: %w[read_file write_file list_files]
|
13
|
+
# NOTE: Removing cwd for now as it causes CLI parsing issues
|
14
|
+
)
|
15
|
+
|
16
|
+
ClaudeCodeSDK.query("Help me understand what Ruby files are in this project", options) do |message|
|
17
|
+
case message
|
18
|
+
when ClaudeCodeSDK::AssistantMessage
|
19
|
+
message.content.each do |block|
|
20
|
+
case block
|
21
|
+
when ClaudeCodeSDK::Content::TextBlock
|
22
|
+
puts block.text
|
23
|
+
when ClaudeCodeSDK::Content::ToolUseBlock
|
24
|
+
puts "[Tool Use] #{block.name}: #{block.input.inspect}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
when ClaudeCodeSDK::SystemMessage
|
28
|
+
puts "[System] #{message.title}"
|
29
|
+
when ClaudeCodeSDK::ResultMessage
|
30
|
+
puts "[Result] Status: #{message.status}"
|
31
|
+
puts " Cost: $#{message.cost[:usd]}" if message.cost && message.cost[:usd]
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Add the lib directory to the load path for local development
|
5
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
6
|
+
require "claude_code_sdk"
|
7
|
+
|
8
|
+
# Example showing real-time streaming of responses
|
9
|
+
puts "=== Streaming Response Example ==="
|
10
|
+
|
11
|
+
# Configure global defaults
|
12
|
+
ClaudeCodeSDK.configure do |config|
|
13
|
+
config.default_system_prompt = "You are a Ruby programming expert"
|
14
|
+
config.default_cwd = Dir.pwd
|
15
|
+
end
|
16
|
+
|
17
|
+
# Track message types
|
18
|
+
message_counts = Hash.new(0)
|
19
|
+
|
20
|
+
ClaudeCodeSDK.query("Write a Ruby function to calculate fibonacci numbers") do |message|
|
21
|
+
message_counts[message.class.name] += 1
|
22
|
+
|
23
|
+
case message
|
24
|
+
when ClaudeCodeSDK::UserMessage
|
25
|
+
puts "\n[You] #{message.text}"
|
26
|
+
when ClaudeCodeSDK::AssistantMessage
|
27
|
+
print "\n[Claude] "
|
28
|
+
message.content.each do |block|
|
29
|
+
print block.text if block.is_a?(ClaudeCodeSDK::Content::TextBlock)
|
30
|
+
end
|
31
|
+
when ClaudeCodeSDK::SystemMessage
|
32
|
+
puts "\n[System] #{message.title}: #{message.message}"
|
33
|
+
when ClaudeCodeSDK::ResultMessage
|
34
|
+
puts "\n\n[Conversation Complete]"
|
35
|
+
puts "Status: #{message.status}"
|
36
|
+
puts "Messages received:"
|
37
|
+
message_counts.each { |type, count| puts " #{type}: #{count}" }
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Add the lib directory to the load path for local development
|
5
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
6
|
+
require "claude_code_sdk"
|
7
|
+
|
8
|
+
puts "=== Testing Ruby SDK ==="
|
9
|
+
puts "SDK Version: #{ClaudeCodeSDK::VERSION}"
|
10
|
+
puts "Environment: #{ENV.fetch("CLAUDE_CODE_SDK", nil)}"
|
11
|
+
|
12
|
+
# Test Options creation
|
13
|
+
options = ClaudeCodeSDK::Options.new(
|
14
|
+
system_prompt: "Test prompt",
|
15
|
+
allowed_tools: %w[read_file write_file],
|
16
|
+
max_thinking_tokens: 5000,
|
17
|
+
cwd: "/tmp"
|
18
|
+
)
|
19
|
+
|
20
|
+
puts "\nOptions created successfully:"
|
21
|
+
puts " System prompt: #{options.system_prompt}"
|
22
|
+
puts " Allowed tools: #{options.allowed_tools.join(", ")}"
|
23
|
+
puts " Max thinking tokens: #{options.max_thinking_tokens}"
|
24
|
+
puts " CWD: #{options.cwd}"
|
25
|
+
|
26
|
+
puts "\nCLI args: #{options.to_cli_args.join(" ")}"
|
27
|
+
|
28
|
+
# Test message creation
|
29
|
+
user_msg = ClaudeCodeSDK::UserMessage.new(id: "1", text: "Hello")
|
30
|
+
puts "\nUser message created: #{user_msg.text}"
|
31
|
+
|
32
|
+
assistant_msg = ClaudeCodeSDK::AssistantMessage.new(
|
33
|
+
id: "2",
|
34
|
+
content: [
|
35
|
+
ClaudeCodeSDK::Content::TextBlock.new(text: "Hi there!"),
|
36
|
+
ClaudeCodeSDK::Content::ToolUseBlock.new(id: "tool1", name: "read_file", input: { path: "test.txt" })
|
37
|
+
]
|
38
|
+
)
|
39
|
+
puts "Assistant message created with #{assistant_msg.content.length} content blocks"
|
40
|
+
|
41
|
+
# Test error creation
|
42
|
+
error = ClaudeCodeSDK::CLINotFoundError.new("Test error", cli_path: "/usr/bin/claude")
|
43
|
+
puts "\nError created: #{error.message}"
|
44
|
+
|
45
|
+
puts "\n✅ Ruby SDK is working correctly!"
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Add the lib directory to the load path for local development
|
5
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
6
|
+
require "claude_code_sdk"
|
7
|
+
|
8
|
+
# Query with tool permissions
|
9
|
+
puts "=== Query with Tools Example ==="
|
10
|
+
|
11
|
+
options = ClaudeCodeSDK::Options.new(
|
12
|
+
system_prompt: "You are a helpful coding assistant",
|
13
|
+
allowed_tools: %w[read_file write_file list_files],
|
14
|
+
max_thinking_tokens: 10_000,
|
15
|
+
cwd: Dir.pwd
|
16
|
+
)
|
17
|
+
|
18
|
+
ClaudeCodeSDK.query("List the files in the current directory", options) do |message|
|
19
|
+
case message
|
20
|
+
when ClaudeCodeSDK::AssistantMessage
|
21
|
+
message.content.each do |block|
|
22
|
+
case block
|
23
|
+
when ClaudeCodeSDK::Content::TextBlock
|
24
|
+
puts block.text
|
25
|
+
when ClaudeCodeSDK::Content::ToolUseBlock
|
26
|
+
puts "[Tool Use] #{block.name}: #{block.input}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
when ClaudeCodeSDK::SystemMessage
|
30
|
+
puts "[System] #{message.title}"
|
31
|
+
when ClaudeCodeSDK::ResultMessage
|
32
|
+
puts "[Result] Status: #{message.status}"
|
33
|
+
puts " Cost: $#{message.cost[:usd]}" if message.cost
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "singleton"
|
4
|
+
|
5
|
+
module ClaudeCodeSDK
|
6
|
+
class Configuration
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
attr_accessor :default_system_prompt, :default_cwd,
|
10
|
+
:default_permission_mode, :cli_path
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@default_system_prompt = nil
|
14
|
+
@default_cwd = nil
|
15
|
+
@default_permission_mode = PermissionMode::DEFAULT
|
16
|
+
@cli_path = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_options
|
20
|
+
Options.new(
|
21
|
+
system_prompt: default_system_prompt,
|
22
|
+
cwd: default_cwd,
|
23
|
+
permission_mode: default_permission_mode
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ClaudeCodeSDK
|
4
|
+
# Base error class for all SDK errors
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
# Raised when unable to connect to Claude Code CLI
|
8
|
+
class CLIConnectionError < Error; end
|
9
|
+
|
10
|
+
# Raised when Claude Code CLI is not found
|
11
|
+
class CLINotFoundError < CLIConnectionError
|
12
|
+
attr_reader :cli_path
|
13
|
+
|
14
|
+
def initialize(message = "Claude Code not found", cli_path: nil)
|
15
|
+
@cli_path = cli_path
|
16
|
+
super(cli_path ? "#{message}: #{cli_path}" : message)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Raised when the CLI process fails
|
21
|
+
class ProcessError < Error
|
22
|
+
attr_reader :exit_code, :stderr
|
23
|
+
|
24
|
+
def initialize(message, exit_code: nil, stderr: nil)
|
25
|
+
@exit_code = exit_code
|
26
|
+
@stderr = stderr
|
27
|
+
|
28
|
+
full_message = message
|
29
|
+
full_message += " (exit code: #{exit_code})" if exit_code
|
30
|
+
full_message += "\nError output: #{stderr}" if stderr && !stderr.empty?
|
31
|
+
|
32
|
+
super(full_message)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Raised when JSON parsing fails
|
37
|
+
class CLIJSONDecodeError < Error
|
38
|
+
attr_reader :line
|
39
|
+
|
40
|
+
def initialize(message, line: nil)
|
41
|
+
@line = line
|
42
|
+
super(line ? "#{message}\nLine: #{line}" : message)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ClaudeCodeSDK
|
4
|
+
module Internal
|
5
|
+
class Client
|
6
|
+
def initialize
|
7
|
+
@transport = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def query(prompt:, options: nil, &block)
|
11
|
+
# Use SubprocessCLI for streaming JSON support
|
12
|
+
require_relative "../transport/subprocess_cli"
|
13
|
+
@transport = Transport::SubprocessCLI.new(prompt: prompt, options: options)
|
14
|
+
|
15
|
+
begin
|
16
|
+
@transport.connect
|
17
|
+
|
18
|
+
@transport.receive_messages do |raw_message|
|
19
|
+
message = parse_message(raw_message)
|
20
|
+
block.call(message) if message
|
21
|
+
end
|
22
|
+
ensure
|
23
|
+
@transport&.disconnect
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def parse_message(raw)
|
30
|
+
case raw[:type]
|
31
|
+
when "user"
|
32
|
+
UserMessage.new(
|
33
|
+
id: raw[:id] || "user-#{Time.now.to_f}",
|
34
|
+
text: raw[:text] || raw[:prompt]
|
35
|
+
)
|
36
|
+
when "assistant"
|
37
|
+
# Handle the actual CLI format
|
38
|
+
if raw[:message]
|
39
|
+
msg = raw[:message]
|
40
|
+
# Extract thinking from content blocks
|
41
|
+
thinking_content = extract_thinking_from_content(msg[:content] || [])
|
42
|
+
# Parse remaining content blocks
|
43
|
+
content_blocks = parse_content_blocks(msg[:content] || [])
|
44
|
+
|
45
|
+
AssistantMessage.new(
|
46
|
+
id: msg[:id],
|
47
|
+
content: content_blocks,
|
48
|
+
thinking: thinking_content
|
49
|
+
)
|
50
|
+
else
|
51
|
+
# Extract thinking from content blocks
|
52
|
+
thinking_content = extract_thinking_from_content(raw[:content] || [])
|
53
|
+
# Parse remaining content blocks
|
54
|
+
content_blocks = parse_content_blocks(raw[:content] || [])
|
55
|
+
|
56
|
+
AssistantMessage.new(
|
57
|
+
id: raw[:id] || "assistant-#{Time.now.to_f}",
|
58
|
+
content: content_blocks,
|
59
|
+
thinking: thinking_content
|
60
|
+
)
|
61
|
+
end
|
62
|
+
when "system"
|
63
|
+
# System messages from CLI have different structure
|
64
|
+
SystemMessage.new(
|
65
|
+
id: raw[:id] || "system-#{Time.now.to_f}",
|
66
|
+
title: raw[:subtype] || "system",
|
67
|
+
message: raw.to_json
|
68
|
+
)
|
69
|
+
when "result"
|
70
|
+
ResultMessage.new(
|
71
|
+
id: raw[:id] || "result-#{Time.now.to_f}",
|
72
|
+
status: raw[:subtype] || raw[:status],
|
73
|
+
cost: raw[:total_cost_usd] ? { usd: raw[:total_cost_usd] } : raw[:cost],
|
74
|
+
usage: raw[:usage]
|
75
|
+
)
|
76
|
+
when "thinking"
|
77
|
+
# Handle thinking messages from the CLI
|
78
|
+
content = raw[:content] || raw[:text] || raw[:message] || ""
|
79
|
+
::ClaudeCodeSDK::ThinkingMessage.new(
|
80
|
+
id: raw[:id] || "thinking-#{Time.now.to_f}",
|
81
|
+
content: content
|
82
|
+
)
|
83
|
+
else
|
84
|
+
# Unknown message type, skip
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def extract_thinking_from_content(content)
|
90
|
+
thinking_blocks = content.select { |block| block[:type] == "thinking" }
|
91
|
+
return nil if thinking_blocks.empty?
|
92
|
+
|
93
|
+
# Combine all thinking blocks
|
94
|
+
thinking_blocks.map { |block| block[:thinking] }.join("\n")
|
95
|
+
end
|
96
|
+
|
97
|
+
def parse_content_blocks(content)
|
98
|
+
content.map do |block|
|
99
|
+
case block[:type]
|
100
|
+
when "text"
|
101
|
+
Content::TextBlock.new(text: block[:text])
|
102
|
+
when "tool_use"
|
103
|
+
Content::ToolUseBlock.new(
|
104
|
+
id: block[:id],
|
105
|
+
name: block[:name],
|
106
|
+
input: block[:input]
|
107
|
+
)
|
108
|
+
when "tool_result"
|
109
|
+
Content::ToolResultBlock.new(
|
110
|
+
tool_use_id: block[:tool_use_id],
|
111
|
+
output: block[:output],
|
112
|
+
is_error: block[:is_error] || false
|
113
|
+
)
|
114
|
+
when "thinking"
|
115
|
+
# Skip thinking blocks as they're handled separately
|
116
|
+
nil
|
117
|
+
end
|
118
|
+
end.compact
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ClaudeCodeSDK
|
4
|
+
# Base message class
|
5
|
+
class Message
|
6
|
+
attr_reader :id, :type
|
7
|
+
|
8
|
+
def initialize(id:, type:)
|
9
|
+
@id = id
|
10
|
+
@type = type
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# User input message
|
15
|
+
class UserMessage < Message
|
16
|
+
attr_reader :text
|
17
|
+
|
18
|
+
def initialize(id:, text:)
|
19
|
+
super(id: id, type: "user")
|
20
|
+
@text = text
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Assistant response message
|
25
|
+
class AssistantMessage < Message
|
26
|
+
attr_reader :content, :thinking
|
27
|
+
|
28
|
+
def initialize(id:, content:, thinking: nil)
|
29
|
+
super(id: id, type: "assistant")
|
30
|
+
@content = content # Array of content blocks
|
31
|
+
@thinking = thinking # Optional thinking content
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# System message
|
36
|
+
class SystemMessage < Message
|
37
|
+
attr_reader :title, :message
|
38
|
+
|
39
|
+
def initialize(id:, title:, message:)
|
40
|
+
super(id: id, type: "system")
|
41
|
+
@title = title
|
42
|
+
@message = message
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Final result message
|
47
|
+
class ResultMessage < Message
|
48
|
+
attr_reader :status, :cost, :usage
|
49
|
+
|
50
|
+
def initialize(id:, status:, cost: nil, usage: nil)
|
51
|
+
super(id: id, type: "result")
|
52
|
+
@status = status
|
53
|
+
@cost = cost
|
54
|
+
@usage = usage
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Thinking message (Claude's internal reasoning)
|
59
|
+
class ThinkingMessage < Message
|
60
|
+
attr_reader :content
|
61
|
+
|
62
|
+
def initialize(id:, content:)
|
63
|
+
super(id: id, type: "thinking")
|
64
|
+
@content = content
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Content blocks
|
69
|
+
module Content
|
70
|
+
class TextBlock
|
71
|
+
attr_reader :type, :text
|
72
|
+
|
73
|
+
def initialize(text:)
|
74
|
+
@type = "text"
|
75
|
+
@text = text
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class ToolUseBlock
|
80
|
+
attr_reader :type, :id, :name, :input
|
81
|
+
|
82
|
+
def initialize(id:, name:, input:)
|
83
|
+
@type = "tool_use"
|
84
|
+
@id = id
|
85
|
+
@name = name
|
86
|
+
@input = input
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class ToolResultBlock
|
91
|
+
attr_reader :type, :tool_use_id, :output, :is_error
|
92
|
+
|
93
|
+
def initialize(tool_use_id:, output:, is_error: false)
|
94
|
+
@type = "tool_result"
|
95
|
+
@tool_use_id = tool_use_id
|
96
|
+
@output = output
|
97
|
+
@is_error = is_error
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ClaudeCodeSDK
|
4
|
+
module Transport
|
5
|
+
# Abstract base class for transports
|
6
|
+
class Base
|
7
|
+
def connect
|
8
|
+
raise NotImplementedError
|
9
|
+
end
|
10
|
+
|
11
|
+
def disconnect
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
|
15
|
+
def connected?
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
|
19
|
+
def receive_messages(&block)
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|