claude-agent-sdk 0.31.0 → 0.32.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 +8 -0
- data/README.md +107 -223
- data/docs/cli-installer.md +57 -0
- data/docs/configuration.md +22 -0
- data/docs/types.md +3 -1
- data/lib/claude_agent_sdk/command_builder.rb +18 -0
- data/lib/claude_agent_sdk/query.rb +6 -2
- data/lib/claude_agent_sdk/types.rb +35 -2
- data/lib/claude_agent_sdk/version.rb +1 -1
- data/lib/claude_agent_sdk.rb +25 -2
- 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: 04f9cdf2c66d67b2af3a2a8c8dd14ee7fd418ccc7a7b4be727f73d70d02a8446
|
|
4
|
+
data.tar.gz: 7d8990d6720a47d104c59cc03d2288feefc55854d10badc19bdbc1d42a6dca04
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d441ee7bfe0f1bb98a1ea2088bb908601ae16b545cd8e05928addb0966d746ae6c0de137b48bc7e3949d8e040d1eec58551e5775886a3658d6f9e9265be54b05
|
|
7
|
+
data.tar.gz: 1ef545a581c38233903ef7da689fcdc93776d6a764c5b1754f1ea77adbd775275c67eb817a4494ca83f8768218ea281bed96507f429c7880380ceb995723b197
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.32.0] - 2026-09-17
|
|
11
|
+
|
|
12
|
+
Syncs the gem with Python SDK **0.2.153** (previously 0.2.147). The intervening Python releases 0.2.148–0.2.152 only bump the CLI binary Python bundles; this gem does not vendor a CLI, so they carry no Ruby-side change.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **`snapshot` on `SystemPromptPreset`, and a new `SystemPromptCustom` type** (port of Python [#1268](https://github.com/anthropics/claude-agent-sdk-python/pull/1268), v0.2.153). By default the CLI records the system prompt on a session's first request and reuses it on every later request, including after resume, so a changed `append` or custom prompt has no effect until the session is compacted or a new one starts. `snapshot: false` makes the CLI rebuild the prompt on every request instead — useful while iterating on prompt text across calls that resume the same session. `SystemPromptCustom` (`prompt:`, `snapshot:`) is the object form of a String prompt, so `snapshot` can travel with it; the `{ type: 'custom', prompt: '...', snapshot: false }` and `{ type: 'preset', ..., snapshot: false }` Hash forms are accepted too. The value rides on the control-protocol `initialize` request as `systemPromptSnapshot` (never as a CLI flag), so it applies to both `query()` and `Client`; `false` is sent explicitly and only an unset value is omitted. `SystemPromptFile` has no `snapshot`, and one given on a file Hash is ignored, as in Python. Requires Claude Code CLI 2.1.257 or later; before 2.1.265 a session with an `append` or custom prompt recorded it only when `snapshot` was `true`. Older CLIs silently ignore the field.
|
|
16
|
+
- **Compatibility:** a `{ type: 'custom', ... }` Hash previously fell through the command builder unrecognised — pushing no flag at all — and so silently activated the *default* Claude Code system prompt. It now forwards `--system-prompt <prompt>` exactly like a String, and a custom prompt without a String `prompt` raises `ArgumentError` at command-build time rather than falling through (Python raises `KeyError` on the same input).
|
|
17
|
+
|
|
10
18
|
## [0.31.0] - 2026-08-28
|
|
11
19
|
|
|
12
20
|
Syncs the gem with Python SDK **0.2.147** (previously 0.2.134). The intervening Python releases 0.2.135/136/138/139/141–147 only bump the CLI binary Python bundles; this gem does not vendor a CLI, so they carry no Ruby-side change.
|
data/README.md
CHANGED
|
@@ -1,143 +1,48 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-

|
|
4
|
-
|
|
5
|
-
[](https://badge.fury.io/rb/claude-agent-sdk)
|
|
6
|
-
|
|
7
|
-
An **unofficial, community-maintained** Ruby SDK for the [Claude Code](https://docs.claude.com/en/docs/claude-code-overview) agent runtime. Not affiliated with or supported by Anthropic.
|
|
8
|
-
|
|
9
|
-
Official SDKs: [TypeScript](https://github.com/anthropics/claude-agent-sdk-typescript) · [Python](https://github.com/anthropics/claude-agent-sdk-python).
|
|
10
|
-
|
|
11
|
-
## Why a Ruby SDK?
|
|
12
|
-
|
|
13
|
-
Ruby powers a massive ecosystem — Rails, Sidekiq, Kamal, countless production web apps — but has no official Claude Agent SDK. This gem fills that gap so Ruby and Rails developers can build AI agents, automate coding workflows, and integrate Claude into existing applications without switching languages or shelling out to Python/Node.
|
|
14
|
-
|
|
15
|
-
All three SDKs share the same underlying mechanism: they spawn the `claude` CLI as a subprocess and communicate over stream-JSON on stdin/stdout. The wire protocol is identical, so Ruby gets the same capabilities as the official SDKs.
|
|
16
|
-
|
|
17
|
-
### Comparison with Official SDKs
|
|
18
|
-
|
|
19
|
-
| Capability | TypeScript | Python | Ruby (this gem) |
|
|
20
|
-
|---|:---:|:---:|:---:|
|
|
21
|
-
| One-shot `query()` | ✅ | ✅ | ✅ |
|
|
22
|
-
| Bidirectional `Client` | ✅ | ✅ | ✅ |
|
|
23
|
-
| Streaming input | `AsyncIterable` | `AsyncIterable` | `Enumerator` |
|
|
24
|
-
| Custom tools (SDK MCP servers) | `tool()` | `@tool` decorator | `create_tool` block |
|
|
25
|
-
| Hooks (all 27 events) | ✅ | ✅ | ✅ |
|
|
26
|
-
| Permission callbacks | ✅ | ✅ | ✅ |
|
|
27
|
-
| Structured output | ✅ | ✅ | ✅ |
|
|
28
|
-
| All 25 message types | ✅ | partial | ✅ |
|
|
29
|
-
| [Sandbox](https://github.com/anthropic-experimental/sandbox-runtime) settings | ✅ | partial | ✅ |
|
|
30
|
-
| Bare mode (`--bare`) | ✅ | ✅ | ✅ |
|
|
31
|
-
| File checkpointing & rewind | ✅ | ✅ | ✅ |
|
|
32
|
-
| Session browsing & mutations | ✅ | ✅ | ✅ |
|
|
33
|
-
| Programmatic subagents | ✅ | ✅ | ✅ |
|
|
34
|
-
| Bundled CLI binary | ✅ | ✅ | — (install `claude` separately) |
|
|
35
|
-
| Observability (OTel / Langfuse) | via [Arize](https://github.com/Arize-ai/openinference) | — | ✅ (built-in) |
|
|
36
|
-
| Custom transport (pluggable I/O) | — | — | ✅ |
|
|
37
|
-
| Rails integration | — | — | ✅ |
|
|
38
|
-
|
|
39
|
-
**Where Ruby goes further:** Built-in OpenTelemetry observer with Langfuse flow diagram support — no third-party instrumentation library needed. Custom transport support lets you swap the subprocess for any I/O layer (e.g., connect to a remote Claude Code instance over SSH or a container). Rails integration provides a `configure` block for initializers with thread-safe observer factories, and plays well with ActionCable for real-time streaming. Full typed coverage for all 25 CLI message types and all 27 hook events.
|
|
1
|
+

|
|
40
2
|
|
|
41
|
-
|
|
3
|
+
# Claude Agent SDK for Ruby
|
|
42
4
|
|
|
43
|
-
|
|
44
|
-
|
|
5
|
+
[](https://rubygems.org/gems/claude-agent-sdk)
|
|
6
|
+
[](https://github.com/ya-luotao/claude-agent-sdk-ruby/actions/workflows/ci.yml)
|
|
7
|
+
[](https://www.ruby-lang.org/)
|
|
8
|
+
[](https://rubydoc.info/gems/claude-agent-sdk)
|
|
9
|
+
[](LICENSE)
|
|
45
10
|
|
|
46
|
-
|
|
11
|
+
A Ruby SDK for the [Claude Code](https://docs.claude.com/en/docs/claude-code-overview) agent runtime. Build AI agents, automate coding workflows, and integrate Claude into Rails and other Ruby applications with the same capabilities as the official [TypeScript](https://github.com/anthropics/claude-agent-sdk-typescript) and [Python](https://github.com/anthropics/claude-agent-sdk-python) SDKs.
|
|
47
12
|
|
|
48
|
-
|
|
49
|
-
Async do
|
|
50
|
-
client = ClaudeAgentSDK::Client.new(options: options)
|
|
51
|
-
client.connect
|
|
52
|
-
client.query("Hello")
|
|
53
|
-
client.receive_response { |msg| puts msg }
|
|
54
|
-
client.disconnect
|
|
55
|
-
end.wait
|
|
56
|
-
```
|
|
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.
|
|
57
14
|
|
|
58
|
-
|
|
15
|
+
## Highlights
|
|
59
16
|
|
|
60
|
-
|
|
17
|
+
- **Same wire protocol as the official SDKs.** Spawns the `claude` CLI as a subprocess and speaks stream-JSON over stdin/stdout, so every feature of the runtime is available: sessions, subagents, sandboxing, structured output, file checkpointing and rewind.
|
|
18
|
+
- **`query()` for one-shot calls, `Client` for bidirectional sessions** with interrupts, mid-session model switching, and streaming input from any `Enumerator`.
|
|
19
|
+
- **In-process custom tools.** Define tools as Ruby blocks; they run inside your process with direct access to your app state (SDK MCP servers), with JSON-Schema-validated arguments.
|
|
20
|
+
- **All 27 hook events and permission callbacks** with typed inputs, so you can gate, audit, or rewrite every tool call.
|
|
21
|
+
- **Rails-ready.** Fiber-safe callback dispatch, an initializer-style `configure` block, ActionCable streaming, background-job session resumption, and a `callback_scheduling: :inline` mode for fiber workers.
|
|
22
|
+
- **Built-in OpenTelemetry observer** with Langfuse support; no third-party instrumentation library required.
|
|
23
|
+
- **Pluggable transport** to run the CLI somewhere else (an E2B microVM, a container, over SSH).
|
|
24
|
+
- **Hermetic deploys.** `CLIInstaller` vendors a checksum-verified, pinned CLI binary into your project so production never depends on a global `npm install`.
|
|
61
25
|
|
|
62
26
|
## Installation
|
|
63
27
|
|
|
64
|
-
Add this line to your application's Gemfile:
|
|
65
|
-
|
|
66
28
|
```ruby
|
|
67
|
-
#
|
|
68
|
-
gem 'claude-agent-sdk',
|
|
69
|
-
|
|
70
|
-
# Or use a stable version from RubyGems
|
|
71
|
-
gem 'claude-agent-sdk', '~> 0.31.0'
|
|
29
|
+
# Gemfile
|
|
30
|
+
gem 'claude-agent-sdk', '~> 0.32.0'
|
|
72
31
|
```
|
|
73
32
|
|
|
74
|
-
Then `bundle install`, or install directly
|
|
33
|
+
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'`.
|
|
75
34
|
|
|
76
|
-
**Prerequisites
|
|
77
|
-
- Ruby 3.2+
|
|
78
|
-
- Node.js
|
|
79
|
-
- Claude Code 2.0.0+: `npm install -g @anthropic-ai/claude-code`
|
|
35
|
+
**Prerequisites**
|
|
80
36
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
The SDK runs the `claude` CLI as a subprocess, so a deploy is only reproducible if the CLI version is pinned with it. `CLIInstaller` downloads a pinned binary from the official release endpoint into a project-local directory (`vendor/claude` by default) — checksum-verified, no npm/Node at runtime, and nothing extra shipped inside the gem.
|
|
37
|
+
- Ruby 3.2 or newer
|
|
38
|
+
- Claude Code CLI 2.0.0 or newer, either installed globally (`npm install -g @anthropic-ai/claude-code`) or vendored with `CLIInstaller`:
|
|
84
39
|
|
|
85
40
|
```ruby
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
# 'stable' (default), 'latest', or a concrete version — pin it in production.
|
|
89
|
-
ClaudeAgentSDK::CLIInstaller.install(version: '2.1.220')
|
|
90
|
-
# => "/app/vendor/claude/claude"
|
|
91
|
-
|
|
92
|
-
ClaudeAgentSDK::CLIInstaller.install(version: '2.1.220', dir: '/opt/claude')
|
|
93
|
-
|
|
94
|
-
# nil unless a binary is already installed there
|
|
95
|
-
ClaudeAgentSDK::CLIInstaller.installed_path
|
|
41
|
+
# bin/setup or a cached Docker layer — pin a concrete version in production
|
|
42
|
+
ClaudeAgentSDK::CLIInstaller.install(version: '2.1.220') # => "/app/vendor/claude/claude"
|
|
96
43
|
```
|
|
97
44
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
- The install directory's `VERSION` file records the installed version **and** the SHA-256 that was verified at download time. The shortcut re-hashes the vendored binary (~0.1s for the real 245MB binary) and only skips the download when both match — a truncated, swapped or half-written binary is reinstalled instead of trusted. It makes **no network request**, so repeat boots work offline — with a pinned concrete version; `'stable'`/`'latest'` must always re-resolve through the endpoint, which is one more reason to pin in production.
|
|
101
|
-
- An exclusive `flock` on `<dir>/.install.lock` covers the whole check → download → place → record sequence, so parallel installs into one directory don't race; the loser simply observes the finished install.
|
|
102
|
-
|
|
103
|
-
Failures (unsupported platform, invalid version, HTTP error, response-size cap, oversized download, checksum mismatch, filesystem errors) raise `ClaudeAgentSDK::CLIInstallError`.
|
|
104
|
-
|
|
105
|
-
**A failed install never breaks a working one.** The new binary is downloaded to a temp file, checksum-verified and recorded, and only then renamed into place — the rename is the last step, and nothing can fail after it. So a failed upgrade leaves the previously installed binary intact and runnable (the SDK keeps working), and the next `install` redoes it cleanly. A first install that fails leaves nothing behind at all.
|
|
106
|
-
|
|
107
|
-
> The vendored directory is trusted input: anything that can write to it can replace the binary the SDK executes. Keep it inside your deploy artifact, owned by the deploy user and not world-writable, exactly as you would treat `bin/`.
|
|
108
|
-
|
|
109
|
-
```dockerfile
|
|
110
|
-
# Dockerfile — pin the CLI in its own cached layer
|
|
111
|
-
RUN bundle exec ruby -e "require 'claude_agent_sdk'; \
|
|
112
|
-
ClaudeAgentSDK::CLIInstaller.install(version: '2.1.220')"
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
```ruby
|
|
116
|
-
#!/usr/bin/env ruby
|
|
117
|
-
# bin/setup
|
|
118
|
-
require 'claude_agent_sdk'
|
|
119
|
-
puts ClaudeAgentSDK::CLIInstaller.install(version: ENV.fetch('CLAUDE_CLI_VERSION', 'stable'))
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
Supported platforms: `darwin-arm64`, `darwin-x64` (Rosetta 2 gets the arm64 build), `linux-x64`, `linux-arm64`, and the `-musl` variants. Windows is not supported.
|
|
123
|
-
|
|
124
|
-
**CLI discovery order.** With no explicit `cli_path:` in `ClaudeAgentOptions`, the transport probes in this order:
|
|
125
|
-
|
|
126
|
-
1. `CLAUDE_CLI_PATH` — an explicit path to an executable, no discovery at all (a relative value is resolved against the process's working directory, not `cwd:`)
|
|
127
|
-
2. The vendored binary (`CLIInstaller.installed_path`) — deliberately ahead of `PATH`, so a pinned install beats whatever is installed globally
|
|
128
|
-
3. `which claude`
|
|
129
|
-
4. Common install locations (`~/.claude/local/claude`, `/usr/local/bin/claude`, …)
|
|
130
|
-
|
|
131
|
-
### Agentic Coding Skill
|
|
132
|
-
|
|
133
|
-
If you're using [Claude Code](https://claude.ai/claude-code), this repo is a Claude Code plugin marketplace. Add it once, then install the skill:
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
/plugin marketplace add ya-luotao/claude-agent-sdk-ruby
|
|
137
|
-
/plugin install claude-agent-ruby@claude-agent-sdk-ruby
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
This skill teaches your AI coding assistant about the SDK's APIs, patterns, and best practices.
|
|
45
|
+
The vendored binary is found ahead of `PATH`, installs are idempotent and concurrency-safe, and a failed upgrade never breaks a working install. See [docs/cli-installer.md](docs/cli-installer.md) for the full behaviour, supported platforms, and the CLI discovery order.
|
|
141
46
|
|
|
142
47
|
## Quick Start
|
|
143
48
|
|
|
@@ -145,46 +50,29 @@ This skill teaches your AI coding assistant about the SDK's APIs, patterns, and
|
|
|
145
50
|
require 'claude_agent_sdk'
|
|
146
51
|
|
|
147
52
|
ClaudeAgentSDK.query(prompt: "What is 2 + 2?") do |message|
|
|
148
|
-
puts message
|
|
53
|
+
puts message.text if message.is_a?(ClaudeAgentSDK::AssistantMessage)
|
|
149
54
|
end
|
|
150
55
|
```
|
|
151
56
|
|
|
152
|
-
|
|
57
|
+
### `query()` — one-shot and streaming
|
|
153
58
|
|
|
154
|
-
`query()`
|
|
59
|
+
`query()` runs a single conversation and yields each response message to the block.
|
|
155
60
|
|
|
156
61
|
```ruby
|
|
157
|
-
require 'claude_agent_sdk'
|
|
158
|
-
|
|
159
|
-
# Simple query
|
|
160
|
-
ClaudeAgentSDK.query(prompt: "Hello Claude") do |message|
|
|
161
|
-
puts message.text if message.is_a?(ClaudeAgentSDK::AssistantMessage)
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
# With options
|
|
165
62
|
options = ClaudeAgentSDK::ClaudeAgentOptions.new(
|
|
166
63
|
system_prompt: "You are a helpful assistant",
|
|
167
|
-
max_turns: 1
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
ClaudeAgentSDK.query(prompt: "Tell me a joke", options: options) do |message|
|
|
171
|
-
puts message
|
|
172
|
-
end
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
**Using tools:**
|
|
176
|
-
|
|
177
|
-
```ruby
|
|
178
|
-
options = ClaudeAgentSDK::ClaudeAgentOptions.new(
|
|
179
64
|
allowed_tools: ['Read', 'Write', 'Bash'],
|
|
180
65
|
permission_mode: 'acceptEdits',
|
|
181
|
-
cwd: "/path/to/project"
|
|
66
|
+
cwd: "/path/to/project",
|
|
67
|
+
max_turns: 5
|
|
182
68
|
)
|
|
183
69
|
|
|
184
|
-
ClaudeAgentSDK.query(prompt: "Create a hello.rb file", options: options)
|
|
70
|
+
ClaudeAgentSDK.query(prompt: "Create a hello.rb file", options: options) do |message|
|
|
71
|
+
puts message
|
|
72
|
+
end
|
|
185
73
|
```
|
|
186
74
|
|
|
187
|
-
|
|
75
|
+
Pass an `Enumerator` instead of a string to stream several user messages into one session:
|
|
188
76
|
|
|
189
77
|
```ruby
|
|
190
78
|
stream = ClaudeAgentSDK::Streaming.from_array(['Hello!', 'What is 2+2?', 'Thanks!'])
|
|
@@ -194,11 +82,9 @@ ClaudeAgentSDK.query(prompt: stream) do |message|
|
|
|
194
82
|
end
|
|
195
83
|
```
|
|
196
84
|
|
|
197
|
-
|
|
85
|
+
### `Client` — bidirectional sessions
|
|
198
86
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
`Client` supports interactive conversations with hooks, permission callbacks, and custom tools. It uses streaming mode automatically.
|
|
87
|
+
`Client` keeps a session open so you can send follow-up queries, interrupt, switch models, and use hooks, permission callbacks, and custom tools. It runs inside an [`async`](https://github.com/socketry/async) block; blocking calls yield automatically, no `await` needed.
|
|
202
88
|
|
|
203
89
|
```ruby
|
|
204
90
|
require 'claude_agent_sdk'
|
|
@@ -217,11 +103,11 @@ Async do
|
|
|
217
103
|
end.wait
|
|
218
104
|
```
|
|
219
105
|
|
|
220
|
-
|
|
106
|
+
See [docs/client.md](docs/client.md) for `interrupt`, mid-session model and permission switching, MCP status, and custom transports.
|
|
221
107
|
|
|
222
|
-
|
|
108
|
+
### Custom tools (SDK MCP servers)
|
|
223
109
|
|
|
224
|
-
|
|
110
|
+
Tools are Ruby blocks that run in-process, with no subprocess or IPC between Claude's tool call and your code.
|
|
225
111
|
|
|
226
112
|
```ruby
|
|
227
113
|
greet = ClaudeAgentSDK.create_tool('greet', 'Greet a user', { name: :string }) do |args|
|
|
@@ -236,13 +122,11 @@ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
|
|
|
236
122
|
)
|
|
237
123
|
```
|
|
238
124
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
Resources, prompts, mixed (SDK + external) servers, RubyLLM schema compatibility → see [docs/mcp-servers.md](docs/mcp-servers.md).
|
|
125
|
+
Arguments are validated against the tool's JSON Schema before your handler runs, and handler exceptions are reported back to the model in-band so it can self-correct. See [docs/mcp-servers.md](docs/mcp-servers.md) for resources, prompts, mixed SDK + external servers, and schema details.
|
|
242
126
|
|
|
243
|
-
|
|
127
|
+
### Hooks and permission callbacks
|
|
244
128
|
|
|
245
|
-
|
|
129
|
+
Hooks run your Ruby code at any of the 27 lifecycle events (`PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `Stop`, `PreCompact`, …) with typed inputs. Permission callbacks decide programmatically whether a tool call may proceed.
|
|
246
130
|
|
|
247
131
|
```ruby
|
|
248
132
|
options = ClaudeAgentSDK::ClaudeAgentOptions.new(
|
|
@@ -251,88 +135,88 @@ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
|
|
|
251
135
|
)
|
|
252
136
|
```
|
|
253
137
|
|
|
254
|
-
|
|
138
|
+
See [docs/hooks-and-permissions.md](docs/hooks-and-permissions.md) for the full event list and worked examples.
|
|
255
139
|
|
|
256
|
-
##
|
|
140
|
+
## Documentation
|
|
257
141
|
|
|
258
|
-
| Topic |
|
|
259
|
-
|
|
260
|
-
|
|
|
261
|
-
|
|
|
262
|
-
|
|
|
263
|
-
|
|
|
142
|
+
| Topic | Guide |
|
|
143
|
+
|-------|-------|
|
|
144
|
+
| `Client` advanced features and custom transports | [docs/client.md](docs/client.md) |
|
|
145
|
+
| SDK MCP servers: tools, resources, prompts, schema compatibility | [docs/mcp-servers.md](docs/mcp-servers.md) |
|
|
146
|
+
| All hook events, typed inputs, permission callbacks | [docs/hooks-and-permissions.md](docs/hooks-and-permissions.md) |
|
|
147
|
+
| Structured output, thinking, budget, fallback and advisor models, sandbox, bare mode, checkpointing | [docs/configuration.md](docs/configuration.md) |
|
|
148
|
+
| Session listing, reading, renaming, tagging, forking, resume-at-message | [docs/sessions.md](docs/sessions.md) |
|
|
149
|
+
| OpenTelemetry tracing, Langfuse, custom observers | [docs/observability.md](docs/observability.md) |
|
|
150
|
+
| Rails: fiber safety, solid_queue fiber workers, ActionCable, jobs, initializer | [docs/rails.md](docs/rails.md) |
|
|
151
|
+
| Vendoring a pinned CLI binary and CLI discovery order | [docs/cli-installer.md](docs/cli-installer.md) |
|
|
264
152
|
| Message, content block, and configuration type reference | [docs/types.md](docs/types.md) |
|
|
265
|
-
| Error handling, exception hierarchy,
|
|
153
|
+
| Error handling, exception hierarchy, timeouts | [docs/errors.md](docs/errors.md) |
|
|
154
|
+
|
|
155
|
+
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).
|
|
266
156
|
|
|
267
157
|
## Examples
|
|
268
158
|
|
|
269
|
-
|
|
159
|
+
Runnable scripts live in [`examples/`](https://github.com/ya-luotao/claude-agent-sdk-ruby/tree/main/examples).
|
|
270
160
|
|
|
271
|
-
|
|
|
272
|
-
|
|
273
|
-
| [quick_start.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/
|
|
274
|
-
| [
|
|
275
|
-
| [
|
|
276
|
-
| [
|
|
277
|
-
| [
|
|
278
|
-
| [
|
|
279
|
-
| [error_handling_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/error_handling_example.rb) | Error handling with `AssistantMessage.error` |
|
|
280
|
-
| [bare_mode_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/bare_mode_example.rb) | Minimal startup with `bare: true` |
|
|
281
|
-
| [sandbox_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/sandbox_example.rb) | Full sandbox settings (network, filesystem, violations) |
|
|
161
|
+
| Area | Examples |
|
|
162
|
+
|------|----------|
|
|
163
|
+
| Getting started | [quick_start](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/quick_start.rb) · [client](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/client_example.rb) · [streaming_input](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/streaming_input_example.rb) · [message_types](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/message_types_example.rb) · [error_handling](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/error_handling_example.rb) |
|
|
164
|
+
| Sessions and output | [session_resumption](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/session_resumption_example.rb) · [structured_output](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/structured_output_example.rb) · [extended_thinking](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/extended_thinking_example.rb) · [session_stores/](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/session_stores/README.md) |
|
|
165
|
+
| Tools and MCP | [mcp_calculator](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/mcp_calculator.rb) · [mcp_resources_prompts](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/mcp_resources_prompts_example.rb) · [http_mcp_server](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/http_mcp_server_example.rb) |
|
|
166
|
+
| Hooks and permissions | [hooks](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/hooks_example.rb) · [advanced_hooks](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/advanced_hooks_example.rb) · [lifecycle_hooks](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/lifecycle_hooks_example.rb) · [permission_callback](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/permission_callback_example.rb) |
|
|
167
|
+
| Models and limits | [budget_control](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/budget_control_example.rb) · [fallback_model](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/fallback_model_example.rb) · [advisor](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/advisor_example.rb) · [bare_mode](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/bare_mode_example.rb) · [sandbox](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/sandbox_example.rb) |
|
|
168
|
+
| Rails, observability, transports | [rails_actioncable](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/rails_actioncable_example.rb) · [rails_background_job](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/rails_background_job_example.rb) · [otel_langfuse](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/otel_langfuse_example.rb) · [e2b_transport](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/e2b_transport_example.rb) |
|
|
282
169
|
|
|
283
|
-
|
|
170
|
+
## Comparison with the official SDKs
|
|
284
171
|
|
|
285
|
-
|
|
286
|
-
|---------|-------------|
|
|
287
|
-
| [mcp_calculator.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/mcp_calculator.rb) | Custom tools with SDK MCP servers |
|
|
288
|
-
| [mcp_resources_prompts_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/mcp_resources_prompts_example.rb) | MCP resources and prompts |
|
|
289
|
-
| [http_mcp_server_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/http_mcp_server_example.rb) | HTTP/SSE MCP server configuration |
|
|
172
|
+
All three SDKs drive the same CLI over the same protocol, so capabilities line up feature for feature. Ruby differs mainly in idiom: `Enumerator` for streaming input, blocks for tools, and the `async` gem with fibers instead of `async`/`await`.
|
|
290
173
|
|
|
291
|
-
|
|
174
|
+
| Capability | TypeScript | Python | Ruby (this gem) |
|
|
175
|
+
|---|:---:|:---:|:---:|
|
|
176
|
+
| One-shot `query()` | ✅ | ✅ | ✅ |
|
|
177
|
+
| Bidirectional `Client` | ✅ | ✅ | ✅ |
|
|
178
|
+
| Streaming input | `AsyncIterable` | `AsyncIterable` | `Enumerator` |
|
|
179
|
+
| Custom tools (SDK MCP servers) | `tool()` | `@tool` decorator | `create_tool` block |
|
|
180
|
+
| Hooks (all 27 events) | ✅ | ✅ | ✅ |
|
|
181
|
+
| Permission callbacks | ✅ | ✅ | ✅ |
|
|
182
|
+
| Structured output | ✅ | ✅ | ✅ |
|
|
183
|
+
| All 25 message types | ✅ | partial | ✅ |
|
|
184
|
+
| [Sandbox](https://github.com/anthropic-experimental/sandbox-runtime) settings | ✅ | partial | ✅ |
|
|
185
|
+
| Bare mode (`--bare`) | ✅ | ✅ | ✅ |
|
|
186
|
+
| File checkpointing & rewind | ✅ | ✅ | ✅ |
|
|
187
|
+
| Session browsing & mutations | ✅ | ✅ | ✅ |
|
|
188
|
+
| Programmatic subagents | ✅ | ✅ | ✅ |
|
|
189
|
+
| CLI binary | bundled | bundled | vendored on demand (`CLIInstaller`) |
|
|
190
|
+
| Observability (OTel / Langfuse) | via [Arize](https://github.com/Arize-ai/openinference) | — | ✅ built-in |
|
|
191
|
+
| Custom transport (pluggable I/O) | — | — | ✅ |
|
|
192
|
+
| Rails integration | — | — | ✅ |
|
|
292
193
|
|
|
293
|
-
|
|
294
|
-
|---------|-------------|
|
|
295
|
-
| [hooks_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/hooks_example.rb) | Using hooks to control tool execution |
|
|
296
|
-
| [advanced_hooks_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/advanced_hooks_example.rb) | Typed hook inputs/outputs |
|
|
297
|
-
| [lifecycle_hooks_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/lifecycle_hooks_example.rb) | All 27 hook events |
|
|
298
|
-
| [permission_callback_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/permission_callback_example.rb) | Dynamic tool permission control |
|
|
194
|
+
Types are plain Ruby classes with `attr_accessor` and keyword arguments, mirroring the field names of the TypeScript Zod schemas and Python dataclasses; there is no runtime type checking.
|
|
299
195
|
|
|
300
|
-
|
|
196
|
+
## Claude Code plugin
|
|
301
197
|
|
|
302
|
-
|
|
303
|
-
|---------|-------------|
|
|
304
|
-
| [budget_control_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/budget_control_example.rb) | Budget control with `max_budget_usd` |
|
|
305
|
-
| [fallback_model_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/fallback_model_example.rb) | Fallback model configuration |
|
|
306
|
-
| [advisor_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/advisor_example.rb) | Server-side advisor tool (`advisor_model`) |
|
|
307
|
-
| [extended_thinking_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/extended_thinking_example.rb) | Extended thinking |
|
|
308
|
-
| [e2b_transport_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/e2b_transport_example.rb) | Custom transport running CLI in an E2B microVM |
|
|
198
|
+
This repository is also a Claude Code plugin marketplace. The bundled skill teaches Claude Code the gem's APIs and patterns:
|
|
309
199
|
|
|
310
|
-
|
|
200
|
+
```bash
|
|
201
|
+
/plugin marketplace add ya-luotao/claude-agent-sdk-ruby
|
|
202
|
+
/plugin install claude-agent-ruby@claude-agent-sdk-ruby
|
|
203
|
+
```
|
|
311
204
|
|
|
312
|
-
|
|
313
|
-
|---------|-------------|
|
|
314
|
-
| [otel_langfuse_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/otel_langfuse_example.rb) | OpenTelemetry tracing with Langfuse backend |
|
|
315
|
-
| [rails_actioncable_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/rails_actioncable_example.rb) | ActionCable streaming to frontend |
|
|
316
|
-
| [rails_background_job_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/rails_background_job_example.rb) | Background jobs with session resumption |
|
|
205
|
+
## Development
|
|
317
206
|
|
|
318
|
-
|
|
207
|
+
```bash
|
|
208
|
+
bundle install
|
|
209
|
+
bundle exec rspec # unit suite
|
|
210
|
+
bundle exec rubocop # lint
|
|
211
|
+
RUN_INTEGRATION=1 bundle exec rspec # also run the real-CLI integration suite (needs `claude` and ANTHROPIC_API_KEY)
|
|
212
|
+
```
|
|
319
213
|
|
|
320
|
-
|
|
214
|
+
CI runs the suite and RuboCop on Ruby 3.2, 3.3, and 3.4. See [spec/README.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/spec/README.md) for the test layout.
|
|
321
215
|
|
|
322
|
-
##
|
|
216
|
+
## Contributing
|
|
323
217
|
|
|
324
|
-
|
|
218
|
+
Bug reports and pull requests are welcome on [GitHub](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues). Please include a failing spec with bug reports where possible, and keep pull requests focused on one change. Releases follow [Semantic Versioning](https://semver.org/) and are recorded in the [CHANGELOG](CHANGELOG.md).
|
|
325
219
|
|
|
326
220
|
## License
|
|
327
221
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
## Star History
|
|
331
|
-
|
|
332
|
-
<a href="https://www.star-history.com/?repos=ya-luotao%2Fclaude-agent-sdk-ruby&type=date&legend=top-left">
|
|
333
|
-
<picture>
|
|
334
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=ya-luotao/claude-agent-sdk-ruby&type=date&theme=dark&legend=top-left" />
|
|
335
|
-
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=ya-luotao/claude-agent-sdk-ruby&type=date&legend=top-left" />
|
|
336
|
-
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=ya-luotao/claude-agent-sdk-ruby&type=date&legend=top-left" />
|
|
337
|
-
</picture>
|
|
338
|
-
</a>
|
|
222
|
+
Released under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Vendoring the CLI (`CLIInstaller`)
|
|
2
|
+
|
|
3
|
+
The SDK runs the `claude` CLI as a subprocess, so a deploy is only reproducible if the CLI version is pinned with it. `ClaudeAgentSDK::CLIInstaller` downloads a pinned binary from the official release endpoint into a project-local directory (`vendor/claude` by default) — checksum-verified, no npm/Node at runtime, and nothing extra shipped inside the gem.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
require 'claude_agent_sdk'
|
|
9
|
+
|
|
10
|
+
# 'stable' (default), 'latest', or a concrete version — pin it in production.
|
|
11
|
+
ClaudeAgentSDK::CLIInstaller.install(version: '2.1.220')
|
|
12
|
+
# => "/app/vendor/claude/claude"
|
|
13
|
+
|
|
14
|
+
ClaudeAgentSDK::CLIInstaller.install(version: '2.1.220', dir: '/opt/claude')
|
|
15
|
+
|
|
16
|
+
# nil unless a binary is already installed there
|
|
17
|
+
ClaudeAgentSDK::CLIInstaller.installed_path
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`install` is idempotent and safe to run concurrently, so it fits `bin/setup`, a cached Docker layer, and every process of a multi-process boot:
|
|
21
|
+
|
|
22
|
+
- The install directory's `VERSION` file records the installed version **and** the SHA-256 that was verified at download time. The shortcut re-hashes the vendored binary (~0.1s for the real 245MB binary) and only skips the download when both match — a truncated, swapped or half-written binary is reinstalled instead of trusted. It makes **no network request**, so repeat boots work offline — with a pinned concrete version; `'stable'`/`'latest'` must always re-resolve through the endpoint, which is one more reason to pin in production.
|
|
23
|
+
- An exclusive `flock` on `<dir>/.install.lock` covers the whole check → download → place → record sequence, so parallel installs into one directory don't race; the loser simply observes the finished install.
|
|
24
|
+
|
|
25
|
+
Failures (unsupported platform, invalid version, HTTP error, response-size cap, oversized download, checksum mismatch, filesystem errors) raise `ClaudeAgentSDK::CLIInstallError`.
|
|
26
|
+
|
|
27
|
+
**A failed install never breaks a working one.** The new binary is downloaded to a temp file, checksum-verified and recorded, and only then renamed into place — the rename is the last step, and nothing can fail after it. So a failed upgrade leaves the previously installed binary intact and runnable (the SDK keeps working), and the next `install` redoes it cleanly. A first install that fails leaves nothing behind at all.
|
|
28
|
+
|
|
29
|
+
> The vendored directory is trusted input: anything that can write to it can replace the binary the SDK executes. Keep it inside your deploy artifact, owned by the deploy user and not world-writable, exactly as you would treat `bin/`.
|
|
30
|
+
|
|
31
|
+
## Docker and `bin/setup`
|
|
32
|
+
|
|
33
|
+
```dockerfile
|
|
34
|
+
# Dockerfile — pin the CLI in its own cached layer
|
|
35
|
+
RUN bundle exec ruby -e "require 'claude_agent_sdk'; \
|
|
36
|
+
ClaudeAgentSDK::CLIInstaller.install(version: '2.1.220')"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
#!/usr/bin/env ruby
|
|
41
|
+
# bin/setup
|
|
42
|
+
require 'claude_agent_sdk'
|
|
43
|
+
puts ClaudeAgentSDK::CLIInstaller.install(version: ENV.fetch('CLAUDE_CLI_VERSION', 'stable'))
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Supported platforms
|
|
47
|
+
|
|
48
|
+
`darwin-arm64`, `darwin-x64` (Rosetta 2 gets the arm64 build), `linux-x64`, `linux-arm64`, and the `-musl` variants. Windows is not supported.
|
|
49
|
+
|
|
50
|
+
## CLI discovery order
|
|
51
|
+
|
|
52
|
+
With no explicit `cli_path:` in `ClaudeAgentOptions`, the transport probes in this order:
|
|
53
|
+
|
|
54
|
+
1. `CLAUDE_CLI_PATH` — an explicit path to an executable, no discovery at all (a relative value is resolved against the process's working directory, not `cwd:`)
|
|
55
|
+
2. The vendored binary (`CLIInstaller.installed_path`) — deliberately ahead of `PATH`, so a pinned install beats whatever is installed globally
|
|
56
|
+
3. `which claude`
|
|
57
|
+
4. Common install locations (`~/.claude/local/claude`, `/usr/local/bin/claude`, …)
|
data/docs/configuration.md
CHANGED
|
@@ -78,6 +78,28 @@ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
|
|
|
78
78
|
|
|
79
79
|
When set, the CLI strips per-user dynamic sections (working directory, auto-memory, git status) from the system prompt and re-injects them into the first user message instead. Older CLIs silently ignore this option.
|
|
80
80
|
|
|
81
|
+
### System Prompt Snapshot
|
|
82
|
+
|
|
83
|
+
By default, Claude Code builds the system prompt on a session's first request, records it, and reuses it on every later request, including after you resume the session. A changed custom prompt, or changed `append` text on the `claude_code` preset, then has no effect until the session is compacted or you start a new session. To rebuild the prompt on every request instead, for example while you iterate on its wording, set `snapshot: false` on a `SystemPromptPreset` or on `SystemPromptCustom` (the object form of a String prompt, which exists so `snapshot` can be set alongside it):
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
options = ClaudeAgentSDK::ClaudeAgentOptions.new(
|
|
87
|
+
system_prompt: ClaudeAgentSDK::SystemPromptCustom.new(
|
|
88
|
+
prompt: 'You are a release bot.',
|
|
89
|
+
snapshot: false
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Hash forms work too:
|
|
94
|
+
options = ClaudeAgentSDK::ClaudeAgentOptions.new(
|
|
95
|
+
system_prompt: { type: 'preset', preset: 'claude_code', append: '...', snapshot: false }
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`snapshot` is sent on the control-protocol `initialize` request (never as a CLI flag), so it applies to both `query()` and `Client`. When omitted it acts as `true`, except in bare mode (`bare: true`), where it acts as `false`. A `SystemPromptFile` has no `snapshot`.
|
|
100
|
+
|
|
101
|
+
Requires Claude Code CLI 2.1.257 or later. Before 2.1.265, a session with an `append` or custom prompt recorded it only when `snapshot` was `true`. See [Modifying system prompts](https://code.claude.com/docs/en/agent-sdk/modifying-system-prompts#change-the-prompt-of-an-existing-session) for details.
|
|
102
|
+
|
|
81
103
|
## Budget Control
|
|
82
104
|
|
|
83
105
|
```ruby
|
data/docs/types.md
CHANGED
|
@@ -267,7 +267,9 @@ end
|
|
|
267
267
|
| `SandboxSettings` | Sandbox settings for isolated command execution |
|
|
268
268
|
| `SandboxNetworkConfig` | Network configuration for sandbox |
|
|
269
269
|
| `SandboxIgnoreViolations` | Configure which sandbox violations to ignore |
|
|
270
|
-
| `SystemPromptPreset` | System prompt preset configuration |
|
|
270
|
+
| `SystemPromptPreset` | System prompt preset configuration (`preset`, `append`, `exclude_dynamic_sections`, `snapshot`) |
|
|
271
|
+
| `SystemPromptCustom` | Custom system prompt configuration — the object form of a String prompt, so `snapshot` can be set alongside it |
|
|
272
|
+
| `SystemPromptFile` | System prompt loaded from a file path |
|
|
271
273
|
| `ToolsPreset` | Tools preset configuration for base tools selection |
|
|
272
274
|
|
|
273
275
|
## Constants
|
|
@@ -72,6 +72,10 @@ module ClaudeAgentSDK
|
|
|
72
72
|
cmd.push("--system-prompt", @options.system_prompt)
|
|
73
73
|
when SystemPromptFile
|
|
74
74
|
cmd.push("--system-prompt-file", @options.system_prompt.path)
|
|
75
|
+
when SystemPromptCustom
|
|
76
|
+
# The object form of a String prompt; snapshot travels on the
|
|
77
|
+
# initialize request, not as a CLI flag.
|
|
78
|
+
cmd.push("--system-prompt", custom_prompt_text(@options.system_prompt.prompt))
|
|
75
79
|
when SystemPromptPreset
|
|
76
80
|
# Preset activates the default Claude Code system prompt by not passing --system-prompt ""
|
|
77
81
|
# Only --append-system-prompt is passed if append text is provided
|
|
@@ -87,6 +91,9 @@ module ClaudeAgentSDK
|
|
|
87
91
|
when "file"
|
|
88
92
|
prompt_path = prompt_hash[:path] || prompt_hash["path"]
|
|
89
93
|
cmd.push("--system-prompt-file", prompt_path) if prompt_path
|
|
94
|
+
when "custom"
|
|
95
|
+
prompt = prompt_hash.fetch(:prompt) { prompt_hash["prompt"] }
|
|
96
|
+
cmd.push("--system-prompt", custom_prompt_text(prompt))
|
|
90
97
|
when "preset"
|
|
91
98
|
append = prompt_hash[:append] || prompt_hash["append"]
|
|
92
99
|
# Preset activates the default Claude Code system prompt by not passing --system-prompt ""
|
|
@@ -94,6 +101,17 @@ module ClaudeAgentSDK
|
|
|
94
101
|
end
|
|
95
102
|
end
|
|
96
103
|
|
|
104
|
+
# A custom prompt is always forwarded, even when empty (an empty String
|
|
105
|
+
# suppresses the default Claude Code prompt, exactly like a nil
|
|
106
|
+
# system_prompt). A missing prompt is rejected loudly rather than
|
|
107
|
+
# falling through and silently activating the default prompt — the
|
|
108
|
+
# Python SDK raises KeyError on the same input.
|
|
109
|
+
def custom_prompt_text(prompt)
|
|
110
|
+
raise ArgumentError, "system_prompt of type 'custom' requires a :prompt String" unless prompt.is_a?(String)
|
|
111
|
+
|
|
112
|
+
prompt
|
|
113
|
+
end
|
|
114
|
+
|
|
97
115
|
def append_allowed_tools(cmd, allowed_tools)
|
|
98
116
|
cmd.push("--allowedTools", allowed_tools.join(",")) unless allowed_tools.empty?
|
|
99
117
|
end
|
|
@@ -63,8 +63,8 @@ module ClaudeAgentSDK
|
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def initialize(transport:, is_streaming_mode:, can_use_tool: nil, hooks: nil, sdk_mcp_servers: nil, agents: nil,
|
|
66
|
-
exclude_dynamic_sections: nil,
|
|
67
|
-
callback_scheduling: :thread, callback_wrapper: nil)
|
|
66
|
+
exclude_dynamic_sections: nil, system_prompt_snapshot: nil, skills: nil,
|
|
67
|
+
forward_subagent_text: false, callback_scheduling: :thread, callback_wrapper: nil)
|
|
68
68
|
@transport = transport
|
|
69
69
|
@is_streaming_mode = is_streaming_mode
|
|
70
70
|
@can_use_tool = can_use_tool
|
|
@@ -74,6 +74,7 @@ module ClaudeAgentSDK
|
|
|
74
74
|
@callback_wrapper = callback_wrapper
|
|
75
75
|
@agents = agents
|
|
76
76
|
@exclude_dynamic_sections = exclude_dynamic_sections
|
|
77
|
+
@system_prompt_snapshot = system_prompt_snapshot
|
|
77
78
|
@skills = skills
|
|
78
79
|
@forward_subagent_text = forward_subagent_text
|
|
79
80
|
|
|
@@ -189,6 +190,9 @@ module ClaudeAgentSDK
|
|
|
189
190
|
agents: agents_dict
|
|
190
191
|
}
|
|
191
192
|
request[:excludeDynamicSections] = @exclude_dynamic_sections unless @exclude_dynamic_sections.nil?
|
|
193
|
+
# false is meaningful (rebuild the prompt every request), so send it
|
|
194
|
+
# explicitly; only nil (unset) is omitted.
|
|
195
|
+
request[:systemPromptSnapshot] = @system_prompt_snapshot unless @system_prompt_snapshot.nil?
|
|
192
196
|
# 'all' and omitted are equivalent at the wire level (no filter), so
|
|
193
197
|
# only send the field when it's an explicit list (mirrors Python).
|
|
194
198
|
request[:skills] = @skills if @skills.is_a?(Array)
|
|
@@ -1640,10 +1640,22 @@ module ClaudeAgentSDK
|
|
|
1640
1640
|
end
|
|
1641
1641
|
end
|
|
1642
1642
|
|
|
1643
|
-
# System prompt preset configuration
|
|
1643
|
+
# System prompt preset configuration.
|
|
1644
|
+
#
|
|
1645
|
+
# +snapshot+ controls whether the session keeps the system prompt it
|
|
1646
|
+
# recorded on its first request. When true, every later request (including
|
|
1647
|
+
# after resume) sends the recorded prompt, so a changed +append+ has no
|
|
1648
|
+
# effect until the session is compacted or a new session starts. When
|
|
1649
|
+
# false, the prompt is rebuilt on every request — useful while iterating on
|
|
1650
|
+
# +append+ text across calls that resume the same session. When nil
|
|
1651
|
+
# (omitted), the CLI treats it as true, except in bare mode (+--bare+),
|
|
1652
|
+
# where it acts as false. Sent on the control-protocol +initialize+ request
|
|
1653
|
+
# (never as a CLI flag); requires Claude Code CLI 2.1.257 or later, and
|
|
1654
|
+
# before 2.1.265 a session with an +append+ prompt recorded it only when
|
|
1655
|
+
# +snapshot+ was true. Older CLIs silently ignore it.
|
|
1644
1656
|
class SystemPromptPreset < Type
|
|
1645
1657
|
attr_reader :type
|
|
1646
|
-
attr_accessor :preset, :append, :exclude_dynamic_sections
|
|
1658
|
+
attr_accessor :preset, :append, :exclude_dynamic_sections, :snapshot
|
|
1647
1659
|
|
|
1648
1660
|
def initialize(attributes = {})
|
|
1649
1661
|
super
|
|
@@ -1654,6 +1666,27 @@ module ClaudeAgentSDK
|
|
|
1654
1666
|
result = { type: @type, preset: @preset }
|
|
1655
1667
|
result[:append] = @append if @append
|
|
1656
1668
|
result[:exclude_dynamic_sections] = @exclude_dynamic_sections unless @exclude_dynamic_sections.nil?
|
|
1669
|
+
result[:snapshot] = @snapshot unless @snapshot.nil?
|
|
1670
|
+
result
|
|
1671
|
+
end
|
|
1672
|
+
end
|
|
1673
|
+
|
|
1674
|
+
# Custom system prompt configuration — the object form of passing a String
|
|
1675
|
+
# as +system_prompt+. Reaches the CLI the same way a String does
|
|
1676
|
+
# (+--system-prompt <prompt>+); the object form exists so +snapshot+ can be
|
|
1677
|
+
# set alongside it (see SystemPromptPreset#snapshot for its semantics).
|
|
1678
|
+
class SystemPromptCustom < Type
|
|
1679
|
+
attr_reader :type
|
|
1680
|
+
attr_accessor :prompt, :snapshot
|
|
1681
|
+
|
|
1682
|
+
def initialize(attributes = {})
|
|
1683
|
+
super
|
|
1684
|
+
@type = 'custom'
|
|
1685
|
+
end
|
|
1686
|
+
|
|
1687
|
+
def to_h
|
|
1688
|
+
result = { type: @type, prompt: @prompt }
|
|
1689
|
+
result[:snapshot] = @snapshot unless @snapshot.nil?
|
|
1657
1690
|
result
|
|
1658
1691
|
end
|
|
1659
1692
|
end
|
data/lib/claude_agent_sdk.rb
CHANGED
|
@@ -109,6 +109,26 @@ module ClaudeAgentSDK
|
|
|
109
109
|
nil
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
+
# Internal: pull snapshot out of a preset or custom system prompt for the
|
|
113
|
+
# initialize request (older CLIs ignore unknown initialize fields). A
|
|
114
|
+
# String or file prompt has no snapshot, and only a genuine true/false is
|
|
115
|
+
# forwarded — `snapshot: false` is the primary use case, so the Hash lookup
|
|
116
|
+
# must not collapse it to nil. Shared by Client#connect and query().
|
|
117
|
+
def self.extract_system_prompt_snapshot(system_prompt)
|
|
118
|
+
case system_prompt
|
|
119
|
+
when SystemPromptPreset, SystemPromptCustom
|
|
120
|
+
snapshot = system_prompt.snapshot
|
|
121
|
+
return snapshot if [true, false].include?(snapshot)
|
|
122
|
+
when Hash
|
|
123
|
+
type = system_prompt[:type] || system_prompt['type']
|
|
124
|
+
if %w[preset custom].include?(type)
|
|
125
|
+
snapshot = system_prompt.fetch(:snapshot) { system_prompt['snapshot'] }
|
|
126
|
+
return snapshot if [true, false].include?(snapshot)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
nil
|
|
130
|
+
end
|
|
131
|
+
|
|
112
132
|
# Safely call a method on each observer, suppressing any errors.
|
|
113
133
|
# Each observer is invoked through FiberBoundary so that user code runs
|
|
114
134
|
# on a plain thread (no Fiber scheduler) even when called from inside
|
|
@@ -549,6 +569,7 @@ module ClaudeAgentSDK
|
|
|
549
569
|
agents: configured_options.agents,
|
|
550
570
|
sdk_mcp_servers: sdk_mcp_servers,
|
|
551
571
|
exclude_dynamic_sections: ClaudeAgentSDK.extract_exclude_dynamic_sections(configured_options.system_prompt),
|
|
572
|
+
system_prompt_snapshot: ClaudeAgentSDK.extract_system_prompt_snapshot(configured_options.system_prompt),
|
|
552
573
|
skills: configured_options.skills,
|
|
553
574
|
forward_subagent_text: configured_options.forward_subagent_text?,
|
|
554
575
|
callback_scheduling: callback_scheduling,
|
|
@@ -1064,9 +1085,10 @@ module ClaudeAgentSDK
|
|
|
1064
1085
|
# Convert hooks to internal format
|
|
1065
1086
|
hooks = convert_hooks_to_internal_format(configured_options.hooks) if configured_options.hooks
|
|
1066
1087
|
|
|
1067
|
-
# Extract exclude_dynamic_sections from
|
|
1068
|
-
# initialize request (older CLIs ignore unknown initialize fields)
|
|
1088
|
+
# Extract exclude_dynamic_sections and snapshot from the system prompt
|
|
1089
|
+
# for the initialize request (older CLIs ignore unknown initialize fields)
|
|
1069
1090
|
exclude_dynamic_sections = ClaudeAgentSDK.extract_exclude_dynamic_sections(configured_options.system_prompt)
|
|
1091
|
+
system_prompt_snapshot = ClaudeAgentSDK.extract_system_prompt_snapshot(configured_options.system_prompt)
|
|
1070
1092
|
|
|
1071
1093
|
# Create Query handler
|
|
1072
1094
|
@query_handler = Query.new(
|
|
@@ -1077,6 +1099,7 @@ module ClaudeAgentSDK
|
|
|
1077
1099
|
sdk_mcp_servers: sdk_mcp_servers,
|
|
1078
1100
|
agents: configured_options.agents,
|
|
1079
1101
|
exclude_dynamic_sections: exclude_dynamic_sections,
|
|
1102
|
+
system_prompt_snapshot: system_prompt_snapshot,
|
|
1080
1103
|
skills: configured_options.skills,
|
|
1081
1104
|
forward_subagent_text: configured_options.forward_subagent_text?,
|
|
1082
1105
|
callback_scheduling: @callback_scheduling,
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: claude-agent-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.32.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Community Contributors
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: async
|
|
@@ -110,6 +110,7 @@ files:
|
|
|
110
110
|
- CHANGELOG.md
|
|
111
111
|
- LICENSE
|
|
112
112
|
- README.md
|
|
113
|
+
- docs/cli-installer.md
|
|
113
114
|
- docs/client.md
|
|
114
115
|
- docs/configuration.md
|
|
115
116
|
- docs/errors.md
|