claude-agent-sdk 0.16.5 → 0.16.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2fe7bf072c2a3b8448b23bf82c95e7f0a896b347bd43039145458b72e21f301
4
- data.tar.gz: 1a58f5e31ef7a872aeaf75a3aada1c8e60a264d9ad5e93615b62c42d96179d99
3
+ metadata.gz: 9b2b002f964cd13c118fd417aa0ed253fe47f469d9c7ad75f3839f64177e7744
4
+ data.tar.gz: a253123d762a01fe8cbfc6f7a798d9ed310c56f8e8970393d2dd053d44c33b27
5
5
  SHA512:
6
- metadata.gz: e76c6cfe62c7b01320edf7a3d659e44fce46921f764132da7ab0f8223bb1d5a4a2b4457f5406b7bc68a1dde91f7af29fd0d4ff565334ed2cae8ad120339bac83
7
- data.tar.gz: 9e1920a7254f5e27dab4ae88cc5ba6d8ac7430a7ec32e8c9157d80c792a65bee8c258096f52ac20ce69d5bedfd1a629a2c8ef852341c9b03e4aaa631c491d912
6
+ metadata.gz: cc338d1a24133685314b1696fc698b709f16b79dd620bb002d5ba9106aea29ce8575894e0120e3885ebaeccc78432c42942d5b642d1695e77264008fb1eb9b6e
7
+ data.tar.gz: 4630953488a3ec83df9be0209cb2731ffd80efdb5b37b6051df11cda5735928a83c98a11db9f55c869e2108ec015e856b33f0f75a2dac2873f022fbbd68d3db2
data/CHANGELOG.md CHANGED
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.16.7] - 2026-05-15
11
+
12
+ ### Added
13
+ - `ClaudeAgentOptions#resume_session_at` — forwarded as `--resume-session-at <message-uuid>` to the CLI. When resuming a session, the conversation is truncated to include only messages up to and including the assistant message with the given UUID, enabling history rewriting / branched continuations from a specific turn. Raises `ArgumentError` from `CommandBuilder` if set without `resume` (matches the CLI's own validation but surfaces it synchronously in the caller's stack).
14
+ - `examples/e2b_transport_example.rb` — working ~320-line custom transport that runs the Claude Code CLI inside an E2B Firecracker microVM via the `e2b` gem. Reuses `CommandBuilder` for argv parity with `SubprocessCLITransport` and demonstrates the 6-method `Transport` interface against a remote backend. README's Custom Transport section now includes an interface table, data-flow diagram, code sketch, and production-hardening checklist that link to the example.
15
+
16
+ ## [0.16.6] - 2026-04-29
17
+
18
+ ### Added
19
+ - Type classes now accept hash construction with mixed key shapes — symbols or strings, snake_case or camelCase — and support bracket access (`obj[:field]`, `obj['field']`). `UserMessage.new({"sessionId" => "abc"})` works the same as `UserMessage.new(session_id: "abc")`. `Type.from_hash(nil)` and `Type.wrap(nil)` are nil-safe.
20
+ - `ClaudeAgentOptions.new(nil)` is accepted and yields an options object populated with configured defaults.
21
+
22
+ ### Changed
23
+ - Discriminator fields on type classes (`type` on `SystemPromptFile`/`McpStdioServerConfig`/etc., `behavior` on `PermissionResultAllow`/`Deny`, `hook_event_name` on every hook input/output) are now `attr_reader`-only, set authoritatively in `initialize`. They cannot be overwritten externally.
24
+ - `ClaudeAgentOptions` continues to raise `ArgumentError` on unknown keys (constructor, `dup_with`, and `[]=`); other type subclasses silently drop unknown keys for forward-compatibility with newer CLI output.
25
+
26
+ ### Internal
27
+ - Unified ~50 type classes (messages, hook inputs/outputs, MCP configs, system prompt configs, sandbox settings) under a shared `Type` base class. `lib/claude_agent_sdk/types.rb` shrank from 1510 to 588 lines; `MessageParser`'s system-message dispatch went from 215 lines to a 14-entry lookup table.
28
+
10
29
  ## [0.16.5] - 2026-04-24
11
30
 
12
31
  ### Added
data/README.md CHANGED
@@ -108,7 +108,7 @@ Add this line to your application's Gemfile:
108
108
  gem 'claude-agent-sdk', github: 'ya-luotao/claude-agent-sdk-ruby'
