claude_agent 0.7.8 → 0.7.10

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.
data/SPEC.md CHANGED
@@ -3,11 +3,11 @@
3
3
  This document provides a comprehensive specification of the Claude Agent SDK, comparing feature parity across the official TypeScript and Python SDKs with this Ruby implementation.
4
4
 
5
5
  **Reference Versions:**
6
- - TypeScript SDK: v0.2.50 (npm package)
7
- - Python SDK: v0.1.39 from GitHub (commit 146e3d6)
6
+ - TypeScript SDK: v0.2.61 (npm package)
7
+ - Python SDK: v0.1.44 from GitHub (commit 7297bdc)
8
8
  - Ruby SDK: This repository
9
9
 
10
- **Last Updated:** 2026-02-22
10
+ **Last Updated:** 2026-02-26
11
11
 
12
12
  ---
13
13
 
@@ -111,6 +111,7 @@ Messages exchanged between SDK and CLI.
111
111
  | `TaskNotificationMessage` | ✅ | ❌ | ✅ | Background task completion |
112
112
  | `ToolUseSummaryMessage` | ✅ | ❌ | ✅ | Summary of tool use (collapsed) |
113
113
  | `TaskStartedMessage` | ✅ | ❌ | ✅ | Subagent task registered (v0.2.45) |
114
+ | `TaskProgressMessage` | ✅ | ❌ | ✅ | Background task progress (v0.2.51) |
114
115
  | `RateLimitEvent` | ✅ | ❌ | ✅ | Rate limit status changes |
115
116
  | `PromptSuggestionMessage` | ✅ | ❌ | ✅ | Suggested next prompt (v0.2.47) |
116
117
  | `FilesPersistedEvent` | ✅ | ❌ | ✅ | File persistence confirmation |
@@ -232,6 +233,8 @@ Bidirectional control protocol for SDK-CLI communication.
232
233
  | `mcp_reconnect` | ✅ | ❌ | ✅ | Reconnect to MCP server |
233
234
  | `mcp_toggle` | ✅ | ❌ | ✅ | Enable/disable MCP server |
234
235
  | `stop_task` | ✅ | ❌ | ✅ | Stop a running background task |
236
+ | `mcp_authenticate` | ✅ | ❌ | ✅ | Authenticate MCP server (v0.2.52) |
237
+ | `mcp_clear_auth` | ✅ | ❌ | ✅ | Clear MCP server auth (v0.2.52) |
235
238
  | `supported_commands` | ✅ | ❌ | ✅ | Get available slash commands |
236
239
  | `supported_models` | ✅ | ❌ | ✅ | Get available models |
237
240
  | `account_info` | ✅ | ❌ | ✅ | Get account information |
@@ -519,6 +522,51 @@ Session management and resumption.
519
522
  | Persist session | ✅ | ❌ | ✅ | `persistSession` option |
520
523
  | Continue most recent | ✅ | ✅ | ✅ | `continue` option |
521
524
 
