claude_agent 0.7.7 → 0.7.9
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 +65 -0
- data/README.md +551 -37
- data/SPEC.md +70 -30
- data/lib/claude_agent/client.rb +197 -7
- data/lib/claude_agent/content_blocks.rb +193 -5
- data/lib/claude_agent/control_protocol.rb +111 -11
- data/lib/claude_agent/conversation.rb +248 -0
- data/lib/claude_agent/cumulative_usage.rb +106 -0
- data/lib/claude_agent/event_handler.rb +152 -0
- data/lib/claude_agent/hooks.rb +106 -225
- data/lib/claude_agent/list_sessions.rb +508 -0
- data/lib/claude_agent/mcp/server.rb +3 -3
- data/lib/claude_agent/mcp/tool.rb +4 -4
- data/lib/claude_agent/message_parser.rb +201 -185
- data/lib/claude_agent/messages.rb +86 -13
- data/lib/claude_agent/options.rb +5 -4
- data/lib/claude_agent/permission_queue.rb +87 -0
- data/lib/claude_agent/permission_request.rb +151 -0
- data/lib/claude_agent/permissions.rb +4 -2
- data/lib/claude_agent/query.rb +34 -0
- data/lib/claude_agent/tool_activity.rb +78 -0
- data/lib/claude_agent/turn_result.rb +239 -0
- data/lib/claude_agent/types.rb +29 -0
- data/lib/claude_agent/version.rb +1 -1
- data/lib/claude_agent.rb +39 -1
- data/sig/claude_agent.rbs +285 -4
- metadata +9 -1
data/README.md
CHANGED
|
@@ -29,9 +29,26 @@ gem install claude_agent
|
|
|
29
29
|
|
|
30
30
|
## Quick Start
|
|
31
31
|
|
|
32
|
+
### Conversation (Recommended)
|
|
33
|
+
|
|
34
|
+
The simplest way to have multi-turn conversations:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
require "claude_agent"
|
|
38
|
+
|
|
39
|
+
ClaudeAgent::Conversation.open(
|
|
40
|
+
permission_mode: "acceptEdits",
|
|
41
|
+
on_stream: ->(text) { print text }
|
|
42
|
+
) do |c|
|
|
43
|
+
c.say("Fix the bug in auth.rb")
|
|
44
|
+
c.say("Now add tests for the fix")
|
|
45
|
+
puts "\nTotal cost: $#{c.total_cost}"
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
32
49
|
### One-Shot Query
|
|
33
50
|
|
|
34
|
-
|
|
51
|
+
For single questions:
|
|
35
52
|
|
|
36
53
|
```ruby
|
|
37
54
|
require "claude_agent"
|
|
@@ -46,21 +63,33 @@ ClaudeAgent.query(prompt: "What is the capital of France?").each do |message|
|
|
|
46
63
|
end
|
|
47
64
|
```
|
|
48
65
|
|
|
66
|
+
### One-Shot with TurnResult
|
|
67
|
+
|
|
68
|
+
Get a structured result without writing `case` statements:
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
require "claude_agent"
|
|
72
|
+
|
|
73
|
+
turn = ClaudeAgent.query_turn(prompt: "What is the capital of France?")
|
|
74
|
+
puts turn.text
|
|
75
|
+
puts "Cost: $#{turn.cost}"
|
|
76
|
+
puts "Model: #{turn.model}"
|
|
77
|
+
```
|
|
78
|
+
|
|
49
79
|
### Interactive Client
|
|
50
80
|
|
|
51
|
-
For
|
|
81
|
+
For fine-grained control over the conversation:
|
|
52
82
|
|
|
53
83
|
```ruby
|
|
54
84
|
require "claude_agent"
|
|
55
85
|
|
|
56
86
|
ClaudeAgent::Client.open do |client|
|
|
57
|
-
client.
|
|
58
|
-
client.receive_response.each { |msg| } # Process first response
|
|
87
|
+
client.on_text { |text| print text }
|
|
59
88
|
|
|
60
|
-
client.
|
|
61
|
-
client.
|
|
62
|
-
|
|
63
|
-
|
|
89
|
+
turn = client.send_and_receive("Remember the number 42")
|
|
90
|
+
turn = client.send_and_receive("What number did I ask you to remember?")
|
|
91
|
+
|
|
92
|
+
puts "\nAnswer: #{turn.text}"
|
|
64
93
|
end
|
|
65
94
|
```
|
|
66
95
|
|
|
@@ -84,6 +113,98 @@ ClaudeAgent.run_setup(trigger: :init, options: options)
|
|
|
84
113
|
ClaudeAgent.run_setup(trigger: :maintenance)
|
|
85
114
|
```
|
|
86
115
|
|
|
116
|
+
## Conversation API
|
|
117
|
+
|
|
118
|
+
The `Conversation` class manages the full lifecycle: auto-connects on first message, tracks multi-turn history, accumulates usage, and builds a unified tool activity timeline.
|
|
119
|
+
|
|
120
|
+
### Basic Usage
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
conversation = ClaudeAgent.conversation(
|
|
124
|
+
model: "claude-sonnet-4-5-20250514",
|
|
125
|
+
permission_mode: "acceptEdits",
|
|
126
|
+
on_text: ->(text) { print text }
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
turn = conversation.say("Help me refactor this module")
|
|
130
|
+
puts turn.tool_uses.map(&:display_label) # ["Read lib/foo.rb", "Edit lib/foo.rb"]
|
|
131
|
+
|
|
132
|
+
turn = conversation.say("Now update the tests")
|
|
133
|
+
puts "Session cost: $#{conversation.total_cost}"
|
|
134
|
+
|
|
135
|
+
conversation.close
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Block Form
|
|
139
|
+
|
|
140
|
+
Automatically cleans up when the block exits:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
ClaudeAgent::Conversation.open(
|
|
144
|
+
max_turns: 10,
|
|
145
|
+
on_tool_use: ->(tool) { puts " Using: #{tool.display_label}" }
|
|
146
|
+
) do |c|
|
|
147
|
+
c.say("Implement the feature described in SPEC.md")
|
|
148
|
+
c.say("Run the tests and fix any failures")
|
|
149
|
+
|
|
150
|
+
puts "Tools used: #{c.tool_activity.size}"
|
|
151
|
+
puts "Total cost: $#{c.total_cost}"
|
|
152
|
+
end
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Resume a Previous Session
|
|
156
|
+
|
|
157
|
+
```ruby
|
|
158
|
+
conversation = ClaudeAgent.resume_conversation("session-abc-123")
|
|
159
|
+
turn = conversation.say("Continue where we left off")
|
|
160
|
+
conversation.close
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Callbacks
|
|
164
|
+
|
|
165
|
+
Register callbacks for real-time event handling:
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
conversation = ClaudeAgent.conversation(
|
|
169
|
+
on_text: ->(text) { print text },
|
|
170
|
+
on_stream: ->(text) { print text }, # Alias for on_text
|
|
171
|
+
on_thinking: ->(thought) { puts "Thinking: #{thought}" },
|
|
172
|
+
on_tool_use: ->(tool) { puts "Tool: #{tool.display_label}" },
|
|
173
|
+
on_tool_result: ->(result) { puts "Result: #{result.content&.slice(0, 80)}" },
|
|
174
|
+
on_result: ->(result) { puts "Done! Cost: $#{result.total_cost_usd}" },
|
|
175
|
+
on_message: ->(msg) { log(msg) } # Catch-all
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Tool Activity Timeline
|
|
180
|
+
|
|
181
|
+
Track all tool executions across turns with timing:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
ClaudeAgent::Conversation.open(permission_mode: "acceptEdits") do |c|
|
|
185
|
+
c.say("Refactor the auth module")
|
|
186
|
+
|
|
187
|
+
c.tool_activity.each do |activity|
|
|
188
|
+
puts "#{activity.display_label} (turn #{activity.turn_index})"
|
|
189
|
+
puts " Duration: #{activity.duration&.round(2)}s" if activity.duration
|
|
190
|
+
puts " Error!" if activity.error?
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Conversation Accessors
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
conversation.turns # Array of TurnResult objects
|
|
199
|
+
conversation.messages # All messages across all turns
|
|
200
|
+
conversation.tool_activity # Array of ToolActivity objects
|
|
201
|
+
conversation.total_cost # Total cost in USD
|
|
202
|
+
conversation.session_id # Session ID from most recent turn
|
|
203
|
+
conversation.usage # CumulativeUsage stats
|
|
204
|
+
conversation.open? # Whether conversation is connected
|
|
205
|
+
conversation.closed? # Whether conversation has been closed
|
|
206
|
+
```
|
|
207
|
+
|
|
87
208
|
## Configuration
|
|
88
209
|
|
|
89
210
|
Use `ClaudeAgent::Options` to customize behavior:
|
|
@@ -97,7 +218,13 @@ options = ClaudeAgent::Options.new(
|
|
|
97
218
|
# Conversation limits
|
|
98
219
|
max_turns: 10,
|
|
99
220
|
max_budget_usd: 1.0,
|
|
100
|
-
|
|
221
|
+
|
|
222
|
+
# Extended thinking
|
|
223
|
+
thinking: { type: "enabled", budgetTokens: 10000 }, # or "adaptive" or "disabled"
|
|
224
|
+
# max_thinking_tokens: 10000, # Shorthand (when thinking not set)
|
|
225
|
+
|
|
226
|
+
# Response effort
|
|
227
|
+
effort: "high", # "low", "medium", "high", "max"
|
|
101
228
|
|
|
102
229
|
# System prompt
|
|
103
230
|
system_prompt: "You are a helpful coding assistant.",
|
|
@@ -108,8 +235,9 @@ options = ClaudeAgent::Options.new(
|
|
|
108
235
|
allowed_tools: ["Read"],
|
|
109
236
|
disallowed_tools: ["Write"],
|
|
110
237
|
|
|
111
|
-
# Permission modes: "default", "acceptEdits", "plan", "
|
|
238
|
+
# Permission modes: "default", "acceptEdits", "plan", "dontAsk", "bypassPermissions"
|
|
112
239
|
permission_mode: "acceptEdits",
|
|
240
|
+
permission_queue: true, # Enable queue-based permissions (see Permissions section)
|
|
113
241
|
|
|
114
242
|
# Working directory for file operations
|
|
115
243
|
cwd: "/path/to/project",
|
|
@@ -120,6 +248,7 @@ options = ClaudeAgent::Options.new(
|
|
|
120
248
|
|
|
121
249
|
# Session management
|
|
122
250
|
resume: "session-id",
|
|
251
|
+
session_id: "custom-uuid", # Custom conversation UUID
|
|
123
252
|
continue_conversation: true,
|
|
124
253
|
fork_session: true,
|
|
125
254
|
persist_session: true, # Default: true
|
|
@@ -128,7 +257,14 @@ options = ClaudeAgent::Options.new(
|
|
|
128
257
|
output_format: {
|
|
129
258
|
type: "object",
|
|
130
259
|
properties: { answer: { type: "string" } }
|
|
131
|
-
}
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
# Prompt suggestions
|
|
263
|
+
prompt_suggestions: true,
|
|
264
|
+
|
|
265
|
+
# Debug logging (CLI-level)
|
|
266
|
+
debug: true,
|
|
267
|
+
debug_file: "/path/to/debug.log"
|
|
132
268
|
)
|
|
133
269
|
|
|
134
270
|
ClaudeAgent.query(prompt: "Help me refactor this code", options: options)
|
|
@@ -161,7 +297,13 @@ sandbox = ClaudeAgent::SandboxSettings.new(
|
|
|
161
297
|
excluded_commands: ["docker", "kubectl"],
|
|
162
298
|
network: ClaudeAgent::SandboxNetworkConfig.new(
|
|
163
299
|
allowed_domains: ["api.example.com"],
|
|
164
|
-
allow_local_binding: true
|
|
300
|
+
allow_local_binding: true,
|
|
301
|
+
allow_managed_domains_only: false
|
|
302
|
+
),
|
|
303
|
+
filesystem: ClaudeAgent::SandboxFilesystemConfig.new(
|
|
304
|
+
allow_write: ["/tmp/*"],
|
|
305
|
+
deny_write: ["/etc/*"],
|
|
306
|
+
deny_read: ["/secrets/*"]
|
|
165
307
|
),
|
|
166
308
|
ripgrep: ClaudeAgent::SandboxRipgrepConfig.new(
|
|
167
309
|
command: "/usr/local/bin/rg"
|
|
@@ -190,9 +332,106 @@ agents = {
|
|
|
190
332
|
options = ClaudeAgent::Options.new(agents: agents)
|
|
191
333
|
```
|
|
192
334
|
|
|
335
|
+
## TurnResult
|
|
336
|
+
|
|
337
|
+
A `TurnResult` accumulates all messages from sending a prompt to receiving the final `ResultMessage`. It eliminates the need for `case` statements over raw message types.
|
|
338
|
+
|
|
339
|
+
### Getting a TurnResult
|
|
340
|
+
|
|
341
|
+
```ruby
|
|
342
|
+
# Via Conversation
|
|
343
|
+
turn = conversation.say("Fix the bug")
|
|
344
|
+
|
|
345
|
+
# Via Client
|
|
346
|
+
turn = client.send_and_receive("Fix the bug")
|
|
347
|
+
|
|
348
|
+
# Via one-shot query
|
|
349
|
+
turn = ClaudeAgent.query_turn(prompt: "Fix the bug")
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Accessors
|
|
353
|
+
|
|
354
|
+
```ruby
|
|
355
|
+
# Text and thinking
|
|
356
|
+
turn.text # All text content concatenated
|
|
357
|
+
turn.thinking # All thinking content concatenated
|
|
358
|
+
|
|
359
|
+
# Tool usage
|
|
360
|
+
turn.tool_uses # Array of ToolUseBlock / ServerToolUseBlock
|
|
361
|
+
turn.tool_results # Array of ToolResultBlock / ServerToolResultBlock
|
|
362
|
+
turn.tool_executions # Array of { tool_use:, tool_result: } pairs
|
|
363
|
+
|
|
364
|
+
# Result data
|
|
365
|
+
turn.cost # Total cost in USD
|
|
366
|
+
turn.duration_ms # Wall-clock duration
|
|
367
|
+
turn.session_id # Session ID for resumption
|
|
368
|
+
turn.model # Model name
|
|
369
|
+
turn.stop_reason # Why the model stopped ("end_turn", "tool_use", etc.)
|
|
370
|
+
turn.usage # Token usage hash
|
|
371
|
+
turn.model_usage # Per-model usage breakdown
|
|
372
|
+
turn.structured_output # Structured output (if requested)
|
|
373
|
+
turn.num_turns # Number of turns in session
|
|
374
|
+
|
|
375
|
+
# Status
|
|
376
|
+
turn.success? # Whether turn completed successfully
|
|
377
|
+
turn.error? # Whether turn ended with error
|
|
378
|
+
turn.complete? # Whether a ResultMessage was received
|
|
379
|
+
turn.errors # Array of error strings
|
|
380
|
+
turn.permission_denials # Array of SDKPermissionDenial
|
|
381
|
+
|
|
382
|
+
# Filtered message access
|
|
383
|
+
turn.assistant_messages # All AssistantMessages
|
|
384
|
+
turn.user_messages # All UserMessages / UserMessageReplays
|
|
385
|
+
turn.stream_events # All StreamEvents
|
|
386
|
+
turn.content_blocks # All content blocks across assistant messages
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## Event Handlers
|
|
390
|
+
|
|
391
|
+
Register typed callbacks instead of writing `case` statements. Works with `Client`, `Conversation`, or standalone.
|
|
392
|
+
|
|
393
|
+
### Via Client
|
|
394
|
+
|
|
395
|
+
```ruby
|
|
396
|
+
ClaudeAgent::Client.open do |client|
|
|
397
|
+
client.on_text { |text| print text }
|
|
398
|
+
client.on_tool_use { |tool| puts "\nUsing: #{tool.display_label}" }
|
|
399
|
+
client.on_tool_result { |result, tool_use| puts "Done: #{tool_use&.name}" }
|
|
400
|
+
client.on_result { |result| puts "\nCost: $#{result.total_cost_usd}" }
|
|
401
|
+
|
|
402
|
+
client.send_and_receive("Fix the bug in auth.rb")
|
|
403
|
+
end
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Via One-Shot Query
|
|
407
|
+
|
|
408
|
+
```ruby
|
|
409
|
+
events = ClaudeAgent::EventHandler.new
|
|
410
|
+
.on_text { |text| print text }
|
|
411
|
+
.on_result { |r| puts "\nDone!" }
|
|
412
|
+
|
|
413
|
+
ClaudeAgent.query_turn(prompt: "Explain this code", events: events)
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Standalone
|
|
417
|
+
|
|
418
|
+
```ruby
|
|
419
|
+
handler = ClaudeAgent::EventHandler.new
|
|
420
|
+
handler.on(:text) { |text| print text }
|
|
421
|
+
handler.on(:thinking) { |thought| puts "Thinking: #{thought}" }
|
|
422
|
+
handler.on(:tool_use) { |tool| puts "Tool: #{tool.display_label}" }
|
|
423
|
+
handler.on(:tool_result) { |result, tool_use| puts "Result for #{tool_use&.name}" }
|
|
424
|
+
handler.on(:result) { |result| puts "Cost: $#{result.total_cost_usd}" }
|
|
425
|
+
handler.on(:message) { |msg| log(msg) } # Catch-all
|
|
426
|
+
|
|
427
|
+
# Dispatch manually
|
|
428
|
+
client.receive_response.each { |msg| handler.handle(msg) }
|
|
429
|
+
handler.reset! # Clear turn state between turns
|
|
430
|
+
```
|
|
431
|
+
|
|
193
432
|
## Message Types
|
|
194
433
|
|
|
195
|
-
The SDK provides strongly-typed message classes
|
|
434
|
+
The SDK provides strongly-typed message classes for all protocol messages.
|
|
196
435
|
|
|
197
436
|
### AssistantMessage
|
|
198
437
|
|
|
@@ -224,6 +463,7 @@ result.success? # Convenience method
|
|
|
224
463
|
result.error? # Convenience method
|
|
225
464
|
result.errors # Array of error messages (if any)
|
|
226
465
|
result.permission_denials # Array of SDKPermissionDenial (if any)
|
|
466
|
+
result.stop_reason # Why the model stopped generating (e.g. "end_turn", "tool_use")
|
|
227
467
|
```
|
|
228
468
|
|
|
229
469
|
### UserMessageReplay
|
|
@@ -295,11 +535,17 @@ progress.elapsed_time_seconds # Time elapsed
|
|
|
295
535
|
Hook execution output:
|
|
296
536
|
|
|
297
537
|
```ruby
|
|
538
|
+
hook_response.hook_id # Hook identifier
|
|
298
539
|
hook_response.hook_name # Hook name
|
|
299
540
|
hook_response.hook_event # Hook event type
|
|
300
541
|
hook_response.stdout # Hook stdout
|
|
301
542
|
hook_response.stderr # Hook stderr
|
|
543
|
+
hook_response.output # Combined output
|
|
302
544
|
hook_response.exit_code # Exit code
|
|
545
|
+
hook_response.outcome # "success", "error", or "cancelled"
|
|
546
|
+
hook_response.success? # Convenience predicate
|
|
547
|
+
hook_response.error? # Convenience predicate
|
|
548
|
+
hook_response.cancelled? # Convenience predicate
|
|
303
549
|
```
|
|
304
550
|
|
|
305
551
|
### HookStartedMessage
|
|
@@ -368,6 +614,56 @@ notification.failed? # Convenience predicate
|
|
|
368
614
|
notification.stopped? # Convenience predicate
|
|
369
615
|
```
|
|
370
616
|
|
|
617
|
+
### TaskStartedMessage
|
|
618
|
+
|
|
619
|
+
Background task (subagent) start notification:
|
|
620
|
+
|
|
621
|
+
```ruby
|
|
622
|
+
task_started.task_id # Task ID
|
|
623
|
+
task_started.tool_use_id # Associated tool use ID (optional)
|
|
624
|
+
task_started.description # Task description (optional)
|
|
625
|
+
task_started.task_type # Task type (optional)
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### TaskProgressMessage
|
|
629
|
+
|
|
630
|
+
Background task progress reporting:
|
|
631
|
+
|
|
632
|
+
```ruby
|
|
633
|
+
task_progress.task_id # Task ID
|
|
634
|
+
task_progress.description # What the task is doing
|
|
635
|
+
task_progress.usage # Token usage so far (optional)
|
|
636
|
+
task_progress.last_tool_name # Most recent tool used (optional)
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### RateLimitEvent
|
|
640
|
+
|
|
641
|
+
Rate limit status and utilization:
|
|
642
|
+
|
|
643
|
+
```ruby
|
|
644
|
+
rate_limit.rate_limit_info # Full rate limit info hash
|
|
645
|
+
rate_limit.status # Rate limit status (e.g. "allowed_warning")
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
### PromptSuggestionMessage
|
|
649
|
+
|
|
650
|
+
Suggested follow-up prompts (requires `prompt_suggestions: true`):
|
|
651
|
+
|
|
652
|
+
```ruby
|
|
653
|
+
suggestion.suggestion # The suggested prompt text
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### GenericMessage
|
|
657
|
+
|
|
658
|
+
Wraps unknown/future message types instead of raising errors:
|
|
659
|
+
|
|
660
|
+
```ruby
|
|
661
|
+
msg.type # Message type as symbol (e.g. :fancy_new)
|
|
662
|
+
msg[:data] # Dynamic field access via []
|
|
663
|
+
msg.data # Dynamic field access via method_missing
|
|
664
|
+
msg.to_h # Raw data hash
|
|
665
|
+
```
|
|
666
|
+
|
|
371
667
|
## Content Blocks
|
|
372
668
|
|
|
373
669
|
Assistant messages contain content blocks:
|
|
@@ -380,19 +676,49 @@ message.content.each do |block|
|
|
|
380
676
|
when ClaudeAgent::ThinkingBlock
|
|
381
677
|
puts "Thinking: #{block.thinking}"
|
|
382
678
|
when ClaudeAgent::ToolUseBlock
|
|
383
|
-
puts "Tool: #{block.
|
|
679
|
+
puts "Tool: #{block.display_label}"
|
|
680
|
+
puts " File: #{block.file_path}" if block.file_path
|
|
681
|
+
puts " Summary: #{block.summary}"
|
|
384
682
|
when ClaudeAgent::ToolResultBlock
|
|
385
683
|
puts "Result for #{block.tool_use_id}: #{block.content}"
|
|
386
684
|
when ClaudeAgent::ServerToolUseBlock
|
|
387
|
-
puts "MCP Tool: #{block.
|
|
685
|
+
puts "MCP Tool: #{block.display_label}" # "server_name/tool_name"
|
|
388
686
|
when ClaudeAgent::ServerToolResultBlock
|
|
389
687
|
puts "MCP Result from #{block.server_name}"
|
|
390
688
|
when ClaudeAgent::ImageContentBlock
|
|
391
689
|
puts "Image: #{block.media_type}, source: #{block.source_type}"
|
|
690
|
+
when ClaudeAgent::GenericBlock
|
|
691
|
+
puts "Unknown block: #{block.type}, data: #{block.to_h}"
|
|
392
692
|
end
|
|
393
693
|
end
|
|
394
694
|
```
|
|
395
695
|
|
|
696
|
+
### ToolUseBlock Introspection
|
|
697
|
+
|
|
698
|
+
Tool use blocks provide human-readable labels and summaries:
|
|
699
|
+
|
|
700
|
+
```ruby
|
|
701
|
+
block.name # "Read", "Write", "Bash", etc.
|
|
702
|
+
block.input # Tool input parameters (symbol-keyed Hash)
|
|
703
|
+
block.file_path # File path for Read/Write/Edit/NotebookEdit (nil otherwise)
|
|
704
|
+
block.display_label # One-line label: "Read lib/foo.rb", "Bash: git status", "Grep: pattern"
|
|
705
|
+
block.summary # Detailed: "Write: /path.rb (3 lines)", "Edit: /path.rb replacing 5 line(s)"
|
|
706
|
+
block.summary(max: 100) # Custom max length
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
`ServerToolUseBlock` provides the same interface with server context in labels (e.g. `"calculator/add"`).
|
|
710
|
+
|
|
711
|
+
### GenericBlock
|
|
712
|
+
|
|
713
|
+
Unknown content block types are wrapped instead of returning raw Hashes:
|
|
714
|
+
|
|
715
|
+
```ruby
|
|
716
|
+
block.type # Block type as symbol
|
|
717
|
+
block[:field] # Dynamic field access via []
|
|
718
|
+
block.field # Dynamic field access via method_missing
|
|
719
|
+
block.to_h # Raw data hash
|
|
720
|
+
```
|
|
721
|
+
|
|
396
722
|
## MCP Tools
|
|
397
723
|
|
|
398
724
|
Create in-process MCP tools that Claude can use:
|
|
@@ -404,7 +730,7 @@ calculator = ClaudeAgent::MCP::Tool.new(
|
|
|
404
730
|
description: "Add two numbers together",
|
|
405
731
|
schema: { a: Float, b: Float }
|
|
406
732
|
) do |args|
|
|
407
|
-
args[
|
|
733
|
+
args[:a] + args[:b]
|
|
408
734
|
end
|
|
409
735
|
|
|
410
736
|
# Create a server with tools
|
|
@@ -426,6 +752,8 @@ ClaudeAgent.query(
|
|
|
426
752
|
)
|
|
427
753
|
```
|
|
428
754
|
|
|
755
|
+
> **Note:** MCP tool handlers receive **symbol-keyed** argument hashes (e.g. `args[:a]` not `args["a"]`).
|
|
756
|
+
|
|
429
757
|
### External MCP Servers
|
|
430
758
|
|
|
431
759
|
Configure external MCP servers:
|
|
@@ -484,7 +812,7 @@ tool = ClaudeAgent::MCP::Tool.new(
|
|
|
484
812
|
openWorldHint: true, # Tool interacts with external systems
|
|
485
813
|
title: "Web Search" # Human-readable display name
|
|
486
814
|
}
|
|
487
|
-
) { |args| "Results for #{args[
|
|
815
|
+
) { |args| "Results for #{args[:query]}" }
|
|
488
816
|
|
|
489
817
|
# Or with the convenience method
|
|
490
818
|
tool = ClaudeAgent::MCP.tool(
|
|
@@ -502,12 +830,12 @@ Tools can return various formats:
|
|
|
502
830
|
```ruby
|
|
503
831
|
# Simple string
|
|
504
832
|
ClaudeAgent::MCP::Tool.new(name: "greet", description: "Greet") do |args|
|
|
505
|
-
"Hello, #{args[
|
|
833
|
+
"Hello, #{args[:name]}!"
|
|
506
834
|
end
|
|
507
835
|
|
|
508
836
|
# Number (converted to string)
|
|
509
837
|
ClaudeAgent::MCP::Tool.new(name: "add", description: "Add") do |args|
|
|
510
|
-
args[
|
|
838
|
+
args[:a] + args[:b]
|
|
511
839
|
end
|
|
512
840
|
|
|
513
841
|
# Custom MCP content
|
|
@@ -551,6 +879,8 @@ options = ClaudeAgent::Options.new(
|
|
|
551
879
|
)
|
|
552
880
|
```
|
|
553
881
|
|
|
882
|
+
> **Note:** Hook callbacks receive **symbol-keyed** input hashes (e.g. `input.tool_input[:file_path]`).
|
|
883
|
+
|
|
554
884
|
### Hook Events
|
|
555
885
|
|
|
556
886
|
All available hook events:
|
|
@@ -568,6 +898,11 @@ All available hook events:
|
|
|
568
898
|
- `PreCompact` - Before conversation compaction
|
|
569
899
|
- `PermissionRequest` - When permission is requested
|
|
570
900
|
- `Setup` - Initial setup or maintenance (trigger: "init" or "maintenance")
|
|
901
|
+
- `TeammateIdle` - When a teammate agent becomes idle
|
|
902
|
+
- `TaskCompleted` - When a background task completes
|
|
903
|
+
- `ConfigChange` - When configuration changes
|
|
904
|
+
- `WorktreeCreate` - When a git worktree is created
|
|
905
|
+
- `WorktreeRemove` - When a git worktree is removed
|
|
571
906
|
|
|
572
907
|
### Hook Input Types
|
|
573
908
|
|
|
@@ -586,6 +921,11 @@ All available hook events:
|
|
|
586
921
|
| PreCompact | `PreCompactInput` | trigger, custom_instructions |
|
|
587
922
|
| PermissionRequest | `PermissionRequestInput` | tool_name, tool_input, permission_suggestions |
|
|
588
923
|
| Setup | `SetupInput` | trigger (init/maintenance) |
|
|
924
|
+
| TeammateIdle | `TeammateIdleInput` | teammate_name, team_name |
|
|
925
|
+
| TaskCompleted | `TaskCompletedInput` | task_id, task_subject, task_description, teammate_name |
|
|
926
|
+
| ConfigChange | `ConfigChangeInput` | source, file_path |
|
|
927
|
+
| WorktreeCreate | `WorktreeCreateInput` | name |
|
|
928
|
+
| WorktreeRemove | `WorktreeRemoveInput` | worktree_path |
|
|
589
929
|
|
|
590
930
|
All hook inputs inherit from `BaseHookInput` with: `hook_event_name`, `session_id`, `transcript_path`, `cwd`, `permission_mode`.
|
|
591
931
|
|
|
@@ -596,13 +936,14 @@ Control tool permissions programmatically:
|
|
|
596
936
|
```ruby
|
|
597
937
|
options = ClaudeAgent::Options.new(
|
|
598
938
|
can_use_tool: ->(tool_name, tool_input, context) {
|
|
599
|
-
# Context includes: permission_suggestions, blocked_path, decision_reason,
|
|
939
|
+
# Context includes: permission_suggestions, blocked_path, decision_reason,
|
|
940
|
+
# tool_use_id, agent_id, description
|
|
600
941
|
|
|
601
942
|
# Allow all read operations
|
|
602
943
|
if tool_name == "Read"
|
|
603
944
|
ClaudeAgent::PermissionResultAllow.new
|
|
604
945
|
# Deny writes to sensitive paths
|
|
605
|
-
elsif tool_name == "Write" && tool_input[
|
|
946
|
+
elsif tool_name == "Write" && tool_input[:file_path]&.include?(".env")
|
|
606
947
|
ClaudeAgent::PermissionResultDeny.new(
|
|
607
948
|
message: "Cannot modify .env files",
|
|
608
949
|
interrupt: true
|
|
@@ -614,6 +955,8 @@ options = ClaudeAgent::Options.new(
|
|
|
614
955
|
)
|
|
615
956
|
```
|
|
616
957
|
|
|
958
|
+
> **Note:** `can_use_tool` callbacks receive **symbol-keyed** `tool_input` hashes.
|
|
959
|
+
|
|
617
960
|
### Permission Results
|
|
618
961
|
|
|
619
962
|
```ruby
|
|
@@ -630,6 +973,67 @@ ClaudeAgent::PermissionResultDeny.new(
|
|
|
630
973
|
)
|
|
631
974
|
```
|
|
632
975
|
|
|
976
|
+
### Permission Queue
|
|
977
|
+
|
|
978
|
+
For UI-driven permission handling (e.g. TUI's, desktop apps, web UIs), use queue-based permissions instead of synchronous callbacks:
|
|
979
|
+
|
|
980
|
+
```ruby
|
|
981
|
+
# Enable via Options
|
|
982
|
+
options = ClaudeAgent::Options.new(permission_queue: true)
|
|
983
|
+
|
|
984
|
+
# Or via Conversation (queue mode is the default)
|
|
985
|
+
conversation = ClaudeAgent.conversation(permission_mode: "default")
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
Resolve permissions from any thread:
|
|
989
|
+
|
|
990
|
+
```ruby
|
|
991
|
+
# Non-blocking poll
|
|
992
|
+
if request = client.pending_permission
|
|
993
|
+
puts "Tool: #{request.tool_name}"
|
|
994
|
+
puts "Input: #{request.input}"
|
|
995
|
+
request.allow! # or request.deny!(message: "Not allowed")
|
|
996
|
+
end
|
|
997
|
+
|
|
998
|
+
# Check if any are waiting
|
|
999
|
+
client.pending_permissions?
|
|
1000
|
+
|
|
1001
|
+
# Blocking wait with timeout
|
|
1002
|
+
request = client.permission_queue.pop(timeout: 30)
|
|
1003
|
+
request&.allow!
|
|
1004
|
+
|
|
1005
|
+
# Drain all pending (e.g. during shutdown)
|
|
1006
|
+
client.permission_queue.drain!(reason: "Session ended")
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
### Hybrid Mode
|
|
1010
|
+
|
|
1011
|
+
Combine synchronous callbacks with deferred queue resolution:
|
|
1012
|
+
|
|
1013
|
+
```ruby
|
|
1014
|
+
options = ClaudeAgent::Options.new(
|
|
1015
|
+
can_use_tool: ->(tool_name, tool_input, context) {
|
|
1016
|
+
if tool_name == "Read"
|
|
1017
|
+
ClaudeAgent::PermissionResultAllow.new # Auto-allow reads
|
|
1018
|
+
else
|
|
1019
|
+
context.request.defer! # Defer everything else to the queue
|
|
1020
|
+
end
|
|
1021
|
+
}
|
|
1022
|
+
)
|
|
1023
|
+
|
|
1024
|
+
client = ClaudeAgent::Client.new(options: options)
|
|
1025
|
+
client.connect
|
|
1026
|
+
|
|
1027
|
+
# In another thread: resolve deferred permissions
|
|
1028
|
+
Thread.new do
|
|
1029
|
+
loop do
|
|
1030
|
+
request = client.permission_queue.pop
|
|
1031
|
+
break unless request
|
|
1032
|
+
request.allow! # Or show UI dialog
|
|
1033
|
+
end
|
|
1034
|
+
end
|
|
1035
|
+
```
|
|
1036
|
+
|
|
633
1037
|
### Permission Updates
|
|
634
1038
|
|
|
635
1039
|
```ruby
|
|
@@ -670,6 +1074,33 @@ rescue ClaudeAgent::AbortError => e
|
|
|
670
1074
|
end
|
|
671
1075
|
```
|
|
672
1076
|
|
|
1077
|
+
## Cumulative Usage
|
|
1078
|
+
|
|
1079
|
+
The `Client` automatically tracks cumulative usage across turns:
|
|
1080
|
+
|
|
1081
|
+
```ruby
|
|
1082
|
+
ClaudeAgent::Client.open do |client|
|
|
1083
|
+
client.send_and_receive("Hello")
|
|
1084
|
+
client.send_and_receive("Follow up")
|
|
1085
|
+
|
|
1086
|
+
usage = client.cumulative_usage
|
|
1087
|
+
puts "Tokens: #{usage.input_tokens} in / #{usage.output_tokens} out"
|
|
1088
|
+
puts "Cache: #{usage.cache_read_input_tokens} read / #{usage.cache_creation_input_tokens} created"
|
|
1089
|
+
puts "Cost: $#{usage.total_cost_usd}"
|
|
1090
|
+
puts "Turns: #{usage.num_turns}"
|
|
1091
|
+
puts "Duration: #{usage.duration_ms}ms"
|
|
1092
|
+
end
|
|
1093
|
+
```
|
|
1094
|
+
|
|
1095
|
+
Also available via `Conversation#usage`:
|
|
1096
|
+
|
|
1097
|
+
```ruby
|
|
1098
|
+
ClaudeAgent::Conversation.open do |c|
|
|
1099
|
+
c.say("Hello")
|
|
1100
|
+
puts c.usage.to_h # => { input_tokens: 100, output_tokens: 50, ... }
|
|
1101
|
+
end
|
|
1102
|
+
```
|
|
1103
|
+
|
|
673
1104
|
## Client API
|
|
674
1105
|
|
|
675
1106
|
For fine-grained control:
|
|
@@ -680,18 +1111,36 @@ client = ClaudeAgent::Client.new(options: options)
|
|
|
680
1111
|
# Connect to CLI
|
|
681
1112
|
client.connect
|
|
682
1113
|
|
|
683
|
-
# Send queries
|
|
684
|
-
client.
|
|
685
|
-
|
|
1114
|
+
# Send queries and receive TurnResults
|
|
1115
|
+
turn = client.send_and_receive("First question")
|
|
1116
|
+
puts turn.text
|
|
686
1117
|
|
|
687
|
-
client.
|
|
1118
|
+
turn = client.send_and_receive("Follow-up question")
|
|
1119
|
+
puts turn.text
|
|
1120
|
+
|
|
1121
|
+
# Or use lower-level send/receive
|
|
1122
|
+
client.send_message("Question")
|
|
688
1123
|
client.receive_response.each { |msg| process(msg) }
|
|
689
1124
|
|
|
1125
|
+
# Or receive as TurnResult without sending
|
|
1126
|
+
client.send_message("Question")
|
|
1127
|
+
turn = client.receive_turn
|
|
1128
|
+
|
|
1129
|
+
# Event handlers (persist across turns)
|
|
1130
|
+
client.on_text { |text| print text }
|
|
1131
|
+
client.on_tool_use { |tool| puts tool.display_label }
|
|
1132
|
+
client.on_tool_result { |result, tool_use| puts "Done: #{tool_use&.name}" }
|
|
1133
|
+
client.on_thinking { |thought| puts thought }
|
|
1134
|
+
client.on_result { |result| puts "Cost: $#{result.total_cost_usd}" }
|
|
1135
|
+
client.on_message { |msg| log(msg) }
|
|
1136
|
+
|
|
690
1137
|
# Control methods
|
|
691
1138
|
client.interrupt # Cancel current operation
|
|
692
1139
|
client.set_model("claude-opus-4-5-20251101") # Change model
|
|
693
1140
|
client.set_permission_mode("acceptEdits") # Change permissions
|
|
694
1141
|
client.set_max_thinking_tokens(5000) # Change thinking limit
|
|
1142
|
+
client.stop_task("task-123") # Stop a running background task
|
|
1143
|
+
client.apply_flag_settings({ "model" => "..." }) # Merge settings into flag layer
|
|
695
1144
|
|
|
696
1145
|
# File checkpointing (requires enable_file_checkpointing: true)
|
|
697
1146
|
result = client.rewind_files("user-message-uuid", dry_run: true)
|
|
@@ -704,12 +1153,20 @@ result = client.set_mcp_servers({
|
|
|
704
1153
|
})
|
|
705
1154
|
puts "Added: #{result.added}, Removed: #{result.removed}"
|
|
706
1155
|
|
|
707
|
-
#
|
|
708
|
-
client.mcp_reconnect("my-server")
|
|
1156
|
+
# MCP server lifecycle
|
|
1157
|
+
client.mcp_reconnect("my-server") # Reconnect a disconnected server
|
|
1158
|
+
client.mcp_toggle("my-server", enabled: false) # Disable a server
|
|
1159
|
+
client.mcp_toggle("my-server", enabled: true) # Re-enable a server
|
|
1160
|
+
client.mcp_authenticate("my-remote-server") # OAuth authentication
|
|
1161
|
+
client.mcp_clear_auth("my-remote-server") # Clear stored credentials
|
|
1162
|
+
|
|
1163
|
+
# Permission queue access
|
|
1164
|
+
client.pending_permission # Non-blocking poll for next request
|
|
1165
|
+
client.pending_permissions? # Check if any requests waiting
|
|
1166
|
+
client.permission_queue # Direct access to PermissionQueue
|
|
709
1167
|
|
|
710
|
-
#
|
|
711
|
-
client.
|
|
712
|
-
client.mcp_toggle("my-server", enabled: true) # Re-enable
|
|
1168
|
+
# Cumulative usage tracking
|
|
1169
|
+
client.cumulative_usage # CumulativeUsage with totals across all turns
|
|
713
1170
|
|
|
714
1171
|
# Query capabilities
|
|
715
1172
|
client.supported_commands.each { |cmd| puts "#{cmd.name}: #{cmd.description}" }
|
|
@@ -721,9 +1178,52 @@ puts client.account_info.email
|
|
|
721
1178
|
client.disconnect
|
|
722
1179
|
```
|
|
723
1180
|
|
|
1181
|
+
## Session Discovery
|
|
1182
|
+
|
|
1183
|
+
List past Claude Code sessions from disk without spawning a CLI subprocess:
|
|
1184
|
+
|
|
1185
|
+
```ruby
|
|
1186
|
+
# All sessions (most recent first)
|
|
1187
|
+
sessions = ClaudeAgent.list_sessions
|
|
1188
|
+
|
|
1189
|
+
# Scoped to a project directory (includes git worktree siblings)
|
|
1190
|
+
sessions = ClaudeAgent.list_sessions(dir: "/path/to/project", limit: 10)
|
|
1191
|
+
|
|
1192
|
+
sessions.each do |s|
|
|
1193
|
+
puts "#{s.summary} (#{s.git_branch || 'no branch'})"
|
|
1194
|
+
puts " Session: #{s.session_id}"
|
|
1195
|
+
puts " Modified: #{Time.at(s.last_modified / 1000)}"
|
|
1196
|
+
puts " Prompt: #{s.first_prompt}" if s.first_prompt
|
|
1197
|
+
end
|
|
1198
|
+
```
|
|
1199
|
+
|
|
1200
|
+
Each session is a `SessionInfo` with these fields:
|
|
1201
|
+
|
|
1202
|
+
| Field | Type | Description |
|
|
1203
|
+
|------------------|-----------------|--------------------------------------------------|
|
|
1204
|
+
| `session_id` | `String` | UUID of the session |
|
|
1205
|
+
| `summary` | `String` | Custom title, last summary, or first prompt |
|
|
1206
|
+
| `last_modified` | `Integer` | Epoch milliseconds of last modification |
|
|
1207
|
+
| `file_size` | `Integer` | Session file size in bytes |
|
|
1208
|
+
| `custom_title` | `String\|nil` | User-set title, if any |
|
|
1209
|
+
| `first_prompt` | `String\|nil` | First meaningful user prompt |
|
|
1210
|
+
| `git_branch` | `String\|nil` | Git branch the session was on |
|
|
1211
|
+
| `cwd` | `String\|nil` | Working directory of the session |
|
|
1212
|
+
|
|
1213
|
+
Use with `Conversation.resume` to pick up where you left off:
|
|
1214
|
+
|
|
1215
|
+
```ruby
|
|
1216
|
+
sessions = ClaudeAgent.list_sessions(dir: Dir.pwd, limit: 5)
|
|
1217
|
+
session = sessions.first
|
|
1218
|
+
|
|
1219
|
+
conversation = ClaudeAgent.resume_conversation(session.session_id)
|
|
1220
|
+
turn = conversation.say("Continue where we left off")
|
|
1221
|
+
conversation.close
|
|
1222
|
+
```
|
|
1223
|
+
|
|
724
1224
|
## V2 Session API (Unstable)
|
|
725
1225
|
|
|
726
|
-
>
|
|
1226
|
+
> **Warning**: This API is unstable and may change without notice.
|
|
727
1227
|
|
|
728
1228
|
The V2 Session API provides a simpler interface for multi-turn conversations, matching the TypeScript SDK's `SDKSession` interface.
|
|
729
1229
|
|
|
@@ -809,6 +1309,12 @@ session = ClaudeAgent.unstable_v2_create_session(options)
|
|
|
809
1309
|
|
|
810
1310
|
| Type | Purpose |
|
|
811
1311
|
|-----------------------|----------------------------------------------------------------------------------|
|
|
1312
|
+
| `TurnResult` | Complete agent turn with text, tools, usage, and status accessors |
|
|
1313
|
+
| `ToolActivity` | Tool use/result pair with turn index and timing |
|
|
1314
|
+
| `CumulativeUsage` | Running totals of tokens, cost, turns, and duration |
|
|
1315
|
+
| `PermissionRequest` | Deferred permission promise resolvable from any thread |
|
|
1316
|
+
| `PermissionQueue` | Thread-safe queue of pending permission requests |
|
|
1317
|
+
| `EventHandler` | Typed event callback registry |
|
|
812
1318
|
| `SlashCommand` | Available slash commands (name, description, argument_hint) |
|
|
813
1319
|
| `ModelInfo` | Available models (value, display_name, description) |
|
|
814
1320
|
| `McpServerStatus` | MCP server status (name, status, server_info) |
|
|
@@ -816,6 +1322,7 @@ session = ClaudeAgent.unstable_v2_create_session(options)
|
|
|
816
1322
|
| `ModelUsage` | Per-model usage stats (input_tokens, output_tokens, cost_usd) |
|
|
817
1323
|
| `McpSetServersResult` | Result of set_mcp_servers (added, removed, errors) |
|
|
818
1324
|
| `RewindFilesResult` | Result of rewind_files (can_rewind, error, files_changed, insertions, deletions) |
|
|
1325
|
+
| `SessionInfo` | Session metadata from `list_sessions` (session_id, summary, git_branch, cwd) |
|
|
819
1326
|
| `SDKPermissionDenial` | Permission denial info (tool_name, tool_use_id, tool_input) |
|
|
820
1327
|
|
|
821
1328
|
## Logging
|
|
@@ -866,12 +1373,12 @@ When enabled, the SDK logs events across transport, protocol, parsing, MCP, and
|
|
|
866
1373
|
|
|
867
1374
|
### Log Levels
|
|
868
1375
|
|
|
869
|
-
| Level | What's Logged
|
|
870
|
-
|
|
871
|
-
| ERROR | Control request failures, unknown message types
|
|
872
|
-
| WARN | Force kills, JSON parse errors during buffering, unknown MCP tools
|
|
1376
|
+
| Level | What's Logged |
|
|
1377
|
+
|-------|----------------------------------------------------------------------------------------------------------------|
|
|
1378
|
+
| ERROR | Control request failures, unknown message types |
|
|
1379
|
+
| WARN | Force kills, JSON parse errors during buffering, unknown MCP tools |
|
|
873
1380
|
| INFO | Process spawn/close, protocol start/stop, permission decisions, tool calls, query start/completion with timing |
|
|
874
|
-
| DEBUG | Full commands, message types received, control request/response routing, reader thread lifecycle
|
|
1381
|
+
| DEBUG | Full commands, message types received, control request/response routing, reader thread lifecycle |
|
|
875
1382
|
|
|
876
1383
|
## Environment Variables
|
|
877
1384
|
|
|
@@ -927,7 +1434,14 @@ bin/release 1.2.0 # Release a new version
|
|
|
927
1434
|
## Architecture
|
|
928
1435
|
|
|
929
1436
|
```
|
|
930
|
-
ClaudeAgent.
|
|
1437
|
+
ClaudeAgent.conversation() / ClaudeAgent::Conversation
|
|
1438
|
+
│
|
|
1439
|
+
│ Manages lifecycle, callbacks, turn history, tool activity
|
|
1440
|
+
│
|
|
1441
|
+
▼
|
|
1442
|
+
ClaudeAgent::Client
|
|
1443
|
+
│
|
|
1444
|
+
│ Event handlers, cumulative usage, permission queue
|
|
931
1445
|
│
|
|
932
1446
|
▼
|
|
933
1447
|
┌──────────────────────────┐
|