109
109
 
110
110
  # Or use a stable version from RubyGems
111
- gem 'claude-agent-sdk', '~> 0.16.5'
111
+ gem 'claude-agent-sdk', '~> 0.16.7'
112
112
  ```
113
113
 
114
114
  And then execute:
@@ -311,57 +311,112 @@ end.wait
311
311
 
312
312
  ### Custom Transport
313
313
 
314
- By default, `Client` uses `SubprocessCLITransport` to spawn the Claude Code CLI locally. You can provide a custom transport class to connect via other channels (e.g., E2B sandbox, remote SSH, WebSocket):
314
+ By default, `Client` uses `SubprocessCLITransport` to spawn the Claude Code CLI locally. You can provide a custom transport class to connect via other channels (e.g., remote SSH, WebSocket, or a sandbox VM).
315
+
316
+ A transport must implement six methods:
317
+
318
+ | Method | Purpose |
319
+ |---|---|
320
+ | `connect` | Establish the connection / spawn the remote CLI |
321
+ | `write(data)` | Send raw JSON-line bytes to stdin |
322
+ | `read_messages { \|hash\| ... }` | Yield parsed JSON messages from stdout; block until the stream closes |
323
+ | `end_input` | Signal EOF on stdin |
324
+ | `close` | Terminate and clean up |
325
+ | `ready?` | Report whether the transport can accept I/O |
326
+
327
+ Then plug it into `Client` via `transport_class:` / `transport_args:`. All connect orchestration (option transforms, MCP extraction, hook conversion, Query lifecycle) is handled for you.
315
328
 
316
329
  ```ruby
317
- # Custom transport must implement the Transport interface:
318
- # connect, write, read_messages, end_input, close, ready?
319
- class E2BSandboxTransport < ClaudeAgentSDK::Transport
320
- def initialize(options, sandbox:)
321
- @options = options
322
- @sandbox = sandbox
323
- end
330
+ client = ClaudeAgentSDK::Client.new(
331
+ options: options,
332
+ transport_class: MyTransport,
333
+ transport_args: { foo: 'bar' } # forwarded to MyTransport.new(options, **transport_args)
334
+ )
335
+ ```
324
336
 
325
- def connect
326
- @sandbox.connect
327
- end
337
+ #### Reference: running `claude` inside an E2B sandbox
328
338
 
329
- def write(data)
330
- @sandbox.stdin_write(data)
331
- end
339
+ [`examples/e2b_transport_example.rb`](examples/e2b_transport_example.rb) is a working transport that runs the Claude Code CLI inside an [E2B](https://e2b.dev) Firecracker microVM instead of on your host. The wire protocol stays identical — only the I/O layer changes:
332
340
 
333
- def read_messages(&block)
334
- @sandbox.stdout_read_lines { |line| yield JSON.parse(line, symbolize_names: true) }
335
- end
341
+ ```
342
+ ClaudeAgentSDK::Client (host)
343
+ JSON-lines
344
+
345
+ E2BCliTransport (host)
346
+ │ send_stdin / commands.run(background:) / CommandHandle#each
347
+
348
+ E2B envd RPC (HTTP/2)
349
+
350
+
351
+ /usr/local/bin/claude (in-VM subprocess)
352
+ ```
353
+
354
+ The example reuses the SDK's `CommandBuilder` to produce the exact same argv that `SubprocessCLITransport` would build (including SDK MCP server `:instance` field stripping), shell-escapes it for E2B's `/bin/bash -l -c` execution path, and streams stdout/stderr back through `CommandHandle#each`.
355
+
356
+ Sketch (full file is ~250 lines):
336
357
 