525
+ ### Session Discovery
526
+
527
+ | Feature | TypeScript | Python | Ruby | Notes |
528
+ |------------------------|:----------:|:------:|:----:|--------------------------------------------------|
529
+ | `listSessions()` | ✅ | ❌ | ✅ | List past sessions with metadata (v0.2.53) |
530
+ | `getSessionMessages()` | ✅ | ❌ | ✅ | Read session transcript messages (v0.2.59) |
531
+
532
+ #### ListSessionsOptions
533
+
534
+ | Field | TypeScript | Python | Ruby | Notes |
535
+ |---------|:----------:|:------:|:----:|-------------------------------------------|
536
+ | `dir` | ✅ | ❌ | ✅ | Project directory (includes worktrees) |
537
+ | `limit` | ✅ | ❌ | ✅ | Maximum number of sessions to return |
538
+
539
+ #### GetSessionMessagesOptions
540
+
541
+ | Field | TypeScript | Python | Ruby | Notes |
542
+ |----------|:----------:|:------:|:----:|----------------------------------------------|
543
+ | `dir` | ✅ | ❌ | ✅ | Project directory to find session in |
544
+ | `limit` | ✅ | ❌ | ✅ | Maximum number of messages to return |
545
+ | `offset` | ✅ | ❌ | ✅ | Number of messages to skip from the start |
546
+
547
+ #### SessionMessage Fields
548
+
549
+ | Field | TypeScript | Python | Ruby | Notes |
550
+ |----------------------|:----------:|:------:|:----:|----------------------------------|
551
+ | `type` | ✅ | ❌ | ✅ | 'user' or 'assistant' |
552
+ | `uuid` | ✅ | ❌ | ✅ | Message UUID |
553
+ | `session_id` | ✅ | ❌ | ✅ | Session ID |
554
+ | `message` | ✅ | ❌ | ✅ | Raw message content |
555
+ | `parent_tool_use_id` | ✅ | ❌ | ✅ | Parent tool use ID (always null) |
556
+
557
+ #### SDKSessionInfo Fields
558
+
559
+ | Field | TypeScript | Python | Ruby | Notes |
560
+ |----------------|:----------:|:------:|:----:|-------------------------------------|
561
+ | `sessionId` | ✅ | ❌ | ✅ | Session UUID |
562
+ | `summary` | ✅ | ❌ | ✅ | Display title/summary |
563
+ | `lastModified` | ✅ | ❌ | ✅ | Last modified time (ms since epoch) |
564
+ | `fileSize` | ✅ | ❌ | ✅ | Session file size in bytes |
565
+ | `customTitle` | ✅ | ❌ | ✅ | User-set title via /rename |
566
+ | `firstPrompt` | ✅ | ❌ | ✅ | First meaningful user prompt |
567
+ | `gitBranch` | ✅ | ❌ | ✅ | Git branch at end of session |
568
+ | `cwd` | ✅ | ❌ | ✅ | Working directory for session |
569
+
522
570
  ### V2 Session API (Unstable)
523
571
 
524
572
  | Feature | TypeScript | Python | Ruby | Notes |
@@ -598,11 +646,11 @@ Error types and hierarchy.
598
646
  |----------------------|:----------:|:------:|:----:|--------------------------------|
599
647
  | Base Error | ✅ | ✅ | ✅ | `Error` / `ClaudeAgent::Error` |
600
648
  | `AbortError` | ✅ | ❌ | ✅ | Operation cancelled |
601
- | `CLINotFoundError` | ❌ | | ✅ | CLI not found |
649
+ | `CLINotFoundError` | ❌ | | ✅ | CLI not found |
602
650
  | `CLIVersionError` | ❌ | ❌ | ✅ | CLI version too old |
603
- | `CLIConnectionError` | ❌ | | ✅ | Connection failed |
604
- | `ProcessError` | ❌ | | ✅ | CLI process failed |
605
- | `JSONDecodeError` | ❌ | | ✅ | JSON parsing failed |
651
+ | `CLIConnectionError` | ❌ | | ✅ | Connection failed |
652
+ | `ProcessError` | ❌ | | ✅ | CLI process failed |
653
+ | `JSONDecodeError` | ❌ | | ✅ | JSON parsing failed |
606
654
  | `MessageParseError` | ❌ | ❌ | ✅ | Message parsing failed |
607
655
  | `TimeoutError` | ❌ | ❌ | ✅ | Control request timeout |
608
656
  | `ConfigurationError` | ❌ | ❌ | ✅ | Invalid configuration |
@@ -625,6 +673,13 @@ Error types and hierarchy.
625
673
 
626
674
  Public API surface for SDK clients.
627
675
 
676
+ ### Standalone Functions
677
+
678
+ | Feature | TypeScript | Python | Ruby | Notes |
679
+ |------------------------|:----------:|:------:|:----:|--------------------------------------------|
680
+ | `listSessions()` | ✅ | ❌ | ✅ | List past sessions with metadata (v0.2.53) |
681
+ | `getSessionMessages()` | ✅ | ❌ | ✅ | Read session transcript (v0.2.59) |
682
+
628
683
  ### Query Interface
629
684
 
630
685
  | Feature | TypeScript | Python | Ruby | Notes |
