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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/rules/conventions.md +66 -16
  3. data/CHANGELOG.md +20 -0
  4. data/CLAUDE.md +24 -4
  5. data/README.md +52 -1529
  6. data/SPEC.md +56 -29
  7. data/docs/architecture.md +339 -0
  8. data/docs/client.md +526 -0
  9. data/docs/configuration.md +571 -0
  10. data/docs/conversations.md +461 -0
  11. data/docs/errors.md +127 -0
  12. data/docs/events.md +225 -0
  13. data/docs/getting-started.md +310 -0
  14. data/docs/hooks.md +380 -0
  15. data/docs/logging.md +96 -0
  16. data/docs/mcp.md +308 -0
  17. data/docs/messages.md +871 -0
  18. data/docs/permissions.md +611 -0
  19. data/docs/queries.md +227 -0
  20. data/docs/sessions.md +335 -0
  21. data/lib/claude_agent/abort_controller.rb +24 -0
  22. data/lib/claude_agent/client/commands.rb +32 -0
  23. data/lib/claude_agent/client.rb +10 -4
  24. data/lib/claude_agent/configuration.rb +129 -0
  25. data/lib/claude_agent/control_protocol/commands.rb +28 -0
  26. data/lib/claude_agent/conversation.rb +37 -4
  27. data/lib/claude_agent/errors.rb +21 -4
  28. data/lib/claude_agent/event_handler.rb +14 -0
  29. data/lib/claude_agent/fork_session.rb +117 -0
  30. data/lib/claude_agent/hook_registry.rb +110 -0
  31. data/lib/claude_agent/hooks.rb +4 -0
  32. data/lib/claude_agent/mcp/server.rb +22 -0
  33. data/lib/claude_agent/mcp/tool.rb +24 -3
  34. data/lib/claude_agent/message.rb +93 -0
  35. data/lib/claude_agent/messages/streaming.rb +37 -0
  36. data/lib/claude_agent/options.rb +10 -0
  37. data/lib/claude_agent/permission_policy.rb +174 -0
  38. data/lib/claude_agent/permission_request.rb +17 -0
  39. data/lib/claude_agent/session.rb +100 -11
  40. data/lib/claude_agent/session_paths.rb +5 -2
  41. data/lib/claude_agent/turn_result.rb +20 -2
  42. data/lib/claude_agent/types/sessions.rb +8 -0
  43. data/lib/claude_agent/version.rb +1 -1
  44. data/lib/claude_agent.rb +187 -0
  45. data/sig/claude_agent.rbs +38 -1
  46. metadata +20 -1