337
- def end_input
338
- @sandbox.close_stdin
358
+ ```ruby
359
+ require 'claude_agent_sdk'
360
+ require 'e2b'
361
+
362
+ class E2BCliTransport < ClaudeAgentSDK::Transport
363
+ def initialize(options, sandbox:, cli_path: '/usr/local/bin/claude')
364
+ @options, @sandbox, @cli_path = options, sandbox, cli_path
339
365
  end
340
366
 
341
- def close
342
- @sandbox.disconnect
367
+ def connect
368
+ argv = ClaudeAgentSDK::CommandBuilder.new(@cli_path, @options).build
369
+ cmd = argv.map { |a| Shellwords.shellescape(a.to_s) }.join(' ')
370
+ @handle = @sandbox.commands.run(cmd, background: true, stdin: true,
371
+ cwd: @options.cwd&.to_s, envs: build_env)
372
+ @pid = @handle.pid
373
+ @ready = true
343
374
  end
344
375
 
345
- def ready?
346
- @sandbox.connected?
376
+ def write(data) = @sandbox.commands.send_stdin(@pid, data)
377
+ def end_input = @sandbox.commands.close_stdin(@pid)
378
+ def close = @handle&.kill
379
+ def ready? = @ready
380
+
381
+ def read_messages(&block)
382
+ buf = +''
383
+ @handle.each do |stdout, stderr, _pty|
384
+ next if stderr && !stderr.empty?
385
+ stdout.each_line do |line|
386
+ buf << line.strip
387
+ begin
388
+ yield JSON.parse(buf, symbolize_names: true)
389
+ buf.clear
390
+ rescue JSON::ParserError
391
+ # JSON line split across reads — keep buffering
392
+ end
393
+ end
394
+ end
395
+ @handle.wait # raises E2B::CommandExitError on non-zero exit
347
396
  end
348
397
  end
349
398
 
350
- # Use it with Client — all connect orchestration (option transforms,
351
- # MCP extraction, hook conversion, Query lifecycle) is handled for you
399
+ # Use it
400
+ sandbox = E2B::Sandbox.create(template: 'base', timeout: 600)
352
401
  Async do
353
402
  client = ClaudeAgentSDK::Client.new(
354
403
  options: options,
355
- transport_class: E2BSandboxTransport,
356
- transport_args: { sandbox: my_sandbox }
404
+ transport_class: E2BCliTransport,
405
+ transport_args: { sandbox: sandbox }
357
406
  )
358
407
  client.connect
359
- client.query("Hello from the sandbox!")
408
+ client.query('Hello from the sandbox!')
360
409
  client.receive_response { |msg| puts msg }
361
410
  client.disconnect
411
+ ensure
412
+ sandbox.kill
362
413
  end.wait
363
414
  ```
364
415
 
416
+ **Why use a remote transport?** Untrusted code execution, multi-tenant agent runs that can't share a host, environments without local Node.js, or simply isolating filesystem/network blast radius. The Firecracker VM gives you a fresh `/home/user` per session and is killable without touching the host.
417
+
418
+ **Production hardening** (intentionally omitted from the example for clarity): inactivity watchdog, keepalive heartbeat, stream reconnect on transient SSL/EOF errors, host env-var blocklist, MCP server filtering for sandbox compatibility. See the example file's header comments for what to add and why.
419
+
365
420
  ## Custom Tools (SDK MCP Servers)
366
421
 
367
422
  A **custom tool** is a Ruby proc/lambda that you can offer to Claude, for Claude to invoke as needed.