@@ -634,24 +689,24 @@ Public API surface for SDK clients.
634
689
 
635
690
  ### Query Control Methods
636
691
 
637
- | Method | TypeScript | Python | Ruby | Notes |
638
- |--------------------------|:----------:|:------:|:----:|------------------------|
639
- | `interrupt()` | ✅ | ✅ | ✅ | Interrupt execution |
640
- | `setPermissionMode()` | ✅ | ✅ | ✅ | Change permission mode |
641
- | `setModel()` | ✅ | ✅ | ✅ | Change model |
642
- | `setMaxThinkingTokens()` | ✅ | ❌ | ✅ | Set thinking limit |
643
- | `supportedCommands()` | ✅ | ❌ | ✅ | Get slash commands |
644
- | `supportedModels()` | ✅ | ❌ | ✅ | Get available models |
645
- | `mcpServerStatus()` | ✅ | ✅ | ✅ | Get MCP status |
646
- | `accountInfo()` | ✅ | ❌ | ✅ | Get account info |
647
- | `rewindFiles()` | ✅ | ✅ | ✅ | Rewind file changes |
648
- | `setMcpServers()` | ✅ | ❌ | ✅ | Dynamic MCP servers |
649
- | `reconnectMcpServer()` | ✅ | ❌ | ✅ | Reconnect MCP server |
650
- | `toggleMcpServer()` | ✅ | ❌ | ✅ | Enable/disable MCP |
651
- | `stopTask()` | ✅ | ❌ | ✅ | Stop running task |
652
- | `streamInput()` | ✅ | ✅ | ✅ | Stream user input |
653
- | `initializationResult()` | ✅ | | ✅ | Full init response |
654
- | `close()` | ✅ | ✅ | ✅ | Close query/session |
692
+ | Method | TypeScript | Python | Ruby | Notes |
693
+ |--------------------------|:----------:|:------:|:----:|----------------------------------------------|
694
+ | `interrupt()` | ✅ | ✅ | ✅ | Interrupt execution |
695
+ | `setPermissionMode()` | ✅ | ✅ | ✅ | Change permission mode |
696
+ | `setModel()` | ✅ | ✅ | ✅ | Change model |
697
+ | `setMaxThinkingTokens()` | ✅ | ❌ | ✅ | Set thinking limit |
698
+ | `supportedCommands()` | ✅ | ❌ | ✅ | Get slash commands |
699
+ | `supportedModels()` | ✅ | ❌ | ✅ | Get available models |
700
+ | `mcpServerStatus()` | ✅ | ✅ | ✅ | Get MCP status |
701
+ | `accountInfo()` | ✅ | ❌ | ✅ | Get account info |
702
+ | `rewindFiles()` | ✅ | ✅ | ✅ | Rewind file changes |
703
+ | `setMcpServers()` | ✅ | ❌ | ✅ | Dynamic MCP servers |
704
+ | `reconnectMcpServer()` | ✅ | ❌ | ✅ | Reconnect MCP server |
705
+ | `toggleMcpServer()` | ✅ | ❌ | ✅ | Enable/disable MCP |
706
+ | `stopTask()` | ✅ | ❌ | ✅ | Stop running task |
707
+ | `streamInput()` | ✅ | ✅ | ✅ | Stream user input |
708
+ | `initializationResult()` | ✅ | | ✅ | Full init response (Py: `get_server_info()`) |
709
+ | `close()` | ✅ | ✅ | ✅ | Close query/session |
655
710
 
656
711
  ### Client Class
657
712
 
@@ -694,22 +749,31 @@ Public API surface for SDK clients.
694
749
  - `executable`/`executableArgs` are JS-specific (`node`/`bun`/`deno`)
695
750
  - v0.2.45: Added `TaskStartedMessage`, `RateLimitEvent` message types
696
751
  - v0.2.47: Added `promptSuggestions` option and `PromptSuggestionMessage`
697
- - v0.2.49: Added `ConfigChange` hook event, `SandboxFilesystemConfig`
752
+ - v0.2.49: Added `ConfigChange` hook event, `SandboxFilesystemConfig`, ModelInfo capability fields
698
753
  - v0.2.50: Added `WorktreeCreate`/`WorktreeRemove` hook events, `apply_flag_settings` control request
