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/client.md
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
# Client
|
|
2
|
+
|
|
3
|
+
The `Client` class provides fine-grained, bidirectional control over a persistent Claude Code CLI connection. It supports multi-turn conversations, streaming, interrupts, dynamic model and permission changes, file checkpointing, and MCP server management.
|
|
4
|
+
|
|
5
|
+
> **Most users should prefer the higher-level APIs.** `ClaudeAgent.ask` handles one-shot queries with global configuration. `ClaudeAgent.chat` and `Conversation` manage multi-turn state, auto-connection, event callbacks, tool activity tracking, and cleanup. Reach for `Client` only when you need direct control over connection lifecycle, split send/receive, or protocol-level commands that `Conversation` does not expose.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
`Client` wraps a `ControlProtocol` over a `Transport::Subprocess`, giving you:
|
|
10
|
+
|
|
11
|
+
- Persistent connection with explicit connect/disconnect
|
|
12
|
+
- Multiple conversation turns over a single CLI process
|
|
13
|
+
- Streaming message delivery via enumerators or blocks
|
|
14
|
+
- Typed event handlers that persist across turns
|
|
15
|
+
- Control commands: model switching, permission changes, file rewind, MCP management
|
|
16
|
+
- Abort/interrupt support with partial result recovery
|
|
17
|
+
- Cumulative usage tracking across all turns
|
|
18
|
+
- Asynchronous permission queue for UI-driven approval flows
|
|
19
|
+
|
|
20
|
+
## Creating and Connecting
|
|
21
|
+
|
|
22
|
+
### Constructor
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
client = ClaudeAgent::Client.new(
|
|
26
|
+
options: ClaudeAgent::Options.new(model: "opus", max_turns: 10),
|
|
27
|
+
transport: nil # defaults to Transport::Subprocess
|
|
28
|
+
)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Both parameters are optional. When omitted, `options` defaults to a bare `Options.new` and `transport` defaults to a `Transport::Subprocess` built from those options.
|
|
32
|
+
|
|
33
|
+
### Connecting
|
|
34
|
+
|
|
35
|
+
Call `connect` to start the CLI subprocess and perform the protocol handshake. An optional `prompt` sends an initial message immediately after connection.
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
client.connect
|
|
39
|
+
client.connect(prompt: "You are a helpful coding assistant")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Raises `CLIConnectionError` if the client is already connected.
|
|
43
|
+
|
|
44
|
+
### Block form
|
|
45
|
+
|
|
46
|
+
`Client.open` connects, yields the client, and guarantees disconnection:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
ClaudeAgent::Client.open(
|
|
50
|
+
options: ClaudeAgent::Options.new(model: "opus"),
|
|
51
|
+
prompt: "Hello"
|
|
52
|
+
) do |client|
|
|
53
|
+
client.send_message("Fix the bug")
|
|
54
|
+
client.receive_response.each { |msg| puts msg }
|
|
55
|
+
end
|
|
56
|
+
# client is automatically disconnected here
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Sending and Receiving
|
|
60
|
+
|
|
61
|
+
### send_and_receive
|
|
62
|
+
|
|
63
|
+
The primary method for a complete turn. Sends a message and blocks until a `ResultMessage` arrives, accumulating everything into a `TurnResult`. Dispatches registered event handlers as messages flow through.
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
turn = client.send_and_receive("Fix the bug in auth.rb")
|
|
67
|
+
puts turn.text
|
|
68
|
+
puts "Cost: $#{turn.cost}"
|
|
69
|
+
puts "Tools used: #{turn.tool_uses.map(&:display_label).join(", ")}"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
With a streaming block:
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
turn = client.send_and_receive("Fix the bug") do |msg|
|
|
76
|
+
case msg
|
|
77
|
+
when ClaudeAgent::AssistantMessage
|
|
78
|
+
print msg.text
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Parameters:
|
|
84
|
+
|
|
85
|
+
| Parameter | Type | Default | Description |
|
|
86
|
+
|---------------|-------------------|-------------|--------------------------------------|
|
|
87
|
+
| `content` | `String`, `Array` | required | Message content |
|
|
88
|
+
| `session_id:` | `String` | `"default"` | Session ID for multi-session support |
|
|
89
|
+
| `uuid:` | `String`, `nil` | `nil` | Message UUID for file checkpointing |
|
|
90
|
+
|
|
91
|
+
Returns a `TurnResult`. See the [Queries](queries.md) doc for `TurnResult` accessors.
|
|
92
|
+
|
|
93
|
+
### Split send/receive
|
|
94
|
+
|
|
95
|
+
For finer control, separate the send and receive steps.
|
|
96
|
+
|
|
97
|
+
**send_message** queues a message to the CLI without waiting for a response:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
client.send_message("Hello")
|
|
101
|
+
client.send_message("Follow up", session_id: "session-2", uuid: "msg-uuid-1")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**query** is an alias for `send_message`:
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
client.query("Hello")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**receive_turn** blocks until a `ResultMessage` arrives, returning a `TurnResult`. It dispatches event handlers and resets turn-level handler state afterward.
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
client.send_message("Fix the bug")
|
|
114
|
+
turn = client.receive_turn
|
|
115
|
+
puts turn.text
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
With a block:
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
turn = client.receive_turn do |msg|
|
|
122
|
+
print msg.text if msg.is_a?(ClaudeAgent::AssistantMessage)
|
|
123
|
+
end
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**receive_response** returns an `Enumerator` of messages for the current turn (until a `ResultMessage`). No event dispatch or `TurnResult` accumulation -- you process each message yourself.
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
client.send_message("Hello")
|
|
130
|
+
client.receive_response.each do |msg|
|
|
131
|
+
case msg
|
|
132
|
+
when ClaudeAgent::AssistantMessage
|
|
133
|
+
print msg.text
|
|
134
|
+
when ClaudeAgent::ResultMessage
|
|
135
|
+
puts "\nDone: #{msg.total_cost_usd}"
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**receive_messages** returns an `Enumerator` over all messages until the connection closes (not just one turn):
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
client.receive_messages.each { |msg| handle(msg) }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Event Handlers
|
|
147
|
+
|
|
148
|
+
Register typed callbacks that fire automatically during `receive_turn` and `send_and_receive`. Handlers persist across turns -- register once, and they fire on every subsequent turn.
|
|
149
|
+
|
|
150
|
+
### Registering handlers
|
|
151
|
+
|
|
152
|
+
Use `on` with a symbol, or the `on_*` convenience methods:
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
client.on(:text) { |text| print text }
|
|
156
|
+
client.on(:tool_use) { |tool| puts "Using: #{tool.display_label}" }
|
|
157
|
+
client.on(:result) { |result| puts "Cost: $#{result.total_cost_usd}" }
|
|
158
|
+
|
|
159
|
+
# Equivalent convenience methods:
|
|
160
|
+
client.on_text { |text| print text }
|
|
161
|
+
client.on_tool_use { |tool| puts "Using: #{tool.display_label}" }
|
|
162
|
+
client.on_result { |result| puts "Cost: $#{result.total_cost_usd}" }
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Chaining
|
|
166
|
+
|
|
167
|
+
All registration methods return `self`, so they chain:
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
client
|
|
171
|
+
.on(:text) { |text| print text }
|
|
172
|
+
.on(:tool_use) { |tool| show_spinner(tool) }
|
|
173
|
+
.on(:result) { |r| puts "\nDone!" }
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Event hierarchy
|
|
177
|
+
|
|
178
|
+
Events fire in three layers for each message:
|
|
179
|
+
|
|
180
|
+
1. **Catch-all** -- `:message` fires for every message
|
|
181
|
+
2. **Type-based** -- the message's type fires (e.g., `:assistant`, `:stream_event`, `:status`)
|
|
182
|
+
3. **Decomposed** -- convenience events extracted from rich content types
|
|
183
|
+
|
|
184
|
+
**Decomposed events:**
|
|
185
|
+
|
|
186
|
+
| Event | Argument | Source |
|
|
187
|
+
|----------------|--------------------------------------------|--------------------------------------------------------------|
|
|
188
|
+
| `:text` | `String` | Text from `AssistantMessage` |
|
|
189
|
+
| `:thinking` | `String` | Thinking from `AssistantMessage` |
|
|
190
|
+
| `:tool_use` | `ToolUseBlock` or `ServerToolUseBlock` | Tool call from `AssistantMessage` |
|
|
191
|
+
| `:tool_result` | `ToolResultBlock`, `ToolUseBlock` or `nil` | Tool result from `UserMessage`, paired with original request |
|
|
192
|
+
|
|
193
|
+
**Type-based events:**
|
|
194
|
+
|
|
195
|
+
`:user`, `:assistant`, `:system`, `:result`, `:stream_event`, `:compact_boundary`, `:status`, `:tool_progress`, `:hook_response`, `:auth_status`, `:task_notification`, `:hook_started`, `:hook_progress`, `:tool_use_summary`, `:task_started`, `:task_progress`, `:rate_limit_event`, `:prompt_suggestion`, `:files_persisted`, `:elicitation_complete`, `:local_command_output`
|
|
196
|
+
|
|
197
|
+
## Control Methods
|
|
198
|
+
|
|
199
|
+
These methods send control commands to the running CLI process. All require an active connection.
|
|
200
|
+
|
|
201
|
+
### set_model
|
|
202
|
+
|
|
203
|
+
Change the model for subsequent turns:
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
client.set_model("claude-sonnet-4-5-20250514")
|
|
207
|
+
client.set_model(nil) # revert to default
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### set_permission_mode
|
|
211
|
+
|
|
212
|
+
Change the permission mode:
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
client.set_permission_mode("acceptEdits")
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### set_max_thinking_tokens
|
|
219
|
+
|
|
220
|
+
Set or reset the maximum thinking tokens:
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
client.set_max_thinking_tokens(10_000)
|
|
224
|
+
client.set_max_thinking_tokens(nil) # reset to default
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### stop_task
|
|
228
|
+
|
|
229
|
+
Stop a running background task by its ID (from `task_notification` events):
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
client.stop_task("task-123")
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### apply_flag_settings
|
|
236
|
+
|
|
237
|
+
Merge settings into the flag settings layer:
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
client.apply_flag_settings({ "model" => "claude-sonnet-4-5-20250514" })
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## File Checkpointing
|
|
244
|
+
|
|
245
|
+
When `enable_file_checkpointing: true` is set in Options and UUIDs are passed with messages, you can rewind files to the state at a specific user message.
|
|
246
|
+
|
|
247
|
+
### rewind_files
|
|
248
|
+
|
|
249
|
+
```ruby
|
|
250
|
+
result = client.rewind_files("user-message-uuid")
|
|
251
|
+
result.can_rewind # => true
|
|
252
|
+
result.files_changed # => ["src/foo.rb", "src/bar.rb"]
|
|
253
|
+
result.insertions # => 10
|
|
254
|
+
result.deletions # => 5
|
|
255
|
+
result.error # => nil
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Dry-run mode previews changes without modifying files:
|
|
259
|
+
|
|
260
|
+
```ruby
|
|
261
|
+
result = client.rewind_files("user-message-uuid", dry_run: true)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Returns a `RewindFilesResult` with fields: `can_rewind`, `error`, `files_changed`, `insertions`, `deletions`.
|
|
265
|
+
|
|
266
|
+
## Dynamic MCP Management
|
|
267
|
+
|
|
268
|
+
Manage MCP (Model Context Protocol) servers on a live connection without restarting.
|
|
269
|
+
|
|
270
|
+
### set_mcp_servers
|
|
271
|
+
|
|
272
|
+
Replace the set of dynamically-added MCP servers. New servers are connected; removed servers are disconnected.
|
|
273
|
+
|
|
274
|
+
```ruby
|
|
275
|
+
result = client.set_mcp_servers({
|
|
276
|
+
"my-server" => { type: "stdio", command: "node", args: ["server.js"] }
|
|
277
|
+
})
|
|
278
|
+
result.added # => ["my-server"]
|
|
279
|
+
result.removed # => ["old-server"]
|
|
280
|
+
result.errors # => {} or {"server2" => "Connection failed"}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Returns a `McpSetServersResult` with fields: `added`, `removed`, `errors`.
|
|
284
|
+
|
|
285
|
+
### mcp_reconnect
|
|
286
|
+
|
|
287
|
+
Reconnect to a disconnected or errored MCP server:
|
|
288
|
+
|
|
289
|
+
```ruby
|
|
290
|
+
client.mcp_reconnect("my-server")
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### mcp_toggle
|
|
294
|
+
|
|
295
|
+
Enable or disable a server without removing its configuration:
|
|
296
|
+
|
|
297
|
+
```ruby
|
|
298
|
+
client.mcp_toggle("my-server", enabled: false)
|
|
299
|
+
client.mcp_toggle("my-server", enabled: true)
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### mcp_authenticate
|
|
303
|
+
|
|
304
|
+
Initiate OAuth authentication for a remote MCP server:
|
|
305
|
+
|
|
306
|
+
```ruby
|
|
307
|
+
client.mcp_authenticate("my-remote-server")
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### mcp_clear_auth
|
|
311
|
+
|
|
312
|
+
Clear stored authentication credentials for an MCP server:
|
|
313
|
+
|
|
314
|
+
```ruby
|
|
315
|
+
client.mcp_clear_auth("my-remote-server")
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Query Capabilities
|
|
319
|
+
|
|
320
|
+
Query the CLI for available resources.
|
|
321
|
+
|
|
322
|
+
### supported_commands
|
|
323
|
+
|
|
324
|
+
```ruby
|
|
325
|
+
commands = client.supported_commands
|
|
326
|
+
# => [#<SlashCommand name="commit" description="Create a commit" argument_hint="[message]">, ...]
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Returns `Array<SlashCommand>`.
|
|
330
|
+
|
|
331
|
+
### supported_models
|
|
332
|
+
|
|
333
|
+
```ruby
|
|
334
|
+
models = client.supported_models
|
|
335
|
+
models.each { |m| puts "#{m.value}: #{m.display_name}" }
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Returns `Array<ModelInfo>`.
|
|
339
|
+
|
|
340
|
+
### supported_agents
|
|
341
|
+
|
|
342
|
+
```ruby
|
|
343
|
+
agents = client.supported_agents
|
|
344
|
+
agents.each { |a| puts "#{a.name}: #{a.description}" }
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Returns `Array<AgentInfo>`.
|
|
348
|
+
|
|
349
|
+
### mcp_server_status
|
|
350
|
+
|
|
351
|
+
```ruby
|
|
352
|
+
statuses = client.mcp_server_status
|
|
353
|
+
statuses.each { |s| puts "#{s.name}: #{s.status}" }
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Returns `Array<McpServerStatus>`. Status values: `"connected"`, `"failed"`, `"needs-auth"`, `"pending"`.
|
|
357
|
+
|
|
358
|
+
### account_info
|
|
359
|
+
|
|
360
|
+
```ruby
|
|
361
|
+
info = client.account_info
|
|
362
|
+
puts "#{info.email} (#{info.organization})"
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Returns an `AccountInfo` with fields: `email`, `organization`, `subscription_type`, `token_source`, `api_key_source`.
|
|
366
|
+
|
|
367
|
+
## Permission Queue
|
|
368
|
+
|
|
369
|
+
When `permission_queue: true` is set in Options (or `can_use_tool` defers a request), the CLI routes tool permission prompts through a thread-safe queue instead of requiring synchronous callback resolution. This is useful for UI-driven applications where a separate thread or event loop handles approval dialogs.
|
|
370
|
+
|
|
371
|
+
### pending_permission
|
|
372
|
+
|
|
373
|
+
Non-blocking poll for the next pending request. Returns `nil` if the queue is empty.
|
|
374
|
+
|
|
375
|
+
```ruby
|
|
376
|
+
if request = client.pending_permission
|
|
377
|
+
puts "Tool: #{request.tool_name}"
|
|
378
|
+
puts "Input: #{request.input}"
|
|
379
|
+
puts "Label: #{request.display_label}"
|
|
380
|
+
|
|
381
|
+
request.allow!
|
|
382
|
+
# or: request.deny!(message: "Not allowed in production")
|
|
383
|
+
end
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### pending_permissions?
|
|
387
|
+
|
|
388
|
+
Check whether any requests are waiting:
|
|
389
|
+
|
|
390
|
+
```ruby
|
|
391
|
+
client.pending_permissions? # => true/false
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### permission_queue
|
|
395
|
+
|
|
396
|
+
Direct access to the underlying `PermissionQueue` for blocking waits or batch draining:
|
|
397
|
+
|
|
398
|
+
```ruby
|
|
399
|
+
# Blocking wait (with optional timeout)
|
|
400
|
+
request = client.permission_queue.pop(timeout: 30)
|
|
401
|
+
|
|
402
|
+
# Drain all pending (used during cleanup)
|
|
403
|
+
client.permission_queue.drain!(reason: "Shutting down")
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
`PermissionRequest` methods:
|
|
407
|
+
|
|
408
|
+
| Method | Description |
|
|
409
|
+
|------------------------------------------------|-----------------------------------------------------------------|
|
|
410
|
+
| `allow!(updated_input:, updated_permissions:)` | Allow execution, optionally modifying input or permission rules |
|
|
411
|
+
| `deny!(message:, interrupt:)` | Deny execution with a reason; optionally interrupt the agent |
|
|
412
|
+
| `tool_name` | Name of the tool requesting permission |
|
|
413
|
+
| `input` | Tool input hash |
|
|
414
|
+
| `display_label` | Human-readable label (e.g., `"Read(path: /tmp/file.txt)"`) |
|
|
415
|
+
| `summary(max:)` | Detailed summary, truncated to `max` characters |
|
|
416
|
+
| `pending?` / `resolved?` | Resolution state |
|
|
417
|
+
| `created_at` | Timestamp of the request |
|
|
418
|
+
|
|
419
|
+
## Cumulative Usage
|
|
420
|
+
|
|
421
|
+
The client tracks token usage, cost, and timing across all turns.
|
|
422
|
+
|
|
423
|
+
```ruby
|
|
424
|
+
usage = client.cumulative_usage
|
|
425
|
+
puts "Input tokens: #{usage.input_tokens}"
|
|
426
|
+
puts "Output tokens: #{usage.output_tokens}"
|
|
427
|
+
puts "Cache read: #{usage.cache_read_input_tokens}"
|
|
428
|
+
puts "Cache created: #{usage.cache_creation_input_tokens}"
|
|
429
|
+
puts "Total cost: $#{usage.total_cost_usd}"
|
|
430
|
+
puts "Turns: #{usage.num_turns}"
|
|
431
|
+
puts "Duration: #{usage.duration_ms}ms"
|
|
432
|
+
puts "API duration: #{usage.duration_api_ms}ms"
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
Returns a `CumulativeUsage` instance. Token counts are summed across turns. Cost and turn count reflect session-cumulative values from the CLI.
|
|
436
|
+
|
|
437
|
+
## Streaming Input
|
|
438
|
+
|
|
439
|
+
Send multiple messages from an enumerable source.
|
|
440
|
+
|
|
441
|
+
### Without block (send only)
|
|
442
|
+
|
|
443
|
+
```ruby
|
|
444
|
+
client.stream_input(["Hello", "How are you?"])
|
|
445
|
+
client.receive_response.each { |msg| puts msg }
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### With block (concurrent send/receive)
|
|
449
|
+
|
|
450
|
+
Messages are sent in a background thread while responses are yielded to the block:
|
|
451
|
+
|
|
452
|
+
```ruby
|
|
453
|
+
client.stream_input(["Hello", "Follow up"], session_id: "default") do |msg|
|
|
454
|
+
case msg
|
|
455
|
+
when ClaudeAgent::AssistantMessage
|
|
456
|
+
puts msg.text
|
|
457
|
+
when ClaudeAgent::ResultMessage
|
|
458
|
+
puts "Done!"
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
## Abort and Interrupt
|
|
464
|
+
|
|
465
|
+
### interrupt
|
|
466
|
+
|
|
467
|
+
Send an interrupt signal to the CLI, stopping the current generation:
|
|
468
|
+
|
|
469
|
+
```ruby
|
|
470
|
+
client.interrupt
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### abort!
|
|
474
|
+
|
|
475
|
+
Abort all pending operations. Triggers the abort controller (if configured), drains the permission queue, and terminates the transport.
|
|
476
|
+
|
|
477
|
+
```ruby
|
|
478
|
+
client.abort!
|
|
479
|
+
client.abort!("User cancelled")
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### AbortController
|
|
483
|
+
|
|
484
|
+
For cross-thread cancellation, configure an `AbortController` on Options:
|
|
485
|
+
|
|
486
|
+
```ruby
|
|
487
|
+
controller = ClaudeAgent::AbortController.new
|
|
488
|
+
|
|
489
|
+
options = ClaudeAgent::Options.new(abort_controller: controller)
|
|
490
|
+
client = ClaudeAgent::Client.new(options: options)
|
|
491
|
+
client.connect
|
|
492
|
+
|
|
493
|
+
# In another thread:
|
|
494
|
+
Thread.new { sleep(5); controller.abort("Timeout") }
|
|
495
|
+
|
|
496
|
+
begin
|
|
497
|
+
turn = client.send_and_receive("Long running task")
|
|
498
|
+
rescue ClaudeAgent::AbortError => e
|
|
499
|
+
partial = e.partial_turn
|
|
500
|
+
puts partial.text # text accumulated before abort
|
|
501
|
+
puts partial.tool_uses # tools that ran before abort
|
|
502
|
+
end
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
The `AbortSignal` is thread-safe and supports callbacks:
|
|
506
|
+
|
|
507
|
+
```ruby
|
|
508
|
+
controller.signal.on_abort { |reason| puts "Aborted: #{reason}" }
|
|
509
|
+
controller.signal.aborted? # => false
|
|
510
|
+
controller.abort("Done")
|
|
511
|
+
controller.signal.aborted? # => true
|
|
512
|
+
controller.signal.reason # => "Done"
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
Call `controller.reset!` to reuse the controller for another turn. `Conversation` does this automatically.
|
|
516
|
+
|
|
517
|
+
## Disconnect
|
|
518
|
+
|
|
519
|
+
```ruby
|
|
520
|
+
client.disconnect
|
|
521
|
+
client.connected? # => false
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
Disconnecting drains the permission queue (denying all pending requests with "Client disconnected"), stops the protocol, and terminates the CLI subprocess. Calling `disconnect` on an already-disconnected client is a no-op.
|
|
525
|
+
|
|
526
|
+
`Client.open` calls `disconnect` automatically in its `ensure` block.
|