claude-agent-sdk 0.7.2 → 0.8.0
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/CHANGELOG.md +60 -1
- data/README.md +222 -8
- data/lib/claude_agent_sdk/message_parser.rb +28 -5
- data/lib/claude_agent_sdk/query.rb +39 -4
- data/lib/claude_agent_sdk/sdk_mcp_server.rb +21 -6
- data/lib/claude_agent_sdk/sessions.rb +499 -0
- data/lib/claude_agent_sdk/types.rb +176 -11
- data/lib/claude_agent_sdk/version.rb +1 -1
- data/lib/claude_agent_sdk.rb +42 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2c723c3e660d135ab87311e27be6a80b8d13127f9e42789a66b1d30ec0395278
|
|
4
|
+
data.tar.gz: 0652f808dd792a6bef8db32afc567567fe9f016abdf095f1d13ae0ac07af0d03
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 399925ff55a9a23ab54cbe97adb18fea804be6c92cf21aa3ca3fdcd4cbe54693dfc8b77b85f15ed2eaf1517acaecd09935701d0248fb99a23be6325d151c99aa
|
|
7
|
+
data.tar.gz: dd14802c444f4d6609cbb8215e79c3f5d8c4d9453e20d0d3af40cd853d653625c93e117082118beee2c5fb0ad29447a170687a44d56f88be6785affb8d118060
|
data/CHANGELOG.md
CHANGED
|
@@ -5,11 +5,70 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.8.0] - 2026-03-05
|
|
9
|
+
|
|
10
|
+
Port of Python SDK v0.1.46 features.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
#### Task Message Types
|
|
15
|
+
- `TaskStartedMessage`, `TaskProgressMessage`, `TaskNotificationMessage` — typed `SystemMessage` subclasses for background task lifecycle events
|
|
16
|
+
- `TASK_NOTIFICATION_STATUSES` constant (`completed`, `failed`, `stopped`)
|
|
17
|
+
- `MessageParser` dispatches on `subtype` within `system` messages, falling back to generic `SystemMessage` for unknown subtypes
|
|
18
|
+
|
|
19
|
+
#### MCP Server Control
|
|
20
|
+
- `reconnect_mcp_server(server_name)` on `Query` and `Client` — retry failed MCP server connections
|
|
21
|
+
- `toggle_mcp_server(server_name, enabled)` on `Query` and `Client` — enable/disable MCP servers live
|
|
22
|
+
- `stop_task(task_id)` on `Query` and `Client` — stop a running background task
|
|
23
|
+
|
|
24
|
+
#### Subagent Context on Hook Inputs
|
|
25
|
+
- `agent_id` and `agent_type` attributes on `PreToolUseHookInput`, `PostToolUseHookInput`, `PostToolUseFailureHookInput`, `PermissionRequestHookInput`
|
|
26
|
+
- Populated when hooks fire inside subagents, allowing attribution of tool calls to specific agents
|
|
27
|
+
|
|
28
|
+
#### Result Message
|
|
29
|
+
- `stop_reason` attribute on `ResultMessage` (e.g., `'end_turn'`, `'max_tokens'`, `'stop_sequence'`)
|
|
30
|
+
|
|
31
|
+
#### Typed MCP Status Response
|
|
32
|
+
- `McpServerInfo`, `McpToolAnnotations`, `McpToolInfo`, `McpServerStatus`, `McpStatusResponse` types
|
|
33
|
+
- `.parse` class methods for hydrating from raw CLI response hashes
|
|
34
|
+
- `MCP_SERVER_CONNECTION_STATUSES` constant (`connected`, `failed`, `needs-auth`, `pending`, `disabled`)
|
|
35
|
+
|
|
36
|
+
#### Session Browsing
|
|
37
|
+
- `ClaudeAgentSDK.list_sessions(directory:, limit:, include_worktrees:)` — list sessions from `~/.claude/projects/` JSONL files
|
|
38
|
+
- `ClaudeAgentSDK.get_session_messages(session_id:, directory:, limit:, offset:)` — reconstruct conversation chain from session transcript
|
|
39
|
+
- `SDKSessionInfo` type with `session_id`, `summary`, `last_modified`, `file_size`, `custom_title`, `first_prompt`, `git_branch`, `cwd`
|
|
40
|
+
- `SessionMessage` type with `type`, `uuid`, `session_id`, `message`
|
|
41
|
+
- Pure filesystem operations — no CLI subprocess required
|
|
42
|
+
- Git worktree-aware session scanning
|
|
43
|
+
- `parentUuid` chain walking with cycle detection for robust conversation reconstruction
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
- **`McpToolAnnotations.parse` losing `false` values:** `readOnly: false` was evaluated as `false || nil → nil` due to `||` short-circuiting. Now uses `.key?` to check presence before falling back to snake_case keys.
|
|
47
|
+
|
|
48
|
+
## [0.7.3] - 2026-02-26
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
- **String-keyed JSON schema crash:** Libraries like [RubyLLM](https://github.com/crmne/ruby_llm) that deep-stringify schema keys (e.g., `{ 'type' => 'object', 'properties' => { ... } }`) were misidentified as simple type-mapping schemas, causing each top-level key to be treated as a parameter name instead of passing the schema through. Now both symbol-keyed and string-keyed schemas are detected and normalized correctly. (PR #9 by [@iuhoay](https://github.com/iuhoay))
|
|
52
|
+
- **Shallow key symbolization:** `convert_schema` used `transform_keys` (shallow) which left nested property keys as strings, breaking downstream `MCP::Tool::InputSchema` construction. Now uses deep symbolization recursively.
|
|
53
|
+
- **Guard ordering crash:** `convert_schema` and `convert_input_schema` accessed `schema[:type]` before the `schema.is_a?(Hash)` guard, which would raise `NoMethodError` on `nil` input.
|
|
54
|
+
- **Schema detection tightened:** Pre-built schema detection now requires `type == 'object'` and `properties.is_a?(Hash)`, preventing false positives when a simple schema happens to have parameters named `type` and `properties`.
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
- `ClaudeAgentSDK.deep_symbolize_keys` utility method for recursive hash key symbolization
|
|
58
|
+
|
|
8
59
|
## [0.7.2] - 2026-02-21
|
|
9
60
|
|
|
10
61
|
### Fixed
|
|
62
|
+
- **Unknown content block crash:** Unrecognized content block types (e.g., `document` blocks from PDF reading) now return `UnknownBlock` instead of raising `MessageParseError`, aligning with the Python SDK's forward-compatible design
|
|
63
|
+
- **Unknown message type crash:** Unrecognized message types now return `nil` (skipped by callers) instead of raising
|
|
11
64
|
- **Empty input schema crash:** Tools with no parameters (`input_schema: {}`) caused `MCP::Tool::InputSchema` validation failure (`required` array must have at least 1 item per JSON Schema draft-04). Now omits `required` when empty.
|
|
12
|
-
|
|
65
|
+
|
|
66
|
+
### Added
|
|
67
|
+
- `UnknownBlock` type that preserves raw data for unrecognized content block types
|
|
68
|
+
|
|
69
|
+
### Changed
|
|
70
|
+
- **Breaking (minor):** `MessageParser.parse` no longer raises `MessageParseError` for unknown message types — returns `nil` instead. If you were rescuing `MessageParseError` to handle unknown types, check for `nil` return values instead.
|
|
71
|
+
- **Breaking (minor):** `MessageParser.parse_content_block` no longer raises `MessageParseError` for unknown content block types — returns `UnknownBlock` instead. Content block iteration using `is_a?` filtering (e.g., `block.is_a?(TextBlock)`) is unaffected.
|
|
13
72
|
|
|
14
73
|
## [0.7.1] - 2026-02-21
|
|
15
74
|
|
data/README.md
CHANGED
|
@@ -6,6 +6,119 @@
|
|
|
6
6
|
|
|
7
7
|
[](https://badge.fury.io/rb/claude-agent-sdk)
|
|
8
8
|
|
|
9
|
+
### Feature Parity with Python SDK (v0.1.46)
|
|
10
|
+
|
|
11
|
+
| Feature | Python | Ruby |
|
|
12
|
+
|---------|:------:|:----:|
|
|
13
|
+
| One-shot `query()` | `query()` | `query()` |
|
|
14
|
+
| Bidirectional `Client` | `ClaudeSDKClient` | `Client` |
|
|
15
|
+
| Streaming input | `AsyncIterable` | `Enumerator` |
|
|
16
|
+
| Custom tools (SDK MCP servers) | `@tool` decorator | `create_tool` block |
|
|
17
|
+
| MCP resources & prompts | ✅ | ✅ |
|
|
18
|
+
| Hooks (all 10 events) | ✅ | ✅ |
|
|
19
|
+
| Permission callbacks (`can_use_tool`) | ✅ | ✅ |
|
|
20
|
+
| Structured output | ✅ | ✅ |
|
|
21
|
+
| Thinking config (adaptive/enabled/disabled) | ✅ | ✅ |
|
|
22
|
+
| Effort levels | ✅ | ✅ |
|
|
23
|
+
| Programmatic subagents | ✅ | ✅ |
|
|
24
|
+
| Sandbox settings | ✅ | ✅ |
|
|
25
|
+
| Beta features (1M context) | ✅ | ✅ |
|
|
26
|
+
| File checkpointing & rewind | ✅ | ✅ |
|
|
27
|
+
| Session browsing (`list_sessions`, `get_session_messages`) | ✅ | ✅ |
|
|
28
|
+
| Task message types (started/progress/notification) | ✅ | ✅ |
|
|
29
|
+
| MCP server control (reconnect/toggle/stop) | ✅ | ✅ |
|
|
30
|
+
| Subagent context on hook inputs | ✅ | ✅ |
|
|
31
|
+
| Typed MCP status response | ✅ | ✅ |
|
|
32
|
+
| `stop_reason` on `ResultMessage` | ✅ | ✅ |
|
|
33
|
+
| Fallback model | ✅ | ✅ |
|
|
34
|
+
| Plugin support | ✅ | ✅ |
|
|
35
|
+
| Rails integration (configure block, ActionCable) | — | ✅ |
|
|
36
|
+
| Bundled CLI binary | ✅ | — |
|
|
37
|
+
|
|
38
|
+
<details>
|
|
39
|
+
<summary><strong>Usage & Implementation Differences</strong></summary>
|
|
40
|
+
|
|
41
|
+
#### Async model
|
|
42
|
+
|
|
43
|
+
Python uses `async`/`await` with `anyio` (works with both asyncio and Trio). Ruby uses the [`async`](https://github.com/socketry/async) gem with fibers — no `await` keyword needed, blocking calls yield automatically.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
# Python
|
|
47
|
+
async with ClaudeSDKClient(options) as client:
|
|
48
|
+
await client.query("Hello")
|
|
49
|
+
async for msg in client.receive_messages():
|
|
50
|
+
print(msg)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
# Ruby
|
|
55
|
+
Async do
|
|
56
|
+
client = ClaudeAgentSDK::Client.new(options: options)
|
|
57
|
+
client.connect
|
|
58
|
+
client.query("Hello")
|
|
59
|
+
client.receive_messages { |msg| puts msg }
|
|
60
|
+
client.disconnect
|
|
61
|
+
end.wait
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
#### Custom tools
|
|
65
|
+
|
|
66
|
+
Python uses a `@tool` decorator. Ruby uses `create_tool` with a block.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
# Python
|
|
70
|
+
@tool(name="add", description="Add numbers", input_schema={...})
|
|
71
|
+
def add(a: int, b: int) -> str:
|
|
72
|
+
return str(a + b)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
# Ruby
|
|
77
|
+
add = ClaudeAgentSDK.create_tool("add", "Add numbers", { a: :number, b: :number }) do |args|
|
|
78
|
+
{ content: [{ type: "text", text: (args[:a] + args[:b]).to_s }] }
|
|
79
|
+
end
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
#### Streaming input
|
|
83
|
+
|
|
84
|
+
Python uses `AsyncIterable`. Ruby uses `Enumerator` or any `#each`-able.
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
# Python
|
|
88
|
+
async def messages():
|
|
89
|
+
yield {"type": "user", "message": {"role": "user", "content": "Hello"}}
|
|
90
|
+
|
|
91
|
+
async for msg in query(prompt=messages(), options=options):
|
|
92
|
+
print(msg)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
# Ruby
|
|
97
|
+
messages = ClaudeAgentSDK::Streaming.from_array(["Hello", "Follow up"])
|
|
98
|
+
ClaudeAgentSDK.query(prompt: messages) { |msg| puts msg }
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### Types
|
|
102
|
+
|
|
103
|
+
Python uses `dataclass` with type annotations and `TypedDict`. Ruby uses plain classes with `attr_accessor` and keyword args — no runtime type checking, but the same structure.
|
|
104
|
+
|
|
105
|
+
#### Configuration defaults
|
|
106
|
+
|
|
107
|
+
Python passes options directly. Ruby adds `ClaudeAgentSDK.configure` for global defaults that merge with per-call options — handy for Rails initializers.
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
# Ruby-only: global defaults
|
|
111
|
+
ClaudeAgentSDK.configure do |config|
|
|
112
|
+
config.default_options = { model: "sonnet", permission_mode: "bypassPermissions" }
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### Subprocess transport
|
|
117
|
+
|
|
118
|
+
Both SDKs spawn `claude` CLI as a subprocess with stream-JSON over stdin/stdout. Python uses `anyio.open_process`; Ruby uses `Open3.popen3` with a background `Thread` for stderr. The wire protocol is identical.
|
|
119
|
+
|
|
120
|
+
</details>
|
|
121
|
+
|
|
9
122
|
## Table of Contents
|
|
10
123
|
|
|
11
124
|
- [Installation](#installation)
|
|
@@ -23,6 +136,7 @@
|
|
|
23
136
|
- [Tools Configuration](#tools-configuration)
|
|
24
137
|
- [Sandbox Settings](#sandbox-settings)
|
|
25
138
|
- [File Checkpointing & Rewind](#file-checkpointing--rewind)
|
|
139
|
+
- [Session Browsing](#session-browsing)
|
|
26
140
|
- [Rails Integration](#rails-integration)
|
|
27
141
|
- [Types](#types)
|
|
28
142
|
- [Error Handling](#error-handling)
|
|
@@ -39,7 +153,7 @@ Add this line to your application's Gemfile:
|
|
|
39
153
|
gem 'claude-agent-sdk', github: 'ya-luotao/claude-agent-sdk-ruby'
|
|
40
154
|
|
|
41
155
|
# Or use a stable version from RubyGems
|
|
42
|
-
gem 'claude-agent-sdk', '~> 0.
|
|
156
|
+
gem 'claude-agent-sdk', '~> 0.8.0'
|
|
43
157
|
```
|
|
44
158
|
|
|
45
159
|
And then execute:
|
|
@@ -225,13 +339,18 @@ Async do
|
|
|
225
339
|
puts "MCP status: #{status}"
|
|
226
340
|
|
|
227
341
|
# Get server initialization info
|
|
228
|
-
info = client.server_info
|
|
229
|
-
puts "Available commands: #{info}"
|
|
230
|
-
|
|
231
|
-
# (Parity alias) Get server initialization info
|
|
232
342
|
info = client.get_server_info
|
|
233
343
|
puts "Available commands: #{info}"
|
|
234
344
|
|
|
345
|
+
# Reconnect a failed MCP server
|
|
346
|
+
client.reconnect_mcp_server('my-server')
|
|
347
|
+
|
|
348
|
+
# Enable or disable an MCP server
|
|
349
|
+
client.toggle_mcp_server('my-server', false)
|
|
350
|
+
|
|
351
|
+
# Stop a running background task
|
|
352
|
+
client.stop_task('task_abc123')
|
|
353
|
+
|
|
235
354
|
client.disconnect
|
|
236
355
|
end.wait
|
|
237
356
|
```
|
|
@@ -282,6 +401,26 @@ Async do
|
|
|
282
401
|
end.wait
|
|
283
402
|
```
|
|
284
403
|
|
|
404
|
+
### Pre-built JSON Schemas
|
|
405
|
+
|
|
406
|
+
If your schemas come from another library (e.g., [RubyLLM](https://github.com/crmne/ruby_llm)) that deep-stringifies keys, the SDK handles them transparently — both symbol-keyed and string-keyed schemas are accepted and normalized:
|
|
407
|
+
|
|
408
|
+
```ruby
|
|
409
|
+
# Symbol keys (standard Ruby)
|
|
410
|
+
tool = ClaudeAgentSDK.create_tool('save', 'Save a fact', {
|
|
411
|
+
type: 'object',
|
|
412
|
+
properties: { fact: { type: 'string' } },
|
|
413
|
+
required: ['fact']
|
|
414
|
+
}) { |args| { content: [{ type: 'text', text: "Saved: #{args[:fact]}" }] } }
|
|
415
|
+
|
|
416
|
+
# String keys (e.g., from RubyLLM or JSON.parse)
|
|
417
|
+
tool = ClaudeAgentSDK.create_tool('save', 'Save a fact', {
|
|
418
|
+
'type' => 'object',
|
|
419
|
+
'properties' => { 'fact' => { 'type' => 'string' } },
|
|
420
|
+
'required' => ['fact']
|
|
421
|
+
}) { |args| { content: [{ type: 'text', text: "Saved: #{args[:fact]}" }] } }
|
|
422
|
+
```
|
|
423
|
+
|
|
285
424
|
### Benefits Over External MCP Servers
|
|
286
425
|
|
|
287
426
|
- **No subprocess management** - Runs in the same process as your application
|
|
@@ -754,6 +893,47 @@ end.wait
|
|
|
754
893
|
|
|
755
894
|
> **Note:** The `uuid` field on `UserMessage` is populated by the CLI and represents checkpoint identifiers. Rewinding to a UUID restores file state to what it was at that point in the conversation.
|
|
756
895
|
|
|
896
|
+
## Session Browsing
|
|
897
|
+
|
|
898
|
+
Browse and inspect previous Claude Code sessions directly from Ruby — no CLI subprocess required.
|
|
899
|
+
|
|
900
|
+
### Listing Sessions
|
|
901
|
+
|
|
902
|
+
```ruby
|
|
903
|
+
# List all sessions (sorted by most recent first)
|
|
904
|
+
sessions = ClaudeAgentSDK.list_sessions
|
|
905
|
+
sessions.each do |session|
|
|
906
|
+
puts "#{session.session_id}: #{session.summary} (#{session.git_branch})"
|
|
907
|
+
end
|
|
908
|
+
|
|
909
|
+
# List sessions for a specific directory
|
|
910
|
+
sessions = ClaudeAgentSDK.list_sessions(directory: '/path/to/project', limit: 10)
|
|
911
|
+
|
|
912
|
+
# Include git worktree sessions
|
|
913
|
+
sessions = ClaudeAgentSDK.list_sessions(directory: '.', include_worktrees: true)
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
Each `SDKSessionInfo` includes:
|
|
917
|
+
- `session_id`, `summary`, `last_modified`, `file_size`
|
|
918
|
+
- `custom_title`, `first_prompt`, `git_branch`, `cwd`
|
|
919
|
+
|
|
920
|
+
### Reading Session Messages
|
|
921
|
+
|
|
922
|
+
```ruby
|
|
923
|
+
# Get the full conversation from a session
|
|
924
|
+
messages = ClaudeAgentSDK.get_session_messages(session_id: 'abc-123-...')
|
|
925
|
+
messages.each do |msg|
|
|
926
|
+
puts "[#{msg.type}] #{msg.message}"
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
# Paginate through messages
|
|
930
|
+
page = ClaudeAgentSDK.get_session_messages(session_id: 'abc-123-...', offset: 10, limit: 20)
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
Each `SessionMessage` includes `type` (`"user"` or `"assistant"`), `uuid`, `session_id`, and `message` (raw API dict).
|
|
934
|
+
|
|
935
|
+
> **Note:** Session browsing reads `~/.claude/projects/` JSONL files directly. It respects the `CLAUDE_CONFIG_DIR` environment variable and automatically detects git worktrees.
|
|
936
|
+
|
|
757
937
|
## Rails Integration
|
|
758
938
|
|
|
759
939
|
The SDK integrates well with Rails applications. Here are common patterns:
|
|
@@ -946,13 +1126,26 @@ end
|
|
|
946
1126
|
|
|
947
1127
|
#### SystemMessage
|
|
948
1128
|
|
|
949
|
-
System message with metadata.
|
|
1129
|
+
System message with metadata. Task lifecycle events are typed subclasses.
|
|
950
1130
|
|
|
951
1131
|
```ruby
|
|
952
1132
|
class SystemMessage
|
|
953
|
-
attr_accessor :subtype, # String ('init', etc.)
|
|
1133
|
+
attr_accessor :subtype, # String ('init', 'task_started', 'task_progress', 'task_notification', etc.)
|
|
954
1134
|
:data # Hash
|
|
955
1135
|
end
|
|
1136
|
+
|
|
1137
|
+
# Typed subclasses (all inherit from SystemMessage, so is_a?(SystemMessage) still works)
|
|
1138
|
+
class TaskStartedMessage < SystemMessage
|
|
1139
|
+
attr_accessor :task_id, :description, :uuid, :session_id, :tool_use_id, :task_type
|
|
1140
|
+
end
|
|
1141
|
+
|
|
1142
|
+
class TaskProgressMessage < SystemMessage
|
|
1143
|
+
attr_accessor :task_id, :description, :usage, :uuid, :session_id, :tool_use_id, :last_tool_name
|
|
1144
|
+
end
|
|
1145
|
+
|
|
1146
|
+
class TaskNotificationMessage < SystemMessage
|
|
1147
|
+
attr_accessor :task_id, :status, :output_file, :summary, :uuid, :session_id, :tool_use_id, :usage
|
|
1148
|
+
end
|
|
956
1149
|
```
|
|
957
1150
|
|
|
958
1151
|
#### ResultMessage
|
|
@@ -967,6 +1160,7 @@ class ResultMessage
|
|
|
967
1160
|
:is_error, # Boolean
|
|
968
1161
|
:num_turns, # Integer
|
|
969
1162
|
:session_id, # String
|
|
1163
|
+
:stop_reason, # String | nil ('end_turn', 'max_tokens', 'stop_sequence')
|
|
970
1164
|
:total_cost_usd, # Float | nil
|
|
971
1165
|
:usage, # Hash | nil
|
|
972
1166
|
:result, # String | nil (final text result)
|
|
@@ -978,7 +1172,7 @@ end
|
|
|
978
1172
|
|
|
979
1173
|
```ruby
|
|
980
1174
|
# Union type of all content blocks
|
|
981
|
-
ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock
|
|
1175
|
+
ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock | UnknownBlock
|
|
982
1176
|
```
|
|
983
1177
|
|
|
984
1178
|
#### TextBlock
|
|
@@ -1026,6 +1220,17 @@ class ToolResultBlock
|
|
|
1026
1220
|
end
|
|
1027
1221
|
```
|
|
1028
1222
|
|
|
1223
|
+
#### UnknownBlock
|
|
1224
|
+
|
|
1225
|
+
Generic content block for types the SDK doesn't explicitly handle (e.g., `document` for PDFs, `image` for inline images). Preserves the raw data for forward compatibility with newer CLI versions.
|
|
1226
|
+
|
|
1227
|
+
```ruby
|
|
1228
|
+
class UnknownBlock
|
|
1229
|
+
attr_accessor :type, # String — the original block type (e.g., "document")
|
|
1230
|
+
:data # Hash — the full raw block hash
|
|
1231
|
+
end
|
|
1232
|
+
```
|
|
1233
|
+
|
|
1029
1234
|
### Error Types
|
|
1030
1235
|
|
|
1031
1236
|
```ruby
|
|
@@ -1080,6 +1285,13 @@ end
|
|
|
1080
1285
|
| `McpSSEServerConfig` | MCP server config for SSE transport |
|
|
1081
1286
|
| `McpHttpServerConfig` | MCP server config for HTTP transport |
|
|
1082
1287
|
| `SdkPluginConfig` | SDK plugin configuration |
|
|
1288
|
+
| `McpServerStatus` | Status of a single MCP server connection (with `.parse`) |
|
|
1289
|
+
| `McpStatusResponse` | Response from `get_mcp_status` containing all server statuses (with `.parse`) |
|
|
1290
|
+
| `McpServerInfo` | MCP server name and version |
|
|
1291
|
+
| `McpToolInfo` | MCP tool name, description, and annotations |
|
|
1292
|
+
| `McpToolAnnotations` | MCP tool annotation hints (`read_only`, `destructive`, `open_world`) |
|
|
1293
|
+
| `SDKSessionInfo` | Session metadata from `list_sessions` |
|
|
1294
|
+
| `SessionMessage` | Single message from `get_session_messages` |
|
|
1083
1295
|
| `SandboxSettings` | Sandbox settings for isolated command execution |
|
|
1084
1296
|
| `SandboxNetworkConfig` | Network configuration for sandbox |
|
|
1085
1297
|
| `SandboxIgnoreViolations` | Configure which sandbox violations to ignore |
|
|
@@ -1095,6 +1307,8 @@ end
|
|
|
1095
1307
|
| `SETTING_SOURCES` | Available setting sources |
|
|
1096
1308
|
| `HOOK_EVENTS` | Available hook events |
|
|
1097
1309
|
| `ASSISTANT_MESSAGE_ERRORS` | Possible error types in AssistantMessage |
|
|
1310
|
+
| `TASK_NOTIFICATION_STATUSES` | Task lifecycle notification statuses (`completed`, `failed`, `stopped`) |
|
|
1311
|
+
| `MCP_SERVER_CONNECTION_STATUSES` | MCP server connection states (`connected`, `failed`, `needs-auth`, `pending`, `disabled`) |
|
|
1098
1312
|
|
|
1099
1313
|
## Error Handling
|
|
1100
1314
|
|
|
@@ -66,10 +66,32 @@ module ClaudeAgentSDK
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def self.parse_system_message(data)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
case data[:subtype]
|
|
70
|
+
when 'task_started'
|
|
71
|
+
TaskStartedMessage.new(
|
|
72
|
+
subtype: data[:subtype], data: data,
|
|
73
|
+
task_id: data[:task_id], description: data[:description],
|
|
74
|
+
uuid: data[:uuid], session_id: data[:session_id],
|
|
75
|
+
tool_use_id: data[:tool_use_id], task_type: data[:task_type]
|
|
76
|
+
)
|
|
77
|
+
when 'task_progress'
|
|
78
|
+
TaskProgressMessage.new(
|
|
79
|
+
subtype: data[:subtype], data: data,
|
|
80
|
+
task_id: data[:task_id], description: data[:description],
|
|
81
|
+
usage: data[:usage], uuid: data[:uuid], session_id: data[:session_id],
|
|
82
|
+
tool_use_id: data[:tool_use_id], last_tool_name: data[:last_tool_name]
|
|
83
|
+
)
|
|
84
|
+
when 'task_notification'
|
|
85
|
+
TaskNotificationMessage.new(
|
|
86
|
+
subtype: data[:subtype], data: data,
|
|
87
|
+
task_id: data[:task_id], status: data[:status],
|
|
88
|
+
output_file: data[:output_file], summary: data[:summary],
|
|
89
|
+
uuid: data[:uuid], session_id: data[:session_id],
|
|
90
|
+
tool_use_id: data[:tool_use_id], usage: data[:usage]
|
|
91
|
+
)
|
|
92
|
+
else
|
|
93
|
+
SystemMessage.new(subtype: data[:subtype], data: data)
|
|
94
|
+
end
|
|
73
95
|
end
|
|
74
96
|
|
|
75
97
|
def self.parse_result_message(data)
|
|
@@ -80,10 +102,11 @@ module ClaudeAgentSDK
|
|
|
80
102
|
is_error: data[:is_error],
|
|
81
103
|
num_turns: data[:num_turns],
|
|
82
104
|
session_id: data[:session_id],
|
|
105
|
+
stop_reason: data[:stop_reason],
|
|
83
106
|
total_cost_usd: data[:total_cost_usd],
|
|
84
107
|
usage: data[:usage],
|
|
85
108
|
result: data[:result],
|
|
86
|
-
structured_output: data[:structured_output]
|
|
109
|
+
structured_output: data[:structured_output]
|
|
87
110
|
)
|
|
88
111
|
end
|
|
89
112
|
|
|
@@ -316,13 +316,19 @@ module ClaudeAgentSDK
|
|
|
316
316
|
permission_mode: fetch.call(:permission_mode)
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
+
# Subagent context fields shared by tool-lifecycle hooks
|
|
320
|
+
subagent_args = {
|
|
321
|
+
agent_id: fetch.call(:agent_id),
|
|
322
|
+
agent_type: fetch.call(:agent_type)
|
|
323
|
+
}
|
|
324
|
+
|
|
319
325
|
case event_name
|
|
320
326
|
when 'PreToolUse'
|
|
321
327
|
PreToolUseHookInput.new(
|
|
322
328
|
tool_name: fetch.call(:tool_name),
|
|
323
329
|
tool_input: fetch.call(:tool_input),
|
|
324
330
|
tool_use_id: fetch.call(:tool_use_id),
|
|
325
|
-
**base_args
|
|
331
|
+
**subagent_args, **base_args
|
|
326
332
|
)
|
|
327
333
|
when 'PostToolUse'
|
|
328
334
|
PostToolUseHookInput.new(
|
|
@@ -330,7 +336,7 @@ module ClaudeAgentSDK
|
|
|
330
336
|
tool_input: fetch.call(:tool_input),
|
|
331
337
|
tool_response: fetch.call(:tool_response),
|
|
332
338
|
tool_use_id: fetch.call(:tool_use_id),
|
|
333
|
-
**base_args
|
|
339
|
+
**subagent_args, **base_args
|
|
334
340
|
)
|
|
335
341
|
when 'PostToolUseFailure'
|
|
336
342
|
PostToolUseFailureHookInput.new(
|
|
@@ -339,7 +345,7 @@ module ClaudeAgentSDK
|
|
|
339
345
|
tool_use_id: fetch.call(:tool_use_id),
|
|
340
346
|
error: fetch.call(:error),
|
|
341
347
|
is_interrupt: fetch.call(:is_interrupt),
|
|
342
|
-
**base_args
|
|
348
|
+
**subagent_args, **base_args
|
|
343
349
|
)
|
|
344
350
|
when 'UserPromptSubmit'
|
|
345
351
|
UserPromptSubmitHookInput.new(
|
|
@@ -377,7 +383,7 @@ module ClaudeAgentSDK
|
|
|
377
383
|
tool_name: fetch.call(:tool_name),
|
|
378
384
|
tool_input: fetch.call(:tool_input),
|
|
379
385
|
permission_suggestions: fetch.call(:permission_suggestions),
|
|
380
|
-
**base_args
|
|
386
|
+
**subagent_args, **base_args
|
|
381
387
|
)
|
|
382
388
|
when 'PreCompact'
|
|
383
389
|
PreCompactHookInput.new(
|
|
@@ -662,6 +668,35 @@ module ClaudeAgentSDK
|
|
|
662
668
|
})
|
|
663
669
|
end
|
|
664
670
|
|
|
671
|
+
# Reconnect a failed MCP server
|
|
672
|
+
# @param server_name [String] Name of the MCP server to reconnect
|
|
673
|
+
def reconnect_mcp_server(server_name)
|
|
674
|
+
send_control_request({
|
|
675
|
+
subtype: 'mcp_reconnect',
|
|
676
|
+
serverName: server_name
|
|
677
|
+
})
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
# Enable or disable an MCP server
|
|
681
|
+
# @param server_name [String] Name of the MCP server
|
|
682
|
+
# @param enabled [Boolean] Whether to enable or disable
|
|
683
|
+
def toggle_mcp_server(server_name, enabled)
|
|
684
|
+
send_control_request({
|
|
685
|
+
subtype: 'mcp_toggle',
|
|
686
|
+
serverName: server_name,
|
|
687
|
+
enabled: enabled
|
|
688
|
+
})
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
# Stop a running background task
|
|
692
|
+
# @param task_id [String] The ID of the task to stop
|
|
693
|
+
def stop_task(task_id)
|
|
694
|
+
send_control_request({
|
|
695
|
+
subtype: 'stop_task',
|
|
696
|
+
task_id: task_id
|
|
697
|
+
})
|
|
698
|
+
end
|
|
699
|
+
|
|
665
700
|
# Rewind files to a previous checkpoint (v0.1.15+)
|
|
666
701
|
# Restores file state to what it was at the given user message
|
|
667
702
|
# Requires enable_file_checkpointing to be true in options
|
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
require 'mcp'
|
|
4
4
|
|
|
5
5
|
module ClaudeAgentSDK
|
|
6
|
+
# Recursively convert all hash keys to symbols
|
|
7
|
+
def self.deep_symbolize_keys(obj)
|
|
8
|
+
case obj
|
|
9
|
+
when Hash then obj.transform_keys(&:to_sym).transform_values { |v| deep_symbolize_keys(v) }
|
|
10
|
+
when Array then obj.map { |v| deep_symbolize_keys(v) }
|
|
11
|
+
else obj
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
6
15
|
# SDK MCP Server - wraps official MCP::Server with block-based API
|
|
7
16
|
#
|
|
8
17
|
# Unlike external MCP servers that run as separate processes, SDK MCP servers
|
|
@@ -191,9 +200,12 @@ module ClaudeAgentSDK
|
|
|
191
200
|
private
|
|
192
201
|
|
|
193
202
|
def convert_schema(schema)
|
|
194
|
-
# If it's already a proper JSON schema,
|
|
195
|
-
|
|
196
|
-
|
|
203
|
+
# If it's already a proper JSON schema (symbol or string keys), normalize
|
|
204
|
+
# to symbol keys so downstream code (schema[:properties]) works uniformly.
|
|
205
|
+
if schema.is_a?(Hash)
|
|
206
|
+
type_val = schema[:type] || schema['type']
|
|
207
|
+
props_val = schema[:properties] || schema['properties']
|
|
208
|
+
return ClaudeAgentSDK.deep_symbolize_keys(schema) if type_val == 'object' && props_val.is_a?(Hash)
|
|
197
209
|
end
|
|
198
210
|
|
|
199
211
|
# Simple schema: hash mapping parameter names to types
|
|
@@ -318,9 +330,12 @@ module ClaudeAgentSDK
|
|
|
318
330
|
end
|
|
319
331
|
|
|
320
332
|
def convert_input_schema(schema)
|
|
321
|
-
# If it's already a proper JSON schema,
|
|
322
|
-
|
|
323
|
-
|
|
333
|
+
# If it's already a proper JSON schema (symbol or string keys), normalize
|
|
334
|
+
# to symbol keys for consistent output.
|
|
335
|
+
if schema.is_a?(Hash)
|
|
336
|
+
type_val = schema[:type] || schema['type']
|
|
337
|
+
props_val = schema[:properties] || schema['properties']
|
|
338
|
+
return ClaudeAgentSDK.deep_symbolize_keys(schema) if type_val == 'object' && props_val.is_a?(Hash)
|
|
324
339
|
end
|
|
325
340
|
|
|
326
341
|
# Simple schema: hash mapping parameter names to types
|