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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a678100ed5fdb6ba11e9895e534b4a87df8a2b8a57d0a0a459900d81d88bf593
|
|
4
|
+
data.tar.gz: 8bc65b4056f315abe23a3f1f9089402819137b2abb38cbd1d57afdcfe5366e42
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b7ec8544374217a99458cc3557504777d5ac4e2a66cab706f9322f326f43220b312fc877388983961a688a1e3c55b3493bae1458c4a7ed71a7614bfe2e6bc0aa
|
|
7
|
+
data.tar.gz: b119e67d145088a4adbf832f611eaf5d7047c255a2037e5a76372f6ac319d8d6d7addaf4af062b58acbb12ba9acf17799027b89175c02498f65471320683533a
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.0] - 2026-09-23
|
|
11
|
+
|
|
12
|
+
**1.0 is a stability commitment.** From here on the public API — everything documented in `docs/` plus the YARD docs without `@api private`, now also described by the RBS signatures in `sig/` — follows Semantic Versioning: no breaking changes within 1.x. Upgrading from 0.x? Read [UPGRADING-1.0.md](UPGRADING-1.0.md) and run your suite on 0.37 first.
|
|
13
|
+
|
|
14
|
+
1.0 ([#126](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)): three breaking changes, the first two of which 0.37 warns about at the call site. **Read [UPGRADING-1.0.md](UPGRADING-1.0.md) before upgrading** and run your suite on 0.37 with warnings visible first: an app that runs on 0.37 without SDK warnings is unaffected by those two, except that `respond_to?` on a camelCase non-attribute (`msg.respond_to?(:toH)`) silently answered `true` on 0.37 and answers `false` now.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **`ClaudeAgentSDK::SessionStoreError`** (a `ClaudeSDKError`), raised by `query`, `ask` and `Client#connect` when resuming from `session_store:` fails: a store call (`#load`, `#list_sessions`, `#list_subkeys`) raised or exceeded `load_timeout_ms` while the SDK materialized the transcript. The message names the store call, and `#cause` holds the adapter's exception (or the timeout). Documented in `docs/errors.md` and `docs/sessions.md`.
|
|
18
|
+
- **RBS signatures for the public API** ([#126](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)). The gem now ships `sig/`, so Steep and other RBS tools type-check code that uses it (through `rbs collection`; `sig/manifest.yaml` declares the one stdlib dependency, `pathname`). The signatures cover the whole public surface (the objects documented in `docs/` and YARD without `@api private`): `ClaudeAgentSDK.query` / `.ask` / `.configure` / `.offload`, the SDK MCP helpers, every session function (including `session_store:` and the deprecated twins), `Client` with its Ruby-style aliases, `ClaudeAgentOptions` (every option as a typed keyword), the message, content-block, hook, permission and MCP types, the error hierarchy, `CLIInstaller`, `Streaming`, `Railtie.callback_wrapper` and the observers. Duck types are interfaces, so any object with the right methods fits: `_Transport`, `_SessionStore` (only `#append` and `#load` are required), and one interface per user callback (`_CanUseTool`, `_HookCallback`, `_ToolHandler`, `_CallbackWrapper`, ...). Hashes follow the documented key rule: `wire_hash` (`Hash[Symbol, untyped]`) for data from the CLI stream, `transcript_hash` (`Hash[String, untyped]`) for transcripts and store data. Internals tagged `@api private` have no signatures. CI validates the signatures (`rake rbs:validate`) and runs the suite under RBS's runtime type checker (`rake rbs:test`), so a signature that disagrees with the code fails the build. `CONTRIBUTING.md` explains how to keep `sig/` in step with API changes.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- **Breaking: unknown keys on user-constructed types raise `ArgumentError`.** `.new` and `#[]=` on the value types you build and pass in (option values such as `AgentDefinition`, `SandboxSettings`, the thinking and MCP server configs; `HookMatcher`; hook outputs; `PermissionResultAllow` / `PermissionResultDeny`; `PermissionUpdate`; `PermissionRuleValue`) raise instead of warning, with a message naming the class, the key and the known keys: `ClaudeAgentSDK::HookMatcher: unknown attribute :matchr (known: hooks, matcher, timeout)`. camelCase and String keys and a type's own discriminator are still accepted; `.from_hash`, `.wrap` and every type parsed from CLI output stay lenient.
|
|
22
|
+
- **Breaking: `Type#[]`, `#[]=` and camelCase methods reach attributes only.** A name that is not an attribute behaves like an undefined one: `msg[:to_h]` is `nil`, `#[]=` ignores it (raises on the strict types above), `msg.toH` raises `NoMethodError` and `respond_to?(:toH)` is `false`. Methods your own code adds to a subclass, mixin or instance still count as attributes.
|
|
23
|
+
- **Breaking: store-backed resume failures raise `SessionStoreError` instead of `RuntimeError`.** The two SDK-raised `RuntimeError`s (store call failed, store call timed out) become `SessionStoreError`, and a `RuntimeError` raised by the adapter itself, which 0.37 let through unwrapped, is now wrapped too, so `rescue ClaudeSDKError` catches every resume-materialization failure. Code that rescued `RuntimeError` there must rescue `SessionStoreError`. The failure message now includes the adapter exception's class (`... failed during resume materialization: IOError: connection reset`).
|
|
24
|
+
- `UPGRADING-1.0.md` ships in the gem and the YARD docs, linked from the README.
|
|
25
|
+
- The deprecation warning printed by the ten `*_from_store` / `*_via_store` session functions, and their YARD and docs, now say they will be removed in **2.0**. 0.36 and 0.37 said 1.0, but they stay, deprecated, for all of 1.x ([#126](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)). Nothing else about them changes: each still works and still warns once per process.
|
|
26
|
+
|
|
27
|
+
## [0.37.0] - 2026-09-23
|
|
28
|
+
|
|
29
|
+
The last 0.x release before 1.0 ([roadmap](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)). No runtime behaviour changes — only new warnings for things 1.0 will reject. **Run your suite on 0.37 with warnings visible before moving to 1.0:**
|
|
30
|
+
- A misspelled key on a value type you build (`HookMatcher`, `AgentDefinition`, `SandboxSettings`, MCP server configs, permission results, hook outputs, …) now warns once, pointing at your call; 1.0 raises `ArgumentError`.
|
|
31
|
+
- `msg[:name]` / camelCase readers reaching a non-attribute method (e.g. `msg[:to_h]`) warn once; in 1.0 they only reach attributes.
|
|
32
|
+
- The 1.0 SemVer surface is now visible: SDK internals are tagged `@api private` and hidden from the API docs, and the documented contracts (Hash-key rule, `mcp_status` / `context_usage` return Hashes, `Type#[]`) are written down.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- **Docs: pre-1.0 contracts written down** ([#126](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)). No code changes. `Client#mcp_status` / `#get_mcp_status` and `#context_usage` / `#get_context_usage` return the CLI's raw Hash with camelCase Symbol keys. `docs/types.md` had described `McpStatusResponse` as the response type; it is the optional typed view, via `McpStatusResponse.parse(client.mcp_status)`, and there is no typed context-usage class (`docs/client.md`). A new "Hash keys" section in `docs/types.md` states one rule: Hashes passed through from the live CLI stream (`origin`, `usage`, `model_usage`, hook `tool_input`, `can_use_tool` input, SDK MCP tool args, control responses) have Symbol keys spelled as on the wire, and Hashes read from transcripts or a `SessionStore` (`SessionMessage#message`, `get_subagent_metadata`, store keys and entries) have String keys. The docs, examples and bundled skill drop their `h[:key] || h['key']` fallbacks in favour of the one correct form. `Type#[]`, `#[]=` and the camelCase readers are documented as public API, including that `#[]=` changes the object you received. `docs/sessions.md` documents `ClaudeAgentSDK.project_key_for_directory` and `ClaudeAgentSDK.fold_session_summary` for adapter authors, and `import_session_to_store`'s batching (`batch_size:` defaults to 500 entries, and a batch also ends at about 1 MiB).
|
|
36
|
+
- **SDK internals are now tagged `@api private` and hidden from the YARD docs** ([#126](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)). The tagged namespaces are `Query`, `MessageParser`, `TranscriptMirrorBatcher`, `Sessions`, `SessionMutations`, `SessionResume`, `MaterializedResume`, `SessionSummary`, `SessionStores`, `OptionWarnings` and `FiberBoundary`, including everything nested in them. Some public classes also have internal members, and those are tagged individually: `SubprocessCLITransport`'s methods outside the `Transport` interface and its constants; `CommandBuilder`'s constants (`.new` and `#build` stay public); `CLIInstaller::Http`, `Platform`, `Release` and `Metadata` and the installer's constants other than `PINNED_CLI_VERSION`; `SdkMcpServer#handle_json`, `#handle_message` and `SdkMcpServer::ToolInputSchema`; `Client#query_handler`; and `ClaudeAgentSDK::OBSERVER_INTERFACE`. Their public faces are unchanged: `ClaudeAgentSDK.offload`, `fold_session_summary`, the session functions and `SDKSessionInfo` / `SessionMessage` remain public. Nothing changes at runtime. No method or constant was renamed, removed or made private, so every existing call keeps working. From 1.0, SemVer covers `docs/` and the YARD API without `@api private`; objects tagged `@api private` are not covered and may change in any release. `CONTRIBUTING.md` has a new "What is public API" section.
|
|
37
|
+
- `lib/claude_agent_sdk/types.rb` is split into one file per area under `lib/claude_agent_sdk/types/` (messages, hooks, options, ...). The code moved without edits and `types.rb` still loads every part, so `require 'claude_agent_sdk/types'` and every class and constant are unchanged; no API change ([#126](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)).
|
|
38
|
+
- `Type`'s internal machinery is tagged `@api private` and leaves the rendered docs: `Type#dup_for_options`, `Type.deep_dup_for_options`, `Type::OptionValue`, `Type.inspect_filtered`, `Type.inspect_filtered_attributes`, and the new `Type.strict_attributes` / `.strict_attributes?` / `.declare_attributes` / `.attribute?` / `.attribute_names`. They are not part of the SemVer surface. `Type#[]`, `#[]=`, the camelCase readers, `#to_h`, `.wrap` and `.from_hash` stay public ([#126](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)).
|
|
39
|
+
|
|
40
|
+
### Deprecated
|
|
41
|
+
- **Unknown keys on user-constructed types.** The value types you build and pass in (option values such as `AgentDefinition`, `SandboxSettings`, the thinking configs and the MCP server configs; `HookMatcher`; hook outputs; `PermissionResultAllow` / `PermissionResultDeny`; `PermissionUpdate`; `PermissionRuleValue`) silently dropped a misspelled key, so `HookMatcher.new(matchr: 'Bash')` quietly built a matcher with no `matcher` set. `.new` and `#[]=` now warn once per class and key, at the caller's line: `ClaudeAgentSDK::HookMatcher: unknown attribute :matchr ignored; this will raise ArgumentError in 1.0 (known: hooks, matcher, timeout)`. **In 1.0 they raise `ArgumentError`**, as `ClaudeAgentOptions` already does. camelCase and String keys and a type's own discriminator (`type`, `hook_event_name`, `behavior`) are accepted, so `klass.new(value.to_h)` round-trips silently. Types parsed from CLI output (messages, content blocks, hook inputs) and every construction through `.from_hash` / `.wrap` stay lenient, so a newer CLI's extra fields never warn; permission suggestions from the CLI are now hydrated through `PermissionUpdate.wrap` for that reason. Full list in `docs/types.md` ([#126](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)).
|
|
42
|
+
- **`Type#[]`, `#[]=` and camelCase methods reaching something other than an attribute.** They are public API for a type's attributes (its `attr_*` fields, the backward-compatible `RateLimitEvent#data`, predicates such as `options.forkSession?`, and any method your own code adds to a subclass, mixin or instance), but they resolved any public method: `msg[:to_h]` returned a Hash, `msg['freeze']` froze the message, `msg.toH` called `#to_h`. Such a call still works but warns once per class and name, e.g. `ClaudeAgentSDK::ResultMessage#[]: :to_h is not an attribute; Type#[] will only read attributes in 1.0`. **In 1.0 a name that is not an attribute behaves like an undefined one:** `#[]` returns `nil`, `#[]=` ignores it (raises on the types above), a camelCase call raises `NoMethodError`, and `respond_to?` says `false`. Names that are not methods at all keep today's behaviour and do not warn. `UserMessage#text` / `AssistantMessage#text` are convenience methods, not attributes ([#126](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)).
|
|
43
|
+
|
|
10
44
|
## [0.36.0] - 2026-09-23
|
|
11
45
|
|
|
12
46
|
The first step on the [road to 1.0](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126): one `session_store:` argument for every session function (the store-specific twins are deprecated), `ClaudeAgentSDK.ask`, String tool results, and the last audit follow-ups (#119–#121). **Read before upgrading:**
|
data/README.md
CHANGED
|
@@ -12,6 +12,8 @@ A Ruby SDK for the [Claude Code](https://docs.claude.com/en/docs/claude-code-ove
|
|
|
12
12
|
|
|
13
13
|
> **Unofficial and community-maintained.** This project is not affiliated with or supported by Anthropic. It tracks the official SDKs release by release; see the [CHANGELOG](CHANGELOG.md) for the currently synced version.
|
|
14
14
|
|
|
15
|
+
> **Upgrading from 0.x?** 1.0 raises on unknown keys, limits `#[]` to attributes and adds `SessionStoreError`. [UPGRADING-1.0.md](UPGRADING-1.0.md) has the checklist.
|
|
16
|
+
|
|
15
17
|
## Highlights
|
|
16
18
|
|
|
17
19
|
- **Rails integration.** `bin/rails generate claude_agent_sdk:install` writes the initializer and `bin/rails claude_agent_sdk:install_cli` vendors the CLI; [docs/rails.md](docs/rails.md) covers jobs, ActionCable streaming, session resumption, and solid_queue fiber workers (`callback_scheduling: :inline`).
|
|
@@ -29,7 +31,7 @@ A Ruby SDK for the [Claude Code](https://docs.claude.com/en/docs/claude-code-ove
|
|
|
29
31
|
|
|
30
32
|
```ruby
|
|
31
33
|
# Gemfile
|
|
32
|
-
gem 'claude-agent-sdk', '~>
|
|
34
|
+
gem 'claude-agent-sdk', '~> 1.0'
|
|
33
35
|
```
|
|
34
36
|
|
|
35
37
|
Then `bundle install`, or install directly with `gem install claude-agent-sdk`. To track unreleased changes, point the Gemfile at GitHub: `gem 'claude-agent-sdk', github: 'ya-luotao/claude-agent-sdk-ruby'`.
|
|
@@ -193,7 +195,7 @@ See [docs/hooks-and-permissions.md](docs/hooks-and-permissions.md) for the full
|
|
|
193
195
|
| OpenTelemetry tracing, Langfuse, custom observers | [docs/observability.md](docs/observability.md) |
|
|
194
196
|
| Rails: generator, `install_cli` task, callback wrapper, fiber safety, solid_queue fiber workers, ActionCable, jobs | [docs/rails.md](docs/rails.md) |
|
|
195
197
|
| Vendoring a pinned CLI binary and CLI discovery order | [docs/cli-installer.md](docs/cli-installer.md) |
|
|
196
|
-
|
|
|
198
|
+
| Hash-key rule, attribute access, and the message, content block, and configuration type reference | [docs/types.md](docs/types.md) |
|
|
197
199
|
| Error handling, exception hierarchy, timeouts | [docs/errors.md](docs/errors.md) |
|
|
198
200
|
|
|
199
201
|
API reference: [rubydoc.info/gems/claude-agent-sdk](https://rubydoc.info/gems/claude-agent-sdk). Available built-in tools: [Claude Code documentation](https://docs.anthropic.com/en/docs/claude-code/settings#tools-available-to-claude).
|
|
@@ -252,11 +254,13 @@ This repository is also a Claude Code plugin marketplace. The bundled skill teac
|
|
|
252
254
|
bundle install
|
|
253
255
|
bundle exec rspec # unit suite
|
|
254
256
|
bundle exec rubocop # lint
|
|
257
|
+
bundle exec rake rbs:validate # validate the RBS signatures in sig/
|
|
258
|
+
bundle exec rake rbs:test # the suite under RBS runtime type checking
|
|
255
259
|
RUN_INTEGRATION=1 bundle exec rspec # also run the real-CLI integration suite (needs `claude` and ANTHROPIC_API_KEY)
|
|
256
260
|
BUNDLE_GEMFILE=gemfiles/rails_8.gemfile bundle exec rspec --options spec/rails/.rspec # Rails integration specs
|
|
257
261
|
```
|
|
258
262
|
|
|
259
|
-
CI runs the suite and RuboCop on Ruby 3.2, 3.3, and 3.4 on Linux, the suite on macOS, and the Rails specs against Rails 7.1 and 8; a weekly job runs the integration suite against the pinned CLI. See [CONTRIBUTING.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/CONTRIBUTING.md) for the development setup and [spec/README.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/spec/README.md) for the test layout.
|
|
263
|
+
CI runs the suite and RuboCop on Ruby 3.2, 3.3, and 3.4 on Linux, the suite on macOS, and the Rails specs against Rails 7.1 and 8, validates the RBS signatures and runs the suite under RBS runtime type checking; a weekly job runs the integration suite against the pinned CLI. The gem ships RBS signatures for its public API in `sig/`, which Steep and other RBS tools pick up through `rbs collection`. See [CONTRIBUTING.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/CONTRIBUTING.md) for the development setup and [spec/README.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/spec/README.md) for the test layout.
|
|
260
264
|
|
|
261
265
|
## Contributing
|
|
262
266
|
|
data/UPGRADING-1.0.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Upgrading from 0.37 to 1.0
|
|
2
|
+
|
|
3
|
+
1.0 is 0.37 plus three breaking changes. 0.37 already warns about each of the
|
|
4
|
+
first two at the exact call site, so **if your app runs on 0.37 without SDK
|
|
5
|
+
warnings, the first two changes will not affect it on 1.0**, with one silent
|
|
6
|
+
exception: `respond_to?` on a camelCase name that is not an attribute
|
|
7
|
+
(`msg.respond_to?(:toH)`) answered `true` on 0.37 without a warning and answers
|
|
8
|
+
`false` on 1.0. The third change is to which exception class you rescue.
|
|
9
|
+
|
|
10
|
+
| Area | 0.37 | 1.0 |
|
|
11
|
+
|------|------|-----|
|
|
12
|
+
| Unknown key on a type you build (`HookMatcher.new(matchr: ...)`) | warns once, key ignored | raises `ArgumentError` |
|
|
13
|
+
| `#[]` / `#[]=` / camelCase reaching a non-attribute (`msg[:to_h]`, `msg.toH`) | works, warns once | treated as undefined |
|
|
14
|
+
| Store-backed resume failure | bare `RuntimeError` | `ClaudeAgentSDK::SessionStoreError` |
|
|
15
|
+
|
|
16
|
+
## Checklist
|
|
17
|
+
|
|
18
|
+
1. Upgrade to 0.37 first: `gem 'claude-agent-sdk', '~> 0.37.0'`, then `bundle update claude-agent-sdk`.
|
|
19
|
+
2. Run your test suite, and exercise a staging boot, with warnings visible and
|
|
20
|
+
stderr kept: no `-W0`, no `$VERBOSE = nil`, and no stderr filtering.
|
|
21
|
+
For example, `bundle exec rspec 2> sdk-warnings.log`.
|
|
22
|
+
3. Find the SDK's warnings:
|
|
23
|
+
```sh
|
|
24
|
+
grep -E 'unknown attribute|is not an attribute|is deprecated' sdk-warnings.log
|
|
25
|
+
```
|
|
26
|
+
Each line starts with the `file:line` of your call. Every warning is
|
|
27
|
+
printed once per process per class and key (or per method), so fix what
|
|
28
|
+
you find and run again until the output is empty.
|
|
29
|
+
4. Fix each hit as described below.
|
|
30
|
+
5. Around code that resumes from a `session_store:` (`query`, `ask`,
|
|
31
|
+
`Client#connect`, `Client.open`), search for `rescue RuntimeError` and for
|
|
32
|
+
rescues of your adapter's own exception classes that inherit from
|
|
33
|
+
`RuntimeError` (for example `Net::ReadTimeout`, a `Timeout::Error`, which is
|
|
34
|
+
a `RuntimeError`). In 1.0 these arrive wrapped: rescue
|
|
35
|
+
`ClaudeAgentSDK::SessionStoreError` and inspect `#cause` for the original.
|
|
36
|
+
6. Upgrade: `gem 'claude-agent-sdk', '~> 1.0'`.
|
|
37
|
+
|
|
38
|
+
## Unknown keys raise `ArgumentError`
|
|
39
|
+
|
|
40
|
+
The value types you build and pass *in* now reject a key they do not define,
|
|
41
|
+
as `ClaudeAgentOptions` always has. Before 0.37 the key was silently dropped,
|
|
42
|
+
so a typo such as `matchr:` built a matcher that matched every tool:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
ClaudeAgentSDK::HookMatcher.new(matchr: 'Bash', hooks: [check])
|
|
46
|
+
# 0.37: warning: ClaudeAgentSDK::HookMatcher: unknown attribute :matchr ignored; this will raise ArgumentError in 1.0 (known: hooks, matcher, timeout)
|
|
47
|
+
# 1.0: ArgumentError: ClaudeAgentSDK::HookMatcher: unknown attribute :matchr (known: hooks, matcher, timeout)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
This covers `.new` and `#[]=` on the option values (`AgentDefinition`,
|
|
51
|
+
`SandboxSettings` and its network and filesystem configs, the three thinking
|
|
52
|
+
configs, `TaskBudget`, the three system prompt types, `ToolsPreset`,
|
|
53
|
+
`SdkPluginConfig`, the four MCP server configs), `HookMatcher`, the hook
|
|
54
|
+
outputs (`SyncHookJSONOutput`, `AsyncHookJSONOutput`, every
|
|
55
|
+
`*HookSpecificOutput`), `PermissionResultAllow`, `PermissionResultDeny`,
|
|
56
|
+
`PermissionUpdate` and `PermissionRuleValue`. The full list is in
|
|
57
|
+
[docs/types.md](docs/types.md#unknown-keys).
|
|
58
|
+
|
|
59
|
+
**Fix:** correct the key, or remove it if the type never had it. Symbol and
|
|
60
|
+
String keys, snake_case and camelCase all still work, and so does a type's own
|
|
61
|
+
discriminator (`type`, `hook_event_name`, `behavior`), so on types that define
|
|
62
|
+
their own `#to_h` (the MCP server configs, `SandboxSettings`, the system prompt
|
|
63
|
+
types, the hook outputs, ...) `klass.new(value.to_h)` round-trips. For a Hash you did not write yourself (deserialized from the CLI,
|
|
64
|
+
a queue or a database), use `.from_hash` or `.wrap`: both stay lenient and
|
|
65
|
+
ignore unknown keys. Types the SDK parses from CLI output (messages, content
|
|
66
|
+
blocks, hook inputs) are not affected.
|
|
67
|
+
|
|
68
|
+
## `#[]`, `#[]=` and camelCase reach attributes only
|
|
69
|
+
|
|
70
|
+
These accessors are public API for a type's attributes: the fields it declares,
|
|
71
|
+
predicates such as `options.forkSession?`, and any method your own code adds to
|
|
72
|
+
a subclass, a mixin or an instance. Through 0.37 they reached *any* public
|
|
73
|
+
method. In 1.0 a name that is not an attribute behaves like an undefined one:
|
|
74
|
+
|
|
75
|
+
| Call | 0.37 | 1.0 |
|
|
76
|
+
|------|------|-----|
|
|
77
|
+
| `msg[:to_h]`, `msg['freeze']` | calls the method, warns | `nil` (method not called) |
|
|
78
|
+
| `msg[:some_method] = x` | calls `some_method=`, warns | ignored; `ArgumentError` on the strict types above |
|
|
79
|
+
| `msg.toH` | calls `to_h`, warns | `NoMethodError`; `respond_to?(:toH)` is `false` |
|
|
80
|
+
|
|
81
|
+
**Fix:** call the method directly (`msg.to_h`). `UserMessage#text` and
|
|
82
|
+
`AssistantMessage#text` are convenience methods, not attributes, so
|
|
83
|
+
`msg[:text]` is `nil`; use `msg.text`.
|
|
84
|
+
|
|
85
|
+
## `SessionStoreError` replaces `RuntimeError` on store-backed resume
|
|
86
|
+
|
|
87
|
+
When `query`, `ask` or `Client#connect` resume a session from
|
|
88
|
+
`session_store:` and a store call raises or exceeds `load_timeout_ms`, they now
|
|
89
|
+
raise `ClaudeAgentSDK::SessionStoreError < ClaudeSDKError`. In 0.37 this was a
|
|
90
|
+
bare `RuntimeError`, and a `RuntimeError` raised by your adapter escaped
|
|
91
|
+
unwrapped, so `rescue ClaudeAgentSDK::ClaudeSDKError` missed both. The message
|
|
92
|
+
names the store call and `#cause` holds your adapter's exception (or the
|
|
93
|
+
timeout):
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
begin
|
|
97
|
+
ClaudeAgentSDK.ask('Continue', options: options)
|
|
98
|
+
rescue ClaudeAgentSDK::SessionStoreError => e
|
|
99
|
+
e.message # "SessionStore#load for session 3f2c… failed during resume materialization: IOError: …"
|
|
100
|
+
e.cause # the IOError your adapter raised
|
|
101
|
+
end
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Fix:** replace `rescue RuntimeError` on these paths with
|
|
105
|
+
`rescue ClaudeAgentSDK::SessionStoreError` (or `ClaudeSDKError`). Other store
|
|
106
|
+
paths are unchanged: the session functions called with `session_store:` behave
|
|
107
|
+
exactly as in 0.37, and mirroring failures still arrive as
|
|
108
|
+
`MirrorErrorMessage`, never as exceptions.
|
|
109
|
+
|
|
110
|
+
## What SemVer covers from 1.0
|
|
111
|
+
|
|
112
|
+
No breaking changes within 1.x to the public API: everything documented in
|
|
113
|
+
`docs/` and the README, and every class, module, method and constant in the
|
|
114
|
+
YARD docs that is not tagged `@api private`. `@api private` objects (`Query`,
|
|
115
|
+
`MessageParser`, `FiberBoundary`, the `Sessions*` modules, ...) stay callable
|
|
116
|
+
but can change in any release. See
|
|
117
|
+
[CONTRIBUTING.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/CONTRIBUTING.md#what-is-public-api).
|
|
118
|
+
|
|
119
|
+
A removal is first deprecated in a minor release with a one-time warning that
|
|
120
|
+
names the replacement, and happens in the next major.
|
|
121
|
+
|
|
122
|
+
## The Hash-key rule
|
|
123
|
+
|
|
124
|
+
Unchanged in 1.0, and now part of the SemVer contract: a plain Hash passed
|
|
125
|
+
through from the CLI's live stream (`usage`, `origin`, hook `tool_input`, the
|
|
126
|
+
`can_use_tool` input, SDK MCP tool `args`, `Client#mcp_status`) has **Symbol**
|
|
127
|
+
keys spelled as on the wire; a Hash read from a transcript or a `SessionStore`
|
|
128
|
+
(`SessionMessage#message`, `get_subagent_metadata`, store keys and entries) has
|
|
129
|
+
**String** keys. The wrong form reads `nil` rather than raising. See
|
|
130
|
+
[docs/types.md](docs/types.md#hash-keys).
|
|
131
|
+
|
|
132
|
+
## Deprecated, kept through 1.x
|
|
133
|
+
|
|
134
|
+
The ten store-specific session functions still work in 1.x and still print a
|
|
135
|
+
one-time warning. They will be removed in 2.0. Each is the same call as its
|
|
136
|
+
replacement:
|
|
137
|
+
|
|
138
|
+
| Deprecated | Replacement |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `list_sessions_from_store(session_store: s, ...)` | `list_sessions(session_store: s, ...)` |
|
|
141
|
+
| `get_session_info_from_store(session_store: s, ...)` | `get_session_info(session_store: s, ...)` |
|
|
142
|
+
| `get_session_messages_from_store(session_store: s, ...)` | `get_session_messages(session_store: s, ...)` |
|
|
143
|
+
| `list_subagents_from_store(session_store: s, ...)` | `list_subagents(session_store: s, ...)` |
|
|
144
|
+
| `get_subagent_metadata_from_store(session_store: s, ...)` | `get_subagent_metadata(session_store: s, ...)` |
|
|
145
|
+
| `get_subagent_messages_from_store(session_store: s, ...)` | `get_subagent_messages(session_store: s, ...)` |
|
|
146
|
+
| `rename_session_via_store(session_store: s, ...)` | `rename_session(session_store: s, ...)` |
|
|
147
|
+
| `tag_session_via_store(session_store: s, ...)` | `tag_session(session_store: s, ...)` |
|
|
148
|
+
| `delete_session_via_store(session_store: s, ...)` | `delete_session(session_store: s, ...)` |
|
|
149
|
+
| `fork_session_via_store(session_store: s, ...)` | `fork_session(session_store: s, ...)` |
|
|
150
|
+
|
|
151
|
+
`import_session_to_store` is not deprecated.
|
data/docs/client.md
CHANGED
|
@@ -77,6 +77,31 @@ The Ruby-style names above sit next to the Python SDK's spellings, and both work
|
|
|
77
77
|
|
|
78
78
|
Each Ruby-style method calls its parity counterpart, so both send the same control request and raise `CLIConnectionError` when the client is not connected. The one exception is `server_info`, which reads the cached initialization result and returns `nil` instead of raising before `connect`. As with any Ruby setter, `client.model = 'haiku'` evaluates to `'haiku'`, not to the control response.
|
|
79
79
|
|
|
80
|
+
### MCP status and context usage return Hashes
|
|
81
|
+
|
|
82
|
+
`mcp_status` / `get_mcp_status` and `context_usage` / `get_context_usage` return the CLI's control response payload as a plain Hash, unchanged: Symbol keys spelled as on the wire, which for these payloads is camelCase (`:mcpServers`, `:serverInfo`, `:totalTokens`), at every level of nesting (see [Hash keys](types.md#hash-keys)). The SDK does not model or filter the payload, so fields added by newer CLI versions come through. A response without a payload reads as `{}`.
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
status = client.mcp_status
|
|
86
|
+
status[:mcpServers].each { |s| puts "#{s[:name]}: #{s[:status]}" }
|
|
87
|
+
status.dig(:mcpServers, 0, :serverInfo, :version)
|
|
88
|
+
|
|
89
|
+
usage = client.context_usage
|
|
90
|
+
puts "#{usage[:totalTokens]} / #{usage[:maxTokens]} tokens"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
For a typed view of the MCP status, parse the Hash yourself:
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
typed = ClaudeAgentSDK::McpStatusResponse.parse(client.mcp_status)
|
|
97
|
+
typed.mcp_servers.each do |server| # McpServerStatus
|
|
98
|
+
puts "#{server.name} #{server.status} #{server.server_info&.version}"
|
|
99
|
+
server.tools&.each { |tool| puts " #{tool.name} read_only=#{tool.annotations&.read_only}" }
|
|
100
|
+
end
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`McpServerStatus#config` is an `McpSdkServerConfigStatus` or `McpClaudeAIProxyServerConfig` for `sdk` and `claudeai-proxy` servers, and the raw Hash for every other server type. There is no typed class for context usage; read the Hash.
|
|
104
|
+
|
|
80
105
|
## Custom Transport
|
|
81
106
|
|
|
82
107
|
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).
|
|
@@ -87,7 +112,7 @@ A transport must implement six methods:
|
|
|
87
112
|
|---|---|
|
|
88
113
|
| `connect` | Establish the connection / spawn the remote CLI |
|
|
89
114
|
| `write(data)` | Send raw JSON-line bytes to stdin |
|
|
90
|
-
| `read_messages { \|hash\| ... }` | Yield parsed JSON
|
|
115
|
+
| `read_messages { \|hash\| ... }` | Yield each stdout line as a Hash parsed with `JSON.parse(line, symbolize_names: true)` (the SDK reads Symbol keys; see [Hash keys](types.md#hash-keys)); block until the stream closes |
|
|
91
116
|
| `end_input` | Signal EOF on stdin |
|
|
92
117
|
| `close` | Terminate and clean up |
|
|
93
118
|
| `ready?` | Report whether the transport can accept I/O |
|
data/docs/errors.md
CHANGED
|
@@ -117,6 +117,11 @@ end
|
|
|
117
117
|
# no usable home directory exists for the default ~/.claude
|
|
118
118
|
class ConfigDirError < ClaudeSDKError; end
|
|
119
119
|
|
|
120
|
+
# Raised when resuming from a SessionStore fails: a store call raised or
|
|
121
|
+
# exceeded load_timeout_ms during resume materialization. #cause holds the
|
|
122
|
+
# adapter's exception (or the timeout)
|
|
123
|
+
class SessionStoreError < ClaudeSDKError; end
|
|
124
|
+
|
|
120
125
|
# Raised when the Claude Code process fails
|
|
121
126
|
class ProcessError < ClaudeSDKError
|
|
122
127
|
attr_reader :exit_code, # Integer | nil
|
|
@@ -155,6 +160,7 @@ end
|
|
|
155
160
|
| `ControlRequestTimeoutError` | Control protocol timeout (configurable via env var) |
|
|
156
161
|
| `CLINotFoundError` | Claude Code not installed |
|
|
157
162
|
| `ConfigDirError` | A local-disk session API (`list_sessions`, `get_session_*`, `rename_session`, ...) could not locate the Claude config directory: `CLAUDE_CONFIG_DIR` is unset and there is no usable home directory (`HOME` unset with no passwd entry, as under `docker --user` in a minimal image, or an empty/relative `HOME`). Set `CLAUDE_CONFIG_DIR` |
|
|
163
|
+
| `SessionStoreError` | Resuming from `session_store:` failed: a store call (`#load`, `#list_sessions`, `#list_subkeys`) raised or exceeded `load_timeout_ms` while the SDK materialized the transcript, before the CLI started. The message names the call; `#cause` is the adapter's exception. Before 1.0 this was a bare `RuntimeError` — see [Sessions](sessions.md#mirroring-to-a-sessionstore) |
|
|
158
164
|
| `ProcessError` | Process failed (includes `exit_code` and `stderr`) — also raised when the CLI is still running 5s after closing stdout and the SDK had to terminate it |
|
|
159
165
|
| `ResultError` | Run ended on a terminal error result (subclasses `ProcessError`; adds `subtype`, `errors`, `api_error_status`, `terminal_reason`, ...) — rescue it first |
|
|
160
166
|
| `CLIJSONDecodeError` | JSON parsing issues — including stdout ending mid-frame (a truncated final message; `line` holds the partial frame) |
|
|
@@ -19,7 +19,9 @@ All hook input objects include common fields like `session_id`, `transcript_path
|
|
|
19
19
|
- `SubagentStart` → `SubagentStartHookInput` (`agent_id`, `agent_type`)
|
|
20
20
|
- `PermissionRequest` → `PermissionRequestHookInput` (`tool_name`, `tool_input`, `permission_suggestions`)
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
`tool_input` (and the `input` a [permission callback](#permission-callbacks) receives) is the CLI's Hash passed through unchanged, so its keys are Symbols spelled as on the wire: `tool_input[:command]`, `input[:file_path]`. See [Hash keys](types.md#hash-keys).
|
|
23
|
+
|
|
24
|
+
All 27 hook events have typed input classes. See [`ClaudeAgentSDK::HOOK_EVENTS`](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/lib/claude_agent_sdk/types/hooks.rb) and [examples/lifecycle_hooks_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/lifecycle_hooks_example.rb).
|
|
23
25
|
|
|
24
26
|
`background_tasks` and `session_crons` are optional arrays of raw CLI hashes.
|
|
25
27
|
`nil` means the CLI did not provide a snapshot; `[]` means it provided an empty
|
|
@@ -40,7 +42,7 @@ Async do
|
|
|
40
42
|
return {} unless input.respond_to?(:tool_name) && input.tool_name == 'Bash'
|
|
41
43
|
|
|
42
44
|
tool_input = input.tool_input || {}
|
|
43
|
-
command = tool_input[:command] ||
|
|
45
|
+
command = tool_input[:command] || ''
|
|
44
46
|
block_patterns = ['rm -rf', 'foo.sh']
|
|
45
47
|
|
|
46
48
|
block_patterns.each do |pattern|
|
|
@@ -105,7 +107,7 @@ Async do
|
|
|
105
107
|
return ClaudeAgentSDK::PermissionResultAllow.new if tool_name == 'Read'
|
|
106
108
|
|
|
107
109
|
if tool_name == 'Write'
|
|
108
|
-
file_path = input[:file_path]
|
|
110
|
+
file_path = input[:file_path]
|
|
109
111
|
if file_path && file_path.include?('/etc/')
|
|
110
112
|
return ClaudeAgentSDK::PermissionResultDeny.new(
|
|
111
113
|
message: 'Cannot write to sensitive system files',
|
data/docs/mcp-servers.md
CHANGED
|
@@ -130,8 +130,7 @@ first gets an `isError` result naming the exception class
|
|
|
130
130
|
(`"SystemExit: exit"`), so it is not left waiting on the tool call, and then
|
|
131
131
|
the exception propagates as Ruby normally would (`exit` ends the process,
|
|
132
132
|
Ctrl-C interrupts it). Called directly, without a session,
|
|
133
|
-
`SdkMcpServer#call_tool`
|
|
134
|
-
propagate. Cancellation of the tool call itself still propagates.
|
|
133
|
+
`SdkMcpServer#call_tool` simply lets such exceptions propagate. Cancellation of the tool call itself still propagates.
|
|
135
134
|
|
|
136
135
|
## Mixed Server Support
|
|
137
136
|
|
data/docs/sessions.md
CHANGED
|
@@ -40,7 +40,7 @@ messages.each { |msg| puts "[#{msg.type}] #{msg.message}" }
|
|
|
40
40
|
ClaudeAgentSDK.get_session_messages(session_id: 'abc-123-...', offset: 10, limit: 20)
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
Each `SessionMessage` includes `type` (`"user"` or `"assistant"`), `uuid`, `session_id`, and `message` (raw API hash).
|
|
43
|
+
Each `SessionMessage` includes `type` (`"user"` or `"assistant"`), `uuid`, `session_id`, and `message` (the raw API message Hash, read from the transcript, so its keys are Strings: `msg.message['content']`; see [Hash keys](types.md#hash-keys)).
|
|
44
44
|
|
|
45
45
|
## Reading Subagent Transcripts
|
|
46
46
|
|
|
@@ -227,6 +227,25 @@ while an append is in flight are coalesced into the next append, so a slow
|
|
|
227
227
|
store never accumulates one background task per frame), and `load_timeout_ms`
|
|
228
228
|
(per store call during resume materialization, default `60_000`).
|
|
229
229
|
|
|
230
|
+
If a store call raises or exceeds `load_timeout_ms` during resume
|
|
231
|
+
materialization, `ClaudeAgentSDK.query`, `.ask` and `Client#connect` raise
|
|
232
|
+
`ClaudeAgentSDK::SessionStoreError` (a `ClaudeSDKError`) before the CLI
|
|
233
|
+
starts. The message names the call (`SessionStore#load for session <id> failed
|
|
234
|
+
during resume materialization: IOError: ...`) and `#cause` holds the adapter's
|
|
235
|
+
own exception, or the internal timeout:
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
begin
|
|
239
|
+
ClaudeAgentSDK.ask('Continue', options: options.dup_with(resume: session_id))
|
|
240
|
+
rescue ClaudeAgentSDK::SessionStoreError => e
|
|
241
|
+
logger.warn("resume from store failed: #{e.message} (#{e.cause&.class})")
|
|
242
|
+
raise
|
|
243
|
+
end
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Before 1.0 this surfaced as a bare `RuntimeError` (and a `RuntimeError` your
|
|
247
|
+
adapter raised escaped unwrapped), which `rescue ClaudeSDKError` missed.
|
|
248
|
+
|
|
230
249
|
Resume materialization re-serializes each loaded entry to JSONL. An entry that
|
|
231
250
|
cannot be serialized (NaN/Infinity, invalid UTF-8, circular nesting), an
|
|
232
251
|
unserializable subagent metadata sidecar, or a subkey that is not a safe
|
|
@@ -292,6 +311,65 @@ require 'claude_agent_sdk/testing/session_store_conformance'
|
|
|
292
311
|
ClaudeAgentSDK::Testing.run_session_store_conformance(-> { MyStore.new(...) })
|
|
293
312
|
```
|
|
294
313
|
|
|
314
|
+
Keys and entries cross the adapter boundary with **String** keys (see
|
|
315
|
+
[Hash keys](types.md#hash-keys)): a key is `{ 'project_key' => ..., 'session_id' => ... }`,
|
|
316
|
+
plus `'subpath'` for a subagent transcript, and entries are the raw JSONL
|
|
317
|
+
objects. Persist entries verbatim and treat `entry['uuid']` as an idempotency
|
|
318
|
+
key, since a retried or re-imported batch can repeat earlier writes.
|
|
319
|
+
|
|
320
|
+
#### Helpers for adapter authors
|
|
321
|
+
|
|
322
|
+
`ClaudeAgentSDK.project_key_for_directory(directory = nil)` returns the
|
|
323
|
+
`project_key` the SDK uses for a directory (a String or Pathname; `nil` means
|
|
324
|
+
the current working directory). It applies the CLI's project-directory naming
|
|
325
|
+
(realpath, Unicode NFC, then the CLI's sanitization), so a key you build
|
|
326
|
+
matches the keys of live-mirrored transcripts:
|
|
327
|
+
|
|
328
|
+
```ruby
|
|
329
|
+
key = { 'project_key' => ClaudeAgentSDK.project_key_for_directory('/path/to/project'),
|
|
330
|
+
'session_id' => '550e8400-e29b-41d4-a716-446655440000' }
|
|
331
|
+
store.load(key)
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
`ClaudeAgentSDK.fold_session_summary(prev, key, entries)` maintains a
|
|
335
|
+
per-session summary incrementally, so an adapter can implement
|
|
336
|
+
`#list_session_summaries` and `list_sessions(session_store:)` can read every
|
|
337
|
+
session's metadata in one call instead of one `#load` per session. Call it
|
|
338
|
+
from `#append`:
|
|
339
|
+
|
|
340
|
+
```ruby
|
|
341
|
+
def append(key, entries)
|
|
342
|
+
return if entries.nil? || entries.empty?
|
|
343
|
+
|
|
344
|
+
write_entries(key, entries)
|
|
345
|
+
return unless key['subpath'].nil? # subagent transcripts never feed the summary
|
|
346
|
+
|
|
347
|
+
summary = ClaudeAgentSDK.fold_session_summary(read_summary(key), key, entries)
|
|
348
|
+
summary['mtime'] = write_time_ms # the clock #list_sessions reports
|
|
349
|
+
write_summary(key, summary)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def list_session_summaries(project_key)
|
|
353
|
+
read_summaries(project_key) # => [{ 'session_id' => ..., 'mtime' => ..., 'data' => {...} }, ...]
|
|
354
|
+
end
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
- `prev` is the summary you stored for the same key on the previous append,
|
|
358
|
+
or `nil` on the first one. `entries` are the entries being appended.
|
|
359
|
+
- It returns a new `{ 'session_id', 'mtime', 'data' }` Hash and leaves `prev`
|
|
360
|
+
unchanged. Every derived field is set-once or last-wins, so the fold never
|
|
361
|
+
needs earlier entries again.
|
|
362
|
+
- Only call it for main-transcript keys (no `'subpath'`).
|
|
363
|
+
- It does not set `mtime`: it carries `prev`'s value, or `0` for a new
|
|
364
|
+
session. Stamp it after persisting, from the same clock as the `mtime`
|
|
365
|
+
your `#list_sessions` returns. When the store also implements
|
|
366
|
+
`#list_sessions`, a summary whose `mtime` is older than the listed one is
|
|
367
|
+
treated as stale and the SDK re-derives it from the transcript.
|
|
368
|
+
- `data` is opaque. Store it as returned; its String keys survive a JSON
|
|
369
|
+
round-trip (JSONB, Redis).
|
|
370
|
+
|
|
371
|
+
`InMemorySessionStore#append` is a working reference.
|
|
372
|
+
|
|
295
373
|
Copy-in reference adapters for **S3, Redis, and Postgres** live in
|
|
296
374
|
[`examples/session_stores/`](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/session_stores/README.md), each with a
|
|
297
375
|
production checklist.
|
|
@@ -391,7 +469,27 @@ Where the store path differs from the disk path:
|
|
|
391
469
|
entries are removed too depends on the store's delete cascade.
|
|
392
470
|
|
|
393
471
|
To migrate, `import_session_to_store` replays a local on-disk session (and its
|
|
394
|
-
subagents) into a store
|
|
472
|
+
subagents) into a store:
|
|
473
|
+
|
|
474
|
+
```ruby
|
|
475
|
+
ClaudeAgentSDK.import_session_to_store(
|
|
476
|
+
session_id: '550e8400-...',
|
|
477
|
+
session_store: store,
|
|
478
|
+
directory: '/path/to/project', # optional; nil searches every project
|
|
479
|
+
include_subagents: true, # default
|
|
480
|
+
batch_size: 500 # default
|
|
481
|
+
)
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
It streams the transcript and calls `store.append` once per batch. A batch ends
|
|
485
|
+
at `batch_size` entries (default **500**; `nil` or a non-positive value also
|
|
486
|
+
means 500) or at about 1 MiB of JSONL, whichever comes first. Entries are
|
|
487
|
+
keyed under the on-disk project directory name, so the imported session can
|
|
488
|
+
be resumed with `session_store:` + `resume:` from the original directory.
|
|
489
|
+
Re-importing appends the entries again, so adapters should dedupe by
|
|
490
|
+
`entry['uuid']`. It raises `ArgumentError` for an invalid `session_id` and
|
|
491
|
+
`Errno::ENOENT` when the transcript cannot be found; an unparseable line is
|
|
492
|
+
skipped with a warning.
|
|
395
493
|
|
|
396
494
|
> **Deprecated:** the separate store functions (`list_sessions_from_store`,
|
|
397
495
|
> `get_session_info_from_store`, `get_session_messages_from_store`,
|
|
@@ -399,7 +497,7 @@ subagents) into a store.
|
|
|
399
497
|
> `get_subagent_messages_from_store`, `rename_session_via_store`,
|
|
400
498
|
> `tag_session_via_store`, `delete_session_via_store`,
|
|
401
499
|
> `fork_session_via_store`) still work unchanged but print a one-time
|
|
402
|
-
> deprecation warning and will be removed in
|
|
500
|
+
> deprecation warning; they stay through 1.x and will be removed in 2.0. Replace
|
|
403
501
|
> `ClaudeAgentSDK.x_from_store(session_store: store, ...)` or
|
|
404
502
|
> `x_via_store(session_store: store, ...)` with
|
|
405
503
|
> `ClaudeAgentSDK.x(..., session_store: store)`.
|