754
+ - v0.2.51: Added `TaskProgressMessage` for real-time background agent progress reporting
755
+ - v0.2.52: Added `mcp_authenticate`/`mcp_clear_auth` control requests for MCP server authentication
756
+ - v0.2.53: Added `listSessions()` for discovering and listing past sessions with `SDKSessionInfo` metadata
757
+ - v0.2.54 – v0.2.58: CLI parity updates (no new SDK-facing features)
758
+ - v0.2.59: Added `getSessionMessages()` for reading session transcript history with pagination (limit/offset)
759
+ - v0.2.61: CLI parity update (no new SDK-facing features)
699
760
 
700
761
  ### Python SDK
701
762
  - Full source available with `Transport` abstract class
702
763
  - Partial control protocol: query and client support interrupt, setPermissionMode, setModel, rewindFiles, mcpStatus
764
+ - Has `CLINotFoundError`, `CLIConnectionError`, `ProcessError`, `CLIJSONDecodeError` error types
703
765
  - Missing hooks: SessionStart, SessionEnd, Setup, TeammateIdle, TaskCompleted, ConfigChange, WorktreeCreate, WorktreeRemove
704
766
  - Missing permission modes: `dontAsk`
705
767
  - Missing options: `allowDangerouslySkipPermissions`, `persistSession`, `resumeSessionAt`, `sessionId`, `strictMcpConfig`, `init`/`initOnly`/`maintenance`, `debug`/`debugFile`, `promptSuggestions`
706
768
  - `ToolPermissionContext` missing `blockedPath`, `decisionReason`, `toolUseID`, `agentID`, `description`
707
769
  - Has SDK MCP server support with `tool()` helper and annotations
708
770
  - Added `thinking` config and `effort` option in v0.1.36
709
- - Handles `rate_limit_event` and unknown message types gracefully (v0.1.39)
771
+ - Handles `rate_limit_event` and unknown message types gracefully (v0.1.40)
772
+ - Client has `get_server_info()` for accessing the initialization result (v0.1.31+)
773
+ - v0.1.42 – v0.1.44: CLI parity updates (no new SDK-facing features; latest commit bumps bundled CLI to v2.1.61)
710
774
 
711
775
  ### Ruby SDK (This Repository)
712
- - Feature parity with TypeScript SDK v0.2.50
776
+ - Feature parity with TypeScript SDK v0.2.61
713
777
  - Ruby-idiomatic patterns (Data.define, snake_case)
714
778
  - Complete control protocol, hook, and V2 Session API support
715
779
  - Dedicated Client class for multi-turn conversations
@@ -32,7 +32,7 @@ module ClaudeAgent
32
32
  # end
33
33
  #
34
34
  class Client
35
- attr_reader :options, :transport, :server_info
35
+ attr_reader :options, :transport, :server_info, :cumulative_usage, :event_handler, :permission_queue
36
36
 
37
37
  # Open a client with automatic cleanup
38
38
  #
@@ -61,6 +61,9 @@ module ClaudeAgent
61
61
  @protocol = nil
62
62
  @server_info = nil
63
63
  @connected = false
64
+ @cumulative_usage = CumulativeUsage.new
65
+ @event_handler = EventHandler.new
66
+ @permission_queue = PermissionQueue.new
64
67
  end
65
68
 
66
69
  # Connect to the CLI
@@ -74,6 +77,7 @@ module ClaudeAgent
74
77
 
75
78
  logger.info("client") { "Connecting" }
76
79
  @protocol = ControlProtocol.new(transport: @transport, options: @options)
80
+ @protocol.permission_queue = @permission_queue
77
81
  @server_info = @protocol.start(streaming: true)
78
82
  @connected = true
79
83
  logger.info("client") { "Connected" }
@@ -88,6 +92,7 @@ module ClaudeAgent
88
92
  return unless @connected
89
93
 
90
94
  logger.info("client") { "Disconnecting" }
