claude-agent-sdk 0.36.0 → 1.0.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 +34 -0
- data/README.md +7 -3
- data/UPGRADING-1.0.md +151 -0
- data/docs/client.md +26 -1
- data/docs/errors.md +6 -0
- data/docs/hooks-and-permissions.md +5 -3
- data/docs/mcp-servers.md +1 -2
- data/docs/sessions.md +101 -3
- data/docs/types.md +109 -4
- data/lib/claude_agent_sdk/cli_installer.rb +30 -3
- data/lib/claude_agent_sdk/command_builder.rb +109 -98
- data/lib/claude_agent_sdk/deprecation.rb +1 -1
- data/lib/claude_agent_sdk/errors.rb +10 -0
- data/lib/claude_agent_sdk/fiber_boundary.rb +2 -0
- data/lib/claude_agent_sdk/instrumentation/otel.rb +15 -7
- data/lib/claude_agent_sdk/message_parser.rb +23 -9
- data/lib/claude_agent_sdk/observer.rb +2 -1
- data/lib/claude_agent_sdk/option_warnings.rb +2 -0
- data/lib/claude_agent_sdk/query.rb +50 -43
- data/lib/claude_agent_sdk/sdk_mcp_server.rb +22 -13
- data/lib/claude_agent_sdk/session_mutations.rb +20 -8
- data/lib/claude_agent_sdk/session_resume.rb +31 -16
- data/lib/claude_agent_sdk/session_store.rb +7 -3
- data/lib/claude_agent_sdk/session_summary.rb +4 -2
- data/lib/claude_agent_sdk/sessions.rb +8 -6
- data/lib/claude_agent_sdk/streaming.rb +1 -1
- data/lib/claude_agent_sdk/subprocess_cli_transport.rb +72 -40
- data/lib/claude_agent_sdk/testing/session_store_conformance.rb +14 -10
- data/lib/claude_agent_sdk/transcript_mirror_batcher.rb +2 -0
- data/lib/claude_agent_sdk/types/attributes.rb +236 -0
- data/lib/claude_agent_sdk/types/base.rb +322 -0
- data/lib/claude_agent_sdk/types/content_blocks.rb +57 -0
- data/lib/claude_agent_sdk/types/hooks.rb +640 -0
- data/lib/claude_agent_sdk/types/mcp.rb +232 -0
- data/lib/claude_agent_sdk/types/messages.rb +614 -0
- data/lib/claude_agent_sdk/types/option_values.rb +302 -0
- data/lib/claude_agent_sdk/types/options.rb +352 -0
- data/lib/claude_agent_sdk/types/permissions.rb +107 -0
- data/lib/claude_agent_sdk/types/sessions.rb +10 -0
- data/lib/claude_agent_sdk/types.rb +13 -2534
- data/lib/claude_agent_sdk/version.rb +1 -1
- data/lib/claude_agent_sdk.rb +62 -28
- data/sig/claude_agent_sdk/cancellation_signal.rbs +14 -0
- data/sig/claude_agent_sdk/configuration.rbs +14 -0
- data/sig/claude_agent_sdk/errors.rbs +86 -0
- data/sig/claude_agent_sdk/observer.rbs +42 -0
- data/sig/claude_agent_sdk/railtie.rbs +10 -0
- data/sig/claude_agent_sdk/sdk_mcp_server.rbs +76 -0
- data/sig/claude_agent_sdk/session_store.rbs +105 -0
- data/sig/claude_agent_sdk/streaming.rbs +15 -0
- data/sig/claude_agent_sdk/transport.rbs +98 -0
- data/sig/claude_agent_sdk/types/base.rbs +39 -0
- data/sig/claude_agent_sdk/types/content_blocks.rbs +79 -0
- data/sig/claude_agent_sdk/types/hooks.rbs +528 -0
- data/sig/claude_agent_sdk/types/mcp.rbs +216 -0
- data/sig/claude_agent_sdk/types/messages.rbs +586 -0
- data/sig/claude_agent_sdk/types/option_values.rbs +245 -0
- data/sig/claude_agent_sdk/types/options.rbs +288 -0
- data/sig/claude_agent_sdk/types/permissions.rbs +108 -0
- data/sig/claude_agent_sdk/types/sessions.rbs +66 -0
- data/sig/claude_agent_sdk.rbs +231 -0
- data/sig/manifest.yaml +5 -0
- metadata +32 -1
data/docs/types.md
CHANGED
|
@@ -1,6 +1,81 @@
|
|
|
1
1
|
# Types Reference
|
|
2
2
|
|
|
3
|
-
See [lib/claude_agent_sdk/types
|
|
3
|
+
See [lib/claude_agent_sdk/types/](https://github.com/ya-luotao/claude-agent-sdk-ruby/tree/main/lib/claude_agent_sdk/types) for complete type definitions (one file per area; `types.rb` loads them all).
|
|
4
|
+
|
|
5
|
+
## Hash keys
|
|
6
|
+
|
|
7
|
+
Where the SDK hands you a plain Hash rather than a typed object, its key form
|
|
8
|
+
depends on where the data came from. One rule covers every case:
|
|
9
|
+
|
|
10
|
+
| Source | Key form | Examples |
|
|
11
|
+
|--------|----------|----------|
|
|
12
|
+
| The CLI's live stream-JSON, passed through as-is | **Symbols**, spelled exactly as on the wire | `UserMessage#origin`, `ResultMessage#origin`, `AssistantMessage#usage`, `ResultMessage#usage`, `ResultMessage#model_usage`, `ResultMessage#structured_output`, `UserMessage#tool_use_result`, `SystemMessage#data`, hook `tool_input`, the `can_use_tool` `input`, SDK MCP tool and prompt `args`, `Client#mcp_status`, `Client#context_usage` |
|
|
13
|
+
| Transcripts read from disk or a `SessionStore` | **Strings**, spelled as in the JSONL | `SessionMessage#message`, `get_subagent_metadata`, `SessionStore` keys and entries, `fold_session_summary` input and output |
|
|
14
|
+
|
|
15
|
+
"As on the wire" means the SDK does not rewrite key names. Structures the CLI
|
|
16
|
+
generates use camelCase (`origin[:fromSession]`, `model_usage` values'
|
|
17
|
+
`:inputTokens` / `:costUSD`, `client.mcp_status[:mcpServers]`), while objects
|
|
18
|
+
the CLI relays from the API keep their snake_case (`usage[:input_tokens]`).
|
|
19
|
+
Nesting follows the same rule all the way down, including keys that are data
|
|
20
|
+
rather than field names: `model_usage` is keyed by model-name Symbols
|
|
21
|
+
(`result.model_usage.each { |model, u| puts "#{model}: $#{u[:costUSD]}" }`).
|
|
22
|
+
|
|
23
|
+
The wrong key form reads as `nil` rather than raising, so look up the source
|
|
24
|
+
before indexing: `tool_input['command']` on a hook input, or
|
|
25
|
+
`meta[:toolUseId]` on subagent metadata, silently returns `nil`. The Python
|
|
26
|
+
SDK uses String keys everywhere; do not port its lookups literally.
|
|
27
|
+
|
|
28
|
+
The Symbol side of the rule relies on the transport parsing each line with
|
|
29
|
+
`JSON.parse(line, symbolize_names: true)`. The built-in
|
|
30
|
+
`SubprocessCLITransport` does; a [custom transport](client.md#custom-transport)
|
|
31
|
+
must too.
|
|
32
|
+
|
|
33
|
+
## Reading and writing attributes
|
|
34
|
+
|
|
35
|
+
The SDK's typed objects (messages, content blocks, hook inputs and outputs,
|
|
36
|
+
option objects: everything built on `ClaudeAgentSDK::Type`) accept the same
|
|
37
|
+
attribute name in several spellings. These accessors are public API:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
msg.session_id # the attr_accessor
|
|
41
|
+
msg[:session_id] # Symbol or String, snake_case or camelCase:
|
|
42
|
+
msg['session_id'] # all four read the same attribute
|
|
43
|
+
msg[:sessionId]
|
|
44
|
+
msg['sessionId']
|
|
45
|
+
msg.sessionId # camelCase reader (also answers respond_to?)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`#[]` returns `nil` for a name the type does not define, while a misspelled
|
|
49
|
+
method call such as `msg.nope` raises `NoMethodError`. These accessors reach a type's
|
|
50
|
+
**attributes** only; any other method (`to_h`, `freeze`, ...) counts as undefined —
|
|
51
|
+
see [Attributes Only](#attributes-only).
|
|
52
|
+
|
|
53
|
+
`#[]=` assigns through the attribute's setter, with the same name
|
|
54
|
+
normalization, and returns the assigned value:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
msg[:result] = 'edited' # same as msg.result = 'edited'
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- It **changes the object you received**. Messages are not frozen or copied
|
|
61
|
+
on delivery, so a change is visible to anything else holding the same
|
|
62
|
+
object (for example an observer that received it before your block did).
|
|
63
|
+
Copy first if you need the original.
|
|
64
|
+
- A name the type does not define is ignored on the types the SDK parses from
|
|
65
|
+
CLI output. `ClaudeAgentOptions` raises `ArgumentError` for an unknown key (as
|
|
66
|
+
its constructor and `dup_with` do), and so do the value types you build and
|
|
67
|
+
pass in — see [Unknown Keys](#unknown-keys).
|
|
68
|
+
- Discriminator fields (`type` on the MCP server and system-prompt configs,
|
|
69
|
+
`behavior` on `PermissionResultAllow` / `PermissionResultDeny`,
|
|
70
|
+
`hook_event_name` on hook inputs and outputs) are read-only, so
|
|
71
|
+
assigning them has no effect.
|
|
72
|
+
|
|
73
|
+
Constructors accept the same spellings: `ResultMessage.new('sessionId' => 'abc')`
|
|
74
|
+
is equivalent to `ResultMessage.new(session_id: 'abc')`.
|
|
75
|
+
|
|
76
|
+
`SDKSessionInfo` and `SessionMessage` (returned by the session functions) are
|
|
77
|
+
currently plain classes, not `Type`s: use their snake_case accessors
|
|
78
|
+
(`info.session_id`); they have no `#[]`, `#[]=` or camelCase readers.
|
|
4
79
|
|
|
5
80
|
## Message Types
|
|
6
81
|
|
|
@@ -134,8 +209,9 @@ turn was cancelled via `Client#interrupt`. `nil` when the CLI did not report
|
|
|
134
209
|
one (older CLIs, or a result that bypassed the query loop such as a local
|
|
135
210
|
slash command).
|
|
136
211
|
|
|
137
|
-
`model_usage`
|
|
138
|
-
|
|
212
|
+
`model_usage` is passed through verbatim from the CLI (see [Hash keys](#hash-keys)):
|
|
213
|
+
it is keyed by model-name Symbols (`:"claude-sonnet-4-5"`), and each value's
|
|
214
|
+
keys are camelCase Symbols (the TypeScript/Python SDKs' `ModelUsage` shape): `inputTokens`,
|
|
139
215
|
`outputTokens`, `cacheReadInputTokens`, `cacheCreationInputTokens`,
|
|
140
216
|
`webSearchRequests`, `costUSD`, `contextWindow`, `maxOutputTokens`, plus
|
|
141
217
|
optional `canonicalModel` (canonical id used for the pricing lookup, which can
|
|
@@ -287,7 +363,7 @@ end
|
|
|
287
363
|
| `McpHttpServerConfig` | MCP server config for HTTP transport |
|
|
288
364
|
| `SdkPluginConfig` | SDK plugin configuration |
|
|
289
365
|
| `McpServerStatus` | Status of a single MCP server connection (with `.parse`) |
|
|
290
|
-
| `McpStatusResponse` |
|
|
366
|
+
| `McpStatusResponse` | Typed view of the `Client#mcp_status` / `#get_mcp_status` Hash: `McpStatusResponse.parse(client.mcp_status).mcp_servers` is an Array of `McpServerStatus`. The client itself returns the raw Hash (see [client.md](client.md#mcp-status-and-context-usage-return-hashes)) |
|
|
291
367
|
| `McpServerInfo` | MCP server name and version |
|
|
292
368
|
| `McpToolInfo` | MCP tool name, description, and annotations |
|
|
293
369
|
| `McpToolAnnotations` | MCP tool annotation hints (`read_only`, `destructive`, `open_world`) |
|
|
@@ -302,6 +378,35 @@ end
|
|
|
302
378
|
| `SystemPromptFile` | System prompt loaded from a file path |
|
|
303
379
|
| `ToolsPreset` | Tools preset configuration for base tools selection |
|
|
304
380
|
|
|
381
|
+
### Unknown Keys
|
|
382
|
+
|
|
383
|
+
`ClaudeAgentOptions` and the value types you build and pass *in* raise `ArgumentError` on a key they do not define, naming the class, the key and the keys it accepts:
|
|
384
|
+
|
|
385
|
+
```ruby
|
|
386
|
+
ClaudeAgentSDK::HookMatcher.new(matchr: 'Bash', hooks: [check])
|
|
387
|
+
# ArgumentError: ClaudeAgentSDK::HookMatcher: unknown attribute :matchr (known: hooks, matcher, timeout)
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
This covers `.new` and `#[]=` on:
|
|
391
|
+
|
|
392
|
+
- option values: `AgentDefinition`, `SandboxSettings`, `SandboxNetworkConfig`, `SandboxFilesystemConfig`, `ThinkingConfigAdaptive` / `Enabled` / `Disabled`, `TaskBudget`, `SystemPromptPreset` / `Custom` / `File`, `ToolsPreset`, `SdkPluginConfig`, `McpStdioServerConfig`, `McpSSEServerConfig`, `McpHttpServerConfig`, `McpSdkServerConfig`
|
|
393
|
+
- `HookMatcher` and hook outputs: `SyncHookJSONOutput`, `AsyncHookJSONOutput`, every `*HookSpecificOutput`
|
|
394
|
+
- `PermissionResultAllow`, `PermissionResultDeny`, `PermissionUpdate`, `PermissionRuleValue`
|
|
395
|
+
|
|
396
|
+
A nested value is checked as its own type: `PermissionUpdate.new(rules: [{ rule_contnt: 'x' }])` raises naming `PermissionRuleValue`.
|
|
397
|
+
|
|
398
|
+
Accepted: Symbol or String keys, snake_case or camelCase spellings, and the fixed discriminator a type sets itself (`type`, `hook_event_name`, `behavior`), so on a type that defines its own `#to_h` (the MCP server configs, `SandboxSettings`, the system prompt types, the hook outputs, ...) `klass.new(value.to_h)` round-trips. Types the SDK parses from CLI output (messages, content blocks, hook inputs, `ToolPermissionContext`, the MCP status types) stay lenient, so a field added by a newer CLI is ignored rather than raising, and so does every construction through `.from_hash` or `.wrap`. Use those two for data you did not write yourself, such as a Hash deserialized from the CLI or from storage.
|
|
399
|
+
|
|
400
|
+
(0.37 printed a one-time warning here and ignored the key; 1.0 raises. See [UPGRADING-1.0.md](../UPGRADING-1.0.md).)
|
|
401
|
+
|
|
402
|
+
### Attributes Only
|
|
403
|
+
|
|
404
|
+
`#[]`, `#[]=` and the camelCase readers (`msg[:session_id]`, `msg['sessionId']`, `msg.sessionId`) are public API for a type's **attributes**: the fields it declares, plus predicates such as `options.forkSession?`. Methods your own code adds to a subclass (an `attr_accessor`, a hand-written reader or setter, a mixin's accessors, a singleton method) count as attributes too.
|
|
405
|
+
|
|
406
|
+
A name that is not an attribute behaves like an undefined one: `#[]` returns `nil`, `#[]=` ignores it (on the strict types above it raises `ArgumentError`), a camelCase call raises `NoMethodError`, and `respond_to?` answers `false`. So `msg[:to_h]` is `nil`, `msg['freeze']` does not freeze the message, and `msg.toH` raises; call the method directly instead (`msg.to_h`). `UserMessage#text` and `AssistantMessage#text` are convenience methods, not attributes.
|
|
407
|
+
|
|
408
|
+
(Through 0.37 these accessors reached any public method, with a one-time warning in 0.37.)
|
|
409
|
+
|
|
305
410
|
## Constants
|
|
306
411
|
|
|
307
412
|
| Constant | Description |
|
|
@@ -30,16 +30,22 @@ module ClaudeAgentSDK
|
|
|
30
30
|
# ClaudeAgentSDK::CLIInstaller.install_pinned
|
|
31
31
|
# @example Pin a version of your own
|
|
32
32
|
# ClaudeAgentSDK::CLIInstaller.install(version: '2.1.220')
|
|
33
|
-
module CLIInstaller
|
|
33
|
+
module CLIInstaller # rubocop:disable Metrics/ModuleLength -- Http/Platform/Release/Metadata submodules in one file
|
|
34
|
+
# @api private
|
|
34
35
|
BASE_URL = 'https://downloads.claude.ai/claude-code-releases'
|
|
35
36
|
# Dist-tags resolved through a GET to BASE_URL/<tag>.
|
|
37
|
+
#
|
|
38
|
+
# @api private
|
|
36
39
|
DIST_TAGS = %w[stable latest].freeze
|
|
37
40
|
# Concrete version, optionally with a pre-release suffix (e.g. 2.1.220-rc1).
|
|
38
41
|
# The suffix is restricted to the semver pre-release character set: every
|
|
39
42
|
# accepted version is interpolated straight into a download URL, and a
|
|
40
43
|
# laxer `\S+` would let "2.1.220-x/../2.1.221" traverse out of the release
|
|
41
44
|
# path — silently installing something other than the pinned version.
|
|
45
|
+
#
|
|
46
|
+
# @api private
|
|
42
47
|
VERSION_PATTERN = /\A\d+\.\d+\.\d+(-[A-Za-z0-9.-]+)?\z/
|
|
48
|
+
# @api private
|
|
43
49
|
CHECKSUM_PATTERN = /\A[0-9a-f]{64}\z/
|
|
44
50
|
# The CLI version this gem release is developed and tested against — the
|
|
45
51
|
# Ruby equivalent of the Python SDK's bundled-CLI pin (_cli_version.py),
|
|
@@ -48,24 +54,35 @@ module ClaudeAgentSDK
|
|
|
48
54
|
# .github/workflows/cli-pin-bump.yml or a Python-sync release, so a
|
|
49
55
|
# Dependabot bump of the gem carries the CLI forward with it.
|
|
50
56
|
PINNED_CLI_VERSION = '2.1.280'
|
|
57
|
+
# @api private
|
|
51
58
|
BINARY_NAME = 'claude'
|
|
59
|
+
# @api private
|
|
52
60
|
VERSION_FILE = 'VERSION'
|
|
61
|
+
# @api private
|
|
53
62
|
LOCK_FILE = '.install.lock'
|
|
54
63
|
# Relative to .root (Dir.pwd when unset), resolved at CALL time by
|
|
55
64
|
# .default_dir — an absolute constant would freeze the working directory
|
|
56
65
|
# as of require time, which is wrong for anything that chdirs (Rake
|
|
57
66
|
# tasks, bin/setup, test suites).
|
|
67
|
+
#
|
|
68
|
+
# @api private
|
|
58
69
|
DEFAULT_DIR = File.join('vendor', 'claude')
|
|
59
70
|
# Response caps. The dist-tag endpoints return a bare version string and
|
|
60
71
|
# manifests are a few KB; anything larger is a misrouted response, not
|
|
61
72
|
# something to buffer in memory. (The binary itself streams to disk.)
|
|
73
|
+
#
|
|
74
|
+
# @api private
|
|
62
75
|
VERSION_RESPONSE_LIMIT = 1024
|
|
76
|
+
# @api private
|
|
63
77
|
MANIFEST_RESPONSE_LIMIT = 5 * 1024 * 1024
|
|
78
|
+
# @api private
|
|
64
79
|
METADATA_READ_LIMIT = 4096
|
|
65
80
|
|
|
66
81
|
# Maps the running Ruby to a release-manifest platform key
|
|
67
82
|
# (darwin-arm64, darwin-x64, linux-x64, linux-arm64, and the -musl
|
|
68
83
|
# variants). Windows is not supported by this gem.
|
|
84
|
+
#
|
|
85
|
+
# @api private
|
|
69
86
|
module Platform
|
|
70
87
|
class << self
|
|
71
88
|
def detect
|
|
@@ -123,6 +140,8 @@ module ClaudeAgentSDK
|
|
|
123
140
|
# and chunked streaming for the binary. Knows nothing about releases; the
|
|
124
141
|
# specs stub .fetch_text / .download_to wholesale so no HTTP stubbing
|
|
125
142
|
# library is needed.
|
|
143
|
+
#
|
|
144
|
+
# @api private
|
|
126
145
|
module Http
|
|
127
146
|
MAX_REDIRECTS = 5
|
|
128
147
|
OPEN_TIMEOUT_SECONDS = 10
|
|
@@ -155,7 +174,9 @@ module ClaudeAgentSDK
|
|
|
155
174
|
File.open(path, File::WRONLY | File::CREAT | File::EXCL | File::BINARY, 0o600) do |file|
|
|
156
175
|
response.read_body do |chunk|
|
|
157
176
|
written += chunk.bytesize
|
|
158
|
-
|
|
177
|
+
if over?(written, max_bytes)
|
|
178
|
+
raise CLIInstallError, "Download from #{url} exceeds the expected #{max_bytes} bytes"
|
|
179
|
+
end
|
|
159
180
|
|
|
160
181
|
file.write(chunk)
|
|
161
182
|
end
|
|
@@ -205,6 +226,8 @@ module ClaudeAgentSDK
|
|
|
205
226
|
|
|
206
227
|
# Talks to the release service: dist-tag resolution, manifest lookup and
|
|
207
228
|
# URL construction. Pure remote reads — no filesystem, no state.
|
|
229
|
+
#
|
|
230
|
+
# @api private
|
|
208
231
|
module Release
|
|
209
232
|
class << self
|
|
210
233
|
# Local, network-free check of what the caller asked for. Returns the
|
|
@@ -239,7 +262,9 @@ module ClaudeAgentSDK
|
|
|
239
262
|
end
|
|
240
263
|
|
|
241
264
|
checksum = entry['checksum'].to_s.downcase
|
|
242
|
-
|
|
265
|
+
unless checksum.match?(CHECKSUM_PATTERN)
|
|
266
|
+
raise CLIInstallError, "#{url} has no valid sha256 checksum for #{platform}"
|
|
267
|
+
end
|
|
243
268
|
|
|
244
269
|
size = entry['size']
|
|
245
270
|
{ checksum: checksum, size: size.is_a?(Integer) && size.positive? ? size : nil }
|
|
@@ -277,6 +302,8 @@ module ClaudeAgentSDK
|
|
|
277
302
|
# per line. Both platform and checksum must match before trusting a cached
|
|
278
303
|
# binary offline: a cache copied between OS/CPU/libc targets is not usable.
|
|
279
304
|
# Older one- or two-line files lack that proof and trigger a clean reinstall.
|
|
305
|
+
#
|
|
306
|
+
# @api private
|
|
280
307
|
module Metadata
|
|
281
308
|
class << self
|
|
282
309
|
def read(dir)
|