claude_agent 0.7.14 → 0.7.16
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/.claude/rules/conventions.md +66 -16
- data/CHANGELOG.md +20 -0
- data/CLAUDE.md +24 -4
- data/README.md +52 -1529
- data/SPEC.md +56 -29
- data/docs/architecture.md +339 -0
- data/docs/client.md +526 -0
- data/docs/configuration.md +571 -0
- data/docs/conversations.md +461 -0
- data/docs/errors.md +127 -0
- data/docs/events.md +225 -0
- data/docs/getting-started.md +310 -0
- data/docs/hooks.md +380 -0
- data/docs/logging.md +96 -0
- data/docs/mcp.md +308 -0
- data/docs/messages.md +871 -0
- data/docs/permissions.md +611 -0
- data/docs/queries.md +227 -0
- data/docs/sessions.md +335 -0
- data/lib/claude_agent/abort_controller.rb +24 -0
- data/lib/claude_agent/client/commands.rb +32 -0
- data/lib/claude_agent/client.rb +10 -4
- data/lib/claude_agent/configuration.rb +129 -0
- data/lib/claude_agent/control_protocol/commands.rb +28 -0
- data/lib/claude_agent/conversation.rb +37 -4
- data/lib/claude_agent/errors.rb +21 -4
- data/lib/claude_agent/event_handler.rb +14 -0
- data/lib/claude_agent/fork_session.rb +117 -0
- data/lib/claude_agent/hook_registry.rb +110 -0
- data/lib/claude_agent/hooks.rb +4 -0
- data/lib/claude_agent/mcp/server.rb +22 -0
- data/lib/claude_agent/mcp/tool.rb +24 -3
- data/lib/claude_agent/message.rb +93 -0
- data/lib/claude_agent/messages/streaming.rb +37 -0
- data/lib/claude_agent/options.rb +10 -0
- data/lib/claude_agent/permission_policy.rb +174 -0
- data/lib/claude_agent/permission_request.rb +17 -0
- data/lib/claude_agent/session.rb +100 -11
- data/lib/claude_agent/session_paths.rb +5 -2
- data/lib/claude_agent/turn_result.rb +20 -2
- data/lib/claude_agent/types/sessions.rb +8 -0
- data/lib/claude_agent/version.rb +1 -1
- data/lib/claude_agent.rb +187 -0
- data/sig/claude_agent.rbs +38 -1
- metadata +20 -1
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ClaudeAgent
|
|
2
2
|
|
|
3
|
-
Ruby
|
|
3
|
+
Ruby SDK for building AI-powered applications with the [Claude Agent SDK](https://platform.claude.com/docs/en/agent-sdk/overview). Wraps the Claude Code CLI with idiomatic Ruby interfaces for one-shot queries, multi-turn conversations, permissions, hooks, and MCP tools.
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
@@ -9,1587 +9,110 @@ Ruby gem for building AI-powered applications with the [Claude Agent SDK](https:
|
|
|
9
9
|
|
|
10
10
|
## Installation
|
|
11
11
|
|
|
12
|
-
Add to your Gemfile:
|
|
13
|
-
|
|
14
12
|
```ruby
|
|
15
13
|
gem "claude_agent"
|
|
16
14
|
```
|
|
17
15
|
|
|
18
|
-
Then run:
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
bundle install
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
Or install directly:
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
gem install claude_agent
|
|
28
|
-
```
|
|
29
|
-
|
|
30
16
|
## Quick Start
|
|
31
17
|
|
|
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
|
-
|
|
49
18
|
### One-Shot Query
|
|
50
19
|
|
|
51
|
-
For single questions:
|
|
52
|
-
|
|
53
|
-
```ruby
|
|
54
|
-
require "claude_agent"
|
|
55
|
-
|
|
56
|
-
ClaudeAgent.query(prompt: "What is the capital of France?").each do |message|
|
|
57
|
-
case message
|
|
58
|
-
when ClaudeAgent::AssistantMessage
|
|
59
|
-
puts message.text
|
|
60
|
-
when ClaudeAgent::ResultMessage
|
|
61
|
-
puts "Cost: $#{message.total_cost_usd}"
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### One-Shot with TurnResult
|
|
67
|
-
|
|
68
|
-
Get a structured result without writing `case` statements:
|
|
69
|
-
|
|
70
20
|
```ruby
|
|
71
21
|
require "claude_agent"
|
|
72
22
|
|
|
73
|
-
turn = ClaudeAgent.
|
|
23
|
+
turn = ClaudeAgent.ask("What is the capital of France?")
|
|
74
24
|
puts turn.text
|
|
75
25
|
puts "Cost: $#{turn.cost}"
|
|
76
|
-
puts "Model: #{turn.model}"
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### Interactive Client
|
|
80
|
-
|
|
81
|
-
For fine-grained control over the conversation:
|
|
82
|
-
|
|
83
|
-
```ruby
|
|
84
|
-
require "claude_agent"
|
|
85
|
-
|
|
86
|
-
ClaudeAgent::Client.open do |client|
|
|
87
|
-
client.on_text { |text| print text }
|
|
88
|
-
|
|
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}"
|
|
93
|
-
end
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Conversation API
|
|
97
|
-
|
|
98
|
-
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.
|
|
99
|
-
|
|
100
|
-
### Basic Usage
|
|
101
|
-
|
|
102
|
-
```ruby
|
|
103
|
-
conversation = ClaudeAgent.conversation(
|
|
104
|
-
model: "claude-sonnet-4-5-20250514",
|
|
105
|
-
permission_mode: "acceptEdits",
|
|
106
|
-
on_text: ->(text) { print text }
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
turn = conversation.say("Help me refactor this module")
|
|
110
|
-
puts turn.tool_uses.map(&:display_label) # ["Read lib/foo.rb", "Edit lib/foo.rb"]
|
|
111
|
-
|
|
112
|
-
turn = conversation.say("Now update the tests")
|
|
113
|
-
puts "Session cost: $#{conversation.total_cost}"
|
|
114
|
-
|
|
115
|
-
conversation.close
|
|
116
26
|
```
|
|
117
27
|
|
|
118
|
-
###
|
|
119
|
-
|
|
120
|
-
Automatically cleans up when the block exits:
|
|
121
|
-
|
|
122
|
-
```ruby
|
|
123
|
-
ClaudeAgent::Conversation.open(
|
|
124
|
-
max_turns: 10,
|
|
125
|
-
on_tool_use: ->(tool) { puts " Using: #{tool.display_label}" }
|
|
126
|
-
) do |c|
|
|
127
|
-
c.say("Implement the feature described in SPEC.md")
|
|
128
|
-
c.say("Run the tests and fix any failures")
|
|
129
|
-
|
|
130
|
-
puts "Tools used: #{c.tool_activity.size}"
|
|
131
|
-
puts "Total cost: $#{c.total_cost}"
|
|
132
|
-
end
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### Resume a Previous Session
|
|
28
|
+
### Multi-Turn Conversation
|
|
136
29
|
|
|
137
30
|
```ruby
|
|
138
|
-
|
|
139
|
-
turn = conversation.say("Continue where we left off")
|
|
140
|
-
conversation.close
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
### Callbacks
|
|
144
|
-
|
|
145
|
-
Register callbacks for real-time event handling. Any `on_*` keyword is accepted — see [Event Handlers](#event-handlers) for the full list.
|
|
146
|
-
|
|
147
|
-
```ruby
|
|
148
|
-
conversation = ClaudeAgent.conversation(
|
|
149
|
-
on_text: ->(text) { print text },
|
|
150
|
-
on_stream: ->(text) { print text }, # Alias for on_text
|
|
151
|
-
on_thinking: ->(thought) { puts "Thinking: #{thought}" },
|
|
152
|
-
on_tool_use: ->(tool) { puts "Tool: #{tool.display_label}" },
|
|
153
|
-
on_tool_result: ->(result) { puts "Result: #{result.content&.slice(0, 80)}" },
|
|
154
|
-
on_result: ->(result) { puts "Done! Cost: $#{result.total_cost_usd}" },
|
|
155
|
-
on_message: ->(msg) { log(msg) }, # Catch-all
|
|
156
|
-
on_stream_event: ->(evt) { handle_stream(evt) }, # Type-based
|
|
157
|
-
on_status: ->(status) { show_status(status) },
|
|
158
|
-
on_tool_progress: ->(prog) { update_spinner(prog) }
|
|
159
|
-
)
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
### Tool Activity Timeline
|
|
163
|
-
|
|
164
|
-
Track all tool executions across turns with timing:
|
|
165
|
-
|
|
166
|
-
```ruby
|
|
167
|
-
ClaudeAgent::Conversation.open(permission_mode: "acceptEdits") do |c|
|
|
168
|
-
c.say("Refactor the auth module")
|
|
169
|
-
|
|
170
|
-
c.tool_activity.each do |activity|
|
|
171
|
-
puts "#{activity.display_label} (turn #{activity.turn_index})"
|
|
172
|
-
puts " Duration: #{activity.duration&.round(2)}s" if activity.duration
|
|
173
|
-
puts " Error!" if activity.error?
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### Live Tool Tracking
|
|
179
|
-
|
|
180
|
-
Track tool status in real time for live UIs. Unlike `tool_activity` (built after a turn), `LiveToolActivity` updates as tools run:
|
|
181
|
-
|
|
182
|
-
```ruby
|
|
183
|
-
# Conversation level — opt in with track_tools: true
|
|
184
|
-
ClaudeAgent::Conversation.open(
|
|
185
|
-
permission_mode: "acceptEdits",
|
|
186
|
-
track_tools: true
|
|
187
|
-
) do |c|
|
|
188
|
-
c.tool_tracker.on_start { |entry| puts "▸ #{entry.display_label}" }
|
|
189
|
-
c.tool_tracker.on_progress { |entry| puts " #{entry.elapsed&.round(1)}s..." }
|
|
190
|
-
c.tool_tracker.on_complete { |entry| puts "✓ #{entry.display_label}" }
|
|
191
|
-
|
|
31
|
+
ClaudeAgent.chat do |c|
|
|
192
32
|
c.say("Fix the bug in auth.rb")
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
# Client level — attach directly
|
|
197
|
-
tracker = ClaudeAgent::ToolActivityTracker.attach(client)
|
|
198
|
-
tracker.on_start { |entry| show_spinner(entry) }
|
|
199
|
-
tracker.on_complete { |entry| hide_spinner(entry) }
|
|
200
|
-
|
|
201
|
-
# Standalone — attach to any EventHandler
|
|
202
|
-
tracker = ClaudeAgent::ToolActivityTracker.attach(event_handler)
|
|
203
|
-
|
|
204
|
-
# Catch-all callback (receives event symbol + entry)
|
|
205
|
-
tracker.on_change { |event, entry| log(event, entry.id) }
|
|
206
|
-
|
|
207
|
-
# Query running/completed tools at any point
|
|
208
|
-
tracker.running # => [LiveToolActivity, ...]
|
|
209
|
-
tracker.done # => [LiveToolActivity, ...]
|
|
210
|
-
tracker.errored # => [LiveToolActivity, ...]
|
|
211
|
-
tracker["toolu_01ABC"] # => LiveToolActivity (O(1) lookup by tool use ID)
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
### Conversation Accessors
|
|
215
|
-
|
|
216
|
-
```ruby
|
|
217
|
-
conversation.turns # Array of TurnResult objects
|
|
218
|
-
conversation.messages # All messages across all turns
|
|
219
|
-
conversation.tool_activity # Array of ToolActivity objects
|
|
220
|
-
conversation.tool_tracker # ToolActivityTracker (when track_tools: true)
|
|
221
|
-
conversation.total_cost # Total cost in USD
|
|
222
|
-
conversation.session_id # Session ID from most recent turn
|
|
223
|
-
conversation.usage # CumulativeUsage stats
|
|
224
|
-
conversation.open? # Whether conversation is connected
|
|
225
|
-
conversation.closed? # Whether conversation has been closed
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
## Configuration
|
|
229
|
-
|
|
230
|
-
Use `ClaudeAgent::Options` to customize behavior:
|
|
231
|
-
|
|
232
|
-
```ruby
|
|
233
|
-
options = ClaudeAgent::Options.new(
|
|
234
|
-
# Model selection
|
|
235
|
-
model: "claude-sonnet-4-5-20250514",
|
|
236
|
-
fallback_model: "claude-haiku-3-5-20241022",
|
|
237
|
-
|
|
238
|
-
# Conversation limits
|
|
239
|
-
max_turns: 10,
|
|
240
|
-
max_budget_usd: 1.0,
|
|
241
|
-
|
|
242
|
-
# Extended thinking
|
|
243
|
-
thinking: { type: "enabled", budgetTokens: 10000 }, # or "adaptive" or "disabled"
|
|
244
|
-
# max_thinking_tokens: 10000, # Shorthand (when thinking not set)
|
|
245
|
-
|
|
246
|
-
# Response effort
|
|
247
|
-
effort: "high", # "low", "medium", "high", "max"
|
|
248
|
-
|
|
249
|
-
# System prompt
|
|
250
|
-
system_prompt: "You are a helpful coding assistant.",
|
|
251
|
-
append_system_prompt: "Always be concise.",
|
|
252
|
-
|
|
253
|
-
# Tool configuration
|
|
254
|
-
tools: ["Read", "Write", "Bash"],
|
|
255
|
-
allowed_tools: ["Read"],
|
|
256
|
-
disallowed_tools: ["Write"],
|
|
257
|
-
|
|
258
|
-
# Permission modes: "default", "acceptEdits", "plan", "dontAsk", "bypassPermissions"
|
|
259
|
-
permission_mode: "acceptEdits",
|
|
260
|
-
permission_queue: true, # Enable queue-based permissions (see Permissions section)
|
|
261
|
-
|
|
262
|
-
# Working directory for file operations
|
|
263
|
-
cwd: "/path/to/project",
|
|
264
|
-
add_dirs: ["/additional/path"],
|
|
265
|
-
|
|
266
|
-
# Agent configuration
|
|
267
|
-
agent: "my-agent", # Agent name for main thread
|
|
268
|
-
|
|
269
|
-
# Session management
|
|
270
|
-
resume: "session-id",
|
|
271
|
-
session_id: "custom-uuid", # Custom conversation UUID
|
|
272
|
-
continue_conversation: true,
|
|
273
|
-
fork_session: true,
|
|
274
|
-
persist_session: true, # Default: true
|
|
275
|
-
|
|
276
|
-
# Structured output
|
|
277
|
-
output_format: {
|
|
278
|
-
type: "object",
|
|
279
|
-
properties: { answer: { type: "string" } }
|
|
280
|
-
},
|
|
281
|
-
|
|
282
|
-
# Prompt suggestions
|
|
283
|
-
prompt_suggestions: true,
|
|
284
|
-
|
|
285
|
-
# Debug logging (CLI-level)
|
|
286
|
-
debug: true,
|
|
287
|
-
debug_file: "/path/to/debug.log"
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
ClaudeAgent.query(prompt: "Help me refactor this code", options: options)
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
### Tools Preset
|
|
294
|
-
|
|
295
|
-
Use a preset tool configuration:
|
|
296
|
-
|
|
297
|
-
```ruby
|
|
298
|
-
# Using ToolsPreset class
|
|
299
|
-
options = ClaudeAgent::Options.new(
|
|
300
|
-
tools: ClaudeAgent::ToolsPreset.new(preset: "claude_code")
|
|
301
|
-
)
|
|
302
|
-
|
|
303
|
-
# Or as a Hash
|
|
304
|
-
options = ClaudeAgent::Options.new(
|
|
305
|
-
tools: { type: "preset", preset: "claude_code" }
|
|
306
|
-
)
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
### Sandbox Settings
|
|
310
|
-
|
|
311
|
-
Configure sandboxed command execution:
|
|
312
|
-
|
|
313
|
-
```ruby
|
|
314
|
-
sandbox = ClaudeAgent::SandboxSettings.new(
|
|
315
|
-
enabled: true,
|
|
316
|
-
auto_allow_bash_if_sandboxed: true,
|
|
317
|
-
excluded_commands: ["docker", "kubectl"],
|
|
318
|
-
network: ClaudeAgent::SandboxNetworkConfig.new(
|
|
319
|
-
allowed_domains: ["api.example.com"],
|
|
320
|
-
allow_local_binding: true,
|
|
321
|
-
allow_managed_domains_only: false
|
|
322
|
-
),
|
|
323
|
-
filesystem: ClaudeAgent::SandboxFilesystemConfig.new(
|
|
324
|
-
allow_write: ["/tmp/*"],
|
|
325
|
-
deny_write: ["/etc/*"],
|
|
326
|
-
deny_read: ["/secrets/*"]
|
|
327
|
-
),
|
|
328
|
-
ripgrep: ClaudeAgent::SandboxRipgrepConfig.new(
|
|
329
|
-
command: "/usr/local/bin/rg"
|
|
330
|
-
)
|
|
331
|
-
)
|
|
332
|
-
|
|
333
|
-
options = ClaudeAgent::Options.new(sandbox: sandbox)
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
### Custom Agents
|
|
337
|
-
|
|
338
|
-
Define custom subagents:
|
|
339
|
-
|
|
340
|
-
```ruby
|
|
341
|
-
agents = {
|
|
342
|
-
"test-runner" => ClaudeAgent::AgentDefinition.new(
|
|
343
|
-
description: "Runs tests and reports results",
|
|
344
|
-
prompt: "You are a test runner. Execute tests and report failures clearly.",
|
|
345
|
-
tools: ["Read", "Bash"],
|
|
346
|
-
model: "haiku",
|
|
347
|
-
max_turns: 5, # Max agentic turns before stopping
|
|
348
|
-
skills: ["testing", "debug"] # Skills to preload into agent context
|
|
349
|
-
)
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
options = ClaudeAgent::Options.new(agents: agents)
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
## TurnResult
|
|
356
|
-
|
|
357
|
-
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.
|
|
358
|
-
|
|
359
|
-
### Getting a TurnResult
|
|
360
|
-
|
|
361
|
-
```ruby
|
|
362
|
-
# Via Conversation
|
|
363
|
-
turn = conversation.say("Fix the bug")
|
|
364
|
-
|
|
365
|
-
# Via Client
|
|
366
|
-
turn = client.send_and_receive("Fix the bug")
|
|
367
|
-
|
|
368
|
-
# Via one-shot query
|
|
369
|
-
turn = ClaudeAgent.query_turn(prompt: "Fix the bug")
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
### Accessors
|
|
373
|
-
|
|
374
|
-
```ruby
|
|
375
|
-
# Text and thinking
|
|
376
|
-
turn.text # All text content concatenated
|
|
377
|
-
turn.thinking # All thinking content concatenated
|
|
378
|
-
|
|
379
|
-
# Tool usage
|
|
380
|
-
turn.tool_uses # Array of ToolUseBlock / ServerToolUseBlock
|
|
381
|
-
turn.tool_results # Array of ToolResultBlock / ServerToolResultBlock
|
|
382
|
-
turn.tool_executions # Array of { tool_use:, tool_result: } pairs
|
|
383
|
-
|
|
384
|
-
# Result data
|
|
385
|
-
turn.cost # Total cost in USD
|
|
386
|
-
turn.duration_ms # Wall-clock duration
|
|
387
|
-
turn.session_id # Session ID for resumption
|
|
388
|
-
turn.model # Model name
|
|
389
|
-
turn.stop_reason # Why the model stopped ("end_turn", "tool_use", etc.)
|
|
390
|
-
turn.usage # Token usage hash
|
|
391
|
-
turn.model_usage # Per-model usage breakdown
|
|
392
|
-
turn.structured_output # Structured output (if requested)
|
|
393
|
-
turn.num_turns # Number of turns in session
|
|
394
|
-
|
|
395
|
-
# Status
|
|
396
|
-
turn.success? # Whether turn completed successfully
|
|
397
|
-
turn.error? # Whether turn ended with error
|
|
398
|
-
turn.complete? # Whether a ResultMessage was received
|
|
399
|
-
turn.errors # Array of error strings
|
|
400
|
-
turn.permission_denials # Array of SDKPermissionDenial
|
|
401
|
-
|
|
402
|
-
# Filtered message access
|
|
403
|
-
turn.assistant_messages # All AssistantMessages
|
|
404
|
-
turn.user_messages # All UserMessages / UserMessageReplays
|
|
405
|
-
turn.stream_events # All StreamEvents
|
|
406
|
-
turn.content_blocks # All content blocks across assistant messages
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
## Event Handlers
|
|
410
|
-
|
|
411
|
-
Register typed callbacks instead of writing `case` statements. Works with `Client`, `Conversation`, or standalone.
|
|
412
|
-
|
|
413
|
-
Three event layers fire for every message:
|
|
414
|
-
|
|
415
|
-
1. **Catch-all** — `:message` fires for every message
|
|
416
|
-
2. **Type-based** — `message.type` fires (e.g. `:assistant`, `:stream_event`, `:status`, `:tool_progress`)
|
|
417
|
-
3. **Decomposed** — convenience events for rich content (`:text`, `:thinking`, `:tool_use`, `:tool_result`)
|
|
418
|
-
|
|
419
|
-
### Via Client
|
|
420
|
-
|
|
421
|
-
```ruby
|
|
422
|
-
ClaudeAgent::Client.open do |client|
|
|
423
|
-
# Decomposed events (extracted content)
|
|
424
|
-
client.on_text { |text| print text }
|
|
425
|
-
client.on_tool_use { |tool| puts "\nUsing: #{tool.display_label}" }
|
|
426
|
-
client.on_tool_result { |result, tool_use| puts "Done: #{tool_use&.name}" }
|
|
427
|
-
client.on_result { |result| puts "\nCost: $#{result.total_cost_usd}" }
|
|
428
|
-
|
|
429
|
-
# Type-based events (full message object)
|
|
430
|
-
client.on_stream_event { |evt| handle_stream(evt) }
|
|
431
|
-
client.on_status { |status| show_status(status) }
|
|
432
|
-
client.on_tool_progress { |prog| update_spinner(prog) }
|
|
433
|
-
|
|
434
|
-
client.send_and_receive("Fix the bug in auth.rb")
|
|
435
|
-
end
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
### Via One-Shot Query
|
|
439
|
-
|
|
440
|
-
```ruby
|
|
441
|
-
events = ClaudeAgent::EventHandler.new
|
|
442
|
-
.on_text { |text| print text }
|
|
443
|
-
.on_result { |r| puts "\nDone!" }
|
|
444
|
-
|
|
445
|
-
ClaudeAgent.query_turn(prompt: "Explain this code", events: events)
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
### Standalone
|
|
449
|
-
|
|
450
|
-
```ruby
|
|
451
|
-
handler = ClaudeAgent::EventHandler.new
|
|
452
|
-
handler.on(:text) { |text| print text }
|
|
453
|
-
handler.on(:thinking) { |thought| puts "Thinking: #{thought}" }
|
|
454
|
-
handler.on(:tool_use) { |tool| puts "Tool: #{tool.display_label}" }
|
|
455
|
-
handler.on(:tool_result) { |result, tool_use| puts "Result for #{tool_use&.name}" }
|
|
456
|
-
handler.on(:result) { |result| puts "Cost: $#{result.total_cost_usd}" }
|
|
457
|
-
handler.on(:message) { |msg| log(msg) } # Catch-all
|
|
458
|
-
|
|
459
|
-
# Type-based events work with on() too
|
|
460
|
-
handler.on(:stream_event) { |evt| handle_stream(evt) }
|
|
461
|
-
handler.on(:status) { |status| show_status(status) }
|
|
462
|
-
|
|
463
|
-
# Dispatch manually
|
|
464
|
-
client.receive_response.each { |msg| handler.handle(msg) }
|
|
465
|
-
handler.reset! # Clear turn state between turns
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
## Message Types
|
|
469
|
-
|
|
470
|
-
The SDK provides strongly-typed message classes for all protocol messages.
|
|
471
|
-
|
|
472
|
-
### AssistantMessage
|
|
473
|
-
|
|
474
|
-
Claude's responses:
|
|
475
|
-
|
|
476
|
-
```ruby
|
|
477
|
-
message.text # Combined text content
|
|
478
|
-
message.thinking # Extended thinking content (if enabled)
|
|
479
|
-
message.model # Model that generated the response
|
|
480
|
-
message.uuid # Message UUID
|
|
481
|
-
message.session_id # Session identifier
|
|
482
|
-
message.tool_uses # Array of ToolUseBlock if Claude wants to use tools
|
|
483
|
-
message.has_tool_use? # Check if tools are being used
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
### ResultMessage
|
|
487
|
-
|
|
488
|
-
Final message with usage statistics:
|
|
489
|
-
|
|
490
|
-
```ruby
|
|
491
|
-
result.uuid # Message UUID
|
|
492
|
-
result.session_id # Session identifier
|
|
493
|
-
result.num_turns # Number of conversation turns
|
|
494
|
-
result.duration_ms # Total duration in milliseconds
|
|
495
|
-
result.total_cost_usd # API cost in USD
|
|
496
|
-
result.usage # Token usage breakdown
|
|
497
|
-
result.model_usage # Per-model usage breakdown
|
|
498
|
-
result.is_error # Whether the session ended in error
|
|
499
|
-
result.success? # Convenience method
|
|
500
|
-
result.error? # Convenience method
|
|
501
|
-
result.errors # Array of error messages (if any)
|
|
502
|
-
result.permission_denials # Array of SDKPermissionDenial (if any)
|
|
503
|
-
result.stop_reason # Why the model stopped generating (e.g. "end_turn", "tool_use")
|
|
504
|
-
result.fast_mode_state # Fast mode status (if applicable)
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
### UserMessageReplay
|
|
508
|
-
|
|
509
|
-
Replayed user messages when resuming a session with existing history:
|
|
510
|
-
|
|
511
|
-
```ruby
|
|
512
|
-
replay.content # Message content
|
|
513
|
-
replay.uuid # Message UUID
|
|
514
|
-
replay.session_id # Session identifier
|
|
515
|
-
replay.parent_tool_use_id # Parent tool use ID (if tool result)
|
|
516
|
-
replay.replay? # true if this is a replayed message
|
|
517
|
-
replay.synthetic? # true if this is a synthetic message
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
### SystemMessage
|
|
521
|
-
|
|
522
|
-
Internal system events:
|
|
523
|
-
|
|
524
|
-
```ruby
|
|
525
|
-
system_msg.subtype # e.g., "init"
|
|
526
|
-
system_msg.data # Event-specific data
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
### StreamEvent
|
|
530
|
-
|
|
531
|
-
Real-time streaming events:
|
|
532
|
-
|
|
533
|
-
```ruby
|
|
534
|
-
event.uuid # Event UUID
|
|
535
|
-
event.session_id # Session identifier
|
|
536
|
-
event.event_type # Type of stream event
|
|
537
|
-
event.event # Raw event data
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
### CompactBoundaryMessage
|
|
541
|
-
|
|
542
|
-
Conversation compaction marker:
|
|
543
|
-
|
|
544
|
-
```ruby
|
|
545
|
-
boundary.uuid # Message UUID
|
|
546
|
-
boundary.session_id # Session identifier
|
|
547
|
-
boundary.trigger # "manual" or "auto"
|
|
548
|
-
boundary.pre_tokens # Token count before compaction
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
### StatusMessage
|
|
552
|
-
|
|
553
|
-
Session status updates:
|
|
554
|
-
|
|
555
|
-
```ruby
|
|
556
|
-
status.uuid # Message UUID
|
|
557
|
-
status.session_id # Session identifier
|
|
558
|
-
status.status # e.g., "compacting"
|
|
559
|
-
```
|
|
560
|
-
|
|
561
|
-
### ToolProgressMessage
|
|
562
|
-
|
|
563
|
-
Long-running tool progress:
|
|
564
|
-
|
|
565
|
-
```ruby
|
|
566
|
-
progress.tool_use_id # Tool use ID
|
|
567
|
-
progress.tool_name # Tool name
|
|
568
|
-
progress.elapsed_time_seconds # Time elapsed
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
### HookResponseMessage
|
|
572
|
-
|
|
573
|
-
Hook execution output:
|
|
574
|
-
|
|
575
|
-
```ruby
|
|
576
|
-
hook_response.hook_id # Hook identifier
|
|
577
|
-
hook_response.hook_name # Hook name
|
|
578
|
-
hook_response.hook_event # Hook event type
|
|
579
|
-
hook_response.stdout # Hook stdout
|
|
580
|
-
hook_response.stderr # Hook stderr
|
|
581
|
-
hook_response.output # Combined output
|
|
582
|
-
hook_response.exit_code # Exit code
|
|
583
|
-
hook_response.outcome # "success", "error", or "cancelled"
|
|
584
|
-
hook_response.success? # Convenience predicate
|
|
585
|
-
hook_response.error? # Convenience predicate
|
|
586
|
-
hook_response.cancelled? # Convenience predicate
|
|
587
|
-
```
|
|
588
|
-
|
|
589
|
-
### HookStartedMessage
|
|
590
|
-
|
|
591
|
-
Hook execution start notification:
|
|
592
|
-
|
|
593
|
-
```ruby
|
|
594
|
-
hook_started.hook_id # Hook identifier
|
|
595
|
-
hook_started.hook_name # Hook name
|
|
596
|
-
hook_started.hook_event # Hook event type
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
### HookProgressMessage
|
|
600
|
-
|
|
601
|
-
Progress during hook execution:
|
|
602
|
-
|
|
603
|
-
```ruby
|
|
604
|
-
hook_progress.hook_id # Hook identifier
|
|
605
|
-
hook_progress.hook_name # Hook name
|
|
606
|
-
hook_progress.hook_event # Hook event type
|
|
607
|
-
hook_progress.stdout # Hook stdout so far
|
|
608
|
-
hook_progress.stderr # Hook stderr so far
|
|
609
|
-
hook_progress.output # Combined output
|
|
610
|
-
```
|
|
611
|
-
|
|
612
|
-
### ToolUseSummaryMessage
|
|
613
|
-
|
|
614
|
-
Summary of tool use for collapsed display:
|
|
615
|
-
|
|
616
|
-
```ruby
|
|
617
|
-
summary.summary # Human-readable summary text
|
|
618
|
-
summary.preceding_tool_use_ids # Tool use IDs this summarizes
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
### FilesPersistedEvent
|
|
622
|
-
|
|
623
|
-
Files persisted to storage during a session:
|
|
624
|
-
|
|
625
|
-
```ruby
|
|
626
|
-
persisted.files # Array of successfully persisted file paths
|
|
627
|
-
persisted.failed # Array of files that failed to persist
|
|
628
|
-
persisted.processed_at # Timestamp of persistence
|
|
629
|
-
```
|
|
630
|
-
|
|
631
|
-
### AuthStatusMessage
|
|
632
|
-
|
|
633
|
-
Authentication status during login:
|
|
634
|
-
|
|
635
|
-
```ruby
|
|
636
|
-
auth.is_authenticating # Whether auth is in progress
|
|
637
|
-
auth.output # Auth output messages
|
|
638
|
-
auth.error # Error message (if any)
|
|
639
|
-
```
|
|
640
|
-
|
|
641
|
-
### TaskNotificationMessage
|
|
642
|
-
|
|
643
|
-
Background task completion notifications:
|
|
644
|
-
|
|
645
|
-
```ruby
|
|
646
|
-
notification.task_id # Background task ID
|
|
647
|
-
notification.status # "completed", "failed", or "stopped"
|
|
648
|
-
notification.output_file # Path to task output file
|
|
649
|
-
notification.summary # Task summary
|
|
650
|
-
notification.completed? # Convenience predicate
|
|
651
|
-
notification.failed? # Convenience predicate
|
|
652
|
-
notification.stopped? # Convenience predicate
|
|
653
|
-
```
|
|
654
|
-
|
|
655
|
-
### TaskStartedMessage
|
|
656
|
-
|
|
657
|
-
Background task (subagent) start notification:
|
|
658
|
-
|
|
659
|
-
```ruby
|
|
660
|
-
task_started.task_id # Task ID
|
|
661
|
-
task_started.tool_use_id # Associated tool use ID (optional)
|
|
662
|
-
task_started.description # Task description (optional)
|
|
663
|
-
task_started.task_type # Task type (optional)
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
### TaskProgressMessage
|
|
667
|
-
|
|
668
|
-
Background task progress reporting:
|
|
669
|
-
|
|
670
|
-
```ruby
|
|
671
|
-
task_progress.task_id # Task ID
|
|
672
|
-
task_progress.description # What the task is doing
|
|
673
|
-
task_progress.usage # Token usage so far (optional)
|
|
674
|
-
task_progress.last_tool_name # Most recent tool used (optional)
|
|
675
|
-
```
|
|
676
|
-
|
|
677
|
-
### RateLimitEvent
|
|
678
|
-
|
|
679
|
-
Rate limit status and utilization:
|
|
680
|
-
|
|
681
|
-
```ruby
|
|
682
|
-
rate_limit.rate_limit_info # Full rate limit info hash
|
|
683
|
-
rate_limit.status # Rate limit status (e.g. "allowed_warning")
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
### PromptSuggestionMessage
|
|
687
|
-
|
|
688
|
-
Suggested follow-up prompts (requires `prompt_suggestions: true`):
|
|
689
|
-
|
|
690
|
-
```ruby
|
|
691
|
-
suggestion.suggestion # The suggested prompt text
|
|
692
|
-
```
|
|
693
|
-
|
|
694
|
-
### ElicitationCompleteMessage
|
|
695
|
-
|
|
696
|
-
MCP elicitation completion:
|
|
697
|
-
|
|
698
|
-
```ruby
|
|
699
|
-
elicitation.uuid # Message UUID
|
|
700
|
-
elicitation.session_id # Session identifier
|
|
701
|
-
elicitation.mcp_server_name # MCP server that requested elicitation
|
|
702
|
-
elicitation.elicitation_id # Elicitation identifier
|
|
703
|
-
```
|
|
704
|
-
|
|
705
|
-
### LocalCommandOutputMessage
|
|
706
|
-
|
|
707
|
-
Local command output:
|
|
708
|
-
|
|
709
|
-
```ruby
|
|
710
|
-
output.uuid # Message UUID
|
|
711
|
-
output.session_id # Session identifier
|
|
712
|
-
output.content # Command output content
|
|
713
|
-
```
|
|
714
|
-
|
|
715
|
-
### GenericMessage
|
|
716
|
-
|
|
717
|
-
Wraps unknown/future message types instead of raising errors:
|
|
718
|
-
|
|
719
|
-
```ruby
|
|
720
|
-
msg.type # Message type as symbol (e.g. :fancy_new)
|
|
721
|
-
msg[:data] # Dynamic field access via []
|
|
722
|
-
msg.data # Dynamic field access via method_missing
|
|
723
|
-
msg.to_h # Raw data hash
|
|
724
|
-
```
|
|
725
|
-
|
|
726
|
-
## Content Blocks
|
|
727
|
-
|
|
728
|
-
Assistant messages contain content blocks:
|
|
729
|
-
|
|
730
|
-
```ruby
|
|
731
|
-
message.content.each do |block|
|
|
732
|
-
case block
|
|
733
|
-
when ClaudeAgent::TextBlock
|
|
734
|
-
puts block.text
|
|
735
|
-
when ClaudeAgent::ThinkingBlock
|
|
736
|
-
puts "Thinking: #{block.thinking}"
|
|
737
|
-
when ClaudeAgent::ToolUseBlock
|
|
738
|
-
puts "Tool: #{block.display_label}"
|
|
739
|
-
puts " File: #{block.file_path}" if block.file_path
|
|
740
|
-
puts " Summary: #{block.summary}"
|
|
741
|
-
when ClaudeAgent::ToolResultBlock
|
|
742
|
-
puts "Result for #{block.tool_use_id}: #{block.content}"
|
|
743
|
-
when ClaudeAgent::ServerToolUseBlock
|
|
744
|
-
puts "MCP Tool: #{block.display_label}" # "server_name/tool_name"
|
|
745
|
-
when ClaudeAgent::ServerToolResultBlock
|
|
746
|
-
puts "MCP Result from #{block.server_name}"
|
|
747
|
-
when ClaudeAgent::ImageContentBlock
|
|
748
|
-
puts "Image: #{block.media_type}, source: #{block.source_type}"
|
|
749
|
-
when ClaudeAgent::GenericBlock
|
|
750
|
-
puts "Unknown block: #{block.type}, data: #{block.to_h}"
|
|
751
|
-
end
|
|
33
|
+
c.say("Now add tests for the fix")
|
|
34
|
+
puts "Total cost: $#{c.total_cost}"
|
|
752
35
|
end
|
|
753
36
|
```
|
|
754
37
|
|
|
755
|
-
###
|
|
756
|
-
|
|
757
|
-
Tool use blocks provide human-readable labels and summaries:
|
|
758
|
-
|
|
759
|
-
```ruby
|
|
760
|
-
block.name # "Read", "Write", "Bash", etc.
|
|
761
|
-
block.input # Tool input parameters (symbol-keyed Hash)
|
|
762
|
-
block.file_path # File path for Read/Write/Edit/NotebookEdit (nil otherwise)
|
|
763
|
-
block.display_label # One-line label: "Read lib/foo.rb", "Bash: git status", "Grep: pattern"
|
|
764
|
-
block.summary # Detailed: "Write: /path.rb (3 lines)", "Edit: /path.rb replacing 5 line(s)"
|
|
765
|
-
block.summary(max: 100) # Custom max length
|
|
766
|
-
```
|
|
767
|
-
|
|
768
|
-
`ServerToolUseBlock` provides the same interface with server context in labels (e.g. `"calculator/add"`).
|
|
769
|
-
|
|
770
|
-
### GenericBlock
|
|
771
|
-
|
|
772
|
-
Unknown content block types are wrapped instead of returning raw Hashes:
|
|
38
|
+
### Streaming
|
|
773
39
|
|
|
774
40
|
```ruby
|
|
775
|
-
|
|
776
|
-
block[:field] # Dynamic field access via []
|
|
777
|
-
block.field # Dynamic field access via method_missing
|
|
778
|
-
block.to_h # Raw data hash
|
|
41
|
+
ClaudeAgent.ask("Explain Ruby blocks") { |msg| print msg.text_content }
|
|
779
42
|
```
|
|
780
43
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
Create in-process MCP tools that Claude can use:
|
|
44
|
+
### Global Configuration
|
|
784
45
|
|
|
785
46
|
```ruby
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
schema: { a: Float, b: Float }
|
|
791
|
-
) do |args|
|
|
792
|
-
args[:a] + args[:b]
|
|
47
|
+
ClaudeAgent.configure do |c|
|
|
48
|
+
c.model = "claude-sonnet-4-5-20250514"
|
|
49
|
+
c.permission_mode = "acceptEdits"
|
|
50
|
+
c.max_turns = 10
|
|
793
51
|
end
|
|
794
52
|
|
|
795
|
-
#
|
|
796
|
-
|
|
797
|
-
name: "calculator",
|
|
798
|
-
tools: [calculator]
|
|
799
|
-
)
|
|
800
|
-
|
|
801
|
-
# Use with options (SDK MCP servers)
|
|
802
|
-
options = ClaudeAgent::Options.new(
|
|
803
|
-
mcp_servers: {
|
|
804
|
-
"calculator" => { type: "sdk", instance: server }
|
|
805
|
-
}
|
|
806
|
-
)
|
|
807
|
-
|
|
808
|
-
ClaudeAgent.query(
|
|
809
|
-
prompt: "What is 25 + 17?",
|
|
810
|
-
options: options
|
|
811
|
-
)
|
|
812
|
-
```
|
|
813
|
-
|
|
814
|
-
> **Note:** MCP tool handlers receive **symbol-keyed** argument hashes (e.g. `args[:a]` not `args["a"]`).
|
|
815
|
-
|
|
816
|
-
### External MCP Servers
|
|
817
|
-
|
|
818
|
-
Configure external MCP servers:
|
|
819
|
-
|
|
820
|
-
```ruby
|
|
821
|
-
options = ClaudeAgent::Options.new(
|
|
822
|
-
mcp_servers: {
|
|
823
|
-
"filesystem" => {
|
|
824
|
-
type: "stdio",
|
|
825
|
-
command: "npx",
|
|
826
|
-
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
)
|
|
830
|
-
```
|
|
831
|
-
|
|
832
|
-
### Tool Schema
|
|
833
|
-
|
|
834
|
-
Define schemas using Ruby types or JSON Schema:
|
|
835
|
-
|
|
836
|
-
```ruby
|
|
837
|
-
# Ruby types (converted automatically)
|
|
838
|
-
schema: {
|
|
839
|
-
name: String,
|
|
840
|
-
age: Integer,
|
|
841
|
-
score: Float,
|
|
842
|
-
active: TrueClass, # boolean
|
|
843
|
-
tags: Array,
|
|
844
|
-
metadata: Hash
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
# Or use JSON Schema directly
|
|
848
|
-
schema: {
|
|
849
|
-
type: "object",
|
|
850
|
-
properties: {
|
|
851
|
-
name: { type: "string", description: "User's name" },
|
|
852
|
-
age: { type: "integer", minimum: 0 }
|
|
853
|
-
},
|
|
854
|
-
required: ["name"]
|
|
855
|
-
}
|
|
856
|
-
```
|
|
857
|
-
|
|
858
|
-
### Tool Annotations
|
|
859
|
-
|
|
860
|
-
Annotate tools with hints about their behavior:
|
|
861
|
-
|
|
862
|
-
```ruby
|
|
863
|
-
tool = ClaudeAgent::MCP::Tool.new(
|
|
864
|
-
name: "search",
|
|
865
|
-
description: "Search the web",
|
|
866
|
-
schema: { query: String },
|
|
867
|
-
annotations: {
|
|
868
|
-
readOnlyHint: true, # Tool only reads data, no side effects
|
|
869
|
-
destructiveHint: false, # Tool does not destroy/delete data
|
|
870
|
-
idempotentHint: true, # Repeated calls with same args have same effect
|
|
871
|
-
openWorldHint: true, # Tool interacts with external systems
|
|
872
|
-
title: "Web Search" # Human-readable display name
|
|
873
|
-
}
|
|
874
|
-
) { |args| "Results for #{args[:query]}" }
|
|
875
|
-
|
|
876
|
-
# Or with the convenience method
|
|
877
|
-
tool = ClaudeAgent::MCP.tool(
|
|
878
|
-
"search", "Search the web", { query: String },
|
|
879
|
-
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
880
|
-
) { |args| "Results" }
|
|
53
|
+
# Or set individual fields
|
|
54
|
+
ClaudeAgent.model = "claude-sonnet-4-5-20250514"
|
|
881
55
|
```
|
|
882
56
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
### Tool Return Values
|
|
886
|
-
|
|
887
|
-
Tools can return various formats:
|
|
57
|
+
### Permissions
|
|
888
58
|
|
|
889
59
|
```ruby
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
"
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
# Number (converted to string)
|
|
896
|
-
ClaudeAgent::MCP::Tool.new(name: "add", description: "Add") do |args|
|
|
897
|
-
args[:a] + args[:b]
|
|
898
|
-
end
|
|
899
|
-
|
|
900
|
-
# Custom MCP content
|
|
901
|
-
ClaudeAgent::MCP::Tool.new(name: "fancy", description: "Fancy") do |args|
|
|
902
|
-
{ content: [{ type: "text", text: "Custom response" }] }
|
|
60
|
+
ClaudeAgent.permissions do |p|
|
|
61
|
+
p.allow "Read", "Grep", "Glob"
|
|
62
|
+
p.deny "Bash", message: "Shell access disabled"
|
|
63
|
+
p.deny_all
|
|
903
64
|
end
|
|
904
65
|
```
|
|
905
66
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
Intercept tool usage and other events with hooks:
|
|
67
|
+
### Hooks
|
|
909
68
|
|
|
910
69
|
```ruby
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
ClaudeAgent::HookMatcher.new(
|
|
915
|
-
matcher: "Bash|Write", # Match specific tools
|
|
916
|
-
callbacks: [
|
|
917
|
-
->(input, context) {
|
|
918
|
-
puts "Tool: #{input.tool_name}"
|
|
919
|
-
puts "Input: #{input.tool_input}"
|
|
920
|
-
puts "Tool Use ID: #{input.tool_use_id}"
|
|
921
|
-
{ continue_: true } # Allow the tool to proceed
|
|
922
|
-
}
|
|
923
|
-
]
|
|
924
|
-
)
|
|
925
|
-
],
|
|
926
|
-
"PostToolUse" => [
|
|
927
|
-
ClaudeAgent::HookMatcher.new(
|
|
928
|
-
matcher: /^mcp__/, # Regex matching
|
|
929
|
-
callbacks: [
|
|
930
|
-
->(input, context) {
|
|
931
|
-
puts "Result: #{input.tool_response}"
|
|
932
|
-
{ continue_: true }
|
|
933
|
-
}
|
|
934
|
-
]
|
|
935
|
-
)
|
|
936
|
-
]
|
|
937
|
-
}
|
|
938
|
-
)
|
|
939
|
-
```
|
|
940
|
-
|
|
941
|
-
> **Note:** Hook callbacks receive **symbol-keyed** input hashes (e.g. `input.tool_input[:file_path]`).
|
|
942
|
-
|
|
943
|
-
### Hook Events
|
|
944
|
-
|
|
945
|
-
All available hook events:
|
|
946
|
-
|
|
947
|
-
- `PreToolUse` - Before tool execution
|
|
948
|
-
- `PostToolUse` - After successful tool execution
|
|
949
|
-
- `PostToolUseFailure` - After tool execution failure
|
|
950
|
-
- `Notification` - System notifications
|
|
951
|
-
- `UserPromptSubmit` - When user submits a prompt
|
|
952
|
-
- `SessionStart` - When session starts
|
|
953
|
-
- `SessionEnd` - When session ends
|
|
954
|
-
- `Stop` - When agent stops
|
|
955
|
-
- `SubagentStart` - When subagent starts
|
|
956
|
-
- `SubagentStop` - When subagent stops
|
|
957
|
-
- `PreCompact` - Before conversation compaction
|
|
958
|
-
- `PermissionRequest` - When permission is requested
|
|
959
|
-
- `Setup` - Initial setup or maintenance (trigger: "init" or "maintenance")
|
|
960
|
-
- `TeammateIdle` - When a teammate agent becomes idle
|
|
961
|
-
- `TaskCompleted` - When a background task completes
|
|
962
|
-
- `ConfigChange` - When configuration changes
|
|
963
|
-
- `WorktreeCreate` - When a git worktree is created
|
|
964
|
-
- `WorktreeRemove` - When a git worktree is removed
|
|
965
|
-
|
|
966
|
-
### Hook Input Types
|
|
967
|
-
|
|
968
|
-
| Event | Input Type | Key Fields |
|
|
969
|
-
|--------------------|---------------------------|---------------------------------------------------------|
|
|
970
|
-
| PreToolUse | `PreToolUseInput` | tool_name, tool_input, tool_use_id |
|
|
971
|
-
| PostToolUse | `PostToolUseInput` | tool_name, tool_input, tool_response, tool_use_id |
|
|
972
|
-
| PostToolUseFailure | `PostToolUseFailureInput` | tool_name, tool_input, error, tool_use_id, is_interrupt |
|
|
973
|
-
| Notification | `NotificationInput` | message, title, notification_type |
|
|
974
|
-
| UserPromptSubmit | `UserPromptSubmitInput` | prompt |
|
|
975
|
-
| SessionStart | `SessionStartInput` | source, agent_type, model |
|
|
976
|
-
| SessionEnd | `SessionEndInput` | reason |
|
|
977
|
-
| Stop | `StopInput` | stop_hook_active |
|
|
978
|
-
| SubagentStart | `SubagentStartInput` | agent_id, agent_type |
|
|
979
|
-
| SubagentStop | `SubagentStopInput` | stop_hook_active, agent_id, agent_transcript_path |
|
|
980
|
-
| PreCompact | `PreCompactInput` | trigger, custom_instructions |
|
|
981
|
-
| PermissionRequest | `PermissionRequestInput` | tool_name, tool_input, permission_suggestions |
|
|
982
|
-
| Setup | `SetupInput` | trigger (init/maintenance) |
|
|
983
|
-
| TeammateIdle | `TeammateIdleInput` | teammate_name, team_name |
|
|
984
|
-
| TaskCompleted | `TaskCompletedInput` | task_id, task_subject, task_description, teammate_name |
|
|
985
|
-
| ConfigChange | `ConfigChangeInput` | source, file_path |
|
|
986
|
-
| WorktreeCreate | `WorktreeCreateInput` | name |
|
|
987
|
-
| WorktreeRemove | `WorktreeRemoveInput` | worktree_path |
|
|
988
|
-
|
|
989
|
-
All hook inputs inherit from `BaseHookInput` with: `hook_event_name`, `session_id`, `transcript_path`, `cwd`, `permission_mode`.
|
|
990
|
-
|
|
991
|
-
## Permissions
|
|
992
|
-
|
|
993
|
-
Control tool permissions programmatically:
|
|
994
|
-
|
|
995
|
-
```ruby
|
|
996
|
-
options = ClaudeAgent::Options.new(
|
|
997
|
-
can_use_tool: ->(tool_name, tool_input, context) {
|
|
998
|
-
# Context includes: permission_suggestions, blocked_path, decision_reason,
|
|
999
|
-
# tool_use_id, agent_id, description
|
|
1000
|
-
|
|
1001
|
-
# Allow all read operations
|
|
1002
|
-
if tool_name == "Read"
|
|
1003
|
-
ClaudeAgent::PermissionResultAllow.new
|
|
1004
|
-
# Deny writes to sensitive paths
|
|
1005
|
-
elsif tool_name == "Write" && tool_input[:file_path]&.include?(".env")
|
|
1006
|
-
ClaudeAgent::PermissionResultDeny.new(
|
|
1007
|
-
message: "Cannot modify .env files",
|
|
1008
|
-
interrupt: true
|
|
1009
|
-
)
|
|
1010
|
-
else
|
|
1011
|
-
ClaudeAgent::PermissionResultAllow.new
|
|
1012
|
-
end
|
|
1013
|
-
}
|
|
1014
|
-
)
|
|
1015
|
-
```
|
|
1016
|
-
|
|
1017
|
-
> **Note:** `can_use_tool` callbacks receive **symbol-keyed** `tool_input` hashes.
|
|
1018
|
-
|
|
1019
|
-
### Permission Results
|
|
1020
|
-
|
|
1021
|
-
```ruby
|
|
1022
|
-
# Allow with optional modifications
|
|
1023
|
-
ClaudeAgent::PermissionResultAllow.new(
|
|
1024
|
-
updated_input: { file_path: "/safe/path" }, # Modify tool input
|
|
1025
|
-
updated_permissions: [...] # Update permission rules
|
|
1026
|
-
)
|
|
1027
|
-
|
|
1028
|
-
# Deny
|
|
1029
|
-
ClaudeAgent::PermissionResultDeny.new(
|
|
1030
|
-
message: "Operation not allowed",
|
|
1031
|
-
interrupt: true # Stop the agent
|
|
1032
|
-
)
|
|
1033
|
-
```
|
|
1034
|
-
|
|
1035
|
-
### Permission Queue
|
|
1036
|
-
|
|
1037
|
-
For UI-driven permission handling (e.g. TUI's, desktop apps, web UIs), use queue-based permissions instead of synchronous callbacks:
|
|
1038
|
-
|
|
1039
|
-
```ruby
|
|
1040
|
-
# Enable via Options
|
|
1041
|
-
options = ClaudeAgent::Options.new(permission_queue: true)
|
|
1042
|
-
|
|
1043
|
-
# Or via Conversation (queue mode is the default)
|
|
1044
|
-
conversation = ClaudeAgent.conversation(permission_mode: "default")
|
|
1045
|
-
```
|
|
1046
|
-
|
|
1047
|
-
Resolve permissions from any thread:
|
|
1048
|
-
|
|
1049
|
-
```ruby
|
|
1050
|
-
# Non-blocking poll
|
|
1051
|
-
if request = client.pending_permission
|
|
1052
|
-
puts "Tool: #{request.tool_name}"
|
|
1053
|
-
puts "Input: #{request.input}"
|
|
1054
|
-
request.allow! # or request.deny!(message: "Not allowed")
|
|
70
|
+
ClaudeAgent.hooks do |h|
|
|
71
|
+
h.before_tool_use(/Bash/) { |input, ctx| { continue_: true } }
|
|
72
|
+
h.after_tool_use { |input, ctx| { continue_: true } }
|
|
1055
73
|
end
|
|
1056
|
-
|
|
1057
|
-
# Check if any are waiting
|
|
1058
|
-
client.pending_permissions?
|
|
1059
|
-
|
|
1060
|
-
# Blocking wait with timeout
|
|
1061
|
-
request = client.permission_queue.pop(timeout: 30)
|
|
1062
|
-
request&.allow!
|
|
1063
|
-
|
|
1064
|
-
# Drain all pending (e.g. during shutdown)
|
|
1065
|
-
client.permission_queue.drain!(reason: "Session ended")
|
|
1066
74
|
```
|
|
1067
75
|
|
|
1068
|
-
###
|
|
1069
|
-
|
|
1070
|
-
Combine synchronous callbacks with deferred queue resolution:
|
|
76
|
+
### MCP Tools
|
|
1071
77
|
|
|
1072
78
|
```ruby
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
ClaudeAgent::PermissionResultAllow.new # Auto-allow reads
|
|
1077
|
-
else
|
|
1078
|
-
context.request.defer! # Defer everything else to the queue
|
|
1079
|
-
end
|
|
1080
|
-
}
|
|
1081
|
-
)
|
|
1082
|
-
|
|
1083
|
-
client = ClaudeAgent::Client.new(options: options)
|
|
1084
|
-
client.connect
|
|
1085
|
-
|
|
1086
|
-
# In another thread: resolve deferred permissions
|
|
1087
|
-
Thread.new do
|
|
1088
|
-
loop do
|
|
1089
|
-
request = client.permission_queue.pop
|
|
1090
|
-
break unless request
|
|
1091
|
-
request.allow! # Or show UI dialog
|
|
79
|
+
server = ClaudeAgent::MCP::Server.new(name: "calc") do |s|
|
|
80
|
+
s.tool("add", "Add two numbers", { a: :number, b: :number }) do |args|
|
|
81
|
+
args[:a] + args[:b]
|
|
1092
82
|
end
|
|
1093
83
|
end
|
|
1094
|
-
```
|
|
1095
84
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
```ruby
|
|
1099
|
-
update = ClaudeAgent::PermissionUpdate.new(
|
|
1100
|
-
type: "addRules", # addRules, replaceRules, removeRules, setMode, addDirectories, removeDirectories
|
|
1101
|
-
rules: [
|
|
1102
|
-
ClaudeAgent::PermissionRuleValue.new(tool_name: "Read", rule_content: "/**")
|
|
1103
|
-
],
|
|
1104
|
-
behavior: "allow",
|
|
1105
|
-
destination: "session" # userSettings, projectSettings, localSettings, session, cliArg
|
|
1106
|
-
)
|
|
1107
|
-
```
|
|
1108
|
-
|
|
1109
|
-
## MCP Elicitation
|
|
1110
|
-
|
|
1111
|
-
Handle MCP server elicitation requests (e.g. OAuth flows, form input):
|
|
1112
|
-
|
|
1113
|
-
```ruby
|
|
1114
|
-
options = ClaudeAgent::Options.new(
|
|
1115
|
-
on_elicitation: ->(request, signal:) {
|
|
1116
|
-
# request contains: server_name, message, mode, url, elicitation_id, requested_schema
|
|
1117
|
-
case request[:mode]
|
|
1118
|
-
when "oauth"
|
|
1119
|
-
# Handle OAuth flow
|
|
1120
|
-
{ action: "accept", content: { token: "..." } }
|
|
1121
|
-
else
|
|
1122
|
-
{ action: "decline" }
|
|
1123
|
-
end
|
|
1124
|
-
}
|
|
1125
|
-
)
|
|
1126
|
-
```
|
|
1127
|
-
|
|
1128
|
-
Without `on_elicitation`, all elicitation requests are declined by default.
|
|
1129
|
-
|
|
1130
|
-
## Error Handling
|
|
1131
|
-
|
|
1132
|
-
The SDK provides specific error types:
|
|
1133
|
-
|
|
1134
|
-
```ruby
|
|
1135
|
-
begin
|
|
1136
|
-
ClaudeAgent.query(prompt: "Hello")
|
|
1137
|
-
rescue ClaudeAgent::CLINotFoundError
|
|
1138
|
-
puts "Claude Code CLI not installed"
|
|
1139
|
-
rescue ClaudeAgent::CLIVersionError => e
|
|
1140
|
-
puts "CLI version too old: #{e.message}"
|
|
1141
|
-
puts "Required: #{e.required_version}, Actual: #{e.actual_version}"
|
|
1142
|
-
rescue ClaudeAgent::CLIConnectionError => e
|
|
1143
|
-
puts "Connection failed: #{e.message}"
|
|
1144
|
-
rescue ClaudeAgent::ProcessError => e
|
|
1145
|
-
puts "Process error: #{e.message}, exit code: #{e.exit_code}"
|
|
1146
|
-
rescue ClaudeAgent::TimeoutError => e
|
|
1147
|
-
puts "Timeout: #{e.message}"
|
|
1148
|
-
rescue ClaudeAgent::JSONDecodeError => e
|
|
1149
|
-
puts "Invalid JSON response"
|
|
1150
|
-
rescue ClaudeAgent::MessageParseError => e
|
|
1151
|
-
puts "Could not parse message"
|
|
1152
|
-
rescue ClaudeAgent::AbortError => e
|
|
1153
|
-
puts "Operation aborted"
|
|
1154
|
-
end
|
|
85
|
+
ClaudeAgent.register_mcp_server(server)
|
|
1155
86
|
```
|
|
1156
87
|
|
|
1157
|
-
##
|
|
88
|
+
## Documentation
|
|
1158
89
|
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
Also available via `Conversation#usage`:
|
|
1176
|
-
|
|
1177
|
-
```ruby
|
|
1178
|
-
ClaudeAgent::Conversation.open do |c|
|
|
1179
|
-
c.say("Hello")
|
|
1180
|
-
puts c.usage.to_h # => { input_tokens: 100, output_tokens: 50, ... }
|
|
1181
|
-
end
|
|
1182
|
-
```
|
|
1183
|
-
|
|
1184
|
-
## Client API
|
|
1185
|
-
|
|
1186
|
-
For fine-grained control:
|
|
1187
|
-
|
|
1188
|
-
```ruby
|
|
1189
|
-
client = ClaudeAgent::Client.new(options: options)
|
|
1190
|
-
|
|
1191
|
-
# Connect to CLI
|
|
1192
|
-
client.connect
|
|
1193
|
-
|
|
1194
|
-
# Send queries and receive TurnResults
|
|
1195
|
-
turn = client.send_and_receive("First question")
|
|
1196
|
-
puts turn.text
|
|
1197
|
-
|
|
1198
|
-
turn = client.send_and_receive("Follow-up question")
|
|
1199
|
-
puts turn.text
|
|
1200
|
-
|
|
1201
|
-
# Or use lower-level send/receive
|
|
1202
|
-
client.send_message("Question")
|
|
1203
|
-
client.receive_response.each { |msg| process(msg) }
|
|
1204
|
-
|
|
1205
|
-
# Or receive as TurnResult without sending
|
|
1206
|
-
client.send_message("Question")
|
|
1207
|
-
turn = client.receive_turn
|
|
1208
|
-
|
|
1209
|
-
# Event handlers (persist across turns)
|
|
1210
|
-
client.on_text { |text| print text }
|
|
1211
|
-
client.on_tool_use { |tool| puts tool.display_label }
|
|
1212
|
-
client.on_tool_result { |result, tool_use| puts "Done: #{tool_use&.name}" }
|
|
1213
|
-
client.on_thinking { |thought| puts thought }
|
|
1214
|
-
client.on_result { |result| puts "Cost: $#{result.total_cost_usd}" }
|
|
1215
|
-
client.on_message { |msg| log(msg) }
|
|
1216
|
-
# Type-based events for all message types
|
|
1217
|
-
client.on_assistant { |msg| handle_assistant(msg) }
|
|
1218
|
-
client.on_stream_event { |evt| handle_stream(evt) }
|
|
1219
|
-
client.on_status { |status| show_status(status) }
|
|
1220
|
-
client.on_tool_progress { |prog| update_spinner(prog) }
|
|
1221
|
-
|
|
1222
|
-
# Control methods
|
|
1223
|
-
client.interrupt # Cancel current operation
|
|
1224
|
-
client.set_model("claude-opus-4-5-20251101") # Change model
|
|
1225
|
-
client.set_permission_mode("acceptEdits") # Change permissions
|
|
1226
|
-
client.set_max_thinking_tokens(5000) # Change thinking limit
|
|
1227
|
-
client.stop_task("task-123") # Stop a running background task
|
|
1228
|
-
client.apply_flag_settings({ "model" => "..." }) # Merge settings into flag layer
|
|
1229
|
-
|
|
1230
|
-
# File checkpointing (requires enable_file_checkpointing: true)
|
|
1231
|
-
result = client.rewind_files("user-message-uuid", dry_run: true)
|
|
1232
|
-
puts "Can rewind: #{result.can_rewind}"
|
|
1233
|
-
puts "Files changed: #{result.files_changed}"
|
|
1234
|
-
|
|
1235
|
-
# Dynamic MCP server management
|
|
1236
|
-
result = client.set_mcp_servers({
|
|
1237
|
-
"my-server" => { type: "stdio", command: "node", args: ["server.js"] }
|
|
1238
|
-
})
|
|
1239
|
-
puts "Added: #{result.added}, Removed: #{result.removed}"
|
|
1240
|
-
|
|
1241
|
-
# MCP server lifecycle
|
|
1242
|
-
client.mcp_reconnect("my-server") # Reconnect a disconnected server
|
|
1243
|
-
client.mcp_toggle("my-server", enabled: false) # Disable a server
|
|
1244
|
-
client.mcp_toggle("my-server", enabled: true) # Re-enable a server
|
|
1245
|
-
client.mcp_authenticate("my-remote-server") # OAuth authentication
|
|
1246
|
-
client.mcp_clear_auth("my-remote-server") # Clear stored credentials
|
|
1247
|
-
|
|
1248
|
-
# Permission queue access
|
|
1249
|
-
client.pending_permission # Non-blocking poll for next request
|
|
1250
|
-
client.pending_permissions? # Check if any requests waiting
|
|
1251
|
-
client.permission_queue # Direct access to PermissionQueue
|
|
1252
|
-
|
|
1253
|
-
# Cumulative usage tracking
|
|
1254
|
-
client.cumulative_usage # CumulativeUsage with totals across all turns
|
|
1255
|
-
|
|
1256
|
-
# Query capabilities
|
|
1257
|
-
client.supported_commands.each { |cmd| puts "#{cmd.name}: #{cmd.description}" }
|
|
1258
|
-
client.supported_models.each { |model| puts "#{model.value}: #{model.display_name}" }
|
|
1259
|
-
client.supported_agents.each { |agent| puts "#{agent.name}: #{agent.description}" }
|
|
1260
|
-
client.mcp_server_status.each { |s| puts "#{s.name}: #{s.status}" }
|
|
1261
|
-
puts client.account_info.email
|
|
1262
|
-
|
|
1263
|
-
# Disconnect
|
|
1264
|
-
client.disconnect
|
|
1265
|
-
```
|
|
1266
|
-
|
|
1267
|
-
## Session Discovery
|
|
1268
|
-
|
|
1269
|
-
Find and inspect past Claude Code sessions from disk without spawning a CLI subprocess.
|
|
1270
|
-
|
|
1271
|
-
### Session.find / Session.all
|
|
1272
|
-
|
|
1273
|
-
```ruby
|
|
1274
|
-
# Find a specific session by ID
|
|
1275
|
-
session = ClaudeAgent::Session.find("abc-123-def")
|
|
1276
|
-
session = ClaudeAgent::Session.find("abc-123-def", dir: "/my/project")
|
|
1277
|
-
|
|
1278
|
-
# List all sessions (most recent first)
|
|
1279
|
-
sessions = ClaudeAgent::Session.all
|
|
1280
|
-
|
|
1281
|
-
# Filter by directory, limit results
|
|
1282
|
-
sessions = ClaudeAgent::Session.where(dir: "/path/to/project", limit: 10)
|
|
1283
|
-
|
|
1284
|
-
sessions.each do |s|
|
|
1285
|
-
puts "#{s.summary} (#{s.git_branch || 'no branch'})"
|
|
1286
|
-
puts " Session: #{s.session_id}"
|
|
1287
|
-
puts " Modified: #{Time.at(s.last_modified / 1000)}"
|
|
1288
|
-
puts " Prompt: #{s.first_prompt}" if s.first_prompt
|
|
1289
|
-
end
|
|
1290
|
-
```
|
|
1291
|
-
|
|
1292
|
-
### Reading Messages
|
|
1293
|
-
|
|
1294
|
-
`Session#messages` returns a chainable, `Enumerable` relation:
|
|
1295
|
-
|
|
1296
|
-
```ruby
|
|
1297
|
-
session = ClaudeAgent::Session.find("abc-123-def")
|
|
1298
|
-
|
|
1299
|
-
# All messages
|
|
1300
|
-
session.messages.each { |m| puts "#{m.type}: #{m.uuid}" }
|
|
1301
|
-
|
|
1302
|
-
# Paginated
|
|
1303
|
-
session.messages.where(limit: 10).map(&:uuid)
|
|
1304
|
-
session.messages.where(offset: 5, limit: 10).to_a
|
|
1305
|
-
|
|
1306
|
-
# Enumerable methods work
|
|
1307
|
-
session.messages.first
|
|
1308
|
-
session.messages.count
|
|
1309
|
-
session.messages.select { |m| m.type == "assistant" }
|
|
1310
|
-
```
|
|
1311
|
-
|
|
1312
|
-
### Session Fields
|
|
1313
|
-
|
|
1314
|
-
| Field | Type | Description |
|
|
1315
|
-
|------------------|-----------------|--------------------------------------------------|
|
|
1316
|
-
| `session_id` | `String` | UUID of the session |
|
|
1317
|
-
| `summary` | `String` | Custom title, last summary, or first prompt |
|
|
1318
|
-
| `last_modified` | `Integer` | Epoch milliseconds of last modification |
|
|
1319
|
-
| `file_size` | `Integer` | Session file size in bytes |
|
|
1320
|
-
| `custom_title` | `String\|nil` | User-set title, if any |
|
|
1321
|
-
| `first_prompt` | `String\|nil` | First meaningful user prompt |
|
|
1322
|
-
| `git_branch` | `String\|nil` | Git branch the session was on |
|
|
1323
|
-
| `cwd` | `String\|nil` | Working directory of the session |
|
|
1324
|
-
|
|
1325
|
-
### Functional API
|
|
1326
|
-
|
|
1327
|
-
The lower-level functional API is also available:
|
|
1328
|
-
|
|
1329
|
-
```ruby
|
|
1330
|
-
# List sessions (returns SessionInfo objects)
|
|
1331
|
-
infos = ClaudeAgent.list_sessions(dir: "/path/to/project", limit: 10)
|
|
1332
|
-
|
|
1333
|
-
# Read messages directly
|
|
1334
|
-
messages = ClaudeAgent.get_session_messages("abc-123-def", limit: 10, offset: 5)
|
|
1335
|
-
```
|
|
1336
|
-
|
|
1337
|
-
### Resume a Past Session
|
|
1338
|
-
|
|
1339
|
-
Use with `Conversation.resume` to pick up where you left off:
|
|
1340
|
-
|
|
1341
|
-
```ruby
|
|
1342
|
-
session = ClaudeAgent::Session.where(dir: Dir.pwd, limit: 5).first
|
|
1343
|
-
|
|
1344
|
-
conversation = ClaudeAgent.resume_conversation(session.session_id)
|
|
1345
|
-
turn = conversation.say("Continue where we left off")
|
|
1346
|
-
conversation.close
|
|
1347
|
-
```
|
|
1348
|
-
|
|
1349
|
-
## V2 Session API (Unstable)
|
|
1350
|
-
|
|
1351
|
-
> **Warning**: This API is unstable and may change without notice.
|
|
1352
|
-
|
|
1353
|
-
The V2 Session API provides a simpler interface for multi-turn conversations, matching the TypeScript SDK's `SDKSession` interface.
|
|
1354
|
-
|
|
1355
|
-
### Create a Session
|
|
1356
|
-
|
|
1357
|
-
```ruby
|
|
1358
|
-
# Create a new session
|
|
1359
|
-
session = ClaudeAgent.unstable_v2_create_session(
|
|
1360
|
-
model: "claude-sonnet-4-5-20250514",
|
|
1361
|
-
permission_mode: "acceptEdits"
|
|
1362
|
-
)
|
|
1363
|
-
|
|
1364
|
-
# Send a message
|
|
1365
|
-
session.send("Hello, Claude!")
|
|
1366
|
-
|
|
1367
|
-
# Stream responses
|
|
1368
|
-
session.stream.each do |msg|
|
|
1369
|
-
case msg
|
|
1370
|
-
when ClaudeAgent::AssistantMessage
|
|
1371
|
-
puts msg.text
|
|
1372
|
-
when ClaudeAgent::ResultMessage
|
|
1373
|
-
puts "Done! Cost: $#{msg.total_cost_usd}"
|
|
1374
|
-
end
|
|
1375
|
-
end
|
|
1376
|
-
|
|
1377
|
-
# Continue the conversation
|
|
1378
|
-
session.send("Tell me more")
|
|
1379
|
-
session.stream.each { |msg| puts msg.text if msg.is_a?(ClaudeAgent::AssistantMessage) }
|
|
1380
|
-
|
|
1381
|
-
# Close when done
|
|
1382
|
-
session.close
|
|
1383
|
-
```
|
|
1384
|
-
|
|
1385
|
-
### Resume a Session
|
|
1386
|
-
|
|
1387
|
-
```ruby
|
|
1388
|
-
# Resume an existing session by ID
|
|
1389
|
-
session = ClaudeAgent.unstable_v2_resume_session(
|
|
1390
|
-
"session-abc123",
|
|
1391
|
-
model: "claude-sonnet-4-5-20250514"
|
|
1392
|
-
)
|
|
1393
|
-
|
|
1394
|
-
session.send("What were we discussing?")
|
|
1395
|
-
session.stream.each { |msg| puts msg.text if msg.is_a?(ClaudeAgent::AssistantMessage) }
|
|
1396
|
-
session.close
|
|
1397
|
-
```
|
|
1398
|
-
|
|
1399
|
-
### One-Shot Prompt
|
|
1400
|
-
|
|
1401
|
-
```ruby
|
|
1402
|
-
# Simple one-shot prompt (auto-closes session)
|
|
1403
|
-
result = ClaudeAgent.unstable_v2_prompt(
|
|
1404
|
-
"What is 2 + 2?",
|
|
1405
|
-
model: "claude-sonnet-4-5-20250514"
|
|
1406
|
-
)
|
|
1407
|
-
|
|
1408
|
-
puts "Success: #{result.success?}"
|
|
1409
|
-
puts "Cost: $#{result.total_cost_usd}"
|
|
1410
|
-
```
|
|
1411
|
-
|
|
1412
|
-
### SessionOptions
|
|
1413
|
-
|
|
1414
|
-
The V2 API uses a simplified options type:
|
|
1415
|
-
|
|
1416
|
-
```ruby
|
|
1417
|
-
options = ClaudeAgent::SessionOptions.new(
|
|
1418
|
-
model: "claude-sonnet-4-5-20250514", # Required
|
|
1419
|
-
permission_mode: "acceptEdits", # Optional
|
|
1420
|
-
allowed_tools: ["Read", "Grep"], # Optional
|
|
1421
|
-
disallowed_tools: ["Write"], # Optional
|
|
1422
|
-
can_use_tool: ->(name, input, ctx) { ... }, # Optional
|
|
1423
|
-
hooks: { "PreToolUse" => [...] }, # Optional
|
|
1424
|
-
env: { "MY_VAR" => "value" }, # Optional
|
|
1425
|
-
path_to_claude_code_executable: "/custom/path" # Optional
|
|
1426
|
-
)
|
|
1427
|
-
|
|
1428
|
-
session = ClaudeAgent.unstable_v2_create_session(options)
|
|
1429
|
-
```
|
|
1430
|
-
|
|
1431
|
-
## Types Reference
|
|
1432
|
-
|
|
1433
|
-
### Return Types
|
|
1434
|
-
|
|
1435
|
-
| Type | Purpose |
|
|
1436
|
-
|--------------------------|----------------------------------------------------------------------------------|
|
|
1437
|
-
| `TurnResult` | Complete agent turn with text, tools, usage, and status accessors |
|
|
1438
|
-
| `ToolActivity` | Tool use/result pair with turn index and timing (immutable, post-turn) |
|
|
1439
|
-
| `LiveToolActivity` | Mutable real-time tool status (running/done/error) with elapsed time |
|
|
1440
|
-
| `ToolActivityTracker` | Enumerable collection of `LiveToolActivity` with auto-wiring and `on_change` |
|
|
1441
|
-
| `CumulativeUsage` | Running totals of tokens, cost, turns, and duration |
|
|
1442
|
-
| `PermissionRequest` | Deferred permission promise resolvable from any thread |
|
|
1443
|
-
| `PermissionQueue` | Thread-safe queue of pending permission requests |
|
|
1444
|
-
| `EventHandler` | Typed event callback registry |
|
|
1445
|
-
| `SlashCommand` | Available slash commands (name, description, argument_hint) |
|
|
1446
|
-
| `ModelInfo` | Available models (value, display_name, description) |
|
|
1447
|
-
| `AgentInfo` | Available agents (name, description, model) |
|
|
1448
|
-
| `McpServerStatus` | MCP server status (name, status, server_info) |
|
|
1449
|
-
| `AccountInfo` | Account information (email, organization, subscription_type) |
|
|
1450
|
-
| `ModelUsage` | Per-model usage stats (input_tokens, output_tokens, cost_usd) |
|
|
1451
|
-
| `McpSetServersResult` | Result of set_mcp_servers (added, removed, errors) |
|
|
1452
|
-
| `RewindFilesResult` | Result of rewind_files (can_rewind, error, files_changed, insertions, deletions) |
|
|
1453
|
-
| `Session` | Session finder with `.find`, `.all`, `#messages` (wraps SessionInfo) |
|
|
1454
|
-
| `SessionMessageRelation` | Chainable, Enumerable query object for session messages |
|
|
1455
|
-
| `SessionInfo` | Session metadata from `list_sessions` (session_id, summary, git_branch, cwd) |
|
|
1456
|
-
| `SessionMessage` | Message from a session transcript (type, uuid, session_id, message) |
|
|
1457
|
-
| `SDKPermissionDenial` | Permission denial info (tool_name, tool_use_id, tool_input) |
|
|
1458
|
-
|
|
1459
|
-
## Logging
|
|
1460
|
-
|
|
1461
|
-
The SDK includes optional logging with zero overhead when disabled. All log output is silent by default.
|
|
1462
|
-
|
|
1463
|
-
### Quick Debug
|
|
1464
|
-
|
|
1465
|
-
```ruby
|
|
1466
|
-
# Enable debug logging to stderr
|
|
1467
|
-
ClaudeAgent.debug!
|
|
1468
|
-
|
|
1469
|
-
# Or to a file
|
|
1470
|
-
ClaudeAgent.debug!(output: File.open("claude_agent.log", "a"))
|
|
1471
|
-
```
|
|
1472
|
-
|
|
1473
|
-
### Custom Logger
|
|
1474
|
-
|
|
1475
|
-
Set any `Logger`-compatible instance at the module level:
|
|
1476
|
-
|
|
1477
|
-
```ruby
|
|
1478
|
-
ClaudeAgent.logger = Logger.new($stderr, level: :info)
|
|
1479
|
-
```
|
|
1480
|
-
|
|
1481
|
-
### Per-Query Logger
|
|
1482
|
-
|
|
1483
|
-
Override the module-level logger for a specific query or client:
|
|
1484
|
-
|
|
1485
|
-
```ruby
|
|
1486
|
-
my_logger = Logger.new("query.log", level: :debug)
|
|
1487
|
-
|
|
1488
|
-
ClaudeAgent.query(prompt: "Hello", options: ClaudeAgent::Options.new(logger: my_logger))
|
|
1489
|
-
|
|
1490
|
-
# Or with Client
|
|
1491
|
-
client = ClaudeAgent::Client.new(options: ClaudeAgent::Options.new(logger: my_logger))
|
|
1492
|
-
```
|
|
1493
|
-
|
|
1494
|
-
### Log Output
|
|
1495
|
-
|
|
1496
|
-
When enabled, the SDK logs events across transport, protocol, parsing, MCP, and query layers:
|
|
1497
|
-
|
|
1498
|
-
```
|
|
1499
|
-
[ClaudeAgent] [12:00:00.123] INFO -- transport: Process spawned (pid=12345)
|
|
1500
|
-
[ClaudeAgent] [12:00:00.456] DEBUG -- protocol: Sending control request: initialize (req_1_abc)
|
|
1501
|
-
[ClaudeAgent] [12:00:01.789] INFO -- protocol: Permission decision for Bash: allow
|
|
1502
|
-
[ClaudeAgent] [12:00:02.100] INFO -- query: Query complete (3.45s, cost=$0.012)
|
|
1503
|
-
```
|
|
1504
|
-
|
|
1505
|
-
### Log Levels
|
|
1506
|
-
|
|
1507
|
-
| Level | What's Logged |
|
|
1508
|
-
|-------|----------------------------------------------------------------------------------------------------------------|
|
|
1509
|
-
| ERROR | Control request failures, unknown message types |
|
|
1510
|
-
| WARN | Force kills, JSON parse errors during buffering, unknown MCP tools |
|
|
1511
|
-
| INFO | Process spawn/close, protocol start/stop, permission decisions, tool calls, query start/completion with timing |
|
|
1512
|
-
| DEBUG | Full commands, message types received, control request/response routing, reader thread lifecycle |
|
|
1513
|
-
|
|
1514
|
-
## Environment Variables
|
|
1515
|
-
|
|
1516
|
-
The SDK sets these automatically:
|
|
1517
|
-
|
|
1518
|
-
- `CLAUDE_CODE_ENTRYPOINT=sdk-rb`
|
|
1519
|
-
- `CLAUDE_AGENT_SDK_VERSION=<version>`
|
|
1520
|
-
|
|
1521
|
-
Enable debug logging via environment variable:
|
|
1522
|
-
|
|
1523
|
-
```bash
|
|
1524
|
-
export CLAUDE_AGENT_DEBUG=1
|
|
1525
|
-
```
|
|
1526
|
-
|
|
1527
|
-
Skip version checking (for development):
|
|
1528
|
-
|
|
1529
|
-
```bash
|
|
1530
|
-
export CLAUDE_AGENT_SDK_SKIP_VERSION_CHECK=true
|
|
1531
|
-
```
|
|
90
|
+
| Guide | Description |
|
|
91
|
+
|--------------------------------------------|----------------------------------------------------------|
|
|
92
|
+
| [Getting Started](docs/getting-started.md) | Installation, first queries, multi-turn basics |
|
|
93
|
+
| [Configuration](docs/configuration.md) | Global config, Options, sandbox, agents, env vars |
|
|
94
|
+
| [Conversations](docs/conversations.md) | Multi-turn API, TurnResult, callbacks, tool tracking |
|
|
95
|
+
| [Queries](docs/queries.md) | One-shot interfaces: ask, query_turn, query |
|
|
96
|
+
| [Permissions](docs/permissions.md) | PermissionPolicy DSL, can_use_tool, queue mode |
|
|
97
|
+
| [Hooks](docs/hooks.md) | HookRegistry DSL, hook events, input types |
|
|
98
|
+
| [MCP Tools](docs/mcp.md) | In-process tools, servers, schemas, elicitation |
|
|
99
|
+
| [Events](docs/events.md) | EventHandler, typed callbacks, event layers |
|
|
100
|
+
| [Messages](docs/messages.md) | All 22 message types, 8 content blocks, pattern matching |
|
|
101
|
+
| [Sessions](docs/sessions.md) | Session discovery, mutations, forking, resume |
|
|
102
|
+
| [Client](docs/client.md) | Low-level bidirectional API (advanced) |
|
|
103
|
+
| [Errors](docs/errors.md) | Error hierarchy and handling patterns |
|
|
104
|
+
| [Logging](docs/logging.md) | Debug logging, custom loggers, log levels |
|
|
105
|
+
| [Architecture](docs/architecture.md) | Internal design, data flow, module map |
|
|
1532
106
|
|
|
1533
107
|
## Development
|
|
1534
108
|
|
|
1535
109
|
```bash
|
|
1536
|
-
# Install dependencies
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
#
|
|
1540
|
-
bundle exec
|
|
1541
|
-
|
|
1542
|
-
# Run integration tests (requires Claude Code CLI v2.0.0+)
|
|
1543
|
-
bundle exec rake test_integration
|
|
1544
|
-
|
|
1545
|
-
# Run all tests
|
|
1546
|
-
bundle exec rake test_all
|
|
1547
|
-
|
|
1548
|
-
# Validate RBS type signatures
|
|
1549
|
-
bundle exec rake rbs
|
|
1550
|
-
|
|
1551
|
-
# Linting
|
|
1552
|
-
bundle exec rubocop
|
|
1553
|
-
|
|
1554
|
-
# Interactive console
|
|
1555
|
-
bin/console
|
|
1556
|
-
|
|
1557
|
-
# Binstubs for convenience
|
|
1558
|
-
bin/test # Unit tests only
|
|
1559
|
-
bin/test-integration # Integration tests
|
|
1560
|
-
bin/test-all # All tests
|
|
1561
|
-
bin/rbs-validate # Validate RBS signatures
|
|
1562
|
-
bin/release 1.2.0 # Release a new version
|
|
1563
|
-
```
|
|
1564
|
-
|
|
1565
|
-
## Architecture
|
|
1566
|
-
|
|
1567
|
-
```
|
|
1568
|
-
ClaudeAgent.conversation() / ClaudeAgent::Conversation
|
|
1569
|
-
│
|
|
1570
|
-
│ Manages lifecycle, callbacks, turn history, tool activity
|
|
1571
|
-
│
|
|
1572
|
-
▼
|
|
1573
|
-
ClaudeAgent::Client
|
|
1574
|
-
│
|
|
1575
|
-
│ Event handlers, cumulative usage, permission queue
|
|
1576
|
-
│
|
|
1577
|
-
▼
|
|
1578
|
-
┌──────────────────────────┐
|
|
1579
|
-
│ Control Protocol │ Request/response routing
|
|
1580
|
-
│ - Hooks │ Permission callbacks
|
|
1581
|
-
│ - MCP bridging │ Tool interception
|
|
1582
|
-
└──────────┬───────────────┘
|
|
1583
|
-
│
|
|
1584
|
-
▼
|
|
1585
|
-
┌──────────────────────────┐
|
|
1586
|
-
│ Subprocess Transport │ JSON Lines protocol
|
|
1587
|
-
│ - stdin/stdout │ Process management
|
|
1588
|
-
│ - stderr handling │
|
|
1589
|
-
└──────────┬───────────────┘
|
|
1590
|
-
│
|
|
1591
|
-
▼
|
|
1592
|
-
Claude Code CLI
|
|
110
|
+
bin/setup # Install dependencies
|
|
111
|
+
bundle exec rake # Tests + RBS + RuboCop
|
|
112
|
+
bundle exec rake test # Unit tests
|
|
113
|
+
bundle exec rake test_integration # Integration tests (requires CLI v2.0.0+)
|
|
114
|
+
bundle exec rubocop # Lint
|
|
115
|
+
bin/console # IRB with gem loaded
|
|
1593
116
|
```
|
|
1594
117
|
|
|
1595
118
|
## License
|