95
+ @permission_queue.drain!(reason: "Client disconnected")
91
96
  @protocol&.stop
92
97
  @protocol = nil
93
98
  @connected = false
@@ -119,20 +124,133 @@ module ClaudeAgent
119
124
  #
120
125
  # @yield [Message] Received messages
121
126
  # @return [Enumerator<Message>] If no block given
122
- def receive_messages(&block)
127
+ def receive_messages
123
128
  require_connection!
124
129
 
125
- @protocol.each_message(&block)
130
+ if block_given?
131
+ @protocol.each_message do |message|
132
+ @cumulative_usage.track(message)
133
+ yield message
134
+ end
135
+ else
136
+ enum_for(:receive_messages)
137
+ end
126
138
  end
127
139
 
128
140
  # Receive messages until a ResultMessage is received
129
141
  #
130
142
  # @yield [Message] Received messages
131
143
  # @return [Enumerator<Message>] If no block given
132
- def receive_response(&block)
144
+ def receive_response
145
+ require_connection!
146
+
147
+ if block_given?
148
+ @protocol.receive_response do |message|
149
+ @cumulative_usage.track(message)
150
+ yield message
151
+ end
152
+ else
153
+ enum_for(:receive_response)
154
+ end
155
+ end
156
+
157
+ # Register an event handler
158
+ #
159
+ # Handlers persist across turns and fire automatically during
160
+ # {#receive_turn} and {#send_and_receive}.
161
+ #
162
+ # @param event [Symbol] Event name (:message, :text, :thinking, :tool_use, :tool_result, :result)
163
+ # @yield Event-specific arguments
164
+ # @return [self]
165
+ #
166
+ # @example
167
+ # client.on(:text) { |text| print text }
168
+ # client.on(:tool_use) { |tool| show_spinner(tool) }
169
+ #
170
+ def on(event, &block)
171
+ @event_handler.on(event, &block)
172
+ self
173
+ end
174
+
175
+ # @!method on_text(&block)
176
+ # Register a handler for assistant text content
177
+ # @yield [String] Text from the AssistantMessage
178
+ # @return [self]
179
+
180
+ # @!method on_thinking(&block)
181
+ # Register a handler for assistant thinking content
182
+ # @yield [String] Thinking from the AssistantMessage
183
+ # @return [self]
184
+
185
+ # @!method on_tool_use(&block)
186
+ # Register a handler for tool use requests
187
+ # @yield [ToolUseBlock, ServerToolUseBlock] The tool use block
188
+ # @return [self]
189
+
190
+ # @!method on_tool_result(&block)
191
+ # Register a handler for tool results, paired with the original request
192
+ # @yield [ToolResultBlock, ToolUseBlock|nil] Result block and matched tool use
193
+ # @return [self]
194
+
195
+ # @!method on_result(&block)
196
+ # Register a handler for the final ResultMessage
197
+ # @yield [ResultMessage] The result
198
+ # @return [self]
199
+
200
+ # @!method on_message(&block)
201
+ # Register a handler for every message (catch-all)
202
+ # @yield [message] Any message object
203
+ # @return [self]
204
+
205
+ %i[message text thinking tool_use tool_result result].each do |event|
206
+ define_method(:"on_#{event}") { |&block| on(event, &block) }
207
+ end
208
+
209
+ # Receive messages until a ResultMessage, accumulating into a TurnResult
210
+ #
211
+ # Dispatches events to registered handlers (see {#on}).
212
+ #
213
+ # @yield [Message] Each message as it arrives (optional)
214
+ # @return [TurnResult] The completed turn
215
+ def receive_turn
133
216
  require_connection!
134
217
 
