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/docs/events.md
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Events
|
|
2
|
+
|
|
3
|
+
The `EventHandler` dispatches typed events as messages flow through a conversation turn, replacing manual `case` statements over raw message types.
|
|
4
|
+
|
|
5
|
+
Three event layers fire for every message, in order:
|
|
6
|
+
|
|
7
|
+
1. **Catch-all** -- `:message` fires for every message regardless of type.
|
|
8
|
+
2. **Type-based** -- the message's `type` fires (e.g., `:assistant`, `:result`, `:stream_event`).
|
|
9
|
+
3. **Decomposed** -- convenience events extracted from rich content (`:text`, `:thinking`, `:tool_use`, `:tool_result`).
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
### Block DSL with `EventHandler.define`
|
|
14
|
+
|
|
15
|
+
Build a handler in a single expression using the block DSL. The block is evaluated in the context of the new handler, so `on_*` methods are available directly:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
handler = ClaudeAgent::EventHandler.define do
|
|
19
|
+
on_text { |text| print text }
|
|
20
|
+
on_result { |r| puts "\nCost: $#{r.total_cost_usd}" }
|
|
21
|
+
on_tool_use { |tool| puts "Tool: #{tool.display_label}" }
|
|
22
|
+
end
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Pass the handler to `query_turn`:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
turn = ClaudeAgent.query_turn(prompt: "Explain Ruby", events: handler)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Traditional construction
|
|
32
|
+
|
|
33
|
+
Create an instance and chain `on_*` calls. Each returns `self`, so calls can be chained:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
handler = ClaudeAgent::EventHandler.new
|
|
37
|
+
.on_text { |text| print text }
|
|
38
|
+
.on_thinking { |thought| $stderr.puts "[thinking] #{thought}" }
|
|
39
|
+
.on_tool_use { |tool| puts "Using: #{tool.display_label}" }
|
|
40
|
+
.on_result { |r| puts "\nDone (cost=$#{r.total_cost_usd})" }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Or register handlers one at a time:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
handler = ClaudeAgent::EventHandler.new
|
|
47
|
+
handler.on_text { |text| print text }
|
|
48
|
+
handler.on_result { |r| puts "Done!" }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Via Conversation
|
|
52
|
+
|
|
53
|
+
`Conversation.new` accepts `on_*` keyword arguments for any event. These are registered on the underlying `Client` and persist across turns:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
conversation = ClaudeAgent::Conversation.new(
|
|
57
|
+
model: "claude-sonnet-4-5-20250514",
|
|
58
|
+
on_text: ->(text) { print text },
|
|
59
|
+
on_thinking: ->(thought) { $stderr.puts thought },
|
|
60
|
+
on_tool_use: ->(tool) { puts "Tool: #{tool.display_label}" },
|
|
61
|
+
on_tool_result: ->(result, tool_use) { puts "Result for #{tool_use&.name}" },
|
|
62
|
+
on_result: ->(r) { puts "\nCost: $#{r.total_cost_usd}" },
|
|
63
|
+
on_message: ->(msg) { log(msg) },
|
|
64
|
+
on_stream_event: ->(evt) { handle_stream(evt) },
|
|
65
|
+
on_status: ->(status) { show_status(status) },
|
|
66
|
+
on_tool_progress: ->(prog) { update_spinner(prog) }
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
turn = conversation.say("Fix the bug in auth.rb")
|
|
70
|
+
conversation.close
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`on_stream` is an alias for `on_text`:
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
conversation = ClaudeAgent::Conversation.new(
|
|
77
|
+
on_stream: ->(text) { print text }
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Via Client
|
|
82
|
+
|
|
83
|
+
Register event handlers directly on a `Client`. Handlers persist across turns and fire automatically during `receive_turn` and `send_and_receive`:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
client = ClaudeAgent::Client.new
|
|
87
|
+
client.on_text { |text| print text }
|
|
88
|
+
client.on_tool_use { |tool| puts "Using: #{tool.display_label}" }
|
|
89
|
+
client.on_result { |r| puts "\nDone!" }
|
|
90
|
+
|
|
91
|
+
client.connect
|
|
92
|
+
turn = client.send_and_receive("Fix the bug")
|
|
93
|
+
client.disconnect
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The generic `on` method also works:
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
client.on(:text) { |text| print text }
|
|
100
|
+
client.on(:stream_event) { |evt| handle_stream(evt) }
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Via one-shot query
|
|
104
|
+
|
|
105
|
+
Pass an `events:` keyword to `ClaudeAgent.query_turn` for one-shot queries:
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
events = ClaudeAgent::EventHandler.define do
|
|
109
|
+
on_text { |text| print text }
|
|
110
|
+
on_result { |r| puts "\nCost: $#{r.total_cost_usd}" }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
turn = ClaudeAgent.query_turn(prompt: "Explain concurrency", events: events)
|
|
114
|
+
puts turn.text
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The handler's `reset!` is called automatically when the turn completes.
|
|
118
|
+
|
|
119
|
+
## Standalone usage
|
|
120
|
+
|
|
121
|
+
Create a handler and dispatch messages manually with `handle`:
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
handler = ClaudeAgent::EventHandler.new
|
|
125
|
+
handler.on_text { |text| print text }
|
|
126
|
+
handler.on_tool_use { |tool| log_tool(tool) }
|
|
127
|
+
handler.on_tool_result { |result, tool_use| log_result(result, tool_use) }
|
|
128
|
+
|
|
129
|
+
client.receive_response.each { |msg| handler.handle(msg) }
|
|
130
|
+
handler.reset!
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`handle` fires events in order:
|
|
134
|
+
|
|
135
|
+
1. `:message` (catch-all)
|
|
136
|
+
2. `message.type` (type-based)
|
|
137
|
+
3. Decomposed events extracted from the message content
|
|
138
|
+
|
|
139
|
+
Call `reset!` between turns to clear internal state (pending tool use tracking). When used via `Client` or `query_turn`, this is called automatically.
|
|
140
|
+
|
|
141
|
+
## Tool use and tool result pairing
|
|
142
|
+
|
|
143
|
+
The handler tracks pending tool uses internally. When a `:tool_result` event fires, it receives both the result block and the original tool use block that triggered it:
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
handler.on_tool_use do |tool|
|
|
147
|
+
puts "Requested: #{tool.name} (#{tool.id})"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
handler.on_tool_result do |result, tool_use|
|
|
151
|
+
puts "Result for: #{tool_use&.name}" # tool_use is the matched ToolUseBlock
|
|
152
|
+
puts "Error: #{result.is_error}" if result.is_error
|
|
153
|
+
end
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The `tool_use` argument is `nil` if no matching tool use was found (which should not happen in normal operation).
|
|
157
|
+
|
|
158
|
+
## Utility methods
|
|
159
|
+
|
|
160
|
+
### `has_handlers?`
|
|
161
|
+
|
|
162
|
+
Returns whether any handlers have been registered:
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
handler = ClaudeAgent::EventHandler.new
|
|
166
|
+
handler.has_handlers? # => false
|
|
167
|
+
|
|
168
|
+
handler.on_text { |t| print t }
|
|
169
|
+
handler.has_handlers? # => true
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### `reset!`
|
|
173
|
+
|
|
174
|
+
Clears turn-level tracking state (pending tool uses). Does not remove registered handlers:
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
handler.reset! # Clear pending tool uses between turns
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Event reference
|
|
181
|
+
|
|
182
|
+
### Meta events
|
|
183
|
+
|
|
184
|
+
| Event | Receives | Description |
|
|
185
|
+
|------------|--------------------|-------------------------------------|
|
|
186
|
+
| `:message` | Any message object | Fires for every message (catch-all) |
|
|
187
|
+
|
|
188
|
+
### Type-based events
|
|
189
|
+
|
|
190
|
+
Each fires when a message with the matching `type` is received. The handler receives the full message object.
|
|
191
|
+
|
|
192
|
+
| Event | Description |
|
|
193
|
+
|-------------------------|-------------------------------------------------------|
|
|
194
|
+
| `:user` | User message |
|
|
195
|
+
| `:assistant` | Assistant message (contains text, thinking, tool use) |
|
|
196
|
+
| `:system` | System message (init, session info) |
|
|
197
|
+
| `:result` | End-of-turn result (cost, usage, session ID) |
|
|
198
|
+
| `:stream_event` | Raw stream event |
|
|
199
|
+
| `:compact_boundary` | Context window compaction boundary |
|
|
200
|
+
| `:status` | Status update |
|
|
201
|
+
| `:tool_progress` | Tool execution progress |
|
|
202
|
+
| `:hook_response` | Hook execution response |
|
|
203
|
+
| `:auth_status` | Authentication status |
|
|
204
|
+
| `:task_notification` | Background task notification |
|
|
205
|
+
| `:hook_started` | Hook execution started |
|
|
206
|
+
| `:hook_progress` | Hook execution progress |
|
|
207
|
+
| `:tool_use_summary` | Tool use summary |
|
|
208
|
+
| `:task_started` | Background task started |
|
|
209
|
+
| `:task_progress` | Background task progress |
|
|
210
|
+
| `:rate_limit_event` | Rate limit information |
|
|
211
|
+
| `:prompt_suggestion` | Suggested follow-up prompt |
|
|
212
|
+
| `:files_persisted` | File checkpoint persisted |
|
|
213
|
+
| `:elicitation_complete` | Elicitation completed |
|
|
214
|
+
| `:local_command_output` | Local command output |
|
|
215
|
+
|
|
216
|
+
### Decomposed events
|
|
217
|
+
|
|
218
|
+
Extracted from the content of assistant and user messages. These fire after the type-based event.
|
|
219
|
+
|
|
220
|
+
| Event | Receives | Description |
|
|
221
|
+
|----------------|--------------------------------------------|-------------------------------------------------------------------------|
|
|
222
|
+
| `:text` | `String` | Concatenated text from an assistant message |
|
|
223
|
+
| `:thinking` | `String` | Concatenated thinking from an assistant message |
|
|
224
|
+
| `:tool_use` | `ToolUseBlock` or `ServerToolUseBlock` | A tool use request from an assistant message |
|
|
225
|
+
| `:tool_result` | `ToolResultBlock`, `ToolUseBlock` or `nil` | A tool result from a user message, paired with its originating tool use |
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
This guide walks you through the ClaudeAgent Ruby SDK from first install to multi-turn conversations. Each section builds on the last, starting with the simplest API.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- **Ruby 3.2+** (the SDK uses `Data.define` for immutable types)
|
|
8
|
+
- **Claude Code CLI v2.0.0+** ([install guide](https://code.claude.com/docs/en/getting-started))
|
|
9
|
+
|
|
10
|
+
Verify both are available:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
ruby -v # >= 3.2.0
|
|
14
|
+
claude -v # >= 2.0.0
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Add to your Gemfile:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
gem "claude_agent"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bundle install
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or install directly:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
gem install claude_agent
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Your First Query
|
|
38
|
+
|
|
39
|
+
The fastest way to get a response is `ClaudeAgent.ask`. It sends a single prompt and returns a `TurnResult`:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
require "claude_agent"
|
|
43
|
+
|
|
44
|
+
turn = ClaudeAgent.ask("What is the capital of France?")
|
|
45
|
+
puts turn.text
|
|
46
|
+
# => "The capital of France is Paris."
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`TurnResult` gives you structured access to everything that happened during the turn:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
turn = ClaudeAgent.ask("Explain Ruby's GIL in one sentence.")
|
|
53
|
+
|
|
54
|
+
puts turn.text # Combined text from all assistant messages
|
|
55
|
+
puts turn.cost # Total cost in USD (e.g., 0.003)
|
|
56
|
+
puts turn.duration_ms # Wall-clock time in milliseconds
|
|
57
|
+
puts turn.session_id # Session ID (for resuming later)
|
|
58
|
+
puts turn.model # Model that handled the request
|
|
59
|
+
puts turn.tool_uses.size # Number of tools Claude invoked
|
|
60
|
+
puts turn.success? # true if no errors
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Passing Options
|
|
64
|
+
|
|
65
|
+
Override defaults with keyword arguments:
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
turn = ClaudeAgent.ask("Fix the bug in auth.rb",
|
|
69
|
+
model: "opus",
|
|
70
|
+
max_turns: 5,
|
|
71
|
+
permission_mode: "acceptEdits"
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Streaming
|
|
76
|
+
|
|
77
|
+
Pass a block to `ask` to receive each message as it arrives:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
turn = ClaudeAgent.ask("Explain how TCP works") do |message|
|
|
81
|
+
case message
|
|
82
|
+
when ClaudeAgent::AssistantMessage
|
|
83
|
+
print message.text
|
|
84
|
+
when ClaudeAgent::ResultMessage
|
|
85
|
+
puts "\n--- Done (cost: $#{message.total_cost_usd}) ---"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The block receives every message in the protocol stream. The return value is still a `TurnResult` with the full accumulated data.
|
|
91
|
+
|
|
92
|
+
### Streaming with Callbacks
|
|
93
|
+
|
|
94
|
+
For cleaner streaming, use `on_stream` to receive just the text:
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
turn = ClaudeAgent.ask("Write a haiku about Ruby",
|
|
98
|
+
on_stream: ->(text) { print text }
|
|
99
|
+
)
|
|
100
|
+
puts "\nCost: $#{turn.cost}"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Multi-Turn Conversations
|
|
104
|
+
|
|
105
|
+
Use `ClaudeAgent.chat` for back-and-forth conversations. The block form auto-closes the connection when done:
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
ClaudeAgent.chat do |c|
|
|
109
|
+
c.say("What files are in the current directory?")
|
|
110
|
+
c.say("Which one is the largest?")
|
|
111
|
+
c.say("Show me the first 10 lines of that file.")
|
|
112
|
+
|
|
113
|
+
puts "Total cost: $#{c.total_cost}"
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Each call to `say` returns a `TurnResult`:
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
ClaudeAgent.chat(model: "opus") do |c|
|
|
121
|
+
turn = c.say("Write a function that reverses a string")
|
|
122
|
+
puts turn.text
|
|
123
|
+
|
|
124
|
+
turn = c.say("Now add error handling")
|
|
125
|
+
puts turn.text
|
|
126
|
+
puts "Tools used: #{turn.tool_uses.map(&:name).join(", ")}"
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Without a Block
|
|
131
|
+
|
|
132
|
+
If you need the conversation to outlive a block, skip it:
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
conversation = ClaudeAgent.chat(model: "sonnet")
|
|
136
|
+
conversation.say("Hello")
|
|
137
|
+
conversation.say("Tell me more")
|
|
138
|
+
conversation.close # Always close when done
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Streaming in Conversations
|
|
142
|
+
|
|
143
|
+
Pass `on_stream` to print text as it arrives:
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
ClaudeAgent.chat(on_stream: ->(text) { print text }) do |c|
|
|
147
|
+
c.say("What is 2+2?")
|
|
148
|
+
puts # newline after streamed output
|
|
149
|
+
c.say("Now multiply that by 10")
|
|
150
|
+
puts
|
|
151
|
+
end
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Or stream per-turn with a block on `say`:
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
ClaudeAgent.chat do |c|
|
|
158
|
+
c.say("Explain monads") do |message|
|
|
159
|
+
print message.text if message.is_a?(ClaudeAgent::AssistantMessage)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Global Configuration
|
|
165
|
+
|
|
166
|
+
Set defaults that apply to every `ask` and `chat` call:
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
ClaudeAgent.model = "opus"
|
|
170
|
+
ClaudeAgent.max_turns = 10
|
|
171
|
+
ClaudeAgent.permission_mode = "acceptEdits"
|
|
172
|
+
ClaudeAgent.system_prompt = "You are a helpful coding assistant."
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Or configure in bulk:
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
ClaudeAgent.configure do |c|
|
|
179
|
+
c.model = "opus"
|
|
180
|
+
c.max_turns = 10
|
|
181
|
+
c.permission_mode = "acceptEdits"
|
|
182
|
+
c.system_prompt = "You are a helpful coding assistant."
|
|
183
|
+
c.max_budget_usd = 1.0
|
|
184
|
+
c.cwd = "/path/to/project"
|
|
185
|
+
end
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Per-request keyword arguments always override global defaults:
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
ClaudeAgent.model = "sonnet"
|
|
192
|
+
|
|
193
|
+
# This request uses opus despite the global default
|
|
194
|
+
turn = ClaudeAgent.ask("Complex question", model: "opus")
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Reset to defaults with:
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
ClaudeAgent.reset_config!
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
See [Configuration](configuration.md) for the full list of options.
|
|
204
|
+
|
|
205
|
+
## The Conversation Class
|
|
206
|
+
|
|
207
|
+
For full control, use `ClaudeAgent::Conversation` directly. It supports callbacks, permission handling, tool tracking, and session management.
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
conversation = ClaudeAgent::Conversation.open(
|
|
211
|
+
model: "opus",
|
|
212
|
+
max_turns: 10,
|
|
213
|
+
permission_mode: "acceptEdits",
|
|
214
|
+
on_stream: ->(text) { print text },
|
|
215
|
+
on_tool_use: ->(tool) { puts "\nUsing tool: #{tool.name}" },
|
|
216
|
+
on_result: ->(result) { puts "\nTurn cost: $#{result.total_cost_usd}" }
|
|
217
|
+
) do |c|
|
|
218
|
+
c.say("Read the file config.rb and explain what it does")
|
|
219
|
+
c.say("Refactor it to use keyword arguments")
|
|
220
|
+
|
|
221
|
+
puts "Session: #{c.session_id}"
|
|
222
|
+
puts "Total cost: $#{c.total_cost}"
|
|
223
|
+
puts "Turns: #{c.turns.size}"
|
|
224
|
+
end
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Available Callbacks
|
|
228
|
+
|
|
229
|
+
| Callback | Receives | When |
|
|
230
|
+
|------------------|---------------------------------|-------------------------------------------|
|
|
231
|
+
| `on_stream` | `String` | Each text chunk as it streams |
|
|
232
|
+
| `on_text` | `String` | Full text from each assistant message |
|
|
233
|
+
| `on_thinking` | `String` | Thinking/reasoning content |
|
|
234
|
+
| `on_tool_use` | `ToolUseBlock` | Claude requests a tool |
|
|
235
|
+
| `on_tool_result` | `ToolResultBlock, ToolUseBlock` | Tool result arrives (paired with request) |
|
|
236
|
+
| `on_result` | `ResultMessage` | Turn completes |
|
|
237
|
+
| `on_message` | `Message` | Every message (catch-all) |
|
|
238
|
+
|
|
239
|
+
### Permission Handling
|
|
240
|
+
|
|
241
|
+
By default, Conversation queues permission requests for you to handle. You can also set a mode or provide a custom callback:
|
|
242
|
+
|
|
243
|
+
```ruby
|
|
244
|
+
# Use a CLI permission mode
|
|
245
|
+
ClaudeAgent::Conversation.open(on_permission: :accept_edits) do |c|
|
|
246
|
+
c.say("Fix the typo in README.md")
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Or provide a lambda
|
|
250
|
+
ClaudeAgent::Conversation.open(
|
|
251
|
+
on_permission: ->(tool_name, input, ctx) {
|
|
252
|
+
ClaudeAgent::PermissionResultAllow.new
|
|
253
|
+
}
|
|
254
|
+
) do |c|
|
|
255
|
+
c.say("Update the config file")
|
|
256
|
+
end
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
For declarative permission rules, see [Permissions](permissions.md).
|
|
260
|
+
|
|
261
|
+
## Resuming Sessions
|
|
262
|
+
|
|
263
|
+
Every turn returns a `session_id`. Save it to resume the conversation later:
|
|
264
|
+
|
|
265
|
+
```ruby
|
|
266
|
+
# First session
|
|
267
|
+
session_id = nil
|
|
268
|
+
ClaudeAgent.chat do |c|
|
|
269
|
+
c.say("Let's work on the authentication module")
|
|
270
|
+
session_id = c.session_id
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Later — resume where you left off
|
|
274
|
+
ClaudeAgent.resume_conversation(session_id) do |c|
|
|
275
|
+
c.say("Continue with the tests we discussed")
|
|
276
|
+
end
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
`resume_conversation` returns a `Conversation`, so it supports the same block form and callbacks.
|
|
280
|
+
|
|
281
|
+
You can also resume via `Conversation.resume` directly:
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
conversation = ClaudeAgent::Conversation.resume(session_id,
|
|
285
|
+
on_stream: ->(text) { print text }
|
|
286
|
+
)
|
|
287
|
+
conversation.say("Pick up where we left off")
|
|
288
|
+
conversation.close
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Listing Past Sessions
|
|
292
|
+
|
|
293
|
+
Browse previous sessions without spawning a CLI process:
|
|
294
|
+
|
|
295
|
+
```ruby
|
|
296
|
+
sessions = ClaudeAgent.list_sessions(dir: "/path/to/project", limit: 10)
|
|
297
|
+
sessions.each do |session|
|
|
298
|
+
puts "#{session.session_id}: #{session.title} (#{session.updated_at})"
|
|
299
|
+
end
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## What's Next
|
|
303
|
+
|
|
304
|
+
- [Configuration](configuration.md) -- full list of options, permission modes, and sandbox settings
|
|
305
|
+
- [Conversations](conversations.md) -- advanced multi-turn patterns, tool tracking, and abort handling
|
|
306
|
+
- [Permissions](permissions.md) -- declarative permission policies and the `can_use_tool` callback
|
|
307
|
+
- [Hooks](hooks.md) -- lifecycle hooks for tool use, session events, and more
|
|
308
|
+
- [Messages](messages.md) -- all message types, content blocks, and the event system
|
|
309
|
+
- [MCP Servers](mcp-servers.md) -- integrating external tools via Model Context Protocol
|
|
310
|
+
- [Sessions](sessions.md) -- listing, reading, forking, and managing past sessions
|