data/docs/messages.md ADDED
@@ -0,0 +1,871 @@
1
+ # Messages & Content Blocks Reference
2
+
3
+ Complete reference for all message types and content blocks in the ClaudeAgent Ruby SDK.
4
+
5
+ All types are immutable (`Data.define`, frozen at construction). All types include the `ClaudeAgent::Message` module.
6
+
7
+ ## Message Module
8
+
9
+ Every message and content block type includes `ClaudeAgent::Message`, which provides:
10
+
11
+ | Method | Returns | Description |
12
+ |--------------------|-----------|--------------------------------------------------------------------------------------------------------|
13
+ | `text_content` | `String` | Universal text extraction. Works on any message or block type. Returns `""` when no text is available. |
14
+ | `session_message?` | `Boolean` | `true` if the message has non-nil `uuid` and `session_id`. |
15
+ | `identifiable?` | `Boolean` | `true` if the message has a non-nil `uuid`. |
16
+ | `deconstruct_keys` | `Hash` | Injects `:type` as a virtual key for pattern matching. |
17
+
18
+ ### text_content behavior by type
19
+
20
+ | Type | Extracts from |
21
+ |------------------------------------|-----------------------------------------------------|
22
+ | `AssistantMessage` | Concatenated `TextBlock` text via `#text` |
23
+ | `UserMessage`, `UserMessageReplay` | `content` if it is a `String` |
24
+ | `TextBlock` | `text` |
25
+ | `ThinkingBlock` | `thinking` |
26
+ | `StreamEvent` | `delta_text` |
27
+ | `GenericMessage` | `raw[:text]` or `raw["text"]` |
28
+ | Everything else | `text` if the object responds to it, otherwise `""` |
29
+
30
+ ## Pattern Matching
31
+
32
+ The `Message` module overrides `deconstruct_keys` to inject a `:type` virtual key, enabling Ruby pattern matching on message types:
33
+
34
+ ```ruby
35
+ case message
36
+ in { type: :assistant }
37
+ puts message.text_content
38
+ in { type: :result, is_error: true }
39
+ warn "Error: #{message.errors}"
40
+ in { type: :result }
41
+ puts "Cost: $#{message.total_cost_usd}"
42
+ in { type: :system, subtype: "init" }
43
+ puts "Session initialized"
44
+ in { type: :stream_event }
45
+ print message.delta_text
46
+ end
47
+ ```
48
+
49
+ You can also combine pattern matching with field extraction:
50
+
51
+ ```ruby
52
+ case message
53
+ in { type: :assistant, model: }
54
+ puts "Model: #{model}"
55
+ in { type: :hook_response, outcome: "error", stderr: }
56
+ warn stderr
57
+ end
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Message Types
63
+
64
+ 22 message types, grouped by category.
65
+
66
+ ### Conversation Messages
67
+
68
+ #### UserMessage
69
+
70
+ User message sent to Claude.
71
+
72
+ ```ruby
73
+ UserMessage = Data.define(:content, :uuid, :session_id, :parent_tool_use_id)
74
+ ```
75
+
76
+ | Field | Type | Default |
77
+ |----------------------|---------------------|----------|
78
+ | `content` | `String` or `Array` | required |
79
+ | `uuid` | `String, nil` | `nil` |
80
+ | `session_id` | `String, nil` | `nil` |
81
+ | `parent_tool_use_id` | `String, nil` | `nil` |
82
+
83
+ Methods:
84
+
85
+ - `type` -- `:user`
86
+ - `text` -- returns `content` if it is a `String`, else `nil`
87
+ - `replay?` -- always `false`
88
+
89
+ #### UserMessageReplay
90
+
91
+ Replayed user message from a resumed session.
92
+
93
+ ```ruby
94
+ UserMessageReplay = Data.define(
95
+ :content, :uuid, :session_id, :parent_tool_use_id,
96
+ :is_replay, :is_synthetic, :tool_use_result
97
+ )
98
+ ```
99
+
100
+ | Field | Type | Default |
101
+ |----------------------|---------------------|----------|
102
+ | `content` | `String` or `Array` | required |
103
+ | `uuid` | `String, nil` | `nil` |
104
+ | `session_id` | `String, nil` | `nil` |
105
+ | `parent_tool_use_id` | `String, nil` | `nil` |
106
+ | `is_replay` | `Boolean` | `true` |
107
+ | `is_synthetic` | `Boolean, nil` | `nil` |
108
+ | `tool_use_result` | `Hash, nil` | `nil` |
109
+
110
+ Methods:
111
+
112
+ - `type` -- `:user`
113
+ - `text` -- returns `content` if it is a `String`, else `nil`
114
+ - `replay?` -- `true` if `is_replay == true`
115
+ - `synthetic?` -- `true` if `is_synthetic == true`
116
+
117
+ #### AssistantMessage
118
+
119
+ Response from Claude containing content blocks.
120
+
121
+ ```ruby
122
+ AssistantMessage = Data.define(:content, :model, :uuid, :session_id, :error, :parent_tool_use_id)
123
+ ```
124
+
125
+ | Field | Type | Default |
126
+ |----------------------|-----------------------|----------|
127
+ | `content` | `Array<ContentBlock>` | required |
128
+ | `model` | `String` | required |
129
+ | `uuid` | `String, nil` | `nil` |
130
+ | `session_id` | `String, nil` | `nil` |
131
+ | `error` | `Hash, nil` | `nil` |
132
+ | `parent_tool_use_id` | `String, nil` | `nil` |
133
+
134
+ Methods:
135
+
136
+ - `type` -- `:assistant`
137
+ - `text` -- concatenated text from all `TextBlock`s
138
+ - `thinking` -- concatenated text from all `ThinkingBlock`s
139
+ - `tool_uses` -- `Array<ToolUseBlock>` from content
140
+ - `has_tool_use?` -- `true` if any content block is a `ToolUseBlock`
141
+
142
+ ```ruby
143
+ msg.text # => "Hello, world!"
144
+ msg.tool_uses # => [#<ToolUseBlock id="tool_1" name="Read" ...>]
145
+ msg.has_tool_use? # => true
146
+ ```
147
+
148
+ ### Result
149
+
150
+ #### ResultMessage
151
+
152
+ Final message with cost, usage, and outcome info.
153
+
154
+ ```ruby
155
+ ResultMessage = Data.define(
156
+ :subtype, :duration_ms, :duration_api_ms, :is_error, :num_turns,
157
+ :session_id, :uuid, :total_cost_usd, :usage, :result, :structured_output,
158
+ :errors, :permission_denials, :model_usage, :stop_reason, :fast_mode_state
159
+ )
160
+ ```
161
+
162
+ | Field | Type | Default |
163
+ |----------------------|----------------------|----------|
164
+ | `subtype` | `String` | required |
165
+ | `duration_ms` | `Integer` | required |
166
+ | `duration_api_ms` | `Integer` | required |
167
+ | `is_error` | `Boolean` | required |
168
+ | `num_turns` | `Integer` | required |
169
+ | `session_id` | `String` | required |
170
+ | `uuid` | `String, nil` | `nil` |
171
+ | `total_cost_usd` | `Float, nil` | `nil` |
172
+ | `usage` | `Hash, nil` | `nil` |
173
+ | `result` | `String, nil` | `nil` |
174
+ | `structured_output` | `Hash, nil` | `nil` |
175
+ | `errors` | `Array<String>, nil` | `nil` |
176
+ | `permission_denials` | `Array, nil` | `nil` |
177
+ | `model_usage` | `Hash, nil` | `nil` |
178
+ | `stop_reason` | `String, nil` | `nil` |
179
+ | `fast_mode_state` | `Hash, nil` | `nil` |
180
+
181
+ Methods:
182
+
183
+ - `type` -- `:result`
184
+ - `error?` -- `true` if `is_error`
185
+ - `success?` -- `true` if not `is_error`
186
+
187
+ ### System & Status
188
+
189
+ #### SystemMessage
190
+
191
+ Internal system event (e.g., session init).
192
+
193
+ ```ruby
194
+ SystemMessage = Data.define(:subtype, :data)
195
+ ```
196
+
197
+ | Field | Type | Default |
198
+ |-----------|----------|----------|
199
+ | `subtype` | `String` | required |
200
+ | `data` | `Hash` | required |
201
+
202
+ Methods:
203
+
204
+ - `type` -- `:system`
205
+
206
+ #### CompactBoundaryMessage
207
+
208
+ Conversation compaction marker.
209
+
210
+ ```ruby
211
+ CompactBoundaryMessage = Data.define(:uuid, :session_id, :compact_metadata)
212
+ ```
213
+
214
+ | Field | Type | Default |
215
+ |--------------------|----------|----------|
216
+ | `uuid` | `String` | required |
217
+ | `session_id` | `String` | required |
218
+ | `compact_metadata` | `Hash` | required |
219
+
220
+ Methods:
221
+
222
+ - `type` -- `:compact_boundary`
223
+ - `trigger` -- compaction trigger type (`"manual"` or `"auto"`)
224
+ - `pre_tokens` -- token count before compaction
225
+
226
+ #### StatusMessage
227
+
228
+ Session status report (e.g., `"compacting"`).
229
+
230
+ ```ruby
231
+ StatusMessage = Data.define(:uuid, :session_id, :status, :permission_mode)
232
+ ```
233
+
234
+ | Field | Type | Default |
235
+ |-------------------|---------------|----------|
236
+ | `uuid` | `String` | required |
237
+ | `session_id` | `String` | required |
238
+ | `status` | `String` | required |
239
+ | `permission_mode` | `String, nil` | `nil` |
240
+
241
+ Methods:
242
+
243
+ - `type` -- `:status`
244
+
245
+ ### Streaming
246
+
247
+ #### StreamEvent
248
+
249
+ Partial message during streaming.
250
+
251
+ ```ruby
252
+ StreamEvent = Data.define(:uuid, :session_id, :event, :parent_tool_use_id)
253
+ ```
254
+
255
+ | Field | Type | Default |
256
+ |----------------------|---------------|----------|
257
+ | `uuid` | `String` | required |
258
+ | `session_id` | `String` | required |
259
+ | `event` | `Hash` | required |
260
+ | `parent_tool_use_id` | `String, nil` | `nil` |
261
+
262
+ Methods:
263
+
264
+ - `type` -- `:stream_event`
265
+ - `event_type` -- raw event type string (e.g., `"content_block_delta"`)
266
+ - `delta_text` -- text delta content, or `nil` if not a text delta
267
+ - `delta_type` -- delta type string (e.g., `"text_delta"`, `"thinking_delta"`)
268
+ - `thinking_text` -- thinking delta text, or `nil` if not a thinking delta
269
+ - `content_index` -- content block index within the message
270
+
271
+ ```ruby
272
+ event.delta_text # => "Hello"
273
+ event.delta_type # => "text_delta"
274
+ event.thinking_text # => nil (only set for thinking deltas)
275
+ ```
276
+
277
+ #### RateLimitEvent
278
+
279
+ Rate limit status and utilization info.
280
+
281
+ ```ruby
282
+ RateLimitEvent = Data.define(:rate_limit_info, :uuid, :session_id)
283
+ ```
284
+
285
+ | Field | Type | Default |
286
+ |-------------------|---------------|----------|
287
+ | `rate_limit_info` | `Hash` | required |
288
+ | `uuid` | `String, nil` | `nil` |
289
+ | `session_id` | `String, nil` | `nil` |
290
+
291
+ Methods:
292
+
293
+ - `type` -- `:rate_limit_event`
294
+ - `status` -- rate limit status string (e.g., `"allowed_warning"`)
295
+
296
+ #### PromptSuggestionMessage
297
+
298
+ Suggested prompt for the user.
299
+
300
+ ```ruby
301
+ PromptSuggestionMessage = Data.define(:uuid, :session_id, :suggestion)
302
+ ```
303
+
304
+ | Field | Type | Default |
305
+ |--------------|---------------|----------|
306
+ | `uuid` | `String, nil` | `nil` |
307
+ | `session_id` | `String, nil` | `nil` |
308
+ | `suggestion` | `String` | required |
309
+
310
+ Methods:
311
+
312
+ - `type` -- `:prompt_suggestion`
313
+
314
+ ### Tool Lifecycle
315
+
316
+ #### ToolProgressMessage
317
+
318
+ Progress during long-running tool execution.
319
+
320
+ ```ruby
321
+ ToolProgressMessage = Data.define(
322
+ :uuid, :session_id, :tool_use_id, :tool_name,
323
+ :parent_tool_use_id, :elapsed_time_seconds, :task_id
324
+ )
325
+ ```
326
+
327
+ | Field | Type | Default |
328
+ |------------------------|---------------|----------|
329
+ | `uuid` | `String` | required |
330
+ | `session_id` | `String` | required |
331
+ | `tool_use_id` | `String` | required |
332
+ | `tool_name` | `String` | required |
333
+ | `elapsed_time_seconds` | `Float` | required |
334
+ | `parent_tool_use_id` | `String, nil` | `nil` |
335
+ | `task_id` | `String, nil` | `nil` |
336
+
337
+ Methods:
338
+
339
+ - `type` -- `:tool_progress`
340
+
341
+ #### ToolUseSummaryMessage
342
+
343
+ Summary of tool use for collapsed display.
344
+
345
+ ```ruby
346
+ ToolUseSummaryMessage = Data.define(:uuid, :session_id, :summary, :preceding_tool_use_ids)
347
+ ```
348
+
349
+ | Field | Type | Default |
350
+ |--------------------------|-----------------|----------|
351
+ | `uuid` | `String` | required |
352
+ | `session_id` | `String` | required |
353
+ | `summary` | `String` | required |
354
+ | `preceding_tool_use_ids` | `Array<String>` | `[]` |
355
+
356
+ Methods:
357
+
358
+ - `type` -- `:tool_use_summary`
359
+
360
+ #### LocalCommandOutputMessage
361
+
362
+ Output from a local command execution.
363
+
364
+ ```ruby
365
+ LocalCommandOutputMessage = Data.define(:uuid, :session_id, :content)
366
+ ```
367
+
368
+ | Field | Type | Default |
369
+ |--------------|----------|---------|
370
+ | `uuid` | `String` | `""` |
371
+ | `session_id` | `String` | `""` |
372
+ | `content` | `String` | `""` |
373
+
374
+ Methods:
375
+
376
+ - `type` -- `:local_command_output`
377
+
378
+ ### Hook Lifecycle
379
+
380
+ #### HookStartedMessage
381
+
382
+ Sent when a hook execution starts.
383
+
384
+ ```ruby
385
+ HookStartedMessage = Data.define(:uuid, :session_id, :hook_id, :hook_name, :hook_event)
386
+ ```
387
+
388
+ | Field | Type | Default |
389
+ |--------------|----------|----------|
390
+ | `uuid` | `String` | required |
391
+ | `session_id` | `String` | required |
392
+ | `hook_id` | `String` | required |
393
+ | `hook_name` | `String` | required |
394
+ | `hook_event` | `String` | required |
395
+
396
+ Methods:
397
+
398
+ - `type` -- `:hook_started`
399
+
400
+ #### HookProgressMessage
401
+
402
+ Progress during hook execution.
403
+
404
+ ```ruby
405
+ HookProgressMessage = Data.define(
406
+ :uuid, :session_id, :hook_id, :hook_name, :hook_event,
407
+ :stdout, :stderr, :output
408
+ )
409
+ ```
410
+
411
+ | Field | Type | Default |
412
+ |--------------|----------|----------|
413
+ | `uuid` | `String` | required |
414
+ | `session_id` | `String` | required |
415
+ | `hook_id` | `String` | required |
416
+ | `hook_name` | `String` | required |
417
+ | `hook_event` | `String` | required |
418
+ | `stdout` | `String` | `""` |
419
+ | `stderr` | `String` | `""` |
420
+ | `output` | `String` | `""` |
421
+
422
+ Methods:
423
+
424
+ - `type` -- `:hook_progress`
425
+
426
+ #### HookResponseMessage
427
+
428
+ Final result of a hook execution.
429
+
430
+ ```ruby
431
+ HookResponseMessage = Data.define(
432
+ :uuid, :session_id, :hook_id, :hook_name, :hook_event,
433
+ :stdout, :stderr, :output, :exit_code, :outcome
434
+ )
435
+ ```
436
+
437
+ | Field | Type | Default |
438
+ |--------------|----------------|----------|
439
+ | `uuid` | `String` | required |
440
+ | `session_id` | `String` | required |
441
+ | `hook_id` | `String, nil` | `nil` |
442
+ | `hook_name` | `String` | required |
443
+ | `hook_event` | `String` | required |
444
+ | `stdout` | `String` | `""` |
445
+ | `stderr` | `String` | `""` |
446
+ | `output` | `String` | `""` |
447
+ | `exit_code` | `Integer, nil` | `nil` |
448
+ | `outcome` | `String, nil` | `nil` |
449
+
450
+ Methods:
451
+
452
+ - `type` -- `:hook_response`
453
+ - `success?` -- `true` if `outcome == "success"`
454
+ - `error?` -- `true` if `outcome == "error"`
455
+ - `cancelled?` -- `true` if `outcome == "cancelled"`
456
+
457
+ ### Task Lifecycle
458
+
459
+ #### TaskStartedMessage
460
+
461
+ Sent when a new task (subagent) starts.
462
+
463
+ ```ruby
464
+ TaskStartedMessage = Data.define(
465
+ :uuid, :session_id, :task_id, :tool_use_id,
466
+ :description, :task_type, :prompt
467
+ )
468
+ ```
469
+
470
+ | Field | Type | Default |
471
+ |---------------|---------------|----------|
472
+ | `uuid` | `String` | required |
473
+ | `session_id` | `String` | required |
474
+ | `task_id` | `String` | required |
475
+ | `tool_use_id` | `String, nil` | `nil` |
476
+ | `description` | `String, nil` | `nil` |
477
+ | `task_type` | `String, nil` | `nil` |
478
+ | `prompt` | `String, nil` | `nil` |
479
+
480
+ Methods:
481
+
482
+ - `type` -- `:task_started`
483
+
484
+ #### TaskProgressMessage
485
+
486
+ Progress during background task (subagent) execution.
487
+
488
+ ```ruby
489
+ TaskProgressMessage = Data.define(
490
+ :uuid, :session_id, :task_id, :tool_use_id,
491
+ :description, :usage, :last_tool_name, :summary
492
+ )
493
+ ```
494
+
495
+ | Field | Type | Default |
496
+ |------------------|---------------|----------|
497
+ | `uuid` | `String` | required |
498
+ | `session_id` | `String` | required |
499
+ | `task_id` | `String` | required |
500
+ | `description` | `String` | required |
501
+ | `tool_use_id` | `String, nil` | `nil` |
502
+ | `usage` | `Hash, nil` | `nil` |
503
+ | `last_tool_name` | `String, nil` | `nil` |
504
+ | `summary` | `String, nil` | `nil` |
505
+
506
+ Methods:
507
+
508
+ - `type` -- `:task_progress`
509
+
510
+ #### TaskNotificationMessage
511
+
512
+ Sent when a background task completes, fails, or is stopped.
513
+
514
+ ```ruby
515
+ TaskNotificationMessage = Data.define(
516
+ :uuid, :session_id, :task_id, :status,
517
+ :output_file, :summary, :tool_use_id, :usage
518
+ )
519
+ ```
520
+
521
+ | Field | Type | Default |
522
+ |---------------|---------------|----------|
523
+ | `uuid` | `String` | required |
524
+ | `session_id` | `String` | required |
525
+ | `task_id` | `String` | required |
526
+ | `status` | `String` | required |
527
+ | `output_file` | `String` | required |
528
+ | `summary` | `String` | required |
529
+ | `tool_use_id` | `String, nil` | `nil` |
530
+ | `usage` | `Hash, nil` | `nil` |
531
+
532
+ Methods:
533
+
534
+ - `type` -- `:task_notification`
535
+ - `completed?` -- `true` if `status == "completed"`
536
+ - `failed?` -- `true` if `status == "failed"`
537
+ - `stopped?` -- `true` if `status == "stopped"`
538
+
539
+ ### Other
540
+
541
+ #### FilesPersistedEvent
542
+
543
+ Sent when files are persisted to storage.
544
+
545
+ ```ruby
546
+ FilesPersistedEvent = Data.define(:uuid, :session_id, :files, :failed, :processed_at)
547
+ ```
548
+
549
+ | Field | Type | Default |
550
+ |----------------|---------------|----------|
551
+ | `uuid` | `String` | required |
552
+ | `session_id` | `String` | required |
553
+ | `files` | `Array<Hash>` | `[]` |
554
+ | `failed` | `Array<Hash>` | `[]` |
555
+ | `processed_at` | `String, nil` | `nil` |
556
+
557
+ Methods:
558
+
559
+ - `type` -- `:files_persisted`
560
+
561
+ #### ElicitationCompleteMessage
562
+
563
+ Sent when an MCP server elicitation request completes.
564
+
565
+ ```ruby
566
+ ElicitationCompleteMessage = Data.define(:uuid, :session_id, :mcp_server_name, :elicitation_id)
567
+ ```
568
+
569
+ | Field | Type | Default |
570
+ |-------------------|----------|---------|
571
+ | `uuid` | `String` | `""` |
572
+ | `session_id` | `String` | `""` |
573
+ | `mcp_server_name` | `String` | `""` |
574
+ | `elicitation_id` | `String` | `""` |
575
+
576
+ Methods:
577
+
578
+ - `type` -- `:elicitation_complete`
579
+
580
+ #### AuthStatusMessage
581
+
582
+ Authentication status during login flows.
583
+
584
+ ```ruby
585
+ AuthStatusMessage = Data.define(:uuid, :session_id, :is_authenticating, :output, :error)
586
+ ```
587
+
588
+ | Field | Type | Default |
589
+ |---------------------|---------------|----------|
590
+ | `uuid` | `String` | required |
591
+ | `session_id` | `String` | required |
592
+ | `is_authenticating` | `Boolean` | required |
593
+ | `output` | `Array` | `[]` |
594
+ | `error` | `String, nil` | `nil` |
595
+
596
+ Methods:
597
+
598
+ - `type` -- `:auth_status`
599
+
600
+ #### GenericMessage
601
+
602
+ Catch-all for unknown/future protocol message types. Supports dynamic field access.
603
+
604
+ ```ruby
605
+ GenericMessage = Data.define(:message_type, :raw)
606
+ ```
607
+
608
+ | Field | Type | Default |
609
+ |----------------|----------|----------|
610
+ | `message_type` | `String` | required |
611
+ | `raw` | `Hash` | required |
612
+
613
+ Methods:
614
+
615
+ - `type` -- `message_type` as a symbol, or `:unknown`
616
+ - `to_h` -- returns `raw`
617
+ - `[](key)` -- hash-style access into `raw`
618
+ - `method_missing` -- dynamic field access into `raw`
619
+
620
+ ```ruby
621
+ msg = GenericMessage.new(message_type: "fancy_new", raw: { data: "hello" })
622
+ msg.type # => :fancy_new
623
+ msg[:data] # => "hello"
624
+ msg.data # => "hello"
625
+ ```
626
+
627
+ ---
628
+
629
+ ## Content Blocks
630
+
631
+ 8 content block types. Found inside `AssistantMessage#content` arrays.
632
+
633
+ ### TextBlock
634
+
635
+ Plain text content.
636
+
637
+ ```ruby
638
+ TextBlock = Data.define(:text)
639
+ ```
640
+
641
+ | Field | Type | Default |
642
+ |--------|----------|----------|
643
+ | `text` | `String` | required |
644
+
645
+ Methods:
646
+
647
+ - `type` -- `:text`
648
+ - `to_h` -- `{ type: "text", text: text }`
649
+
650
+ ### ThinkingBlock
651
+
652
+ Extended thinking content.
653
+
654
+ ```ruby
655
+ ThinkingBlock = Data.define(:thinking, :signature)
656
+ ```
657
+
658
+ | Field | Type | Default |
659
+ |-------------|----------|----------|
660
+ | `thinking` | `String` | required |
661
+ | `signature` | `String` | required |
662
+
663
+ Methods:
664
+
665
+ - `type` -- `:thinking`
666
+ - `to_h` -- `{ type: "thinking", thinking: thinking, signature: signature }`
667
+
668
+ ### ToolUseBlock
669
+
670
+ Tool use request from Claude.
671
+
672
+ ```ruby
673
+ ToolUseBlock = Data.define(:id, :name, :input)
674
+ ```
675
+
676
+ | Field | Type | Default |
677
+ |---------|----------|----------|
678
+ | `id` | `String` | required |
679
+ | `name` | `String` | required |
680
+ | `input` | `Hash` | required |
681
+
682
+ Methods:
683
+
684
+ - `type` -- `:tool_use`
685
+ - `file_path` -- file path for file-based tools (`Read`, `Write`, `Edit`, `NotebookEdit`), else `nil`
686
+ - `display_label` -- one-line human-readable label (e.g., `"Read config/app.rb"`, `"Bash: ls -la"`)
687
+ - `summary(max: 60)` -- detailed summary, truncated to `max` characters
688
+ - `to_h` -- `{ type: "tool_use", id: id, name: name, input: input }`
689
+
690
+ ```ruby
691
+ block = ToolUseBlock.new(id: "tool_1", name: "Read", input: { file_path: "/tmp/file.rb" })
692
+ block.file_path # => "/tmp/file.rb"
693
+ block.display_label # => "Read file.rb"
694
+ block.summary # => "Read: /tmp/file.rb"
695
+ ```
696
+
697
+ ### ToolResultBlock
698
+
699
+ Result returned from a tool execution.
700
+
701
+ ```ruby
702
+ ToolResultBlock = Data.define(:tool_use_id, :content, :is_error)
703
+ ```
704
+
705
+ | Field | Type | Default |
706
+ |---------------|----------------|----------|
707
+ | `tool_use_id` | `String` | required |
708
+ | `content` | `String, nil` | `nil` |
709
+ | `is_error` | `Boolean, nil` | `nil` |
710
+
711
+ Methods:
712
+
713
+ - `type` -- `:tool_result`
714
+ - `to_h` -- includes `content` and `is_error` only when non-nil
715
+
716
+ ### ServerToolUseBlock
717
+
718
+ Tool use request for an MCP server tool.
719
+
720
+ ```ruby
721
+ ServerToolUseBlock = Data.define(:id, :name, :input, :server_name)
722
+ ```
723
+
724
+ | Field | Type | Default |
725
+ |---------------|----------|----------|
726
+ | `id` | `String` | required |
727
+ | `name` | `String` | required |
728
+ | `input` | `Hash` | required |
729
+ | `server_name` | `String` | required |
730
+
731
+ Methods:
732
+
733
+ - `type` -- `:server_tool_use`
734
+ - `file_path` -- file path for file-based tools, else `nil`
735
+ - `display_label` -- label with server context (e.g., `"my-server/tool-name"`)
736
+ - `summary(max: 60)` -- detailed summary with server context, truncated
737
+ - `to_h` -- `{ type: "server_tool_use", id: id, name: name, input: input, server_name: server_name }`
738
+
739
+ ### ServerToolResultBlock
740
+
741
+ Result from an MCP server tool execution.
742
+
743
+ ```ruby
744
+ ServerToolResultBlock = Data.define(:tool_use_id, :content, :is_error, :server_name)
745
+ ```
746
+
747
+ | Field | Type | Default |
748
+ |---------------|----------------|----------|
749
+ | `tool_use_id` | `String` | required |
750
+ | `server_name` | `String` | required |
751
+ | `content` | `String, nil` | `nil` |
752
+ | `is_error` | `Boolean, nil` | `nil` |
753
+
754
+ Methods:
755
+
756
+ - `type` -- `:server_tool_result`
757
+ - `to_h` -- includes `content` and `is_error` only when non-nil
758
+
759
+ ### ImageContentBlock
760
+
761
+ Image content (base64-encoded or URL-sourced).
762
+
763
+ ```ruby
764
+ ImageContentBlock = Data.define(:source)
765
+ ```
766
+
767
+ | Field | Type | Default |
768
+ |----------|--------|----------|
769
+ | `source` | `Hash` | required |
770
+
771
+ Methods:
772
+
773
+ - `type` -- `:image`
774
+ - `source_type` -- `"base64"` or `"url"`
775
+ - `media_type` -- MIME type (e.g., `"image/png"`)
776
+ - `data` -- base64-encoded image data
777
+ - `url` -- URL for URL-sourced images
778
+ - `to_h` -- `{ type: "image", source: source }`
779
+
780
+ ```ruby
781
+ block = ImageContentBlock.new(source: { type: "base64", media_type: "image/png", data: "..." })
782
+ block.source_type # => "base64"
783
+ block.media_type # => "image/png"
784
+ ```
785
+
786
+ ### GenericBlock
787
+
788
+ Catch-all for unknown/future content block types. Supports dynamic field access.
789
+
790
+ ```ruby
791
+ GenericBlock = Data.define(:block_type, :raw)
792
+ ```
793
+
794
+ | Field | Type | Default |
795
+ |--------------|----------|----------|
796
+ | `block_type` | `String` | required |
797
+ | `raw` | `Hash` | required |
798
+
799
+ Methods:
800
+
801
+ - `type` -- `block_type` as a symbol, or `:unknown`
802
+ - `to_h` -- returns `raw`
803
+ - `[](key)` -- hash-style access into `raw`
804
+ - `method_missing` -- dynamic field access into `raw`
805
+
806
+ ---
807
+
808
+ ## Common Patterns
809
+
810
+ ### Iterating content blocks with case
811
+
812
+ ```ruby
813
+ message.content.each do |block|
814
+ case block
815
+ when ClaudeAgent::TextBlock
816
+ puts block.text
817
+ when ClaudeAgent::ThinkingBlock
818
+ puts "[thinking] #{block.thinking}"
819
+ when ClaudeAgent::ToolUseBlock
820
+ puts "Tool: #{block.display_label}"
821
+ when ClaudeAgent::ServerToolUseBlock
822
+ puts "MCP Tool: #{block.display_label}"
823
+ when ClaudeAgent::ImageContentBlock
824
+ puts "Image (#{block.media_type})"
825
+ else
826
+ puts "Unknown block: #{block.type}"
827
+ end
828
+ end
829
+ ```
830
+
831
+ ### Filtering message streams
832
+
833
+ ```ruby
834
+ messages = ClaudeAgent.query(prompt: "Hello", options: options).to_a
835
+
836
+ # Find the final result
837
+ result = messages.find { |m| m.is_a?(ClaudeAgent::ResultMessage) }
838
+
839
+ # Collect all assistant text
840
+ text = messages
841
+ .select { |m| m.is_a?(ClaudeAgent::AssistantMessage) }
842
+ .map(&:text)
843
+ .join
844
+
845
+ # Stream deltas
846
+ messages.each do |msg|
847
+ case msg
848
+ when ClaudeAgent::StreamEvent
849
+ print msg.delta_text if msg.delta_text
850
+ when ClaudeAgent::ResultMessage
851
+ puts "\nDone (#{msg.duration_ms}ms, $#{msg.total_cost_usd})"
852
+ end
853
+ end
854
+ ```
855
+
856
+ ### Using text_content universally
857
+
858
+ ```ruby
859
+ messages.each do |msg|
860
+ text = msg.text_content
861
+ puts text unless text.empty?
862
+ end
863
+ ```
864
+
865
+ ### Checking message identity
866
+
867
+ ```ruby
868
+ messages.select(&:session_message?).each do |msg|
869
+ puts "#{msg.type} [#{msg.uuid}] session=#{msg.session_id}"
870
+ end
871
+ ```