@@ -1053,6 +1108,24 @@ result = ClaudeAgentSDK.fork_session(
1053
1108
 
1054
1109
  > **Note:** Session mutations use append-only JSONL writes with `O_WRONLY | O_APPEND` (no `O_CREAT`) for TOCTOU safety. They are safe to call while the session is open in a CLI process. `fork_session` uses `O_CREAT | O_EXCL` to prevent race conditions.
1055
1110
 
1111
+ ### Resuming at a Specific Message
1112
+
1113
+ `resume_session_at` truncates the resumed conversation to messages up to **and including** the assistant message with the given UUID — useful for rewriting history from a known point or branching exploration without forking the session file. The flag rides on top of `resume`, so the original session ID is preserved; only the in-memory history loaded for the new turn is shortened.
1114
+
1115
+ ```ruby
1116
+ ClaudeAgentSDK.query(
1117
+ prompt: 'Try a different approach',
1118
+ options: ClaudeAgentSDK::ClaudeAgentOptions.new(
1119
+ resume: '550e8400-e29b-41d4-a716-446655440000',
1120
+ resume_session_at: 'assistant-message-uuid-from-history'
1121
+ )
1122
+ ) do |message|
1123
+ # ...
1124
+ end
1125
+ ```
1126
+
1127
+ `resume_session_at` requires `resume`; the SDK raises `ArgumentError` from `CommandBuilder` when this constraint is violated, matching the underlying CLI's validation but surfacing it synchronously in the caller's stack.
1128
+
1056
1129
  ## Observability (OpenTelemetry / Langfuse)
1057
1130
 
1058
1131
  The SDK includes a built-in **observer interface** and an **OpenTelemetry observer** for tracing agent sessions. Traces are emitted using standard `gen_ai.*` semantic conventions, compatible with Langfuse, Jaeger, Datadog, and any OTel backend.
@@ -105,9 +105,23 @@ module ClaudeAgentSDK
105
105
  def append_session(cmd)
106
106
  cmd.push("--continue") if @options.continue_conversation
107
107
  cmd.push("--resume", @options.resume) if @options.resume
108
+ append_resume_session_at(cmd)
108
109
  cmd.push("--session-id", @options.session_id) if @options.session_id
109
110
  end
110
111
 
112
+ # `--resume-session-at <message-uuid>` truncates the resumed conversation
113
+ # to include messages up to and including the given assistant message UUID.
114
+ # The CLI rejects this flag without `--resume`; we raise early to surface
115
+ # the misconfiguration in the caller's stack rather than as a remote
116
+ # process exit.
117
+ def append_resume_session_at(cmd)
118
+ return unless @options.resume_session_at
119
+
120
+ raise ArgumentError, "resume_session_at requires resume to be set" unless @options.resume
121
+
122
+ cmd.push("--resume-session-at", @options.resume_session_at.to_s)
123
+ end
124
+
111
125
  def append_settings(cmd)
112
126
  return unless @options.settings || @options.sandbox
113
127
 
@@ -78,214 +78,57 @@ module ClaudeAgentSDK
78
78
  )
79
79
  end
80
80
 
81
+ # Typed SystemMessage subclasses inherit from `Type` and accept the raw
82
+ # CLI hash directly — camelCase and snake_case keys are normalized by the
83
+ # base class, and the full hash is captured as `#data`.
84
+ SYSTEM_MESSAGE_CLASSES = {
85
+ 'init' => InitMessage,
86
+ 'compact_boundary' => CompactBoundaryMessage,
87
+ 'status' => StatusMessage,
88
+ 'api_retry' => APIRetryMessage,
89
+ 'local_command_output' => LocalCommandOutputMessage,
90
+ 'hook_started' => HookStartedMessage,
91
+ 'hook_progress' => HookProgressMessage,
92
+ 'hook_response' => HookResponseMessage,
93
+ 'session_state_changed' => SessionStateChangedMessage,
94
+ 'files_persisted' => FilesPersistedMessage,
95
+ 'elicitation_complete' => ElicitationCompleteMessage,
96
+ 'task_started' => TaskStartedMessage,
97
+ 'task_progress' => TaskProgressMessage,
98
+ 'task_notification' => TaskNotificationMessage
99
+ }.freeze
100
+
81
101
  def self.parse_system_message(data)
