claude_code 0.0.14
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/.rspec_status +114 -0
- data/.rubocop.yml +51 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +37 -0
- data/LICENSE +21 -0
- data/README.md +288 -0
- data/Rakefile +32 -0
- data/USAGE_EXAMPLES.md +428 -0
- data/claude_code.gemspec +47 -0
- data/docs/README.md +254 -0
- data/docs/mcp_integration.md +404 -0
- data/docs/streaming.md +316 -0
- data/examples/authentication_examples.rb +133 -0
- data/examples/basic_usage.rb +73 -0
- data/examples/conversation_resuming.rb +96 -0
- data/examples/irb_helpers.rb +168 -0
- data/examples/jsonl_cli_equivalent.rb +108 -0
- data/examples/mcp_examples.rb +166 -0
- data/examples/model_examples.rb +111 -0
- data/examples/quick_start.rb +75 -0
- data/examples/rails_sidekiq_example.rb +336 -0
- data/examples/streaming_examples.rb +195 -0
- data/examples/streaming_json_input.rb +171 -0
- data/hello.txt +1 -0
- data/lib/claude_code/client.rb +437 -0
- data/lib/claude_code/errors.rb +48 -0
- data/lib/claude_code/types.rb +237 -0
- data/lib/claude_code/version.rb +5 -0
- data/lib/claude_code.rb +265 -0
- metadata +219 -0
@@ -0,0 +1,336 @@
|
|
1
|
+
# Rails + Sidekiq streaming example for Ruby Claude Code SDK
|
2
|
+
# This shows how to use the SDK in a Rails background job with real-time streaming
|
3
|
+
|
4
|
+
require 'sidekiq'
|
5
|
+
require 'action_cable'
|
6
|
+
require_relative '../lib/claude_code'
|
7
|
+
|
8
|
+
# Sidekiq job for streaming Claude responses
|
9
|
+
class ClaudeStreamingJob
|
10
|
+
include Sidekiq::Job
|
11
|
+
|
12
|
+
# Job to process a Claude query with streaming updates
|
13
|
+
def perform(user_id, query_id, prompt, options_hash = {})
|
14
|
+
# Parse options
|
15
|
+
options = build_claude_options(options_hash)
|
16
|
+
|
17
|
+
# Set up streaming
|
18
|
+
channel_name = "claude_stream_#{user_id}_#{query_id}"
|
19
|
+
|
20
|
+
begin
|
21
|
+
# Broadcast start
|
22
|
+
ActionCable.server.broadcast(channel_name, {
|
23
|
+
type: 'start',
|
24
|
+
query_id: query_id,
|
25
|
+
timestamp: Time.current
|
26
|
+
})
|
27
|
+
|
28
|
+
message_count = 0
|
29
|
+
|
30
|
+
# Stream Claude responses
|
31
|
+
ClaudeCode.query(
|
32
|
+
prompt: prompt,
|
33
|
+
options: options,
|
34
|
+
cli_path: claude_cli_path
|
35
|
+
).each do |message|
|
36
|
+
message_count += 1
|
37
|
+
|
38
|
+
# Broadcast each message as it arrives
|
39
|
+
broadcast_data = {
|
40
|
+
type: 'message',
|
41
|
+
query_id: query_id,
|
42
|
+
message_index: message_count,
|
43
|
+
timestamp: Time.current
|
44
|
+
}
|
45
|
+
|
46
|
+
case message
|
47
|
+
when ClaudeCode::SystemMessage
|
48
|
+
broadcast_data.merge!(
|
49
|
+
message_type: 'system',
|
50
|
+
subtype: message.subtype,
|
51
|
+
data: message.data
|
52
|
+
)
|
53
|
+
|
54
|
+
when ClaudeCode::AssistantMessage
|
55
|
+
# Process content blocks
|
56
|
+
content_blocks = message.content.map do |block|
|
57
|
+
case block
|
58
|
+
when ClaudeCode::TextBlock
|
59
|
+
{ type: 'text', text: block.text }
|
60
|
+
when ClaudeCode::ToolUseBlock
|
61
|
+
{
|
62
|
+
type: 'tool_use',
|
63
|
+
id: block.id,
|
64
|
+
name: block.name,
|
65
|
+
input: block.input
|
66
|
+
}
|
67
|
+
when ClaudeCode::ToolResultBlock
|
68
|
+
{
|
69
|
+
type: 'tool_result',
|
70
|
+
tool_use_id: block.tool_use_id,
|
71
|
+
content: block.content,
|
72
|
+
is_error: block.is_error
|
73
|
+
}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
broadcast_data.merge!(
|
78
|
+
message_type: 'assistant',
|
79
|
+
content: content_blocks
|
80
|
+
)
|
81
|
+
|
82
|
+
when ClaudeCode::ResultMessage
|
83
|
+
broadcast_data.merge!(
|
84
|
+
message_type: 'result',
|
85
|
+
subtype: message.subtype,
|
86
|
+
duration_ms: message.duration_ms,
|
87
|
+
duration_api_ms: message.duration_api_ms,
|
88
|
+
is_error: message.is_error,
|
89
|
+
num_turns: message.num_turns,
|
90
|
+
session_id: message.session_id,
|
91
|
+
total_cost_usd: message.total_cost_usd,
|
92
|
+
usage: message.usage,
|
93
|
+
result: message.result
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Broadcast to WebSocket
|
98
|
+
ActionCable.server.broadcast(channel_name, broadcast_data)
|
99
|
+
|
100
|
+
# Optional: Save to database for persistence
|
101
|
+
save_message_to_db(user_id, query_id, message_count, broadcast_data)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Broadcast completion
|
105
|
+
ActionCable.server.broadcast(channel_name, {
|
106
|
+
type: 'complete',
|
107
|
+
query_id: query_id,
|
108
|
+
total_messages: message_count,
|
109
|
+
timestamp: Time.current
|
110
|
+
})
|
111
|
+
|
112
|
+
rescue => e
|
113
|
+
# Broadcast error
|
114
|
+
ActionCable.server.broadcast(channel_name, {
|
115
|
+
type: 'error',
|
116
|
+
query_id: query_id,
|
117
|
+
error: e.message,
|
118
|
+
timestamp: Time.current
|
119
|
+
})
|
120
|
+
|
121
|
+
# Log error
|
122
|
+
Rails.logger.error "Claude streaming job failed: #{e.message}"
|
123
|
+
Rails.logger.error e.backtrace.join("\n")
|
124
|
+
|
125
|
+
raise e # Re-raise for Sidekiq retry logic
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def build_claude_options(options_hash)
|
132
|
+
ClaudeCode::ClaudeCodeOptions.new(
|
133
|
+
model: options_hash['model'],
|
134
|
+
max_turns: options_hash['max_turns'] || 1,
|
135
|
+
system_prompt: options_hash['system_prompt'],
|
136
|
+
allowed_tools: options_hash['allowed_tools'] || [],
|
137
|
+
mcp_servers: build_mcp_servers(options_hash['mcp_servers'] || {}),
|
138
|
+
permission_mode: options_hash['permission_mode']
|
139
|
+
)
|
140
|
+
end
|
141
|
+
|
142
|
+
def build_mcp_servers(mcp_config)
|
143
|
+
return {} if mcp_config.empty?
|
144
|
+
|
145
|
+
servers = {}
|
146
|
+
mcp_config.each do |name, config|
|
147
|
+
servers.merge!(ClaudeCode.add_mcp_server(name, config))
|
148
|
+
end
|
149
|
+
servers
|
150
|
+
end
|
151
|
+
|
152
|
+
def claude_cli_path
|
153
|
+
# Try environment variable first, then common paths
|
154
|
+
ENV['CLAUDE_CLI_PATH'] ||
|
155
|
+
Rails.application.config.claude_cli_path ||
|
156
|
+
'/usr/local/bin/claude'
|
157
|
+
end
|
158
|
+
|
159
|
+
def save_message_to_db(user_id, query_id, message_index, data)
|
160
|
+
# Example: Save to database for persistence/replay
|
161
|
+
# ClaudeMessage.create!(
|
162
|
+
# user_id: user_id,
|
163
|
+
# query_id: query_id,
|
164
|
+
# message_index: message_index,
|
165
|
+
# message_type: data[:message_type],
|
166
|
+
# content: data,
|
167
|
+
# timestamp: data[:timestamp]
|
168
|
+
# )
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# ActionCable channel for real-time streaming
|
173
|
+
class ClaudeStreamChannel < ApplicationCable::Channel
|
174
|
+
def subscribed
|
175
|
+
stream_from "claude_stream_#{params[:user_id]}_#{params[:query_id]}"
|
176
|
+
end
|
177
|
+
|
178
|
+
def unsubscribed
|
179
|
+
# Cleanup when channel is unsubscribed
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Controller example
|
184
|
+
class ClaudeController < ApplicationController
|
185
|
+
def create_stream_query
|
186
|
+
query_id = SecureRandom.uuid
|
187
|
+
|
188
|
+
# Validate and sanitize input
|
189
|
+
prompt = params[:prompt]
|
190
|
+
options = {
|
191
|
+
'model' => params[:model],
|
192
|
+
'max_turns' => params[:max_turns]&.to_i,
|
193
|
+
'system_prompt' => params[:system_prompt],
|
194
|
+
'allowed_tools' => params[:allowed_tools] || [],
|
195
|
+
'mcp_servers' => params[:mcp_servers] || {}
|
196
|
+
}
|
197
|
+
|
198
|
+
# Start background job
|
199
|
+
ClaudeStreamingJob.perform_async(
|
200
|
+
current_user.id,
|
201
|
+
query_id,
|
202
|
+
prompt,
|
203
|
+
options
|
204
|
+
)
|
205
|
+
|
206
|
+
render json: {
|
207
|
+
query_id: query_id,
|
208
|
+
channel: "claude_stream_#{current_user.id}_#{query_id}",
|
209
|
+
status: 'started'
|
210
|
+
}
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Frontend JavaScript example (with ActionCable)
|
215
|
+
FRONTEND_EXAMPLE = <<~JAVASCRIPT
|
216
|
+
// Connect to the streaming channel
|
217
|
+
const subscription = App.cable.subscriptions.create(
|
218
|
+
{
|
219
|
+
channel: "ClaudeStreamChannel",
|
220
|
+
user_id: currentUserId,
|
221
|
+
query_id: queryId
|
222
|
+
},
|
223
|
+
{
|
224
|
+
received(data) {
|
225
|
+
switch(data.type) {
|
226
|
+
case 'start':
|
227
|
+
console.log('Claude query started:', data.query_id);
|
228
|
+
showLoadingIndicator();
|
229
|
+
break;
|
230
|
+
|
231
|
+
case 'message':
|
232
|
+
handleClaudeMessage(data);
|
233
|
+
break;
|
234
|
+
|
235
|
+
case 'complete':
|
236
|
+
console.log('Claude query completed');
|
237
|
+
hideLoadingIndicator();
|
238
|
+
break;
|
239
|
+
|
240
|
+
case 'error':
|
241
|
+
console.error('Claude query failed:', data.error);
|
242
|
+
showError(data.error);
|
243
|
+
break;
|
244
|
+
}
|
245
|
+
}
|
246
|
+
}
|
247
|
+
);
|
248
|
+
|
249
|
+
function handleClaudeMessage(data) {
|
250
|
+
switch(data.message_type) {
|
251
|
+
case 'system':
|
252
|
+
console.log('System message:', data.subtype);
|
253
|
+
break;
|
254
|
+
|
255
|
+
case 'assistant':
|
256
|
+
data.content.forEach(block => {
|
257
|
+
if (block.type === 'text') {
|
258
|
+
appendText(block.text);
|
259
|
+
} else if (block.type === 'tool_use') {
|
260
|
+
showToolUsage(block.name, block.input);
|
261
|
+
}
|
262
|
+
});
|
263
|
+
break;
|
264
|
+
|
265
|
+
case 'result':
|
266
|
+
showFinalResult(data);
|
267
|
+
break;
|
268
|
+
}
|
269
|
+
}
|
270
|
+
|
271
|
+
// Start a Claude query
|
272
|
+
function startClaudeQuery(prompt, options = {}) {
|
273
|
+
fetch('/claude/stream_query', {
|
274
|
+
method: 'POST',
|
275
|
+
headers: {
|
276
|
+
'Content-Type': 'application/json',
|
277
|
+
'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content
|
278
|
+
},
|
279
|
+
body: JSON.stringify({
|
280
|
+
prompt: prompt,
|
281
|
+
model: options.model,
|
282
|
+
max_turns: options.maxTurns,
|
283
|
+
system_prompt: options.systemPrompt,
|
284
|
+
allowed_tools: options.allowedTools,
|
285
|
+
mcp_servers: options.mcpServers
|
286
|
+
})
|
287
|
+
})
|
288
|
+
.then(response => response.json())
|
289
|
+
.then(data => {
|
290
|
+
console.log('Query started with ID:', data.query_id);
|
291
|
+
});
|
292
|
+
}
|
293
|
+
JAVASCRIPT
|
294
|
+
|
295
|
+
# Usage example in Rails console or initializer
|
296
|
+
RAILS_USAGE_EXAMPLE = <<~RUBY
|
297
|
+
# In Rails console:
|
298
|
+
|
299
|
+
# Start a simple streaming query
|
300
|
+
ClaudeStreamingJob.perform_async(
|
301
|
+
1, # user_id
|
302
|
+
SecureRandom.uuid, # query_id
|
303
|
+
"Explain Ruby on Rails", # prompt
|
304
|
+
{ 'model' => 'sonnet', 'max_turns' => 1 }
|
305
|
+
)
|
306
|
+
|
307
|
+
# Start an MCP-enabled streaming query
|
308
|
+
ClaudeStreamingJob.perform_async(
|
309
|
+
1,
|
310
|
+
SecureRandom.uuid,
|
311
|
+
"Use the about tool to describe yourself",
|
312
|
+
{
|
313
|
+
'model' => 'sonnet',
|
314
|
+
'max_turns' => 1,
|
315
|
+
'allowed_tools' => ['mcp__ninja__about'],
|
316
|
+
'mcp_servers' => {
|
317
|
+
'ninja' => 'https://mcp-creator-ninja-v1-4-0.mcp.soy/'
|
318
|
+
}
|
319
|
+
}
|
320
|
+
)
|
321
|
+
RUBY
|
322
|
+
|
323
|
+
puts "Rails + Sidekiq + ActionCable streaming example loaded!"
|
324
|
+
puts
|
325
|
+
puts "Key components:"
|
326
|
+
puts "⢠ClaudeStreamingJob - Sidekiq background job"
|
327
|
+
puts "⢠ClaudeStreamChannel - ActionCable channel"
|
328
|
+
puts "⢠Real-time WebSocket streaming to frontend"
|
329
|
+
puts "⢠Error handling and retry logic"
|
330
|
+
puts "⢠MCP server support"
|
331
|
+
puts
|
332
|
+
puts "Usage examples:"
|
333
|
+
puts RAILS_USAGE_EXAMPLE
|
334
|
+
puts
|
335
|
+
puts "Frontend JavaScript:"
|
336
|
+
puts FRONTEND_EXAMPLE
|
@@ -0,0 +1,195 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Streaming examples for Ruby Claude Code SDK
|
3
|
+
|
4
|
+
require_relative '../lib/claude_code'
|
5
|
+
|
6
|
+
def streaming_example
|
7
|
+
claude_path = "/Users/admin/.claude/local/claude"
|
8
|
+
|
9
|
+
puts "=== Streaming Messages Example ==="
|
10
|
+
puts "Watch messages arrive in real-time!"
|
11
|
+
puts
|
12
|
+
|
13
|
+
options = ClaudeCode::ClaudeCodeOptions.new(
|
14
|
+
model: "sonnet",
|
15
|
+
max_turns: 1,
|
16
|
+
system_prompt: "You are helpful. Provide detailed explanations."
|
17
|
+
)
|
18
|
+
|
19
|
+
start_time = Time.now
|
20
|
+
message_count = 0
|
21
|
+
|
22
|
+
ClaudeCode.query(
|
23
|
+
prompt: "Explain how Ruby blocks work and give some examples",
|
24
|
+
options: options,
|
25
|
+
cli_path: claude_path
|
26
|
+
).each_with_index do |message, index|
|
27
|
+
timestamp = Time.now - start_time
|
28
|
+
message_count += 1
|
29
|
+
|
30
|
+
puts "[#{format('%.3f', timestamp)}s] Message #{message_count}:"
|
31
|
+
|
32
|
+
case message
|
33
|
+
when ClaudeCode::SystemMessage
|
34
|
+
puts " š§ SYSTEM: #{message.subtype}"
|
35
|
+
if message.subtype == "init"
|
36
|
+
puts " Session: #{message.data['session_id']}"
|
37
|
+
puts " Model: #{message.data['model']}"
|
38
|
+
puts " Tools: #{message.data['tools'].length} available"
|
39
|
+
puts " MCP Servers: #{message.data['mcp_servers'].length}"
|
40
|
+
end
|
41
|
+
|
42
|
+
when ClaudeCode::UserMessage
|
43
|
+
puts " š¤ USER: #{message.content[0, 50]}..."
|
44
|
+
|
45
|
+
when ClaudeCode::AssistantMessage
|
46
|
+
puts " š¤ ASSISTANT:"
|
47
|
+
message.content.each do |block|
|
48
|
+
case block
|
49
|
+
when ClaudeCode::TextBlock
|
50
|
+
# Stream text in chunks to show real-time effect
|
51
|
+
text = block.text
|
52
|
+
if text.length > 100
|
53
|
+
puts " š¬ #{text[0, 100]}..."
|
54
|
+
puts " (#{text.length} total characters)"
|
55
|
+
else
|
56
|
+
puts " š¬ #{text}"
|
57
|
+
end
|
58
|
+
|
59
|
+
when ClaudeCode::ToolUseBlock
|
60
|
+
puts " š§ TOOL: #{block.name}"
|
61
|
+
puts " Input: #{block.input}"
|
62
|
+
|
63
|
+
when ClaudeCode::ToolResultBlock
|
64
|
+
puts " š¤ RESULT: #{block.content}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
when ClaudeCode::ResultMessage
|
69
|
+
puts " ā
RESULT: #{message.subtype}"
|
70
|
+
puts " Duration: #{message.duration_ms}ms (API: #{message.duration_api_ms}ms)"
|
71
|
+
puts " Turns: #{message.num_turns}"
|
72
|
+
puts " Cost: $#{format('%.6f', message.total_cost_usd)}" if message.total_cost_usd
|
73
|
+
puts " Session: #{message.session_id}"
|
74
|
+
if message.result
|
75
|
+
puts " Final: #{message.result[0, 100]}..."
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
puts
|
80
|
+
$stdout.flush # Ensure immediate output
|
81
|
+
end
|
82
|
+
|
83
|
+
total_time = Time.now - start_time
|
84
|
+
puts "š Streaming completed in #{format('%.3f', total_time)}s with #{message_count} messages"
|
85
|
+
end
|
86
|
+
|
87
|
+
def streaming_mcp_example
|
88
|
+
puts "\n" + "="*60 + "\n"
|
89
|
+
puts "=== Streaming MCP Example ==="
|
90
|
+
puts
|
91
|
+
|
92
|
+
start_time = Time.now
|
93
|
+
|
94
|
+
ClaudeCode.quick_mcp_query(
|
95
|
+
"Use the about tool and then explain what you learned about this MCP server",
|
96
|
+
server_name: "ninja",
|
97
|
+
server_url: "https://mcp-creator-ninja-v1-4-0.mcp.soy/",
|
98
|
+
tools: "about",
|
99
|
+
max_turns: 1
|
100
|
+
).each_with_index do |message, index|
|
101
|
+
timestamp = Time.now - start_time
|
102
|
+
|
103
|
+
case message
|
104
|
+
when ClaudeCode::SystemMessage
|
105
|
+
puts "[#{format('%.3f', timestamp)}s] š§ System init - MCP servers: #{message.data['mcp_servers'].length}"
|
106
|
+
|
107
|
+
when ClaudeCode::AssistantMessage
|
108
|
+
puts "[#{format('%.3f', timestamp)}s] š¤ Assistant response:"
|
109
|
+
message.content.each do |block|
|
110
|
+
case block
|
111
|
+
when ClaudeCode::TextBlock
|
112
|
+
puts " š¬ #{block.text}"
|
113
|
+
when ClaudeCode::ToolUseBlock
|
114
|
+
puts " š§ Using tool: #{block.name}"
|
115
|
+
puts " š„ Input: #{block.input}"
|
116
|
+
when ClaudeCode::ToolResultBlock
|
117
|
+
puts " š¤ Tool result received"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
when ClaudeCode::ResultMessage
|
122
|
+
puts "[#{format('%.3f', timestamp)}s] ā
Final result - Cost: $#{format('%.6f', message.total_cost_usd || 0)}"
|
123
|
+
end
|
124
|
+
|
125
|
+
$stdout.flush
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def simple_streaming_examples
|
130
|
+
puts "\n" + "="*60 + "\n"
|
131
|
+
puts "=== Simple Streaming Examples ==="
|
132
|
+
puts
|
133
|
+
|
134
|
+
# Example 1: Default streaming output
|
135
|
+
puts "1. Default streaming (auto-formatted):"
|
136
|
+
ClaudeCode.stream_query(
|
137
|
+
prompt: "Count from 1 to 3 and explain each number",
|
138
|
+
options: ClaudeCode::ClaudeCodeOptions.new(
|
139
|
+
model: "sonnet",
|
140
|
+
max_turns: 1
|
141
|
+
),
|
142
|
+
cli_path: "/Users/admin/.claude/local/claude"
|
143
|
+
)
|
144
|
+
|
145
|
+
puts "\n" + "="*50 + "\n"
|
146
|
+
|
147
|
+
# Example 2: Custom streaming with block
|
148
|
+
puts "2. Custom streaming with timestamps:"
|
149
|
+
start_time = Time.now
|
150
|
+
|
151
|
+
ClaudeCode.stream_query(
|
152
|
+
prompt: "What is Ruby?",
|
153
|
+
options: ClaudeCode::ClaudeCodeOptions.new(max_turns: 1),
|
154
|
+
cli_path: "/Users/admin/.claude/local/claude"
|
155
|
+
) do |message, index|
|
156
|
+
timestamp = Time.now - start_time
|
157
|
+
|
158
|
+
case message
|
159
|
+
when ClaudeCode::AssistantMessage
|
160
|
+
message.content.each do |block|
|
161
|
+
if block.is_a?(ClaudeCode::TextBlock)
|
162
|
+
puts "[#{format('%.2f', timestamp)}s] #{block.text}"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
when ClaudeCode::ResultMessage
|
166
|
+
puts "[#{format('%.2f', timestamp)}s] š° $#{format('%.6f', message.total_cost_usd || 0)}"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def show_streaming_benefits
|
172
|
+
puts "=== Benefits of Streaming ==="
|
173
|
+
puts
|
174
|
+
puts "ā
Real-time feedback - see messages as they arrive"
|
175
|
+
puts "ā
Lower memory usage - process messages one by one"
|
176
|
+
puts "ā
Better user experience - immediate response indication"
|
177
|
+
puts "ā
Early error detection - catch issues quickly"
|
178
|
+
puts "ā
Progress tracking - monitor long-running operations"
|
179
|
+
puts
|
180
|
+
puts "Perfect for:"
|
181
|
+
puts "⢠Interactive applications"
|
182
|
+
puts "⢠Long-running code generation"
|
183
|
+
puts "⢠Tool-heavy workflows"
|
184
|
+
puts "⢠Real-time monitoring"
|
185
|
+
puts
|
186
|
+
puts "="*60
|
187
|
+
puts
|
188
|
+
end
|
189
|
+
|
190
|
+
if __FILE__ == $0
|
191
|
+
show_streaming_benefits
|
192
|
+
streaming_example
|
193
|
+
streaming_mcp_example
|
194
|
+
simple_streaming_examples
|
195
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative '../lib/claude_code'
|
5
|
+
|
6
|
+
# Example: Streaming JSON input for multi-turn conversations
|
7
|
+
|
8
|
+
puts "=== Streaming JSON Input Example ==="
|
9
|
+
|
10
|
+
# Set your API key
|
11
|
+
# ENV['ANTHROPIC_API_KEY'] = 'your-api-key-here'
|
12
|
+
|
13
|
+
puts "\n1. Multi-turn conversation using JSONL messages..."
|
14
|
+
|
15
|
+
# Create multiple user messages for a conversation
|
16
|
+
messages = [
|
17
|
+
ClaudeCode::JSONLHelpers.create_user_message("Hello! I'm working on a Ruby project."),
|
18
|
+
ClaudeCode::JSONLHelpers.create_user_message("Can you help me understand how modules work?"),
|
19
|
+
ClaudeCode::JSONLHelpers.create_user_message("Show me a practical example of a module.")
|
20
|
+
]
|
21
|
+
|
22
|
+
puts "Sending #{messages.length} messages via streaming JSON input..."
|
23
|
+
|
24
|
+
begin
|
25
|
+
ClaudeCode.stream_json_query(messages) do |message|
|
26
|
+
case message
|
27
|
+
when ClaudeCode::SystemMessage
|
28
|
+
if message.subtype == 'init'
|
29
|
+
puts "š§ Session started: #{message.data['session_id']}"
|
30
|
+
puts "š¤ Model: #{message.data['model']}"
|
31
|
+
end
|
32
|
+
when ClaudeCode::AssistantMessage
|
33
|
+
message.content.each do |block|
|
34
|
+
case block
|
35
|
+
when ClaudeCode::TextBlock
|
36
|
+
puts "š¬ #{block.text}"
|
37
|
+
when ClaudeCode::ToolUseBlock
|
38
|
+
puts "š§ Tool: #{block.name}"
|
39
|
+
puts "š„ Input: #{block.input}"
|
40
|
+
when ClaudeCode::ToolResultBlock
|
41
|
+
puts "š¤ Result: #{block.content}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
when ClaudeCode::ResultMessage
|
45
|
+
puts "\nā
Conversation completed!"
|
46
|
+
puts "š Duration: #{message.duration_ms}ms"
|
47
|
+
puts "š° Cost: $#{format('%.6f', message.total_cost_usd || 0)}"
|
48
|
+
puts "š Turns: #{message.num_turns}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
rescue ClaudeCode::CLINotFoundError => e
|
52
|
+
puts "ā Claude CLI not found: #{e.message}"
|
53
|
+
rescue ClaudeCode::ProcessError => e
|
54
|
+
puts "ā Process error: #{e.message}"
|
55
|
+
rescue StandardError => e
|
56
|
+
puts "ā Unexpected error: #{e.message}"
|
57
|
+
end
|
58
|
+
|
59
|
+
puts "\n2. Interactive conversation with streaming JSON..."
|
60
|
+
|
61
|
+
# Create a more complex conversation with different message types
|
62
|
+
conversation_messages = ClaudeCode::JSONLHelpers.create_conversation(
|
63
|
+
"I need help with a Ruby script",
|
64
|
+
"The script should read a CSV file and process the data",
|
65
|
+
"Can you show me how to handle errors when reading the file?"
|
66
|
+
)
|
67
|
+
|
68
|
+
puts "Starting interactive conversation..."
|
69
|
+
|
70
|
+
ClaudeCode.stream_json_query(conversation_messages) do |message|
|
71
|
+
case message
|
72
|
+
when ClaudeCode::AssistantMessage
|
73
|
+
message.content.each do |block|
|
74
|
+
if block.is_a?(ClaudeCode::TextBlock)
|
75
|
+
# Print text content with a slight delay to simulate real-time streaming
|
76
|
+
puts "š¤ #{block.text}"
|
77
|
+
sleep(0.1) if block.text.length > 100 # Slight delay for longer responses
|
78
|
+
end
|
79
|
+
end
|
80
|
+
when ClaudeCode::ResultMessage
|
81
|
+
puts "\nš Final Session: #{message.session_id}"
|
82
|
+
puts "š° Total Cost: $#{format('%.6f', message.total_cost_usd || 0)}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
puts "\n3. Custom JSONL format example..."
|
87
|
+
|
88
|
+
# Manually create JSONL format messages
|
89
|
+
custom_messages = [
|
90
|
+
{
|
91
|
+
'type' => 'user',
|
92
|
+
'message' => {
|
93
|
+
'role' => 'user',
|
94
|
+
'content' => [
|
95
|
+
{
|
96
|
+
'type' => 'text',
|
97
|
+
'text' => 'Explain Ruby metaprogramming in simple terms'
|
98
|
+
}
|
99
|
+
]
|
100
|
+
}
|
101
|
+
},
|
102
|
+
{
|
103
|
+
'type' => 'user',
|
104
|
+
'message' => {
|
105
|
+
'role' => 'user',
|
106
|
+
'content' => [
|
107
|
+
{
|
108
|
+
'type' => 'text',
|
109
|
+
'text' => 'Give me a practical example I can try'
|
110
|
+
}
|
111
|
+
]
|
112
|
+
}
|
113
|
+
}
|
114
|
+
]
|
115
|
+
|
116
|
+
puts "Using custom JSONL messages..."
|
117
|
+
|
118
|
+
ClaudeCode.stream_json_query(custom_messages) do |message|
|
119
|
+
if message.is_a?(ClaudeCode::AssistantMessage)
|
120
|
+
message.content.each do |block|
|
121
|
+
if block.is_a?(ClaudeCode::TextBlock)
|
122
|
+
puts "š #{block.text}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
puts "\n4. Streaming JSON with options..."
|
129
|
+
|
130
|
+
# Use streaming JSON with specific options
|
131
|
+
options = ClaudeCode::ClaudeCodeOptions.new(
|
132
|
+
model: 'claude-3-haiku', # Use faster model for quick responses
|
133
|
+
max_turns: 5,
|
134
|
+
system_prompt: 'You are a Ruby programming tutor. Keep responses concise and practical.'
|
135
|
+
)
|
136
|
+
|
137
|
+
tutorial_messages = ClaudeCode::JSONLHelpers.create_conversation(
|
138
|
+
"Teach me about Ruby blocks",
|
139
|
+
"Show me different ways to use blocks",
|
140
|
+
"What's the difference between blocks and procs?"
|
141
|
+
)
|
142
|
+
|
143
|
+
puts "Tutorial conversation with custom options..."
|
144
|
+
|
145
|
+
ClaudeCode.stream_json_query(tutorial_messages, options: options) do |message|
|
146
|
+
case message
|
147
|
+
when ClaudeCode::AssistantMessage
|
148
|
+
message.content.each do |block|
|
149
|
+
if block.is_a?(ClaudeCode::TextBlock)
|
150
|
+
puts "šØāš« #{block.text}"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
when ClaudeCode::ResultMessage
|
154
|
+
puts "\nš Tutorial completed - Cost: $#{format('%.6f', message.total_cost_usd || 0)}"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
puts "\nā
Streaming JSON input examples completed!"
|
159
|
+
puts "\nKey features demonstrated:"
|
160
|
+
puts "- Multi-turn conversations via JSONL"
|
161
|
+
puts "- Real-time streaming responses"
|
162
|
+
puts "- Custom message creation with JSONLHelpers"
|
163
|
+
puts "- Manual JSONL format for advanced usage"
|
164
|
+
puts "- Integration with ClaudeCodeOptions"
|
165
|
+
puts "- Error handling for streaming conversations"
|
166
|
+
|
167
|
+
puts "\nš” Use streaming JSON input when you need:"
|
168
|
+
puts "- Multiple conversation turns without restarting the CLI"
|
169
|
+
puts "- Interactive conversations with guidance during processing"
|
170
|
+
puts "- Complex multi-step conversations"
|
171
|
+
puts "- Efficient batch processing of conversation turns"
|
data/hello.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Hello, World!
|