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/queries.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# One-Shot Queries
|
|
2
|
+
|
|
3
|
+
One-shot queries send a single prompt to the Claude Code CLI and return the response. No persistent connection or multi-turn state is maintained. For multi-turn conversations, see `Conversation`.
|
|
4
|
+
|
|
5
|
+
The SDK provides three query methods at increasing levels of control:
|
|
6
|
+
|
|
7
|
+
| Method | Returns | Config integration | Streaming | Best for |
|
|
8
|
+
|--------------------------|-----------------------|----------------------------|----------------------|--------------------------------------|
|
|
9
|
+
| `ClaudeAgent.ask` | `TurnResult` | Yes (merges global config) | Block form | Most applications |
|
|
10
|
+
| `ClaudeAgent.query_turn` | `TurnResult` | No (explicit Options) | Block + EventHandler | Custom transports, event dispatch |
|
|
11
|
+
| `ClaudeAgent.query` | `Enumerator<Message>` | No (explicit Options) | Enumerator | Full control over message processing |
|
|
12
|
+
|
|
13
|
+
## ClaudeAgent.ask
|
|
14
|
+
|
|
15
|
+
The primary entry point. Merges per-request keyword arguments with the global `Configuration`, builds an `Options` instance, and delegates to `query_turn`.
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
turn = ClaudeAgent.ask("What is 2+2?")
|
|
19
|
+
puts turn.text # => "4"
|
|
20
|
+
puts turn.cost # => 0.002
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### With configuration overrides
|
|
24
|
+
|
|
25
|
+
Keyword arguments override global config for this request only:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
turn = ClaudeAgent.ask("Fix the bug in auth.rb",
|
|
29
|
+
model: "opus",
|
|
30
|
+
max_turns: 5,
|
|
31
|
+
permission_mode: "acceptEdits"
|
|
32
|
+
)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### With callbacks
|
|
36
|
+
|
|
37
|
+
Pass `on_*` lambdas to receive events as they stream in. The method still returns a `TurnResult` after the turn completes.
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
turn = ClaudeAgent.ask("Explain Ruby GC",
|
|
41
|
+
on_text: ->(text) { print text },
|
|
42
|
+
on_tool_use: ->(tool) { puts "\nUsing: #{tool.name}" },
|
|
43
|
+
on_result: ->(result) { puts "\nCost: $#{result.total_cost_usd}" }
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Available callback keys correspond to `EventHandler` events: `on_text`, `on_thinking`, `on_tool_use`, `on_tool_result`, `on_result`, `on_assistant`, `on_stream_event`, `on_message` (catch-all), and others. See `EventHandler::EVENTS` for the full list.
|
|
48
|
+
|
|
49
|
+
### With streaming block
|
|
50
|
+
|
|
51
|
+
The block receives each raw message as it arrives:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
turn = ClaudeAgent.ask("Explain Ruby GC") do |msg|
|
|
55
|
+
case msg
|
|
56
|
+
when ClaudeAgent::AssistantMessage
|
|
57
|
+
print msg.text
|
|
58
|
+
when ClaudeAgent::ResultMessage
|
|
59
|
+
puts "\nDone in #{msg.duration_ms}ms"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### With explicit Options
|
|
65
|
+
|
|
66
|
+
Pass a pre-built `Options` to bypass the global `Configuration` entirely:
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
opts = ClaudeAgent::Options.new(
|
|
70
|
+
model: "claude-sonnet-4-5-20250514",
|
|
71
|
+
max_turns: 3,
|
|
72
|
+
permission_mode: "acceptEdits",
|
|
73
|
+
tools: ["Read", "Bash"]
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
turn = ClaudeAgent.ask("List files in /tmp", options: opts)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### With global configuration
|
|
80
|
+
|
|
81
|
+
Set defaults once, then call `ask` without repeating them:
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
ClaudeAgent.configure do |c|
|
|
85
|
+
c.model = "opus"
|
|
86
|
+
c.max_turns = 10
|
|
87
|
+
c.permission_mode = "acceptEdits"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# These calls inherit the global config
|
|
91
|
+
turn = ClaudeAgent.ask("Fix the failing test")
|
|
92
|
+
turn = ClaudeAgent.ask("Now update the docs", max_turns: 3) # override max_turns
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## ClaudeAgent.query_turn
|
|
96
|
+
|
|
97
|
+
Wraps `query` and accumulates all messages into a `TurnResult`. Use this when you need to pass an explicit `Options` or `EventHandler` without going through `Configuration`.
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
def query_turn(prompt:, options: nil, transport: nil, events: nil, &block)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Basic usage
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
turn = ClaudeAgent.query_turn(prompt: "What is 2+2?")
|
|
107
|
+
puts turn.text
|
|
108
|
+
puts turn.cost
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### With EventHandler
|
|
112
|
+
|
|
113
|
+
Build an `EventHandler` for typed event dispatch:
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
events = ClaudeAgent::EventHandler.new
|
|
117
|
+
.on_text { |text| print text }
|
|
118
|
+
.on_tool_use { |tool| puts "Tool: #{tool.name}" }
|
|
119
|
+
.on_result { |r| puts "\nCost: $#{r.total_cost_usd}" }
|
|
120
|
+
|
|
121
|
+
turn = ClaudeAgent.query_turn(
|
|
122
|
+
prompt: "Refactor the parser",
|
|
123
|
+
options: ClaudeAgent::Options.new(model: "opus", max_turns: 5),
|
|
124
|
+
events: events
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### With block
|
|
129
|
+
|
|
130
|
+
The block receives each message, just like the block form of `ask`:
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
turn = ClaudeAgent.query_turn(prompt: "Explain closures") do |msg|
|
|
134
|
+
print msg.text if msg.is_a?(ClaudeAgent::AssistantMessage)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
puts turn.session_id
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### With custom transport
|
|
141
|
+
|
|
142
|
+
Inject a transport for testing or custom subprocess management:
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
transport = ClaudeAgent::Transport::Subprocess.new(options: opts)
|
|
146
|
+
turn = ClaudeAgent.query_turn(prompt: "Hello", options: opts, transport: transport)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## ClaudeAgent.query
|
|
150
|
+
|
|
151
|
+
The lowest-level one-shot interface. Returns an `Enumerator` that yields each `Message` as it arrives from the CLI. You are responsible for iterating and interpreting message types.
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
def query(prompt:, options: nil, transport: nil)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Basic usage with case statement
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
ClaudeAgent.query(prompt: "What is 2+2?").each do |message|
|
|
161
|
+
case message
|
|
162
|
+
when ClaudeAgent::SystemMessage
|
|
163
|
+
# Init message with session metadata
|
|
164
|
+
when ClaudeAgent::AssistantMessage
|
|
165
|
+
print message.text
|
|
166
|
+
when ClaudeAgent::UserMessage
|
|
167
|
+
# Tool results (system-generated)
|
|
168
|
+
when ClaudeAgent::StreamEvent
|
|
169
|
+
# Streaming deltas
|
|
170
|
+
when ClaudeAgent::ResultMessage
|
|
171
|
+
puts "\nCost: $#{message.total_cost_usd}"
|
|
172
|
+
puts "Duration: #{message.duration_ms}ms"
|
|
173
|
+
puts "Session: #{message.session_id}"
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Collecting all messages
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
messages = ClaudeAgent.query(prompt: "Hello").to_a
|
|
182
|
+
result = messages.find { |m| m.is_a?(ClaudeAgent::ResultMessage) }
|
|
183
|
+
puts result.total_cost_usd
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### With custom options
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
options = ClaudeAgent::Options.new(
|
|
190
|
+
model: "claude-sonnet-4-5-20250514",
|
|
191
|
+
max_turns: 5,
|
|
192
|
+
permission_mode: "acceptEdits"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
ClaudeAgent.query(prompt: "Fix the bug", options: options).each do |message|
|
|
196
|
+
# ...
|
|
197
|
+
end
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## TurnResult
|
|
201
|
+
|
|
202
|
+
All three methods ultimately produce a `TurnResult` (for `query`, you build one yourself or use `query_turn`). Key accessors:
|
|
203
|
+
|
|
204
|
+
| Accessor | Type | Description |
|
|
205
|
+
|-------------------|-----------------|---------------------------------------------|
|
|
206
|
+
| `text` | `String` | All assistant text concatenated |
|
|
207
|
+
| `thinking` | `String` | All thinking content concatenated |
|
|
208
|
+
| `tool_uses` | `Array` | Tool use blocks from assistant messages |
|
|
209
|
+
| `tool_results` | `Array` | Tool result blocks from user messages |
|
|
210
|
+
| `tool_executions` | `Array<Hash>` | Matched `{ tool_use:, tool_result: }` pairs |
|
|
211
|
+
| `result` | `ResultMessage` | Final result message (nil if incomplete) |
|
|
212
|
+
| `cost` | `Float` | Total cost in USD |
|
|
213
|
+
| `usage` | `Hash` | Token usage breakdown |
|
|
214
|
+
| `duration_ms` | `Integer` | Wall-clock duration |
|
|
215
|
+
| `session_id` | `String` | Session ID for resumption |
|
|
216
|
+
| `model` | `String` | Model used |
|
|
217
|
+
| `success?` | `Boolean` | Whether the turn completed without error |
|
|
218
|
+
| `error?` | `Boolean` | Whether the turn ended with an error |
|
|
219
|
+
| `messages` | `Array` | All raw messages received |
|
|
220
|
+
|
|
221
|
+
## Choosing the Right Method
|
|
222
|
+
|
|
223
|
+
**Use `ask`** when you want the simplest path with global configuration support. This is the right choice for most applications.
|
|
224
|
+
|
|
225
|
+
**Use `query_turn`** when you need explicit `Options` or `EventHandler` without going through `Configuration`, or when injecting a custom transport.
|
|
226
|
+
|
|
227
|
+
**Use `query`** when you need full control over message iteration -- for example, to build custom accumulators, forward messages to another system, or handle message types that `TurnResult` does not expose.
|
data/docs/sessions.md
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# Sessions
|
|
2
|
+
|
|
3
|
+
Claude Code CLI persists every conversation as a session on disk. The SDK can find, inspect, mutate, fork, and resume these sessions without spawning a CLI subprocess -- all operations read and write the session JSONL files directly.
|
|
4
|
+
|
|
5
|
+
## Session Resource
|
|
6
|
+
|
|
7
|
+
The `Session` class wraps `SessionInfo` with a rich, Rails-like API for discovering and working with past sessions.
|
|
8
|
+
|
|
9
|
+
### Finding a Session
|
|
10
|
+
|
|
11
|
+
Use `Session.find` for a safe lookup that returns `nil` when the session does not exist, or `Session.retrieve` when you want an exception on missing sessions.
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
# Returns Session or nil (targeted lookup by ID, not a full scan)
|
|
15
|
+
session = ClaudeAgent::Session.find("abc-123-def-456")
|
|
16
|
+
|
|
17
|
+
# Returns Session or raises ClaudeAgent::NotFoundError
|
|
18
|
+
session = ClaudeAgent::Session.retrieve("abc-123-def-456")
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Both accept an optional `dir:` keyword to scope the search to a specific project directory:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
session = ClaudeAgent::Session.find("abc-123-def-456", dir: "/path/to/project")
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Listing Sessions
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
# All sessions across all projects, sorted by last modified (most recent first)
|
|
31
|
+
sessions = ClaudeAgent::Session.all
|
|
32
|
+
|
|
33
|
+
# With optional filters
|
|
34
|
+
sessions = ClaudeAgent::Session.where(dir: "/path/to/project", limit: 10)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Fields
|
|
38
|
+
|
|
39
|
+
Every `Session` exposes the following attributes:
|
|
40
|
+
|
|
41
|
+
| Field | Type | Description |
|
|
42
|
+
|-----------------|------------------|-----------------------------------------------------------------------------------|
|
|
43
|
+
| `session_id` | `String` | UUID of the session. |
|
|
44
|
+
| `summary` | `String` | Display summary: custom title, last auto-summary, first prompt, or `"(session)"`. |
|
|
45
|
+
| `last_modified` | `Integer` | Last modification time as epoch milliseconds. |
|
|
46
|
+
| `file_size` | `Integer` | Size of the session JSONL file in bytes. |
|
|
47
|
+
| `custom_title` | `String`, `nil` | User-assigned title, if any. |
|
|
48
|
+
| `first_prompt` | `String`, `nil` | First meaningful user prompt (truncated to 200 chars). |
|
|
49
|
+
| `git_branch` | `String`, `nil` | Git branch active during the session. |
|
|
50
|
+
| `cwd` | `String`, `nil` | Working directory the session was started in. |
|
|
51
|
+
| `tag` | `String`, `nil` | User-assigned tag, if any. |
|
|
52
|
+
| `created_at` | `Integer`, `nil` | Creation timestamp (epoch milliseconds), if available. |
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
session = ClaudeAgent::Session.retrieve("abc-123-def-456")
|
|
56
|
+
|
|
57
|
+
puts session.summary # => "Fix login bug"
|
|
58
|
+
puts session.git_branch # => "fix/login"
|
|
59
|
+
puts session.custom_title # => nil (no custom title set)
|
|
60
|
+
puts session.cwd # => "/Users/dev/myapp"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Messages
|
|
64
|
+
|
|
65
|
+
`session.messages` returns a chainable, `Enumerable` `SessionMessageRelation`. Messages are loaded lazily on first access.
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
session = ClaudeAgent::Session.retrieve("abc-123-def-456")
|
|
69
|
+
|
|
70
|
+
# All messages
|
|
71
|
+
session.messages.each { |m| puts "#{m.type}: #{m.uuid}" }
|
|
72
|
+
|
|
73
|
+
# Pagination via .where
|
|
74
|
+
session.messages.where(limit: 10).to_a
|
|
75
|
+
session.messages.where(limit: 10, offset: 5).to_a
|
|
76
|
+
|
|
77
|
+
# Enumerable methods work directly
|
|
78
|
+
session.messages.first
|
|
79
|
+
session.messages.count
|
|
80
|
+
session.messages.select { |m| m.type == "assistant" }
|
|
81
|
+
session.messages.map(&:uuid)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Each message in the relation is a `SessionMessage` with these fields:
|
|
85
|
+
|
|
86
|
+
| Field | Type | Description |
|
|
87
|
+
|----------------------|-----------------|---------------------------------------------------|
|
|
88
|
+
| `type` | `String` | `"user"` or `"assistant"`. |
|
|
89
|
+
| `uuid` | `String` | Message UUID. |
|
|
90
|
+
| `session_id` | `String` | Session UUID this message belongs to. |
|
|
91
|
+
| `message` | `Hash` | Raw message payload (role, content blocks, etc.). |
|
|
92
|
+
| `parent_tool_use_id` | `String`, `nil` | Parent tool use ID for tool result messages. |
|
|
93
|
+
|
|
94
|
+
### Mutations
|
|
95
|
+
|
|
96
|
+
#### Renaming
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
session.rename("My descriptive title")
|
|
100
|
+
session.custom_title # => "My descriptive title"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Appends a `custom-title` JSONL entry to the session file. The `custom_title` attribute is updated in place.
|
|
104
|
+
|
|
105
|
+
#### Tagging
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
session.tag_session("important")
|
|
109
|
+
session.tag # => "important"
|
|
110
|
+
|
|
111
|
+
# Clear the tag
|
|
112
|
+
session.tag_session(nil)
|
|
113
|
+
session.tag # => nil
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Appends a `tag` JSONL entry. Unicode zero-width and directional characters are automatically stripped from tag values.
|
|
117
|
+
|
|
118
|
+
Both mutations return `self` for chaining:
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
session.rename("Refactored auth module").tag_session("refactor")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Forking
|
|
125
|
+
|
|
126
|
+
Create a new session by copying an existing one. All UUIDs in the forked session are remapped to fresh values.
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
# Fork the entire session
|
|
130
|
+
forked = session.fork
|
|
131
|
+
forked.session_id # => new UUID
|
|
132
|
+
|
|
133
|
+
# Fork up to a specific message (inclusive)
|
|
134
|
+
forked = session.fork(up_to: "message-uuid-here")
|
|
135
|
+
|
|
136
|
+
# Fork with a custom title
|
|
137
|
+
forked = session.fork(title: "Branch: try alternative approach")
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The returned value is a new `Session` instance pointing to the forked session file.
|
|
141
|
+
|
|
142
|
+
### Reloading
|
|
143
|
+
|
|
144
|
+
Re-read session metadata from disk to pick up external changes:
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
session.reload
|
|
148
|
+
session.summary # reflects current file state
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Raises `ClaudeAgent::NotFoundError` if the session file no longer exists.
|
|
152
|
+
|
|
153
|
+
### Resuming
|
|
154
|
+
|
|
155
|
+
Open a `Conversation` that continues from this session. The CLI restores full conversation context from the session transcript.
|
|
156
|
+
|
|
157
|
+
```ruby
|
|
158
|
+
# Block form -- auto-closes when the block exits
|
|
159
|
+
session.resume(model: "opus") do |c|
|
|
160
|
+
turn = c.say("Continue where we left off")
|
|
161
|
+
puts turn.text
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Without a block -- caller is responsible for closing
|
|
165
|
+
conversation = session.resume(max_turns: 5)
|
|
166
|
+
conversation.say("What did we discuss last time?")
|
|
167
|
+
conversation.close
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Accepts the same keyword arguments as `Conversation.new`.
|
|
171
|
+
|
|
172
|
+
## Functional API
|
|
173
|
+
|
|
174
|
+
The module-level methods provide direct access to session operations without wrapping results in `Session` objects. These return the underlying data types (`SessionInfo`, `SessionMessage`, `ForkSessionResult`) and are useful when you need lower-level control.
|
|
175
|
+
|
|
176
|
+
### `ClaudeAgent.list_sessions`
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
# All sessions
|
|
180
|
+
sessions = ClaudeAgent.list_sessions
|
|
181
|
+
# => Array<SessionInfo>
|
|
182
|
+
|
|
183
|
+
# Scoped to a directory with pagination
|
|
184
|
+
sessions = ClaudeAgent.list_sessions(
|
|
185
|
+
dir: "/path/to/project",
|
|
186
|
+
limit: 20,
|
|
187
|
+
offset: 10,
|
|
188
|
+
include_worktrees: true # default: true
|
|
189
|
+
)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
When `dir` is inside a git repository and `include_worktrees` is `true`, sessions from all git worktree paths are included automatically.
|
|
193
|
+
|
|
194
|
+
### `ClaudeAgent.get_session_info`
|
|
195
|
+
|
|
196
|
+
Targeted lookup of a single session by UUID. Returns `SessionInfo` or `nil`.
|
|
197
|
+
|
|
198
|
+
```ruby
|
|
199
|
+
info = ClaudeAgent.get_session_info("abc-123-def-456")
|
|
200
|
+
info = ClaudeAgent.get_session_info("abc-123-def-456", dir: "/path/to/project")
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### `ClaudeAgent.get_session_messages`
|
|
204
|
+
|
|
205
|
+
Read the conversation transcript for a session. Returns user and assistant messages in chronological order, reconstructing the main conversation thread from branches and forks.
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
messages = ClaudeAgent.get_session_messages("abc-123-def-456")
|
|
209
|
+
# => Array<SessionMessage>
|
|
210
|
+
|
|
211
|
+
messages = ClaudeAgent.get_session_messages("abc-123-def-456",
|
|
212
|
+
dir: "/path/to/project",
|
|
213
|
+
limit: 10,
|
|
214
|
+
offset: 5
|
|
215
|
+
)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### `ClaudeAgent.rename_session`
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
ClaudeAgent.rename_session("abc-123-def-456", "New title")
|
|
222
|
+
ClaudeAgent.rename_session("abc-123-def-456", "New title", dir: "/path/to/project")
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Raises `ArgumentError` if the title is empty, `ClaudeAgent::Error` if the session is not found.
|
|
226
|
+
|
|
227
|
+
### `ClaudeAgent.tag_session`
|
|
228
|
+
|
|
229
|
+
```ruby
|
|
230
|
+
ClaudeAgent.tag_session("abc-123-def-456", "important")
|
|
231
|
+
ClaudeAgent.tag_session("abc-123-def-456", nil) # clear tag
|
|
232
|
+
ClaudeAgent.tag_session("abc-123-def-456", "v2", dir: "/path/to/project")
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Raises `ClaudeAgent::Error` if the session is not found.
|
|
236
|
+
|
|
237
|
+
### `ClaudeAgent.fork_session`
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
result = ClaudeAgent.fork_session("abc-123-def-456")
|
|
241
|
+
# => ForkSessionResult
|
|
242
|
+
|
|
243
|
+
result.session_id # => new UUID
|
|
244
|
+
|
|
245
|
+
result = ClaudeAgent.fork_session("abc-123-def-456",
|
|
246
|
+
up_to_message_id: "msg-uuid",
|
|
247
|
+
title: "Forked conversation",
|
|
248
|
+
dir: "/path/to/project"
|
|
249
|
+
)
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Raises `ArgumentError` if `up_to_message_id` is provided but not found in the session transcript.
|
|
253
|
+
|
|
254
|
+
## V2 Session API (Unstable)
|
|
255
|
+
|
|
256
|
+
> **Warning:** The V2 Session API is unstable and may change without notice in any release. It is marked `@alpha` in the source and should not be used in production.
|
|
257
|
+
|
|
258
|
+
The V2 API provides a lower-level, multi-turn session interface that maps directly to the TypeScript SDK's `SDKSession` pattern. Unlike `Conversation`, it gives you explicit control over send/stream cycles.
|
|
259
|
+
|
|
260
|
+
### Creating a Session
|
|
261
|
+
|
|
262
|
+
```ruby
|
|
263
|
+
session = ClaudeAgent.unstable_v2_create_session(
|
|
264
|
+
model: "claude-sonnet-4-5-20250929",
|
|
265
|
+
permission_mode: "acceptEdits"
|
|
266
|
+
)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Sending and Streaming
|
|
270
|
+
|
|
271
|
+
```ruby
|
|
272
|
+
session.send("Hello, Claude!")
|
|
273
|
+
|
|
274
|
+
session.stream.each do |msg|
|
|
275
|
+
case msg
|
|
276
|
+
when ClaudeAgent::AssistantMessage
|
|
277
|
+
print msg.text
|
|
278
|
+
when ClaudeAgent::ResultMessage
|
|
279
|
+
puts "\nDone!"
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Resuming
|
|
285
|
+
|
|
286
|
+
```ruby
|
|
287
|
+
session = ClaudeAgent.unstable_v2_resume_session(
|
|
288
|
+
"session-abc-123",
|
|
289
|
+
model: "claude-sonnet-4-5-20250929"
|
|
290
|
+
)
|
|
291
|
+
session.send("Continue our conversation")
|
|
292
|
+
session.stream.each { |msg| puts msg.inspect }
|
|
293
|
+
session.close
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### One-Shot Prompt
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
result = ClaudeAgent.unstable_v2_prompt(
|
|
300
|
+
"What files are in this directory?",
|
|
301
|
+
model: "claude-sonnet-4-5-20250929"
|
|
302
|
+
)
|
|
303
|
+
puts result.text
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### SessionOptions
|
|
307
|
+
|
|
308
|
+
`SessionOptions` is a `Data.define` type with the following fields:
|
|
309
|
+
|
|
310
|
+
| Field | Type | Description |
|
|
311
|
+
|----------------------------------|-----------------|----------------------------------------------------|
|
|
312
|
+
| `model` | `String` | Model identifier (required). |
|
|
313
|
+
| `path_to_claude_code_executable` | `String`, `nil` | Custom path to the Claude Code CLI binary. |
|
|
314
|
+
| `env` | `Hash`, `nil` | Environment variables to pass to the CLI process. |
|
|
315
|
+
| `allowed_tools` | `Array`, `nil` | Tools the agent is allowed to use. |
|
|
316
|
+
| `disallowed_tools` | `Array`, `nil` | Tools the agent is not allowed to use. |
|
|
317
|
+
| `can_use_tool` | `Proc`, `nil` | Callback for dynamic tool permission decisions. |
|
|
318
|
+
| `hooks` | `Hash`, `nil` | Hook configuration. |
|
|
319
|
+
| `permission_mode` | `String`, `nil` | Permission mode (e.g., `"acceptEdits"`, `"plan"`). |
|
|
320
|
+
|
|
321
|
+
### Lifecycle
|
|
322
|
+
|
|
323
|
+
Always close V2 sessions when done to clean up the underlying CLI subprocess:
|
|
324
|
+
|
|
325
|
+
```ruby
|
|
326
|
+
session = ClaudeAgent.unstable_v2_create_session(model: "claude-sonnet-4-5-20250929")
|
|
327
|
+
begin
|
|
328
|
+
session.send("Do something")
|
|
329
|
+
session.stream.each { |msg| process(msg) }
|
|
330
|
+
ensure
|
|
331
|
+
session.close
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
session.closed? # => true
|
|
335
|
+
```
|
|
@@ -34,6 +34,17 @@ module ClaudeAgent
|
|
|
34
34
|
def abort(reason = nil)
|
|
35
35
|
@signal.abort!(reason)
|
|
36
36
|
end
|
|
37
|
+
|
|
38
|
+
# Reset the controller so it can be reused for another turn.
|
|
39
|
+
#
|
|
40
|
+
# After calling {#abort}, the controller is in an aborted state
|
|
41
|
+
# and cannot be reused without resetting. Call this before the
|
|
42
|
+
# next operation, or use {Conversation} which auto-resets.
|
|
43
|
+
#
|
|
44
|
+
# @return [void]
|
|
45
|
+
def reset!
|
|
46
|
+
@signal.reset!
|
|
47
|
+
end
|
|
37
48
|
end
|
|
38
49
|
|
|
39
50
|
# Signal object that tracks abort state (TypeScript SDK parity)
|
|
@@ -93,6 +104,19 @@ module ClaudeAgent
|
|
|
93
104
|
raise AbortError, reason if aborted?
|
|
94
105
|
end
|
|
95
106
|
|
|
107
|
+
# Reset the signal so the controller can be reused.
|
|
108
|
+
#
|
|
109
|
+
# No-op if the signal has not been aborted.
|
|
110
|
+
#
|
|
111
|
+
# @return [void]
|
|
112
|
+
def reset!
|
|
113
|
+
@mutex.synchronize do
|
|
114
|
+
return unless @aborted
|
|
115
|
+
@aborted = false
|
|
116
|
+
@reason = nil
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
96
120
|
# @api private
|
|
97
121
|
# Trigger the abort
|
|
98
122
|
# @param reason [String, nil] Reason for aborting
|
|
@@ -220,6 +220,38 @@ module ClaudeAgent
|
|
|
220
220
|
|
|
221
221
|
@protocol.mcp_clear_auth(server_name)
|
|
222
222
|
end
|
|
223
|
+
|
|
224
|
+
# Cancel a queued async user message (TypeScript SDK v0.2.76 parity)
|
|
225
|
+
#
|
|
226
|
+
# Drops a previously queued user message before it is processed.
|
|
227
|
+
#
|
|
228
|
+
# @param message_uuid [String] UUID of the message to cancel
|
|
229
|
+
# @return [Hash] Response from the CLI
|
|
230
|
+
#
|
|
231
|
+
# @example
|
|
232
|
+
# client.cancel_async_message("msg-uuid-123")
|
|
233
|
+
#
|
|
234
|
+
def cancel_async_message(message_uuid)
|
|
235
|
+
require_connection!
|
|
236
|
+
|
|
237
|
+
@protocol.cancel_async_message(message_uuid)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Get effective merged settings (TypeScript SDK v0.2.76 parity)
|
|
241
|
+
#
|
|
242
|
+
# Returns the current effective settings after merging all layers.
|
|
243
|
+
#
|
|
244
|
+
# @return [Hash] Merged settings
|
|
245
|
+
#
|
|
246
|
+
# @example
|
|
247
|
+
# settings = client.get_settings
|
|
248
|
+
# puts settings["model"]
|
|
249
|
+
#
|
|
250
|
+
def get_settings
|
|
251
|
+
require_connection!
|
|
252
|
+
|
|
253
|
+
@protocol.get_settings
|
|
254
|
+
end
|
|
223
255
|
end
|
|
224
256
|
end
|
|
225
257
|
end
|
data/lib/claude_agent/client.rb
CHANGED
|
@@ -189,17 +189,23 @@ module ClaudeAgent
|
|
|
189
189
|
# Receive messages until a ResultMessage, accumulating into a TurnResult
|
|
190
190
|
#
|
|
191
191
|
# Dispatches events to registered handlers (see {#on}).
|
|
192
|
+
# On abort, raises {AbortError} with the partial {TurnResult} attached.
|
|
192
193
|
#
|
|
193
194
|
# @yield [Message] Each message as it arrives (optional)
|
|
194
195
|
# @return [TurnResult] The completed turn
|
|
196
|
+
# @raise [AbortError] If abort signal is triggered (with partial_turn attached)
|
|
195
197
|
def receive_turn
|
|
196
198
|
require_connection!
|
|
197
199
|
|
|
198
200
|
turn = TurnResult.new
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
begin
|
|
202
|
+
receive_response do |message|
|
|
203
|
+
turn << message
|
|
204
|
+
@event_handler.handle(message)
|
|
205
|
+
yield message if block_given?
|
|
206
|
+
end
|
|
207
|
+
rescue AbortError => e
|
|
208
|
+
raise AbortError.new(e.message, partial_turn: turn)
|
|
203
209
|
end
|
|
204
210
|
@event_handler.reset!
|
|
205
211
|
turn
|