82
- case data[:subtype]
83
- when 'init'
84
- InitMessage.new(
85
- subtype: data[:subtype], data: data,
86
- uuid: data[:uuid], session_id: data[:session_id],
87
- agents: data[:agents], api_key_source: data[:apiKeySource] || data[:api_key_source],
88
- betas: data[:betas], claude_code_version: data[:claude_code_version],
89
- cwd: data[:cwd], tools: data[:tools], mcp_servers: data[:mcp_servers],
90
- model: data[:model], permission_mode: data[:permissionMode] || data[:permission_mode],
91
- slash_commands: data[:slash_commands], output_style: data[:output_style],
92
- skills: data[:skills], plugins: data[:plugins],
93
- fast_mode_state: data[:fastModeState] || data[:fast_mode_state]
94
- )
95
- when 'compact_boundary'
96
- raw_metadata = data[:compact_metadata]
97
- CompactBoundaryMessage.new(
98
- subtype: data[:subtype], data: data,
99
- uuid: data[:uuid], session_id: data[:session_id],
100
- compact_metadata: CompactMetadata.from_hash(raw_metadata)
101
- )
102
- when 'status'
103
- StatusMessage.new(
104
- subtype: data[:subtype], data: data,
105
- uuid: data[:uuid], session_id: data[:session_id],
106
- status: data[:status],
107
- permission_mode: data[:permissionMode] || data[:permission_mode]
108
- )
109
- when 'api_retry'
110
- APIRetryMessage.new(
111
- subtype: data[:subtype], data: data,
112
- uuid: data[:uuid], session_id: data[:session_id],
113
- attempt: data[:attempt], max_retries: data[:maxRetries] || data[:max_retries],
114
- retry_delay_ms: data[:retryDelayMs] || data[:retry_delay_ms],
115
- error_status: data[:errorStatus] || data[:error_status],
116
- error: data[:error]
117
- )
118
- when 'local_command_output'
119
- LocalCommandOutputMessage.new(
120
- subtype: data[:subtype], data: data,
121
- uuid: data[:uuid], session_id: data[:session_id],
122
- content: data[:content]
123
- )
124
- when 'hook_started'
125
- HookStartedMessage.new(
126
- subtype: data[:subtype], data: data,
127
- uuid: data[:uuid], session_id: data[:session_id],
128
- hook_id: data[:hookId] || data[:hook_id],
129
- hook_name: data[:hookName] || data[:hook_name],
130
- hook_event: data[:hookEvent] || data[:hook_event]
131
- )
132
- when 'hook_progress'
133
- HookProgressMessage.new(
134
- subtype: data[:subtype], data: data,
135
- uuid: data[:uuid], session_id: data[:session_id],
136
- hook_id: data[:hookId] || data[:hook_id],
137
- hook_name: data[:hookName] || data[:hook_name],
138
- hook_event: data[:hookEvent] || data[:hook_event],
139
- stdout: data[:stdout], stderr: data[:stderr], output: data[:output]
140
- )
141
- when 'hook_response'
142
- HookResponseMessage.new(
143
- subtype: data[:subtype], data: data,
144
- uuid: data[:uuid], session_id: data[:session_id],
145
- hook_id: data[:hookId] || data[:hook_id],
146
- hook_name: data[:hookName] || data[:hook_name],
147
- hook_event: data[:hookEvent] || data[:hook_event],
148
- output: data[:output], stdout: data[:stdout], stderr: data[:stderr],
149
- exit_code: data[:exitCode] || data[:exit_code],
150
- outcome: data[:outcome]
151
- )
152
- when 'session_state_changed'
153
- SessionStateChangedMessage.new(
154
- subtype: data[:subtype], data: data,
155
- uuid: data[:uuid], session_id: data[:session_id],
156
- state: data[:state]
157
- )
158
- when 'files_persisted'
159
- FilesPersistedMessage.new(
160
- subtype: data[:subtype], data: data,
161
- uuid: data[:uuid], session_id: data[:session_id],
162
- files: data[:files], failed: data[:failed],
163
- processed_at: data[:processedAt] || data[:processed_at]
164
- )
165
- when 'elicitation_complete'
166
- ElicitationCompleteMessage.new(
167
- subtype: data[:subtype], data: data,
168
- uuid: data[:uuid], session_id: data[:session_id],
169
- mcp_server_name: data[:mcpServerName] || data[:mcp_server_name],
170
- elicitation_id: data[:elicitationId] || data[:elicitation_id]
171
- )
172
- when 'task_started'
173
- TaskStartedMessage.new(
174
- subtype: data[:subtype], data: data,
175
- task_id: data[:task_id], description: data[:description],
176
- uuid: data[:uuid], session_id: data[:session_id],
177
- tool_use_id: data[:tool_use_id], task_type: data[:task_type],
178
- workflow_name: data[:workflowName] || data[:workflow_name],
179
- prompt: data[:prompt]
180
- )
181
- when 'task_progress'
182
- TaskProgressMessage.new(
183
- subtype: data[:subtype], data: data,
184
- task_id: data[:task_id], description: data[:description],
185
- usage: data[:usage], uuid: data[:uuid], session_id: data[:session_id],
186
- tool_use_id: data[:tool_use_id], last_tool_name: data[:last_tool_name],
187
- summary: data[:summary]
188
- )
189
- when 'task_notification'
190
- TaskNotificationMessage.new(
191
- subtype: data[:subtype], data: data,
192
- task_id: data[:task_id], status: data[:status],
193
- output_file: data[:output_file], summary: data[:summary],
194
- uuid: data[:uuid], session_id: data[:session_id],
195
- tool_use_id: data[:tool_use_id], usage: data[:usage]
196
- )
197
- else
198
- SystemMessage.new(subtype: data[:subtype], data: data)
199
- end
102
+ klass = SYSTEM_MESSAGE_CLASSES[data[:subtype]] || SystemMessage
103
+ klass.new(data)
200
104
  end