135
- @protocol.receive_response(&block)
218
+ turn = TurnResult.new
219
+ receive_response do |message|
220
+ turn << message
221
+ @event_handler.handle(message)
222
+ yield message if block_given?
223
+ end
224
+ @event_handler.reset!
225
+ turn
226
+ end
227
+
228
+ # Send a message and receive the complete turn result
229
+ #
230
+ # Combines {#send_message} and {#receive_turn} into a single call.
231
+ #
232
+ # @param content [String, Array] Message content
233
+ # @param session_id [String] Session ID
234
+ # @param uuid [String, nil] Message UUID for file checkpointing
235
+ # @yield [Message] Each message as it arrives (optional)
236
+ # @return [TurnResult] The completed turn
237
+ #
238
+ # @example Simple
239
+ # turn = client.send_and_receive("Fix the bug")
240
+ # puts turn.text
241
+ # puts "Cost: $#{turn.cost}"
242
+ #
243
+ # @example With streaming
244
+ # turn = client.send_and_receive("Fix the bug") do |msg|
245
+ # case msg
246
+ # when ClaudeAgent::AssistantMessage
247
+ # print msg.text
248
+ # end
249
+ # end
250
+ #
251
+ def send_and_receive(content, session_id: "default", uuid: nil, &block)
252
+ send_message(content, session_id: session_id, uuid: uuid)
253
+ receive_turn(&block)
136
254
  end
137
255
 
138
256
  # Stream user input from an enumerable (TypeScript SDK parity)
@@ -161,11 +279,14 @@ module ClaudeAgent
161
279
  # end
162
280
  # end
163
281
  #
164
- def stream_input(stream, session_id: "default", &block)
282
+ def stream_input(stream, session_id: "default")
165
283
  require_connection!
166
284
 
167
285
  if block_given?
168
- @protocol.stream_conversation(stream, session_id: session_id, &block)
286
+ @protocol.stream_conversation(stream, session_id: session_id) do |message|
287
+ @cumulative_usage.track(message)
288
+ yield message
289
+ end
169
290
  else
170
291
  @protocol.stream_input(stream, session_id: session_id)
171
292
  end
@@ -191,6 +312,7 @@ module ClaudeAgent
191
312
  def abort!(reason = nil)
192
313
  return unless @connected
193
314
 
315
+ @permission_queue.drain!(reason: reason || "Operation aborted")
194
316
  @options.abort_controller&.abort(reason)
195
317
  @protocol&.abort!
196
318
  end
@@ -371,6 +493,58 @@ module ClaudeAgent
371
493
  @protocol.mcp_toggle(server_name, enabled: enabled)
372
494
  end
373
495
 
496
+ # Initiate OAuth authentication for an MCP server (TypeScript SDK v0.2.52 parity)
497
+ #
498
+ # @param server_name [String] Name of the MCP server to authenticate
499
+ # @return [Hash] Response from the CLI
500
+ #
501
+ # @example
502
+ # client.mcp_authenticate("my-remote-server")
503
+ #
504
+ def mcp_authenticate(server_name)
505
+ require_connection!
506
+
507
+ @protocol.mcp_authenticate(server_name)
508
+ end
509
+
510
+ # Clear stored auth credentials for an MCP server (TypeScript SDK v0.2.52 parity)
511
+ #
512
+ # @param server_name [String] Name of the MCP server to clear auth for
513
+ # @return [Hash] Response from the CLI
514
+ #
515
+ # @example
516
+ # client.mcp_clear_auth("my-remote-server")
517
+ #
518
+ def mcp_clear_auth(server_name)
519
+ require_connection!
520
+
521
+ @protocol.mcp_clear_auth(server_name)
522
+ end
523
+
524
+ # Non-blocking poll for the next pending permission request.
525
+ #
526
+ # Returns the next {PermissionRequest} from the queue, or nil if
527
+ # no requests are pending. Call {PermissionRequest#allow!} or
528
+ # {PermissionRequest#deny!} to resolve it.
529
+ #
530
+ # @return [PermissionRequest, nil] The next pending request, or nil
531
+ #
532
+ # @example UI poll loop
533
+ # if request = client.pending_permission
534
+ # show_permission_dialog(request)
535
+ # end
536
+ #
537
+ def pending_permission
538
+ @permission_queue.poll
539
+ end
540
+
541
+ # Check if there are any pending permission requests.
542
+ #
543
+ # @return [Boolean]
544
+ def pending_permissions?
545
+ !@permission_queue.empty?
546
+ end
547
+
374
548
  private
375
549
 
376
550
  def logger