201
105
 
202
106
  def self.parse_result_message(data)
203
- ResultMessage.new(
204
- subtype: data[:subtype],
205
- duration_ms: data[:duration_ms],
206
- duration_api_ms: data[:duration_api_ms],
207
- is_error: data[:is_error],
208
- num_turns: data[:num_turns],
209
- session_id: data[:session_id],
210
- stop_reason: data[:stop_reason],
211
- total_cost_usd: data[:total_cost_usd],
212
- usage: data[:usage],
213
- result: data[:result],
214
- structured_output: data[:structured_output],
215
- model_usage: data[:modelUsage] || data[:model_usage],
216
- permission_denials: data[:permission_denials],
217
- errors: data[:errors],
218
- uuid: data[:uuid],
219
- fast_mode_state: data[:fastModeState] || data[:fast_mode_state]
220
- )
107
+ ResultMessage.new(data)
221
108
  end
222
109
 
223
110
  def self.parse_stream_event(data)
224
- StreamEvent.new(
225
- uuid: data[:uuid],
226
- session_id: data[:session_id],
227
- event: data[:event],
228
- parent_tool_use_id: data[:parent_tool_use_id]
229
- )
111
+ StreamEvent.new(data)
230
112
  end
231
113
 
232
114
  def self.parse_rate_limit_event(data)
233
- info = data[:rate_limit_info] || {}
234
- rate_limit_info = RateLimitInfo.new(
235
- status: info[:status],
236
- resets_at: info[:resetsAt],
237
- rate_limit_type: info[:rateLimitType],
238
- utilization: info[:utilization],
239
- overage_status: info[:overageStatus],
240
- overage_resets_at: info[:overageResetsAt],
241
- overage_disabled_reason: info[:overageDisabledReason],
242
- raw: info
243
- )
244
- RateLimitEvent.new(
245
- rate_limit_info: rate_limit_info,
246
- uuid: data[:uuid],
247
- session_id: data[:session_id],
248
- raw_data: data
249
- )
115
+ RateLimitEvent.new(data.merge(raw_data: data))
250
116
  end
251
117
 
252
118
  def self.parse_tool_progress_message(data)
253
- ToolProgressMessage.new(
254
- uuid: data[:uuid],
255
- session_id: data[:session_id],
256
- tool_use_id: data[:toolUseId] || data[:tool_use_id],
257
- tool_name: data[:toolName] || data[:tool_name],
258
- parent_tool_use_id: data[:parentToolUseId] || data[:parent_tool_use_id],
259
- elapsed_time_seconds: data[:elapsedTimeSeconds] || data[:elapsed_time_seconds],
260
- task_id: data[:taskId] || data[:task_id]
261
- )
119
+ ToolProgressMessage.new(data)
262
120
  end
263
121
 
264
122
  def self.parse_auth_status_message(data)
265
- AuthStatusMessage.new(
266
- uuid: data[:uuid],
267
- session_id: data[:session_id],
268
- is_authenticating: data[:isAuthenticating] || data[:is_authenticating],
269
- output: data[:output],
270
- error: data[:error]
271
- )
123
+ AuthStatusMessage.new(data)
272
124
  end
273
125
 
274
126
  def self.parse_tool_use_summary_message(data)
275
- ToolUseSummaryMessage.new(
276
- uuid: data[:uuid],
277
- session_id: data[:session_id],
278
- summary: data[:summary],
279
- preceding_tool_use_ids: data[:precedingToolUseIds] || data[:preceding_tool_use_ids]
280
- )
127
+ ToolUseSummaryMessage.new(data)
281
128
  end
282
129
 
283
130
  def self.parse_prompt_suggestion_message(data)
284
- PromptSuggestionMessage.new(
285
- uuid: data[:uuid],
286
- session_id: data[:session_id],
287
- suggestion: data[:suggestion]
288
- )
131
+ PromptSuggestionMessage.new(data)
289
132
  end
290
133
 
291
134
  # Accepts blocks with either symbol or string keys — live CLI messages